feat(16-01): update auth middleware and services to resolve userId

- verifyApiKey returns { userId } | null instead of boolean
- verifyAccessToken returns { userId } | null instead of boolean
- Add getOrCreateUser upsert function in auth.service
- Add getOrCreateUncategorized helper in category.service
- requireAuth sets userId on Hono context for all 3 auth methods
- Remove GET bypass: all API routes require auth for userId resolution
- Keep bypass for /api/auth and /api/health paths
This commit is contained in:
2026-04-05 10:34:19 +02:00
parent 91e93a31a5
commit b6d562f082
5 changed files with 144 additions and 196 deletions

View File

@@ -1,37 +1,46 @@
import type { Context, Next } from "hono";
import { getCookie } from "hono/cookie";
import {
getSession,
getUserCount,
refreshSession,
verifyApiKey,
} from "../services/auth.service";
import { getOrCreateUser, verifyApiKey } from "../services/auth.service";
import { getOrCreateUncategorized } from "../services/category.service";
import { verifyAccessToken } from "../services/oauth.service";
export async function requireAuth(c: Context, next: Next) {
const db = c.get("db");
// Check if any users exist at all
if (getUserCount(db) === 0) {
return c.json({ error: "setup_required" }, 403);
}
// Check API key first
const apiKey = c.req.header("X-API-Key");
if (apiKey) {
const valid = await verifyApiKey(db, apiKey);
if (valid) return next();
const result = await verifyApiKey(db, apiKey);
if (result) {
c.set("userId", result.userId);
return next();
}
return c.json({ error: "Invalid API key" }, 401);
}
// Check session cookie
const sessionId = getCookie(c, "gearbox_session");
if (sessionId) {
const session = getSession(db, sessionId);
if (session) {
// Refresh session expiry on use
refreshSession(db, sessionId);
// Check OAuth Bearer token
const authHeader = c.req.header("Authorization");
if (authHeader?.startsWith("Bearer ")) {
const token = authHeader.slice(7);
const result = await verifyAccessToken(db, token);
if (result) {
c.set("userId", result.userId);
return next();
}
return c.json({ error: "Invalid or expired token" }, 401);
}
// Check OIDC session (browser users via Logto)
try {
const { getAuth } = await import("@hono/oidc-auth");
const auth = await getAuth(c);
if (auth?.sub) {
const user = await getOrCreateUser(db, auth.sub);
await getOrCreateUncategorized(db, user.id);
c.set("userId", user.id);
return next();
}
} catch {
// OIDC not configured or session invalid — fall through
}
return c.json({ error: "Authentication required" }, 401);