diff options
Diffstat (limited to 'pkgs/server/src/routes/auth.ts')
| -rw-r--r-- | pkgs/server/src/routes/auth.ts | 66 |
1 files changed, 65 insertions, 1 deletions
diff --git a/pkgs/server/src/routes/auth.ts b/pkgs/server/src/routes/auth.ts index 3906d65..ed497b1 100644 --- a/pkgs/server/src/routes/auth.ts +++ b/pkgs/server/src/routes/auth.ts @@ -1,10 +1,17 @@ -import { createUserSchema } from "@kioku/shared"; +import { createUserSchema, loginSchema } from "@kioku/shared"; import * as argon2 from "argon2"; import { eq } from "drizzle-orm"; import { Hono } from "hono"; +import { sign } from "hono/jwt"; import { db, users } from "../db"; import { Errors } from "../middleware"; +const JWT_SECRET = process.env.JWT_SECRET; +if (!JWT_SECRET) { + throw new Error("JWT_SECRET environment variable is required"); +} +const ACCESS_TOKEN_EXPIRES_IN = 60 * 15; // 15 minutes + const auth = new Hono(); auth.post("/register", async (c) => { @@ -47,4 +54,61 @@ auth.post("/register", async (c) => { return c.json({ user: newUser }, 201); }); +auth.post("/login", async (c) => { + const body = await c.req.json(); + + const parsed = loginSchema.safeParse(body); + if (!parsed.success) { + throw Errors.validationError(parsed.error.issues[0]?.message); + } + + const { username, password } = parsed.data; + + // Find user by username + const [user] = await db + .select({ + id: users.id, + username: users.username, + passwordHash: users.passwordHash, + }) + .from(users) + .where(eq(users.username, username)) + .limit(1); + + if (!user) { + throw Errors.unauthorized( + "Invalid username or password", + "INVALID_CREDENTIALS", + ); + } + + // Verify password + const isPasswordValid = await argon2.verify(user.passwordHash, password); + if (!isPasswordValid) { + throw Errors.unauthorized( + "Invalid username or password", + "INVALID_CREDENTIALS", + ); + } + + // Generate JWT access token + const now = Math.floor(Date.now() / 1000); + const accessToken = await sign( + { + sub: user.id, + iat: now, + exp: now + ACCESS_TOKEN_EXPIRES_IN, + }, + JWT_SECRET, + ); + + return c.json({ + accessToken, + user: { + id: user.id, + username: user.username, + }, + }); +}); + export { auth }; |
