fix(smirk): sign first, grant Nostr scope only on demand — one approval for returning users

Previously always called getNostrPublicKey() then signNostrEvent() = two approvals every login. Now signNostrEvent() runs first; only if the wallet reports the origin lacks the Nostr scope (NOT_AUTHORIZED, thrown without a popup) do we call getNostrPublicKey() to grant it and retry. First-time still does the one-time scope grant; returning users get a single approval. (The double PASSWORD is the wallet's autoLockMinutes=0 default and is user-configurable, not dapp-controllable.)
This commit is contained in:
jwinterm
2026-07-10 11:07:11 -04:00
parent 65305bf7de
commit 20cbdd364a
+14 -6
View File
@@ -141,11 +141,6 @@ const SmirkAuth = {
* @returns {Promise<{verifyData: object, pubkey: string|null}>}
*/
async _verifyNip98(socketId, challenge) {
// Grant this origin the Nostr scope first. Per the Smirk dapp API, getNostrPublicKey()
// IS the connection for a Nostr origin (it prompts on first use); signNostrEvent() throws
// "Origin lacks the Nostr scope — call getNostrPublicKey() first" without it.
await window.smirk.getNostrPublicKey();
// Build the NIP-98 HTTP auth event. The wallet fills in pubkey/id/sig.
const evt = {
kind: 27235,
@@ -158,7 +153,20 @@ const SmirkAuth = {
]
};
const signed = await window.smirk.signNostrEvent(evt);
// Sign directly. If this origin doesn't yet hold the Nostr scope, the wallet throws
// NOT_AUTHORIZED (no popup); grant it once via getNostrPublicKey() and retry. Signing
// first means a RETURNING user (scope already granted) gets a SINGLE approval instead of
// two. The wallet still asks per-signature for kind 27235 — that can't be silenced.
let signed;
try {
signed = await window.smirk.signNostrEvent(evt);
} catch (scopeErr) {
const needsScope = (scopeErr && scopeErr.code === 'NOT_AUTHORIZED')
|| /nostr scope|getNostrPublicKey/i.test((scopeErr && scopeErr.message) || '');
if (!needsScope) throw scopeErr;
await window.smirk.getNostrPublicKey(); // grant the Nostr scope (one-time per origin)
signed = await window.smirk.signNostrEvent(evt);
}
if (!signed || typeof signed !== 'object') {
throw new Error('Smirk wallet did not return a signed nostr event');
}