Collapsed-by-default 'paste an nsec (unsafe)' option on the login screen, signs the NIP-98 login event client-side with nostr-tools and never persists the key (used once in memory, discarded) — for testing/kiosk use without a NIP-07 extension. Clearly labeled as unsafe in the UI.
46 lines
1.5 KiB
TypeScript
46 lines
1.5 KiB
TypeScript
// Raw-nsec signing — an *unsafe* login fallback for when there's no NIP-07
|
|
// extension or NIP-46 signer available (testing, kiosk devices, etc). The
|
|
// private key only ever lives in page memory for the duration of one sign
|
|
// call; it's never persisted (no localStorage/sessionStorage), matching how
|
|
// the NIP-07 path also never retains key material — only the resulting
|
|
// session cookie survives.
|
|
import { nip19 } from 'nostr-tools';
|
|
import { finalizeEvent, getPublicKey } from 'nostr-tools/pure';
|
|
import type { SignedEvent, UnsignedEvent } from './nip07';
|
|
|
|
export function isValidNsec(nsec: string): boolean {
|
|
try {
|
|
return nip19.decode(nsec.trim()).type === 'nsec';
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function decodeNsec(nsec: string): Uint8Array {
|
|
const decoded = nip19.decode(nsec.trim());
|
|
if (decoded.type !== 'nsec') throw new Error('Not an nsec key');
|
|
return decoded.data;
|
|
}
|
|
|
|
export function nsecPubkey(nsec: string): string {
|
|
return getPublicKey(decodeNsec(nsec));
|
|
}
|
|
|
|
function signWithNsec(nsec: string, event: UnsignedEvent): SignedEvent {
|
|
return finalizeEvent(event, decodeNsec(nsec));
|
|
}
|
|
|
|
/** Sign a NIP-98 (kind 27235) event with a raw nsec and return the Authorization header value. */
|
|
export function buildNip98HeaderWithNsec(nsec: string, url: string, method: string): string {
|
|
const event = signWithNsec(nsec, {
|
|
kind: 27235,
|
|
created_at: Math.floor(Date.now() / 1000),
|
|
content: '',
|
|
tags: [
|
|
['u', url],
|
|
['method', method],
|
|
],
|
|
});
|
|
return `Nostr ${btoa(JSON.stringify(event))}`;
|
|
}
|