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