import { Hono } from "hono"; import { setCookie } from "hono/cookie"; import { Effect, ManagedRuntime } from "effect"; import { AuthService } from "../services/AuthService.js"; import { AppRuntimeContext } from "../middleware/authMiddleware.js"; import { AuthResponse, LoginRequest, RegisterRequest, } from "../../../shared/types.js"; // Cookie configuration const COOKIE_OPTIONS = { httpOnly: true, secure: process.env["NODE_ENV"] === "production", sameSite: "strict" as const, maxAge: 7 * 24 * 60 * 60, // 7 days in seconds path: "/", }; /** * Factory function to create auth routes with proper Effect-TS ManagedRuntime injection * @param runtime - The Effect-TS ManagedRuntime with all required services * @param authMiddleware - The configured auth middleware instance * @returns Configured Hono router instance */ export const createAuthRoutes = ( runtime: ManagedRuntime.ManagedRuntime, authMiddleware: ReturnType ) => { const authRouter = new Hono(); /** * POST /api/auth/register * Register a new user */ authRouter.post("/register", async (c) => { const body = await c.req.json(); const { username, email, password } = body; if (!username || !email || !password) { return c.json( { error: "VALIDATION_ERROR", message: "Username, email, and password are required", }, 400 ); } // Extract IP address and user agent const ipAddress = c.req.header("x-forwarded-for") || c.req.header("x-real-ip") || null; const userAgent = c.req.header("user-agent") || null; // Register user const registerEffect = Effect.gen(function* () { const authService = yield* AuthService; return yield* authService.register( username, email, password, ipAddress, userAgent ); }); const result = await runtime.runPromiseExit(registerEffect); if (result._tag === "Failure") { const cause = result.cause; if (cause._tag === "Fail") { const failureError = cause.error; if (failureError && typeof failureError === "object" && "_tag" in failureError) { const typedError = failureError as { _tag: string; message: string; field?: string; }; switch (typedError._tag) { case "ValidationError": return c.json( { error: "VALIDATION_ERROR", field: typedError.field, message: typedError.message, }, 400 ); case "AuthError": return c.json( { error: "AUTH_ERROR", message: typedError.message, }, 400 ); case "DatabaseError": return c.json( { error: "INTERNAL_ERROR", message: "Database error occurred", }, 500 ); } } } return c.json( { error: "INTERNAL_ERROR", message: "An unexpected error occurred", }, 500 ); } const user = result.value; return c.json( { user: { id: user.id, username: user.username, email: user.email, emailVerified: user.emailVerified, }, }, 201 ); }); /** * POST /api/auth/login * Login user and create session */ authRouter.post("/login", async (c) => { const body = await c.req.json(); const { email, password } = body; if (!email || !password) { return c.json( { error: "VALIDATION_ERROR", message: "Email and password are required", }, 400 ); } // Extract IP address and user agent const ipAddress = c.req.header("x-forwarded-for") || c.req.header("x-real-ip") || null; const userAgent = c.req.header("user-agent") || null; // Login user const loginEffect = Effect.gen(function* () { const authService = yield* AuthService; return yield* authService.login(email, password, ipAddress, userAgent); }); const result = await runtime.runPromiseExit(loginEffect); if (result._tag === "Failure") { const cause = result.cause; if (cause._tag === "Fail") { const failureError = cause.error; if (failureError && typeof failureError === "object" && "_tag" in failureError) { const typedError = failureError as { _tag: string; message: string; }; switch (typedError._tag) { case "AuthError": return c.json( { error: "AUTH_ERROR", message: typedError.message, }, 401 ); case "DatabaseError": return c.json( { error: "INTERNAL_ERROR", message: "Database error occurred", }, 500 ); } } } return c.json( { error: "INTERNAL_ERROR", message: "An unexpected error occurred", }, 500 ); } const loginResult = result.value; // Set httpOnly cookie with token setCookie(c, "auth_token", loginResult.token, COOKIE_OPTIONS); const response: AuthResponse = { user: { id: loginResult.user.id, username: loginResult.user.username, email: loginResult.user.email, emailVerified: loginResult.user.emailVerified, }, token: loginResult.token, }; return c.json(response, 200); }); /** * POST /api/auth/logout * Logout user and revoke session * Requires authentication */ authRouter.post("/logout", authMiddleware, async (c) => { const user = c.get("user"); const session = c.get("session"); // Logout user const logoutEffect = Effect.gen(function* () { const authService = yield* AuthService; return yield* authService.logout(session.id, user.id); }); const result = await runtime.runPromiseExit(logoutEffect); if (result._tag === "Failure") { const cause = result.cause; if (cause._tag === "Fail") { const failureError = cause.error; if (failureError && typeof failureError === "object" && "_tag" in failureError) { const typedError = failureError as { _tag: string; message: string; }; if (typedError._tag === "DatabaseError") { return c.json( { error: "INTERNAL_ERROR", message: "Database error occurred", }, 500 ); } } } return c.json( { error: "INTERNAL_ERROR", message: "An unexpected error occurred", }, 500 ); } // Clear cookie setCookie(c, "auth_token", "", { ...COOKIE_OPTIONS, maxAge: 0, }); return c.json({ message: "Logged out successfully" }, 200); }); /** * GET /api/auth/me * Get current authenticated user * Requires authentication */ authRouter.get("/me", authMiddleware, async (c) => { try { const user = c.get("user"); return c.json( { user: { id: user.id, username: user.username, email: user.email, emailVerified: user.emailVerified, createdAt: user.createdAt, lastLoginAt: user.lastLoginAt, }, }, 200 ); } catch (error) { return c.json( { error: "INTERNAL_ERROR", message: "An unexpected error occurred", }, 500 ); } }); return authRouter; };