diff --git a/app/Support/NostrAuth.php b/app/Support/NostrAuth.php index dd1bbce..2a74d40 100644 --- a/app/Support/NostrAuth.php +++ b/app/Support/NostrAuth.php @@ -39,6 +39,25 @@ class NostrAuth return $challenge; } + /** + * Return the active session challenge if still valid, otherwise issue a + * fresh one. Keeps the sidebar + navbar auth-button mounts in sync — + * without this, the second mount would overwrite the first in the + * session and leave the first instance's rendered data-attribute + * pointing at a stale challenge. + */ + public static function currentOrIssueChallenge(): string + { + $existing = Session::get(self::CHALLENGE_SESSION_KEY); + $expiresAt = (int) Session::get(self::CHALLENGE_EXPIRES_SESSION_KEY, 0); + + if (is_string($existing) && $existing !== '' && $expiresAt >= now()->timestamp) { + return $existing; + } + + return self::issueChallenge(); + } + /** * Verify a signed NIP-42-style login event and log the holder of the pubkey in. * @@ -106,7 +125,10 @@ class NostrAuth && $expiresAt >= now()->timestamp && hash_equals($expectedChallenge, $challengeFromEvent); - $eventJson = json_encode([ + // swentel's verify() accepts an object directly, so we pass a normalized + // stdClass and skip an unnecessary json_encode + json_decode round-trip. + // Casts here enforce the property types swentel checks against. + $normalizedEvent = (object) [ 'id' => (string) $signedEvent['id'], 'pubkey' => (string) $signedEvent['pubkey'], 'created_at' => $createdAt, @@ -114,11 +136,11 @@ class NostrAuth 'tags' => $signedEvent['tags'], 'content' => (string) $signedEvent['content'], 'sig' => (string) $signedEvent['sig'], - ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + ]; $sigValid = false; try { - $sigValid = (new NostrEvent)->verify($eventJson); + $sigValid = (new NostrEvent)->verify($normalizedEvent); } catch (\Throwable) { $sigValid = false; } diff --git a/resources/views/livewire/association/benefits.blade.php b/resources/views/livewire/association/benefits.blade.php index 03ca70a..f3a07a9 100644 --- a/resources/views/livewire/association/benefits.blade.php +++ b/resources/views/livewire/association/benefits.blade.php @@ -109,9 +109,9 @@ new class extends Component Flux::toast('Watchtower-Adresse in die Zwischenablage kopiert!'); } - public function handleNostrLoggedIn(string $pubkey): void + public function handleNostrLoggedIn($signedEvent = null): void { - NostrAuth::login($pubkey); + NostrAuth::loginWithSignedEvent($signedEvent); $this->mount(); } diff --git a/resources/views/livewire/association/election/admin.blade.php b/resources/views/livewire/association/election/admin.blade.php index eeb59dc..f75fb4d 100644 --- a/resources/views/livewire/association/election/admin.blade.php +++ b/resources/views/livewire/association/election/admin.blade.php @@ -60,9 +60,9 @@ new class extends Component { } } - public function handleNostrLoggedIn(string $pubkey): void + public function handleNostrLoggedIn($signedEvent = null): void { - NostrAuth::login($pubkey); + $pubkey = NostrAuth::loginWithSignedEvent($signedEvent); $this->currentPubkey = $pubkey; $this->currentPleb = \App\Models\EinundzwanzigPleb::query() diff --git a/resources/views/livewire/association/election/index.blade.php b/resources/views/livewire/association/election/index.blade.php index 5f0b385..fc8c5ad 100644 --- a/resources/views/livewire/association/election/index.blade.php +++ b/resources/views/livewire/association/election/index.blade.php @@ -39,9 +39,9 @@ new class extends Component { } } - public function handleNostrLoggedIn(string $pubkey): void + public function handleNostrLoggedIn($signedEvent = null): void { - NostrAuth::login($pubkey); + $pubkey = NostrAuth::loginWithSignedEvent($signedEvent); $this->currentPubkey = $pubkey; $this->currentPleb = EinundzwanzigPleb::query() diff --git a/resources/views/livewire/association/election/show.blade.php b/resources/views/livewire/association/election/show.blade.php index c13737b..7f59117 100644 --- a/resources/views/livewire/association/election/show.blade.php +++ b/resources/views/livewire/association/election/show.blade.php @@ -207,7 +207,7 @@ new class extends Component { } } - public function handleNostrLoggedIn(string $pubkey): void + public function handleNostrLoggedIn($signedEvent = null): void { $executed = RateLimiter::attempt( 'nostr-login:'.request()->ip(), @@ -219,7 +219,7 @@ new class extends Component { abort(429, 'Too many login attempts.'); } - NostrAuth::login($pubkey); + $pubkey = NostrAuth::loginWithSignedEvent($signedEvent); $this->currentPubkey = $pubkey; $this->currentPleb = EinundzwanzigPleb::query() diff --git a/resources/views/livewire/association/members/admin.blade.php b/resources/views/livewire/association/members/admin.blade.php index e6f36e3..15a3eb4 100644 --- a/resources/views/livewire/association/members/admin.blade.php +++ b/resources/views/livewire/association/members/admin.blade.php @@ -69,8 +69,10 @@ new class extends Component $this->plebs = $this->loadPlebs(); } - public function handleNostrLoggedIn(string $pubkey): void + public function handleNostrLoggedIn($signedEvent = null): void { + $pubkey = NostrAuth::loginWithSignedEvent($signedEvent); + $this->currentPubkey = $pubkey; $this->currentPleb = EinundzwanzigPleb::query() ->where('pubkey', $pubkey)->first(); diff --git a/resources/views/livewire/association/profile.blade.php b/resources/views/livewire/association/profile.blade.php index 169bad5..02bc110 100644 --- a/resources/views/livewire/association/profile.blade.php +++ b/resources/views/livewire/association/profile.blade.php @@ -166,8 +166,10 @@ new class extends Component { $this->profileForm->nip05Handle = strtolower($this->profileForm->nip05Handle); } - public function handleNostrLoggedIn(string $pubkey): void + public function handleNostrLoggedIn($signedEvent = null): void { + $pubkey = NostrAuth::loginWithSignedEvent($signedEvent); + $this->currentPubkey = $pubkey; $this->currentPleb = EinundzwanzigPleb::query() ->with([ diff --git a/resources/views/livewire/auth-button.blade.php b/resources/views/livewire/auth-button.blade.php index a246d8b..46a1f04 100644 --- a/resources/views/livewire/auth-button.blade.php +++ b/resources/views/livewire/auth-button.blade.php @@ -21,7 +21,10 @@ new class extends Component $this->isLoggedIn = NostrAuth::check(); if (! $this->isLoggedIn) { - $this->nostrChallenge = NostrAuth::issueChallenge(); + // Sidebar + navbar mount the same component on the same page; using + // currentOrIssueChallenge keeps both rendered data-attributes + // pointing at the same live session value. + $this->nostrChallenge = NostrAuth::currentOrIssueChallenge(); } } @@ -87,8 +90,8 @@ new class extends Component x-bind:aria-busy="nostrLoginInProgress" aria-labelledby="nostr-login-progress-heading-{{ $location }}" aria-describedby="nostr-login-progress-description-{{ $location }}" - @keydown.window.escape.prevent.stop - @keydown.window.tab.prevent.stop + @keydown.window.escape="if (nostrLoginInProgress) { $event.preventDefault(); $event.stopPropagation(); }" + @keydown.window.tab="if (nostrLoginInProgress) { $event.preventDefault(); $event.stopPropagation(); }" x-effect="document.body.style.overflow = nostrLoginInProgress ? 'hidden' : ''" class="fixed inset-0 z-[100] flex items-center justify-center bg-zinc-950/70 backdrop-blur-md">