Skip to content
File

Blob: src/worker/middleware/auth.ts

typescript82 lines
1import { createMiddleware } from "hono/factory";
2import { eq } from "drizzle-orm";
3import { users } from "@/worker/db/d1/schema";
4import type { Db } from "@/worker/db/d1/client";
5import { verifyAccessToken } from "@/worker/lib/auth";
6import { createLogger } from "@/worker/lib/logger";
7 
8const log = createLogger("auth.middleware");
9 
10type AuthVariables = {
11 user: typeof users.$inferSelect | null;
12 jwtPayload: { sub: string; jti: string } | null;
13};
14 
15export function extractBearerToken(header: string | undefined): string | null {
16 if (!header) return null;
17 const parts = header.split(" ");
18 if (parts.length !== 2 || parts[0] !== "Bearer") return null;
19 return parts[1];
20}
21 
22async function verifyAndLoadUser(
23 authorization: string | undefined,
24 env: Env,
25 db: Db,
26): Promise<{
27 user: typeof users.$inferSelect | null;
28 jwtPayload: { sub: string; jti: string } | null;
29}> {
30 const token = extractBearerToken(authorization);
31 if (!token) return { user: null, jwtPayload: null };
32 
33 log.debug("verify_token");
34 
35 try {
36 const { sub, jti } = await verifyAccessToken(token, env);
37 
38 const user = await db.select().from(users).where(eq(users.id, sub)).get();
39 
40 if (!user) {
41 log.debug("token_rejected", { reason: "user_not_found", userId: sub });
42 return { user: null, jwtPayload: null };
43 }
44 
45 log.debug("token_verified", { userId: sub });
46 return { user, jwtPayload: { sub, jti } };
47 } catch {
48 log.debug("token_rejected", { reason: "invalid_jwt" });
49 return { user: null, jwtPayload: null };
50 }
51}
52 
53export const requireAuth = createMiddleware<{
54 Bindings: Env;
55 Variables: AuthVariables & { db: Db };
56}>(async (c, next) => {
57 const db = c.get("db");
58 const authorization = c.req.header("authorization");
59 const { user, jwtPayload } = await verifyAndLoadUser(authorization, c.env, db);
60 
61 if (!user || !jwtPayload) {
62 return c.json({ error: "unauthorized", message: "Invalid or missing authentication token" }, 401);
63 }
64 
65 c.set("user", user);
66 c.set("jwtPayload", jwtPayload);
67 await next();
68});
69 
70export const optionalAuth = createMiddleware<{
71 Bindings: Env;
72 Variables: AuthVariables & { db: Db };
73}>(async (c, next) => {
74 const db = c.get("db");
75 const authorization = c.req.header("authorization");
76 const { user, jwtPayload } = await verifyAndLoadUser(authorization, c.env, db);
77 
78 c.set("user", user ?? null);
79 c.set("jwtPayload", jwtPayload ?? null);
80 await next();
81});