Store the bunkerSeckey in the keyring, include only the pubkey in the plaintext connection storage

This commit is contained in:
Alex Gleason 2024-10-30 11:59:53 -05:00
parent 36427f52d7
commit fdb53c8906
No known key found for this signature in database
GPG Key ID: 7211D1F99744FBB7
3 changed files with 15 additions and 38 deletions

View File

@ -2,6 +2,7 @@ import { NostrSigner, NRelay1, NSecSigner } from '@nostrify/nostrify';
import { generateSecretKey } from 'nostr-tools'; import { generateSecretKey } from 'nostr-tools';
import { NBunker } from 'soapbox/features/nostr/NBunker'; import { NBunker } from 'soapbox/features/nostr/NBunker';
import { keyring } from 'soapbox/features/nostr/keyring';
import { useBunkerStore } from 'soapbox/hooks/nostr/useBunkerStore'; import { useBunkerStore } from 'soapbox/hooks/nostr/useBunkerStore';
import { type AppDispatch } from 'soapbox/store'; import { type AppDispatch } from 'soapbox/store';
@ -53,11 +54,13 @@ function logInNostr(signer: NostrSigner, relay: NRelay1) {
const accessToken = dispatch(authLoggedIn(token)).access_token as string; const accessToken = dispatch(authLoggedIn(token)).access_token as string;
const bunkerState = useBunkerStore.getState(); const bunkerState = useBunkerStore.getState();
keyring.add(authorization.seckey);
bunkerState.connect({ bunkerState.connect({
pubkey, pubkey,
accessToken, accessToken,
authorizedPubkey, authorizedPubkey,
bunkerSeckey: authorization.seckey, bunkerPubkey,
}); });
await dispatch(verifyCredentials(accessToken)); await dispatch(verifyCredentials(accessToken));

View File

@ -1,5 +1,5 @@
import { NSchema as n } from '@nostrify/nostrify';
import { produce } from 'immer'; import { produce } from 'immer';
import { nip19 } from 'nostr-tools';
import { z } from 'zod'; import { z } from 'zod';
import { create } from 'zustand'; import { create } from 'zustand';
// eslint-disable-next-line import/extensions // eslint-disable-next-line import/extensions
@ -19,15 +19,15 @@ interface BunkerConnection {
accessToken: string; accessToken: string;
/** Pubkey of the app authorized to sign events with this connection. */ /** Pubkey of the app authorized to sign events with this connection. */
authorizedPubkey: string; authorizedPubkey: string;
/** Secret key for this connection. NIP-46 responses will be signed by this key. */ /** Pubkey for this connection. Secret key is stored in the keyring. NIP-46 responses will be signed by this key. */
bunkerSeckey: Uint8Array; bunkerPubkey: string;
} }
const connectionSchema = z.object({ const connectionSchema: z.ZodType<BunkerConnection> = z.object({
pubkey: z.string(), pubkey: n.id(),
accessToken: z.string(), accessToken: z.string(),
authorizedPubkey: z.string(), authorizedPubkey: n.id(),
bunkerSeckey: z.custom<Uint8Array>((value) => value instanceof Uint8Array), bunkerPubkey: n.id(),
}); });
interface BunkerState { interface BunkerState {
@ -65,7 +65,7 @@ export const useBunkerStore = create<BunkerState>()(
getItem(name) { getItem(name) {
const value = localStorage.getItem(name); const value = localStorage.getItem(name);
const connections = jsonSchema(nsecReviver) const connections = jsonSchema()
.pipe(filteredArray(connectionSchema)) .pipe(filteredArray(connectionSchema))
.catch([]) .catch([])
.parse(value); .parse(value);
@ -73,7 +73,7 @@ export const useBunkerStore = create<BunkerState>()(
return { state: { connections } }; return { state: { connections } };
}, },
setItem(name, { state }) { setItem(name, { state }) {
localStorage.setItem(name, JSON.stringify(state.connections, nsecReplacer)); localStorage.setItem(name, JSON.stringify(state.connections));
}, },
removeItem(name) { removeItem(name) {
localStorage.removeItem(name); localStorage.removeItem(name);
@ -82,21 +82,3 @@ export const useBunkerStore = create<BunkerState>()(
}, },
), ),
); );
/** Encode Uint8Arrays into nsec strings. */
function nsecReplacer(_key: string, value: unknown): unknown {
if (value instanceof Uint8Array) {
return nip19.nsecEncode(value);
}
return value;
}
/** Decode nsec strings into Uint8Arrays. */
function nsecReviver(_key: string, value: unknown): unknown {
if (typeof value === 'string' && value.startsWith('nsec1')) {
return nip19.decode(value as `nsec1${string}`).data;
}
return value;
}

View File

@ -1,6 +1,4 @@
import { NSecSigner } from '@nostrify/nostrify';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { useMemo } from 'react';
import { keyring } from 'soapbox/features/nostr/keyring'; import { keyring } from 'soapbox/features/nostr/keyring';
import { useAppSelector } from 'soapbox/hooks'; import { useAppSelector } from 'soapbox/hooks';
@ -16,7 +14,7 @@ export function useSigner() {
} }
}); });
const { pubkey, bunkerSeckey, authorizedPubkey } = connection ?? {}; const { pubkey, bunkerPubkey, authorizedPubkey } = connection ?? {};
const { data: signer, ...rest } = useQuery({ const { data: signer, ...rest } = useQuery({
queryKey: ['nostr', 'signer', pubkey ?? ''], queryKey: ['nostr', 'signer', pubkey ?? ''],
@ -35,15 +33,9 @@ export function useSigner() {
enabled: !!pubkey, enabled: !!pubkey,
}); });
const bunkerSigner = useMemo(() => {
if (bunkerSeckey) {
return new NSecSigner(bunkerSeckey);
}
}, [bunkerSeckey]);
return { return {
signer: signer ?? undefined, signer: signer ?? undefined,
bunkerSigner, bunkerSigner: bunkerPubkey ? keyring.get(bunkerPubkey) : undefined,
authorizedPubkey, authorizedPubkey,
...rest, ...rest,
}; };