diff --git a/frontend/src/composables/useNostr.ts b/frontend/src/composables/useNostr.ts index e59265a..234c12d 100644 --- a/frontend/src/composables/useNostr.ts +++ b/frontend/src/composables/useNostr.ts @@ -86,6 +86,7 @@ function clearAllState() { store('bf_bot', null) store('bf_pic', null) setToken(null) + sessionNsec = null localStorage.removeItem('bf_nsec') } @@ -98,6 +99,8 @@ const isLoading = ref(false) let autoRestoreRan = false // Flag: skip relay pic fetch for freshly generated keys (no profile exists) let freshlyGenerated = false +// In-memory nsec for current session (never auto-persisted to localStorage) +let sessionNsec: string | null = null // Sync in-memory auth state when tab regains focus (handles external localStorage clearing) if (typeof document !== 'undefined') { @@ -196,8 +199,8 @@ export function useNostr() { // Mobile signers (Amber) inject window.nostr late — poll for up to 3s const found = await waitForSigner(3000) if (!found) { - // Fall back to stored nsec if available - const storedNsec = localStorage.getItem('bf_nsec') + // Fall back to session or persisted nsec if available + const storedNsec = sessionNsec || localStorage.getItem('bf_nsec') if (storedNsec) { return loginWithNsec(storedNsec) } @@ -247,16 +250,16 @@ export function useNostr() { // Mark as freshly generated so login() skips relay pic fetch freshlyGenerated = true - // Store new key (will be cleared on logout) - localStorage.setItem('bf_nsec', nsecHex) + // Hold key in session memory only — user must opt in to persist + sessionNsec = nsecHex pubkey.value = pk store('bf_pubkey', pk) return { pubkey: pk, nsec: nsecBech32 } } - /** Login with an existing nsec (hex). Signs NIP-98 locally. */ - async function loginWithNsec(nsecHex: string): Promise<{ pubkey: string; bot: BotData | null }> { + /** Login with an existing nsec (hex). Signs NIP-98 locally. If persist=true, saves to localStorage. */ + async function loginWithNsec(nsecHex: string, persist = false): Promise<{ pubkey: string; bot: BotData | null }> { let secretKey: Uint8Array try { secretKey = hexToBytes(nsecHex) @@ -271,7 +274,8 @@ export function useNostr() { store('bf_bot', null) store('bf_pic', null) - localStorage.setItem('bf_nsec', nsecHex) + sessionNsec = nsecHex + if (persist) localStorage.setItem('bf_nsec', nsecHex) isLoading.value = true try { @@ -332,8 +336,7 @@ export function useNostr() { // Re-authenticate to get fresh JWT with botId try { - const nsec = localStorage.getItem('bf_nsec') - await authenticateSession(nsec) + await authenticateSession(sessionNsec) } catch { // Non-critical: existing JWT still works, just missing botId } @@ -426,8 +429,7 @@ export function useNostr() { // Re-authenticate to get fresh JWT with botId try { - const nsec = localStorage.getItem('bf_nsec') - await authenticateSession(nsec) + await authenticateSession(sessionNsec) } catch { // Non-critical } @@ -443,9 +445,14 @@ export function useNostr() { /** Check if user has a locally stored key (no extension needed) */ const hasStoredKey = computed(() => !!localStorage.getItem('bf_nsec')) - /** Get the stored nsec hex for backup display */ + /** Get the current nsec hex (session memory first, then localStorage) */ function getStoredNsec(): string | null { - return localStorage.getItem('bf_nsec') + return sessionNsec || localStorage.getItem('bf_nsec') + } + + /** Persist the current session key to localStorage (opt-in) */ + function persistKey(): void { + if (sessionNsec) localStorage.setItem('bf_nsec', sessionNsec) } /** @@ -539,6 +546,7 @@ export function useNostr() { updateCustomization, updateWebhook, getStoredNsec, + persistKey, logout, fetchNostrProfile, initiateNip55Login, diff --git a/frontend/src/pages/DocsPage.vue b/frontend/src/pages/DocsPage.vue index eff98db..8075132 100644 --- a/frontend/src/pages/DocsPage.vue +++ b/frontend/src/pages/DocsPage.vue @@ -1,5 +1,5 @@