auth-yes/server/auth-session.ts
google-labs-jules[bot] 9988df3218 audit: verify 3-tier auth, rbac, and decouple server side effects
- Add `app` export and wrap startup logic behind `if (import.meta.main)`
- Extract `hono` middleware into `sdk/hono.ts` for clean separation
- Refactor module imports slightly to support in-memory native mocking (`db`, `valkey`, `spire_ffi`, `ratelimit`, `audit`)
- Implement comprehensive native Deno mock tests in `server/main.test.ts`
- Fix type checking across project files

Co-authored-by: mrteye <1945243+mrteye@users.noreply.github.com>
2026-08-22 00:29:19 +00:00

110 lines
3.0 KiB
TypeScript

import type { Context } from "jsr:@hono/hono@4";
import { getCookie } from "jsr:@hono/hono@4/cookie";
import { sqlWrapper } from "./db.ts";
import { valkey } from "./valkey.ts";
export interface AuthenticatedUser {
userId: string;
sessionId: string;
username: string;
}
/**
* Helper to get authenticated user from session cookie.
* Checks Valkey cache first, with automatic PostgreSQL sessions table fallback.
*/
export async function getAuthenticatedUser(
c: Context,
): Promise<AuthenticatedUser | null> {
const sessionId = getCookie(c, "session_id");
if (!sessionId) return null;
// 1. Try Valkey cache
try {
const sessionDataStr = await valkey.get(sessionId);
if (sessionDataStr) {
const sessionData = JSON.parse(sessionDataStr);
if (sessionData && sessionData.uuid) {
return {
userId: sessionData.uuid,
sessionId,
username: sessionData.username || "",
};
}
}
} catch (_err) {
// Valkey cache miss or connection hiccup - fallback to DB
}
// 2. Fallback to PostgreSQL sessions table
try {
const session = await sqlWrapper.sql`
SELECT s.user_id, s.expires_at, u.username
FROM sessions s
JOIN users u ON s.user_id = u.id
WHERE s.id = ${sessionId} AND s.expires_at > NOW()
`.then((res: any) => res[0]);
if (session) {
const username = session.username || "";
// Repopulate Valkey in background
try {
const ttlSeconds = Math.max(
1,
Math.floor(
(new Date(session.expires_at).getTime() - Date.now()) / 1000,
),
);
await valkey.setex(
sessionId,
ttlSeconds,
JSON.stringify({ uuid: session.user_id, username }),
);
} catch (_e) {}
return { userId: session.user_id, sessionId, username };
}
} catch (_err) {
return null;
}
return null;
}
/**
* Helper to check if user has global admin privileges.
* Strict check: Requires an explicit 'admin' grant on the Management Console
* or global role, or is the bootstrap root user.
*/
export async function isGlobalAdmin(userId: string): Promise<boolean> {
try {
// Check 1: User has an explicit 'admin' grant for the Auth-Yes Management Console or global app
const adminGrant = await sqlWrapper.sql`
SELECT g.id
FROM grants g
LEFT JOIN apps a ON g.app_id = a.id
WHERE g.user_id = ${userId}
AND g.role = 'admin'
AND (
a.spiffe_id = 'spiffe://system.local/auth-yes-management'
OR a.name = 'Auth-Yes Management Console'
OR g.app_id IS NULL
)
`.then((res: any) => res[0]);
if (adminGrant) return true;
// Check 2: First registered user in system fallback
const firstUser = await sqlWrapper.sql`
SELECT id FROM users ORDER BY created_at ASC LIMIT 1
`.then((res: any) => res[0]);
if (firstUser && firstUser.id === userId) {
return true;
}
} catch (err) {
console.error("[Auth API] isGlobalAdmin error:", err);
}
return false;
}