mirror of
https://github.com/HolgerHatGarKeineNode/einundzwanzig-app.git
synced 2026-05-05 04:54:53 +00:00
2efc88a7f8
- 🔄 `requestNostrChallenge` now issues a new challenge when needed. - 🛡️ Enhanced fallback logic in `nostrLogin.js` to ensure robust challenge retrieval. - ✅ Added test coverage for fresh challenge issuance.
102 lines
3.1 KiB
PHP
102 lines
3.1 KiB
PHP
<?php
|
|
|
|
use App\Jobs\FetchNostrProfileJob;
|
|
use App\Models\User;
|
|
use Illuminate\Support\Facades\Queue;
|
|
use Illuminate\Support\Facades\Session;
|
|
use Livewire\Livewire;
|
|
use swentel\nostr\Event\Event as NostrEvent;
|
|
use swentel\nostr\Key\Key as NostrKey;
|
|
use swentel\nostr\Sign\Sign as NostrSign;
|
|
|
|
/**
|
|
* Build a NIP-42-style signed login event using the challenge that the
|
|
* login component placed in the session during mount(), and return
|
|
* [signedEventArray, npubBech32].
|
|
*
|
|
* @return array{0: array<string, mixed>, 1: string}
|
|
*/
|
|
function makeSignedNostrLoginEvent(): array
|
|
{
|
|
$keyGen = new NostrKey;
|
|
$privateKey = $keyGen->generatePrivateKey();
|
|
$publicKey = $keyGen->getPublicKey($privateKey);
|
|
|
|
$challenge = Session::get('nostr_login_challenge');
|
|
|
|
$event = new NostrEvent;
|
|
$event->setKind(22242)
|
|
->setCreatedAt(time())
|
|
->setContent('')
|
|
->setTags([['challenge', (string) $challenge]]);
|
|
|
|
(new NostrSign)->signEvent($event, $privateKey);
|
|
|
|
$signed = [
|
|
'id' => $event->getId(),
|
|
'pubkey' => $event->getPublicKey(),
|
|
'created_at' => $event->getCreatedAt(),
|
|
'kind' => $event->getKind(),
|
|
'tags' => $event->getTags(),
|
|
'content' => $event->getContent(),
|
|
'sig' => $event->getSignature(),
|
|
];
|
|
|
|
$npub = $keyGen->convertPublicKeyToBech32($publicKey);
|
|
|
|
return [$signed, $npub];
|
|
}
|
|
|
|
it('creates a new user and dispatches FetchNostrProfileJob when an unknown pubkey logs in', function () {
|
|
Queue::fake();
|
|
|
|
$component = Livewire::test('auth.login');
|
|
[$signedEvent, $npub] = makeSignedNostrLoginEvent();
|
|
|
|
$component
|
|
->dispatch('nostrLoggedIn', signedEvent: $signedEvent)
|
|
->assertRedirect();
|
|
|
|
$user = User::query()->where('nostr', $npub)->first();
|
|
expect($user)->not->toBeNull()
|
|
->and((bool) $user->is_lecturer)->toBeTrue()
|
|
->and($user->email)->toEndWith('@portal.einundzwanzig.space');
|
|
|
|
Queue::assertPushed(FetchNostrProfileJob::class);
|
|
expect(auth()->id())->toBe($user->id);
|
|
});
|
|
|
|
it('issues a fresh challenge when requestNostrChallenge is called and the same value is verifiable', function () {
|
|
$component = Livewire::test('auth.login');
|
|
|
|
$initial = Session::get('nostr_login_challenge');
|
|
expect($initial)->toBeString()->not->toBe('');
|
|
|
|
$component->call('requestNostrChallenge');
|
|
|
|
$refreshed = Session::get('nostr_login_challenge');
|
|
expect($refreshed)
|
|
->toBeString()
|
|
->not->toBe('')
|
|
->not->toBe($initial);
|
|
|
|
$component->assertSet('nostrChallenge', $refreshed);
|
|
});
|
|
|
|
it('logs in an existing user without creating a duplicate when their pubkey is already known', function () {
|
|
Queue::fake();
|
|
|
|
$component = Livewire::test('auth.login');
|
|
[$signedEvent, $npub] = makeSignedNostrLoginEvent();
|
|
|
|
$existing = User::factory()->create(['nostr' => $npub]);
|
|
|
|
$component
|
|
->dispatch('nostrLoggedIn', signedEvent: $signedEvent)
|
|
->assertRedirect();
|
|
|
|
expect(User::query()->where('nostr', $npub)->count())->toBe(1);
|
|
expect(auth()->id())->toBe($existing->id);
|
|
Queue::assertPushed(FetchNostrProfileJob::class);
|
|
});
|