import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; import { phishingDetector } from './lib/phishing-detector'; interface UrlCheckRequest { url: string; } interface PhishingReportRequest { url: string; pageTitle: string; tabId: number; timestamp: number; reason: string; heuristics: Record; } export async function extensionRoutes(fastify: FastifyInstance) { fastify.post('/url-check', async (request: FastifyRequest, reply: FastifyReply) => { const authReq = request as FastifyRequest & { user?: { id: string; tier?: string } }; const userId = authReq.user?.id; if (!userId) { return reply.code(401).send({ error: 'Authentication required' }); } const body = request.body as UrlCheckRequest; if (!body.url) { return reply.code(400).send({ error: 'url is required' }); } try { const url = new URL(body.url); const heuristic = phishingDetector.analyzeUrl(body.url); const threats = heuristic.threats.map((t) => ({ type: t.type, severity: t.severity, source: t.source, description: t.description, })); return reply.send({ url: body.url, domain: url.hostname, verdict: heuristic.verdict, confidence: heuristic.score / 100, threats, timestamp: Date.now(), }); } catch (error) { const message = error instanceof Error ? error.message : 'URL check failed'; return reply.code(500).send({ error: message }); } }); fastify.post('/phishing-report', async (request: FastifyRequest, reply: FastifyReply) => { const authReq = request as FastifyRequest & { user?: { id: string } }; const userId = authReq.user?.id; if (!userId) { return reply.code(401).send({ error: 'Authentication required' }); } const body = request.body as PhishingReportRequest; try { fastify.log.info({ url: body.url, userId, reason: body.reason }, 'Phishing report received'); return reply.send({ success: true, reportId: `report_${Date.now()}_${userId}`, timestamp: new Date().toISOString(), }); } catch (error) { const message = error instanceof Error ? error.message : 'Report submission failed'; return reply.code(500).send({ error: message }); } }); fastify.post('/auth', async (request: FastifyRequest, reply: FastifyReply) => { const authHeader = request.headers.authorization; if (!authHeader?.startsWith('Bearer ')) { return reply.code(401).send({ error: 'Bearer token required' }); } const token = authHeader.slice(7); try { const result = await validateExtensionToken(token, fastify); return reply.send(result); } catch (error) { const message = error instanceof Error ? error.message : 'Authentication failed'; return reply.code(401).send({ error: message }); } }); fastify.get('/stats', async (request: FastifyRequest, reply: FastifyReply) => { const authReq = request as FastifyRequest & { user?: { id: string } }; const userId = authReq.user?.id; if (!userId) { return reply.code(401).send({ error: 'Authentication required' }); } try { const today = new Date().toDateString(); return reply.send({ threatsBlockedToday: 0, urlsCheckedToday: 0, lastSyncAt: new Date().toISOString(), syncDate: today, }); } catch (error) { const message = error instanceof Error ? error.message : 'Stats retrieval failed'; return reply.code(500).send({ error: message }); } }); fastify.post('/exposures/check', async (request: FastifyRequest, reply: FastifyReply) => { const authReq = request as FastifyRequest & { user?: { id: string } }; const userId = authReq.user?.id; if (!userId) { return reply.code(401).send({ error: 'Authentication required' }); } const body = request.body as { domain: string }; if (!body.domain) { return reply.code(400).send({ error: 'domain is required' }); } try { const { prisma } = await import('@shieldai/db'); const exposures = await prisma.exposure.findMany({ where: { alert: { some: { userId, }, }, }, select: { dataSource: true, breachName: true, metadata: true, }, take: 10, }); const domainLower = body.domain.toLowerCase(); const relevantExposures = exposures.filter((e) => { const meta = e.metadata as Record | null; return meta?.domain?.toLowerCase() === domainLower || String(e.breachName).toLowerCase().includes(domainLower); }); return reply.send({ exposed: relevantExposures.length > 0, sources: relevantExposures.map((e) => e.dataSource), count: relevantExposures.length, }); } catch (error) { const message = error instanceof Error ? error.message : 'Exposure check failed'; return reply.code(500).send({ error: message }); } }); } async function validateExtensionToken( token: string, fastify: FastifyInstance ): Promise<{ userId: string; tier: string }> { try { const { prisma } = await import('@shieldai/db'); const session = await prisma.session.findFirst({ where: { token }, include: { user: { include: { subscription: { where: { status: 'active' }, take: 1, }, }, }, }, }); if (!session) { throw new Error('Session not found'); } const tier = session.user.subscription[0]?.tier || 'basic'; return { userId: session.userId, tier: tier.toLowerCase(), }; } catch (error) { if (error instanceof Error && error.message === 'Session not found') { throw error; } fastify.log.warn({ error }, 'Extension token validation failed'); throw new Error('Token validation failed'); } }