diff --git a/apps/api/src/routes/auth.ts b/apps/api/src/routes/auth.ts new file mode 100644 index 0000000..6d1299f --- /dev/null +++ b/apps/api/src/routes/auth.ts @@ -0,0 +1,77 @@ +import { FastifyPluginAsync } from "fastify" +import argon2 from "argon2" +import { db } from "../db" +import { users } from "../db/schema" +import { eq } from "drizzle-orm" +import { LoginRequestSchema } from "@emberclone/shared" + +export default async function authRoutes(fastify: FastifyPluginAsync) { + fastify.post("/login", async (request, reply) => { + const body = LoginRequestSchema.parse(request.body) + + const [user] = await db + .select() + .from(users) + .where(eq(users.email, body.email)) + .limit(1) + + if (!user || !(await argon2.verify(user.passwordHash, body.password))) { + return reply.code(401).send({ message: "Invalid credentials" }) + } + + const token = fastify.jwt.sign({ + sub: user.id, + role: user.role + }) + + reply + .setCookie("token", token, { + path: "/", + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "lax" + }) + .send({ + user: { + id: user.id, + email: user.email, + name: user.name, + role: user.role + } + }) + }) + + fastify.get("/me", async (request, reply) => { + try { + await request.jwtVerify() + const { sub } = request.user as { sub: string } + + const [user] = await db + .select() + .from(users) + .where(eq(users.id, sub)) + .limit(1) + + if (!user) { + return reply.code(404).send({ message: "User not found" }) + } + + return { + id: user.id, + email: user.email, + name: user.name, + role: user.role + } + } catch (err) { + return reply.code(401).send({ message: "Unauthorized" }) + } + }) + + fastify.post("/logout", async (request, reply) => { + reply + .clearCookie("token", { + path: "/" + }) + .send({ message: "Logged out" }) + }) +} \ No newline at end of file