diff --git a/components/utility-components/nostr-context-provider.tsx b/components/utility-components/nostr-context-provider.tsx index f826c2f14..f1c7f3e75 100644 --- a/components/utility-components/nostr-context-provider.tsx +++ b/components/utility-components/nostr-context-provider.tsx @@ -207,12 +207,20 @@ export function SignerContextProvider({ children }: { children: ReactNode }) { loadKeys(signerObject); const isAlreadyLoaded = localStorage.getItem("signer"); - if ( - !isAlreadyLoaded || - JSON.stringify(existingSigner) !== isAlreadyLoaded - ) { - localStorage.setItem("signer", JSON.stringify(existingSigner)); + const shouldPersistSigner = existingSigner?.type !== "nip46"; + const serializedSigner = JSON.stringify(existingSigner); + const hasStorageMismatch = + !isAlreadyLoaded || serializedSigner !== isAlreadyLoaded; + if (shouldPersistSigner && hasStorageMismatch) { + localStorage.setItem("signer", serializedSigner); + } + + if (!shouldPersistSigner && isAlreadyLoaded) { + localStorage.removeItem("signer"); + } + + if (hasStorageMismatch || (!shouldPersistSigner && isAlreadyLoaded)) { const shouldReloadSigner = false; window.dispatchEvent( new CustomEvent("storage", { detail: { shouldReloadSigner } }) diff --git a/utils/nostr/__tests__/local-storage-data.test.ts b/utils/nostr/__tests__/local-storage-data.test.ts index e62a6d1ec..85025cb28 100644 --- a/utils/nostr/__tests__/local-storage-data.test.ts +++ b/utils/nostr/__tests__/local-storage-data.test.ts @@ -1,13 +1,15 @@ import { + LogOut, getDefaultBlossomServer, getDefaultMint, getDefaultRelays, getLocalStorageData, + setLocalStorageDataOnSignIn, } from "../nostr-helper-functions"; describe("getLocalStorageData", () => { beforeEach(() => { - localStorage.clear(); + LogOut(); jest.restoreAllMocks(); }); @@ -70,4 +72,41 @@ describe("getLocalStorageData", () => { encryptedPrivKey: "ncryptsec1mock", }); }); + + it("keeps bunker signer data in runtime memory only", () => { + setLocalStorageDataOnSignIn({ + signer: { + toJSON: () => ({ + type: "nip46", + bunker: "bunker://pubkey?secret=supersecret", + appPrivKey: "app-private-key", + }), + } as any, + }); + + const data = getLocalStorageData(); + + expect(data.signer).toEqual({ + type: "nip46", + bunker: "bunker://pubkey?secret=supersecret", + appPrivKey: "app-private-key", + }); + expect(localStorage.getItem("signer")).toBeNull(); + }); + + it("removes legacy persisted bunker signer data on read", () => { + localStorage.setItem( + "signer", + JSON.stringify({ + type: "nip46", + bunker: "bunker://pubkey?secret=legacysecret", + appPrivKey: "legacy-app-privkey", + }) + ); + + const data = getLocalStorageData(); + + expect(data.signer).toBeUndefined(); + expect(localStorage.getItem("signer")).toBeNull(); + }); }); diff --git a/utils/nostr/nostr-helper-functions.ts b/utils/nostr/nostr-helper-functions.ts index adbcea590..7c14ed57f 100644 --- a/utils/nostr/nostr-helper-functions.ts +++ b/utils/nostr/nostr-helper-functions.ts @@ -1261,6 +1261,17 @@ const LOCALSTORAGECONSTANTS = { nwcInfo: "nwcInfo", }; +export type StoredSignerData = + | { type: "nip07" } + | { type: "nip46"; bunker: string; appPrivKey?: string } + | { type: "nsec"; encryptedPrivKey: string; pubkey?: string }; + +let runtimeSignerData: StoredSignerData | undefined; + +function shouldPersistSignerData(signerData: StoredSignerData): boolean { + return signerData.type !== "nip46"; +} + export const setLocalStorageDataOnSignIn = ({ encryptedPrivateKey, relays, @@ -1347,7 +1358,17 @@ export const setLocalStorageDataOnSignIn = ({ } if (signer) { - localStorage.setItem(LOCALSTORAGECONSTANTS.signer, JSON.stringify(signer)); + const signerData = signer.toJSON() as StoredSignerData; + runtimeSignerData = signerData; + + if (shouldPersistSignerData(signerData)) { + localStorage.setItem( + LOCALSTORAGECONSTANTS.signer, + JSON.stringify(signerData) + ); + } else { + localStorage.removeItem(LOCALSTORAGECONSTANTS.signer); + } } if (migrationComplete) { @@ -1375,10 +1396,7 @@ export interface LocalStorageInterface { bunkerRemotePubkey?: string; bunkerRelays?: string[]; bunkerSecret?: string; - signer?: - | { type: "nip07" } - | { type: "nip46"; bunker: string; appPrivKey?: string } - | { type: "nsec"; encryptedPrivKey: string; pubkey?: string }; + signer?: StoredSignerData; nwcString?: string | null; nwcInfo?: string | null; migrationComplete?: boolean; @@ -1386,7 +1404,7 @@ export interface LocalStorageInterface { function isStoredSignerData( value: unknown -): value is NonNullable { +): value is StoredSignerData { if (!value || typeof value !== "object" || Array.isArray(value)) { return false; } @@ -1440,7 +1458,7 @@ export const getLocalStorageData = (): LocalStorageInterface => { let bunkerRemotePubkey; let bunkerRelays; let bunkerSecret; - let signer: LocalStorageInterface["signer"] | undefined; + let signer: StoredSignerData | undefined = runtimeSignerData; let migrationComplete; let nwcString; let nwcInfo; @@ -1576,7 +1594,7 @@ export const getLocalStorageData = (): LocalStorageInterface => { ? localStorage.getItem(LOCALSTORAGECONSTANTS.bunkerSecret) : undefined; - signer = getLocalStorageJson( + const persistedSigner = getLocalStorageJson( LOCALSTORAGECONSTANTS.signer, undefined, { @@ -1584,6 +1602,12 @@ export const getLocalStorageData = (): LocalStorageInterface => { validate: isStoredSignerData, } ); + if (persistedSigner?.type === "nip46") { + localStorage.removeItem(LOCALSTORAGECONSTANTS.signer); + } else if (persistedSigner) { + signer = persistedSigner; + } + if (!signer) { switch (signInMethod) { case "extension": @@ -1647,6 +1671,8 @@ export const getLocalStorageData = (): LocalStorageInterface => { }; export const LogOut = () => { + runtimeSignerData = undefined; + // remove old data localStorage.removeItem("npub"); localStorage.removeItem("signIn");