206 lines
7.8 KiB
TypeScript
206 lines
7.8 KiB
TypeScript
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 authRoutes(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 });
|
|
});
|
|
|
|
} |