aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/server/middleware/auth.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/server/middleware/auth.ts')
-rw-r--r--src/server/middleware/auth.ts65
1 files changed, 65 insertions, 0 deletions
diff --git a/src/server/middleware/auth.ts b/src/server/middleware/auth.ts
new file mode 100644
index 0000000..51b4d9d
--- /dev/null
+++ b/src/server/middleware/auth.ts
@@ -0,0 +1,65 @@
+import type { Context, Next } from "hono";
+import { verify } from "hono/jwt";
+import { Errors } from "./error-handler.js";
+
+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;
+}