// routes.ts // // Authentication route handlers. import { z } from "zod"; import { contentTypes } from "../content-types"; import { httpCodes } from "../http-codes"; import { request } from "../request"; import type { Call, Result, Route } from "../types"; import { forgotPasswordInputParser, loginInputParser, registerInputParser, resetPasswordInputParser, } from "./types"; // Helper for JSON responses const jsonResponse = ( code: (typeof httpCodes.success)[keyof typeof httpCodes.success], data: object, ): Result => ({ code, contentType: contentTypes.application.json, result: JSON.stringify(data), }); const errorResponse = ( code: (typeof httpCodes.clientErrors)[keyof typeof httpCodes.clientErrors], error: string, ): Result => ({ code, contentType: contentTypes.application.json, result: JSON.stringify({ error }), }); // POST /auth/login const loginHandler = async (call: Call): Promise => { try { const body = call.request.body; const { email, password } = loginInputParser.parse(body); const result = await request.auth.login(email, password, "cookie", { userAgent: call.request.get("User-Agent"), ipAddress: call.request.ip, }); if (!result.success) { return errorResponse( httpCodes.clientErrors.Unauthorized, result.error, ); } return jsonResponse(httpCodes.success.OK, { token: result.token, user: { id: result.user.id, email: result.user.email, displayName: result.user.displayName, }, }); } catch (error) { if (error instanceof z.ZodError) { return errorResponse( httpCodes.clientErrors.BadRequest, "Invalid input", ); } throw error; } }; // POST /auth/logout const logoutHandler = async (call: Call): Promise => { const token = request.auth.extractToken(call.request); if (token) { await request.auth.logout(token); } return jsonResponse(httpCodes.success.OK, { message: "Logged out" }); }; // POST /auth/register const registerHandler = async (call: Call): Promise => { try { const body = call.request.body; const { email, password, displayName } = registerInputParser.parse(body); const result = await request.auth.register( email, password, displayName, ); if (!result.success) { return errorResponse(httpCodes.clientErrors.Conflict, result.error); } // TODO: Send verification email with result.verificationToken // For now, log it for development console.log( `[AUTH] Verification token for ${email}: ${result.verificationToken}`, ); return jsonResponse(httpCodes.success.Created, { message: "Registration successful. Please check your email to verify your account.", user: { id: result.user.id, email: result.user.email, }, }); } catch (error) { if (error instanceof z.ZodError) { return errorResponse( httpCodes.clientErrors.BadRequest, "Invalid input", ); } throw error; } }; // POST /auth/forgot-password const forgotPasswordHandler = async (call: Call): Promise => { try { const body = call.request.body; const { email } = forgotPasswordInputParser.parse(body); const result = await request.auth.createPasswordResetToken(email); // Always return success (don't reveal if email exists) if (result) { // TODO: Send password reset email console.log( `[AUTH] Password reset token for ${email}: ${result.token}`, ); } return jsonResponse(httpCodes.success.OK, { message: "If an account exists with that email, a password reset link has been sent.", }); } catch (error) { if (error instanceof z.ZodError) { return errorResponse( httpCodes.clientErrors.BadRequest, "Invalid input", ); } throw error; } }; // POST /auth/reset-password const resetPasswordHandler = async (call: Call): Promise => { try { const body = call.request.body; const { token, password } = resetPasswordInputParser.parse(body); const result = await request.auth.resetPassword(token, password); if (!result.success) { return errorResponse( httpCodes.clientErrors.BadRequest, result.error, ); } return jsonResponse(httpCodes.success.OK, { message: "Password has been reset. You can now log in with your new password.", }); } catch (error) { if (error instanceof z.ZodError) { return errorResponse( httpCodes.clientErrors.BadRequest, "Invalid input", ); } throw error; } }; // GET /auth/verify-email?token=xxx const verifyEmailHandler = async (call: Call): Promise => { const url = new URL(call.path, "http://localhost"); const token = url.searchParams.get("token"); if (!token) { return errorResponse( httpCodes.clientErrors.BadRequest, "Missing token", ); } const result = await request.auth.verifyEmail(token); if (!result.success) { return errorResponse(httpCodes.clientErrors.BadRequest, result.error); } return jsonResponse(httpCodes.success.OK, { message: "Email verified successfully. You can now log in.", }); }; // Export routes const authRoutes: Route[] = [ { path: "/auth/login", methods: ["POST"], handler: loginHandler }, { path: "/auth/logout", methods: ["POST"], handler: logoutHandler }, { path: "/auth/register", methods: ["POST"], handler: registerHandler }, { path: "/auth/forgot-password", methods: ["POST"], handler: forgotPasswordHandler, }, { path: "/auth/reset-password", methods: ["POST"], handler: resetPasswordHandler, }, { path: "/auth/verify-email", methods: ["GET"], handler: verifyEmailHandler, }, ]; export { authRoutes };