import jwt from 'jsonwebtoken'; import { v4 as uuidv4 } from 'uuid'; import { verifyEvent, type VerifiedEvent, nip19 } from 'nostr-tools'; import { prisma } from '../db/prisma'; const JWT_SECRET = process.env.JWT_SECRET || 'change-me-in-production'; const CHALLENGE_TTL_MS = 5 * 60 * 1000; interface StoredChallenge { challenge: string; expiresAt: number; } const challenges = new Map(); // Periodically clean up expired challenges setInterval(() => { const now = Date.now(); for (const [key, value] of challenges) { if (value.expiresAt < now) { challenges.delete(key); } } }, 60_000); export const authService = { createChallenge(pubkey: string): string { const challenge = uuidv4(); challenges.set(pubkey, { challenge, expiresAt: Date.now() + CHALLENGE_TTL_MS, }); return challenge; }, verifySignature(pubkey: string, signedEvent: VerifiedEvent): boolean { const stored = challenges.get(pubkey); if (!stored) return false; if (stored.expiresAt < Date.now()) { challenges.delete(pubkey); return false; } // Verify the event signature if (!verifyEvent(signedEvent)) return false; // Kind 22242 is the NIP-42 auth kind if (signedEvent.kind !== 22242) return false; if (signedEvent.pubkey !== pubkey) return false; // Check that the challenge tag matches const challengeTag = signedEvent.tags.find( (t) => t[0] === 'challenge' ); if (!challengeTag || challengeTag[1] !== stored.challenge) return false; challenges.delete(pubkey); return true; }, generateToken(pubkey: string, role: string): string { return jwt.sign({ pubkey, role }, JWT_SECRET, { expiresIn: '7d' }); }, async getRole(pubkey: string): Promise { const adminPubkeys = (process.env.ADMIN_PUBKEYS || '') .split(',') .map((p) => p.trim()) .filter(Boolean) .map((p) => { if (p.startsWith('npub1')) { try { const { data } = nip19.decode(p); return data as string; } catch { return p; } } return p; }); if (adminPubkeys.includes(pubkey)) return 'ADMIN'; const user = await prisma.user.findUnique({ where: { pubkey } }); if (user?.role === 'MODERATOR') return 'MODERATOR'; if (user?.role === 'ADMIN') return 'ADMIN'; return 'USER'; }, };