import { z } from "zod"; import { type Fastify } from "../types"; import * as privacyKit from "privacy-kit"; import { db } from "@/storage/db"; import { auth } from "@/app/auth/auth"; import { log } from "@/utils/log"; export function registerAuthRoutes(app: Fastify) { app.post('/v1/auth', { schema: { body: z.object({ publicKey: z.string(), challenge: z.string(), signature: z.string() }) } }, async (request, reply) => { const tweetnacl = (await import("tweetnacl")).default; const publicKey = privacyKit.decodeBase64(request.body.publicKey); const challenge = privacyKit.decodeBase64(request.body.challenge); const signature = privacyKit.decodeBase64(request.body.signature); const isValid = tweetnacl.sign.detached.verify(challenge, signature, publicKey); if (!isValid) { return reply.code(401).send({ error: 'Invalid signature' }); } // Create or update user in database const publicKeyHex = privacyKit.encodeHex(publicKey); const user = await db.account.upsert({ where: { publicKey: publicKeyHex }, update: { updatedAt: new Date() }, create: { publicKey: publicKeyHex } }); return reply.send({ success: true, token: await auth.createToken(user.id) }); }); app.post('/v1/auth/request', { schema: { body: z.object({ publicKey: z.string(), }), response: { 200: z.union([z.object({ state: z.literal('requested'), }), z.object({ state: z.literal('authorized'), token: z.string(), response: z.string() })]), 401: z.object({ error: z.literal('Invalid public key') }) } } }, async (request, reply) => { const tweetnacl = (await import("tweetnacl")).default; const publicKey = privacyKit.decodeBase64(request.body.publicKey); const isValid = tweetnacl.box.publicKeyLength === publicKey.length; if (!isValid) { return reply.code(401).send({ error: 'Invalid public key' }); } const publicKeyHex = privacyKit.encodeHex(publicKey); log({ module: 'auth-request' }, `Terminal auth request - publicKey hex: ${publicKeyHex}`); const answer = await db.terminalAuthRequest.upsert({ where: { publicKey: publicKeyHex }, update: {}, create: { publicKey: publicKeyHex } }); if (answer.response && answer.responseAccountId) { const token = await auth.createToken(answer.responseAccountId!, { session: answer.id }); return reply.send({ state: 'authorized', token: token, response: answer.response }); } return reply.send({ state: 'requested' }); }); // Approve auth request app.post('/v1/auth/response', { preHandler: app.authenticate, schema: { body: z.object({ response: z.string(), publicKey: z.string() }) } }, async (request, reply) => { log({ module: 'auth-response' }, `Auth response endpoint hit - user: ${request.userId}, publicKey: ${request.body.publicKey.substring(0, 20)}...`); const tweetnacl = (await import("tweetnacl")).default; const publicKey = privacyKit.decodeBase64(request.body.publicKey); const isValid = tweetnacl.box.publicKeyLength === publicKey.length; if (!isValid) { log({ module: 'auth-response' }, `Invalid public key length: ${publicKey.length}`); return reply.code(401).send({ error: 'Invalid public key' }); } const publicKeyHex = privacyKit.encodeHex(publicKey); log({ module: 'auth-response' }, `Looking for auth request with publicKey hex: ${publicKeyHex}`); const authRequest = await db.terminalAuthRequest.findUnique({ where: { publicKey: publicKeyHex } }); if (!authRequest) { log({ module: 'auth-response' }, `Auth request not found for publicKey: ${publicKeyHex}`); // Let's also check what auth requests exist const allRequests = await db.terminalAuthRequest.findMany({ take: 5, orderBy: { createdAt: 'desc' } }); log({ module: 'auth-response' }, `Recent auth requests in DB: ${JSON.stringify(allRequests.map(r => ({ id: r.id, publicKey: r.publicKey.substring(0, 20) + '...', hasResponse: !!r.response })))}`); return reply.code(404).send({ error: 'Request not found' }); } if (!authRequest.response) { await db.terminalAuthRequest.update({ where: { id: authRequest.id }, data: { response: request.body.response, responseAccountId: request.userId } }); } return reply.send({ success: true }); }); // Account auth request app.post('/v1/auth/account/request', { schema: { body: z.object({ publicKey: z.string(), }), response: { 200: z.union([z.object({ state: z.literal('requested'), }), z.object({ state: z.literal('authorized'), token: z.string(), response: z.string() })]), 401: z.object({ error: z.literal('Invalid public key') }) } } }, async (request, reply) => { const tweetnacl = (await import("tweetnacl")).default; const publicKey = privacyKit.decodeBase64(request.body.publicKey); const isValid = tweetnacl.box.publicKeyLength === publicKey.length; if (!isValid) { return reply.code(401).send({ error: 'Invalid public key' }); } const answer = await db.accountAuthRequest.upsert({ where: { publicKey: privacyKit.encodeHex(publicKey) }, update: {}, create: { publicKey: privacyKit.encodeHex(publicKey) } }); if (answer.response && answer.responseAccountId) { const token = await auth.createToken(answer.responseAccountId!); return reply.send({ state: 'authorized', token: token, response: answer.response }); } return reply.send({ state: 'requested' }); }); // Approve account auth request app.post('/v1/auth/account/response', { preHandler: app.authenticate, schema: { body: z.object({ response: z.string(), publicKey: z.string() }) } }, async (request, reply) => { const tweetnacl = (await import("tweetnacl")).default; const publicKey = privacyKit.decodeBase64(request.body.publicKey); const isValid = tweetnacl.box.publicKeyLength === publicKey.length; if (!isValid) { return reply.code(401).send({ error: 'Invalid public key' }); } const authRequest = await db.accountAuthRequest.findUnique({ where: { publicKey: privacyKit.encodeHex(publicKey) } }); if (!authRequest) { return reply.code(404).send({ error: 'Request not found' }); } if (!authRequest.response) { await db.accountAuthRequest.update({ where: { id: authRequest.id }, data: { response: request.body.response, responseAccountId: request.userId } }); } return reply.send({ success: true }); }); }