diff options
| author | JustZvan <justzvan@justzvan.xyz> | 2026-02-06 12:16:40 +0100 |
|---|---|---|
| committer | JustZvan <justzvan@justzvan.xyz> | 2026-02-06 12:16:40 +0100 |
| commit | e904e9634548e47d611bdcbb88d7b180b927fd5f (patch) | |
| tree | 21aa5be08fc5b22585508c0263ee5ea4effcc593 /src/middleware/auth.ts | |
feat: initial commit!
Diffstat (limited to 'src/middleware/auth.ts')
| -rw-r--r-- | src/middleware/auth.ts | 201 |
1 files changed, 201 insertions, 0 deletions
diff --git a/src/middleware/auth.ts b/src/middleware/auth.ts new file mode 100644 index 0000000..f64ddfb --- /dev/null +++ b/src/middleware/auth.ts @@ -0,0 +1,201 @@ +import { Request, Response, NextFunction } from "express"; +import * as jose from "jose"; +import { verifyJwt } from "../account/jwt"; +import { logger } from "../lib/pino"; + +/** + * Extends Express Request interface to include authenticated user information. + * Used by both parent and device authentication middleware. + */ +declare module "express" { + interface Request { + user?: { + id: number; + type: "parent" | "child"; + }; + } +} + +interface ParentJwtPayload extends jose.JWTPayload { + type: "parent"; + id: number; +} + +interface ChildJwtPayload extends jose.JWTPayload { + type: "child"; + id: number; +} + +/** + * Middleware to authenticate parent users. + * Verifies JWT tokens and ensures they're for parent accounts. + */ +export async function authParent( + req: Request, + res: Response, + next: NextFunction, +) { + const authHeader = req.headers.authorization; + + if (!authHeader || !authHeader.startsWith("Bearer ")) { + logger.warn( + { path: req.path }, + "Parent auth: Missing or invalid authorization header", + ); + res.status(401).json({ + success: false, + reason: "Missing or invalid authorization header", + }); + return; + } + + const token = authHeader.substring(7); + + if (!token) { + logger.warn({ path: req.path }, "Parent auth: Empty token"); + res.status(401).json({ + success: false, + reason: "Missing authorization token", + }); + return; + } + + try { + const payload = (await verifyJwt( + token, + "urn:buddy:users", + )) as ParentJwtPayload; + + if (payload.type !== "parent") { + logger.warn( + { path: req.path, tokenType: payload.type }, + "Parent auth: Invalid token type", + ); + res.status(401).json({ + success: false, + reason: "Invalid token type", + }); + return; + } + + const userId = payload.id; + if (!userId || typeof userId !== "number") { + logger.error( + { path: req.path, userId }, + "Parent auth: Invalid user ID in token", + ); + res.status(401).json({ + success: false, + reason: "Invalid token payload", + }); + return; + } + + req.user = { + id: userId, + type: "parent", + }; + + logger.debug( + { path: req.path, userId }, + "Parent authenticated successfully", + ); + next(); + } catch (e) { + logger.warn( + { error: e, path: req.path }, + "Parent auth: Token verification failed", + ); + res.status(401).json({ + success: false, + reason: "Invalid or expired token", + }); + } +} + +/** + * Middleware to authenticate child devices. + * Verifies JWT tokens and ensures they're for device accounts. + */ +export async function authDevice( + req: Request, + res: Response, + next: NextFunction, +) { + const authHeader = req.headers.authorization; + + if (!authHeader || !authHeader.startsWith("Bearer ")) { + logger.warn( + { path: req.path }, + "Device auth: Missing or invalid authorization header", + ); + res.status(401).json({ + success: false, + reason: "Missing or invalid authorization header", + }); + return; + } + + const token = authHeader.substring(7); + + if (!token) { + logger.warn({ path: req.path }, "Device auth: Empty token"); + res.status(401).json({ + success: false, + reason: "Missing authorization token", + }); + return; + } + + try { + const payload = (await verifyJwt( + token, + "urn:buddy:devices", + )) as ChildJwtPayload; + + if (payload.type !== "child") { + logger.warn( + { path: req.path, tokenType: payload.type }, + "Device auth: Invalid token type", + ); + res.status(401).json({ + success: false, + reason: "Invalid token type", + }); + return; + } + + const deviceId = payload.id; + if (!deviceId || typeof deviceId !== "number") { + logger.error( + { path: req.path, deviceId }, + "Device auth: Invalid device ID in token", + ); + res.status(401).json({ + success: false, + reason: "Invalid token payload", + }); + return; + } + + req.user = { + id: deviceId, + type: "child", + }; + + logger.debug( + { path: req.path, deviceId }, + "Device authenticated successfully", + ); + next(); + } catch (e) { + logger.warn( + { error: e, path: req.path }, + "Device auth: Token verification failed", + ); + res.status(401).json({ + success: false, + reason: "Invalid or expired token", + }); + } +} |