diff options
| author | nsfisis <nsfisis@gmail.com> | 2025-12-03 05:54:50 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2025-12-04 23:26:31 +0900 |
| commit | 4d30f46d4691c9aead411b893b1ab279b05d439c (patch) | |
| tree | b5c05015a4b367daf7d9d885f67a7fb1cbc0d0bf /pkgs/server/src/middleware/auth.ts | |
| parent | 0763153865e2157e0d06c946993dd8b235b06c83 (diff) | |
| download | kioku-4d30f46d4691c9aead411b893b1ab279b05d439c.tar.gz kioku-4d30f46d4691c9aead411b893b1ab279b05d439c.tar.zst kioku-4d30f46d4691c9aead411b893b1ab279b05d439c.zip | |
feat(auth): add auth middleware for JWT validation
Add middleware that validates JWT tokens from Authorization header
and sets authenticated user in request context. Includes helper
function getAuthUser() to retrieve user from context with proper
error handling.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Diffstat (limited to 'pkgs/server/src/middleware/auth.ts')
| -rw-r--r-- | pkgs/server/src/middleware/auth.ts | 65 |
1 files changed, 65 insertions, 0 deletions
diff --git a/pkgs/server/src/middleware/auth.ts b/pkgs/server/src/middleware/auth.ts new file mode 100644 index 0000000..c295834 --- /dev/null +++ b/pkgs/server/src/middleware/auth.ts @@ -0,0 +1,65 @@ +import type { Context, Next } from "hono"; +import { verify } from "hono/jwt"; +import { Errors } from "./error-handler"; + +const JWT_SECRET = process.env.JWT_SECRET; +if (!JWT_SECRET) { + throw new Error("JWT_SECRET environment variable is required"); +} + +export interface AuthUser { + id: string; +} + +interface JWTPayload { + sub: string; + iat: number; + exp: number; +} + +/** + * Auth middleware that validates JWT tokens from Authorization header + * Sets the authenticated user in context variables + */ +export async function authMiddleware(c: Context, next: Next) { + const authHeader = c.req.header("Authorization"); + + if (!authHeader) { + throw Errors.unauthorized("Missing Authorization header", "MISSING_AUTH"); + } + + if (!authHeader.startsWith("Bearer ")) { + throw Errors.unauthorized( + "Invalid Authorization header format", + "INVALID_AUTH_FORMAT", + ); + } + + const token = authHeader.slice(7); + + try { + const payload = (await verify(token, JWT_SECRET)) as unknown as JWTPayload; + + const user: AuthUser = { + id: payload.sub, + }; + + c.set("user", user); + + await next(); + } catch { + throw Errors.unauthorized("Invalid or expired token", "INVALID_TOKEN"); + } +} + +/** + * Helper function to get the authenticated user from context + * Throws if user is not authenticated + */ +export function getAuthUser(c: Context): AuthUser { + const user = c.get("user") as AuthUser | undefined; + if (!user) { + throw Errors.unauthorized("Not authenticated", "NOT_AUTHENTICATED"); + } + return user; +} |
