Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

auth.ts

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

auth.tsBlame116 lines · 1 contributor
06d5ffeClaude1/**
2 * Auth middleware — reads session cookie, injects user into context.
05b973eClaude3 * Uses in-memory session cache to avoid DB roundtrip on every request.
06d5ffeClaude4 */
5
6import { createMiddleware } from "hono/factory";
7import { getCookie } from "hono/cookie";
8import { eq, gt } from "drizzle-orm";
9import { db } from "../db";
10import { sessions, users } from "../db/schema";
11import type { User } from "../db/schema";
05b973eClaude12import { sessionCache } from "../lib/cache";
06d5ffeClaude13
14export type AuthEnv = {
15 Variables: {
16 user: User | null;
17 };
18};
19
20/**
21 * Soft auth — sets c.get("user") to the current user or null.
22 * Does NOT block unauthenticated requests.
05b973eClaude23 * Caches session->user mapping for 2 minutes to avoid DB roundtrip per request.
06d5ffeClaude24 */
25export const softAuth = createMiddleware<AuthEnv>(async (c, next) => {
26 const token = getCookie(c, "session");
27 if (!token) {
28 c.set("user", null);
29 return next();
30 }
31
05b973eClaude32 // Check session cache first
33 const cachedUser = sessionCache.get(token) as User | null | undefined;
34 if (cachedUser !== undefined) {
35 c.set("user", cachedUser);
36 return next();
37 }
38
06d5ffeClaude39 try {
40 const [session] = await db
41 .select()
42 .from(sessions)
43 .where(eq(sessions.token, token))
44 .limit(1);
45
7298a17Claude46 if (
47 !session ||
48 new Date(session.expiresAt) < new Date() ||
49 session.requires2fa
50 ) {
05b973eClaude51 sessionCache.set(token, null as any);
06d5ffeClaude52 c.set("user", null);
53 return next();
54 }
55
56 const [user] = await db
57 .select()
58 .from(users)
59 .where(eq(users.id, session.userId))
60 .limit(1);
61
05b973eClaude62 // Cache the result (user or null)
63 sessionCache.set(token, (user || null) as any);
06d5ffeClaude64 c.set("user", user || null);
65 } catch {
66 c.set("user", null);
67 }
68
69 return next();
70});
71
72/**
73 * Hard auth — redirects to /login if not authenticated.
74 */
75export const requireAuth = createMiddleware<AuthEnv>(async (c, next) => {
76 const token = getCookie(c, "session");
77 if (!token) {
78 return c.redirect(`/login?redirect=${encodeURIComponent(c.req.path)}`);
79 }
80
81 try {
82 const [session] = await db
83 .select()
84 .from(sessions)
85 .where(eq(sessions.token, token))
86 .limit(1);
87
88 if (!session || new Date(session.expiresAt) < new Date()) {
89 return c.redirect(`/login?redirect=${encodeURIComponent(c.req.path)}`);
90 }
91
7298a17Claude92 // 2FA pending — route the user to the code prompt instead of letting
93 // them access protected pages.
94 if (session.requires2fa) {
95 return c.redirect(
96 `/login/2fa?redirect=${encodeURIComponent(c.req.path)}`
97 );
98 }
99
06d5ffeClaude100 const [user] = await db
101 .select()
102 .from(users)
103 .where(eq(users.id, session.userId))
104 .limit(1);
105
106 if (!user) {
107 return c.redirect(`/login?redirect=${encodeURIComponent(c.req.path)}`);
108 }
109
110 c.set("user", user);
111 } catch {
112 return c.redirect(`/login?redirect=${encodeURIComponent(c.req.path)}`);
113 }
114
115 return next();
116});