You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

204 lines
5.4 KiB

/**
* Unified authentication handler
*/
import { getNIP07Signer, signEventWithNIP07, getPublicKeyWithNIP07 } from '../auth/nip07-signer.js';
import { signEventWithNsec, getPublicKeyFromNsec } from '../auth/nsec-signer.js';
import {
signEventWithAnonymous,
generateAnonymousKey
} from '../auth/anonymous-signer.js';
import { decryptPrivateKey } from '../security/key-management.js';
import { sessionManager, type AuthMethod } from '../auth/session-manager.js';
import { fetchRelayLists, fetchProfile } from '../user-data.js';
import { nostrClient } from './nostr-client.js';
import { relayManager } from './relay-manager.js';
import type { NostrEvent } from '../../types/nostr.js';
import { KIND } from '../../types/kind-lookup.js';
// Mute list and blocked relays management
const muteList: Set<string> = new Set();
const blockedRelays: Set<string> = new Set();
/**
* Authenticate with NIP-07
*/
export async function authenticateWithNIP07(): Promise<string> {
const pubkey = await getPublicKeyWithNIP07();
sessionManager.setSession({
pubkey,
method: 'nip07',
signer: signEventWithNIP07,
createdAt: Date.now()
}, {}); // No metadata needed for NIP-07
// Fetch user relay lists and mute list
await loadUserPreferences(pubkey);
// Fetch and cache user's own profile (background-update if already cached)
fetchProfile(pubkey).catch(() => {
// Silently fail - profile fetch errors shouldn't break login
});
return pubkey;
}
/**
* Authenticate with nsec
*/
export async function authenticateWithNsec(
ncryptsec: string,
password: string
): Promise<string> {
// Decrypt the encrypted private key
const nsec = await decryptPrivateKey(ncryptsec, password);
// Derive public key from private key
const pubkey = await getPublicKeyFromNsec(nsec);
sessionManager.setSession({
pubkey,
method: 'nsec',
signer: async (event) => signEventWithNsec(event, ncryptsec, password),
createdAt: Date.now()
});
await loadUserPreferences(pubkey);
// Fetch and cache user's own profile (background-update if already cached)
fetchProfile(pubkey).catch(() => {
// Silently fail - profile fetch errors shouldn't break login
});
return pubkey;
}
/**
* Authenticate as anonymous
*/
export async function authenticateAsAnonymous(password: string): Promise<string> {
const { pubkey, nsec } = await generateAnonymousKey(password);
// Store the key for later use
// In practice, we'd need to store the ncryptsec and decrypt when needed
// For now, this is simplified
sessionManager.setSession({
pubkey,
method: 'anonymous',
signer: async (event) => {
// Simplified - would decrypt and sign
return signEventWithAnonymous(event, pubkey, password);
},
createdAt: Date.now()
});
return pubkey;
}
/**
* Load user preferences (relay lists, mute list, blocked relays)
*/
async function loadUserPreferences(pubkey: string): Promise<void> {
// Fetch relay lists and load into relay manager
await relayManager.loadUserPreferences(pubkey);
// Fetch mute list (kind 10000)
const muteEvents = await nostrClient.fetchEvents(
[{ kinds: [KIND.MUTE_LIST], authors: [pubkey], limit: 1 }],
relayManager.getProfileReadRelays(),
{ useCache: true, cacheResults: true }
);
if (muteEvents.length > 0) {
const mutedPubkeys = muteEvents[0].tags
.filter((t) => t[0] === 'p')
.map((t) => t[1])
.filter(Boolean) as string[];
muteList.clear();
mutedPubkeys.forEach(pk => muteList.add(pk));
}
// Fetch blocked relays (kind 10006)
const blockedRelayEvents = await nostrClient.fetchEvents(
[{ kinds: [KIND.BLOCKED_RELAYS], authors: [pubkey], limit: 1 }],
relayManager.getProfileReadRelays(),
{ useCache: true, cacheResults: true }
);
if (blockedRelayEvents.length > 0) {
const blocked = blockedRelayEvents[0].tags
.filter((t) => t[0] === 'relay')
.map((t) => t[1])
.filter(Boolean) as string[];
blockedRelays.clear();
blocked.forEach(r => blockedRelays.add(r));
// Update relay manager with blocked relays
relayManager.updateBlockedRelays(blockedRelays);
}
}
/**
* Sign HTTP auth (NIP-98) for authenticated HTTP requests
* Returns Authorization header value: "Nostr <base64-encoded-event>"
*/
export async function signHttpAuth(
url: string,
method: string,
description: string = ''
): Promise<string> {
const event = await sessionManager.signEvent({
kind: KIND.HTTP_AUTH,
pubkey: sessionManager.getCurrentPubkey()!,
created_at: Math.floor(Date.now() / 1000),
tags: [
['u', url],
['method', method]
],
content: description
});
// Base64 encode the event JSON and return as "Nostr <base64>"
const eventJson = JSON.stringify(event);
const base64 = btoa(eventJson);
return `Nostr ${base64}`;
}
/**
* Sign and publish event
*/
export async function signAndPublish(
event: Omit<NostrEvent, 'sig' | 'id'>,
relays?: string[]
): Promise<{
success: string[];
failed: Array<{ relay: string; error: string }>;
}> {
const signed = await sessionManager.signEvent(event);
return nostrClient.publish(signed, { relays });
}
/**
* Logout
*/
export function logout(): void {
sessionManager.clearSession();
muteList.clear();
blockedRelays.clear();
relayManager.clearUserPreferences();
}
/**
* Get mute list
*/
export function getMuteList(): Set<string> {
return muteList;
}
/**
* Get blocked relays
*/
export function getBlockedRelays(): Set<string> {
return blockedRelays;
}