/** * NIP-98 HTTP Auth implementation * Generates authentication tokens for HTTP requests using Nostr events * See: https://github.com/nostr-protocol/nips/blob/master/98.md */ import type { EventTemplate } from 'nostr-tools' import { finalizeEvent } from 'nostr-tools' import { hexToBytes } from 'nostr-tools/utils' import { nostrService } from './nostr' import { nostrAuthService } from './nostrAuth' /** * Generate NIP-98 authentication token for HTTP request * @param method HTTP method (GET, POST, etc.) * @param url Full URL of the request * @param payloadHash Optional SHA256 hash of the request body (for POST/PUT) * @returns Base64-encoded signed event token */ export async function generateNip98Token(method: string, url: string, payloadHash?: string): Promise { const pubkey = nostrService.getPublicKey() if (!pubkey) { throw new Error('Public key required for NIP-98 authentication. Please unlock your account.') } // Check if private key is available (unlocked) if (!nostrAuthService.isUnlocked()) { throw new Error('Private key required for NIP-98 authentication. Please unlock your account with your recovery phrase.') } const privateKey = nostrAuthService.getPrivateKey() if (!privateKey) { throw new Error('Private key not available. Please unlock your account.') } // Parse URL to get components const urlObj = new URL(url) const path = urlObj.pathname + urlObj.search // Build event template for NIP-98 const tags: string[][] = [ ['u', urlObj.origin + path], ['method', method], ] // Add payload hash if provided (for POST/PUT requests) if (payloadHash) { tags.push(['payload', payloadHash]) } const eventTemplate: EventTemplate & { pubkey: string } = { kind: 27235, // NIP-98 kind for HTTP auth created_at: Math.floor(Date.now() / 1000), tags, content: '', pubkey, } // Sign the event directly with the private key (no plugin needed) const secretKey = hexToBytes(privateKey) const signedEvent = finalizeEvent(eventTemplate, secretKey) // Encode event as base64 JSON const eventJson = JSON.stringify(signedEvent) const eventBytes = new TextEncoder().encode(eventJson) const base64Token = btoa(String.fromCharCode(...eventBytes)) return base64Token } /** * Check if NIP-98 authentication is available * Requires both public key and unlocked private key */ export function isNip98Available(): boolean { const pubkey = nostrService.getPublicKey() const isUnlocked = nostrAuthService.isUnlocked() return Boolean(pubkey) && isUnlocked }