mirror of
https://github.com/HolgerHatGarKeineNode/einundzwanzig-nostr.git
synced 2026-05-23 13:15:36 +00:00
fix(auth): route all nostrLoggedIn listeners through signed-event verification
The previous commit only updated auth-button + the WithNostrAuth trait, but six Volt pages (profile, benefits, election/*, members/admin) carry their own handleNostrLoggedIn(string $pubkey) handlers. The dispatched payload is now an array, so Livewire's container could not resolve the string parameter and threw BindingResolutionException on every login. - All six per-page handlers now accept the signed event and route it through NostrAuth::loginWithSignedEvent() like the trait does. - NostrAuth: add currentOrIssueChallenge() so the sidebar + navbar auth-button mounts share one live session challenge instead of overwriting each other. - verifySignedEvent: pass a normalized stdClass to swentel's verify() directly, skipping an unnecessary json_encode + json_decode round-trip. - auth-button: gate the global Escape/Tab capture so it only intercepts keys while the overlay is actually visible. - Update three test files that still called handleNostrLoggedIn with a raw pubkey to authenticate via NostrAuth::login() instead.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user