import type { Context, MiddlewareHandler } from 'hono'; import { getCookie } from 'hono/cookie'; import type { AppContext } from '../context'; import type { UserRow } from '../db/schema'; import { clientIp, fail, hostnameOf, isLocalHost, requestHost, requestProto, type Env } from '../http'; import { clearSessionCookie, loadSession, SESSION_COOKIE, setSessionCookie, touchSession } from './sessions'; /** Attach the signed-in person (if any) to every API request, and keep their session fresh. */ export function sessionMiddleware(ctx: AppContext): MiddlewareHandler { return async (c, next) => { c.set('user', null); c.set('session', null); const token = getCookie(c, SESSION_COOKIE); if (token) { const found = loadSession(ctx, token); if (found) { c.set('user', found.user); c.set('session', found.session); if (touchSession(ctx, found.session, clientIp(c, ctx))) setSessionCookie(c, ctx, token); } else { clearSessionCookie(c); } } await next(); }; } export function requireUser(c: Context): UserRow { const user = c.get('user'); if (!user) fail(401, 'signed_out', 'Please sign in to continue.'); return user; } /** Changing household data needs Full access; Read-only people can look but not touch. */ export function requireWrite(c: Context): UserRow { const user = requireUser(c); if (user.access !== 'full') fail(403, 'read_only', 'Your access is read-only, so this change isn’t available.'); return user; } export function requireAdmin(c: Context): UserRow { const user = requireUser(c); if (!user.isAdmin) fail(403, 'admin_only', 'Only an admin can do that.'); return user; } /** * Refuse password sign-in over plain HTTP from anywhere but this machine, so * passwords and cookies only ever travel encrypted. */ export function assertSecureSignIn(c: Context, ctx: AppContext): void { if (ctx.config.allowInsecureSignin) return; if (requestProto(c, ctx) === 'https') return; if (isLocalHost(hostnameOf(requestHost(c, ctx)))) return; const where = ctx.config.publicUrl ? `: ${ctx.config.publicUrl.origin}` : ''; fail(400, 'insecure_connection', `For your security, open Spendlight at its secure (https) address to sign in${where}.`); }