fix(auth): bind current local registry and CORS

This commit is contained in:
2026-08-17 00:34:58 +02:00
parent c34e01e9b9
commit 94c2cd3709
15 changed files with 514 additions and 87 deletions
+50 -20
View File
@@ -2,7 +2,7 @@ import Fastify, { type FastifyInstance } from "fastify";
import cors from "@fastify/cors";
import cookie from "@fastify/cookie";
import rateLimit from "@fastify/rate-limit";
import { dirname, join } from "node:path";
import { join } from "node:path";
import { tmpdir } from "node:os";
import type { AppConfig } from "./config.js";
import { ThtRunner } from "./tht/tht-runner.js";
@@ -10,8 +10,9 @@ import { PiProcessManager } from "./pi/pi-process-manager.js";
import { SseHub } from "./sse/sse-hub.js";
import { authenticateSession } from "./auth/auth.js";
import type { PrincipalContext } from "./auth/principal.js";
import { createLocalUserRegistry, type LocalUserRegistry } from "./auth/local-registry.js";
import { createFileAuthSessionStore, type AuthSessionStore } from "./auth/session-store.js";
import type { LoadedAuthConfig } from "./auth/types.js";
import { createCurrentLocalUserRegistryResolver, type LocalUserRegistry } from "./auth/local-registry.js";
import { AuthSessionOperationalError, createFileAuthSessionStore, type AuthSessionStore } from "./auth/session-store.js";
import { registerAuthRoutes } from "./auth/routes.js";
import { sessionRoutes } from "./routes/sessions.js";
import { sqlRoutes } from "./routes/sql.js";
@@ -66,10 +67,20 @@ export function buildApp(config: AppConfig, deps?: BuildAppDeps): FastifyInstanc
installationId: config.workspaceRegistry.installationId,
});
// Allow any origin in dev/e2e; tighten in production via config if needed.
const cookieAuth = config.authMode === "local" || config.authMode === "oidc";
app.register(cors, {
origin: true,
credentials: true,
origin: cookieAuth
? (origin, callback) => {
try {
const allowed = new URL(config.authentication?.current().value.publicUrl ?? "").origin;
const requested = origin === undefined ? undefined : new URL(origin).origin;
callback(null, requested === allowed ? allowed : false);
} catch {
callback(null, false);
}
}
: true,
credentials: cookieAuth,
methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
});
// Cookie parsing and the rate-limit plugin must precede every auth/application route.
@@ -150,22 +161,40 @@ export function buildApp(config: AppConfig, deps?: BuildAppDeps): FastifyInstanc
const piManagement = deps?.piManagement ?? createPiManagement(config, { listModels });
const maintenanceBarrier = deps?.maintenanceBarrier ?? new MaintenanceBarrier(config.maintenanceFile);
const loadedAuthentication = config.authentication?.current();
const configuredLocalRegistry = loadedAuthentication?.value.mode === "local"
? createLocalUserRegistry(join(dirname(loadedAuthentication.sourcePath), loadedAuthentication.value.local.usersFile))
const localRegistryResolver = deps?.localUserRegistry === undefined
? createCurrentLocalUserRegistryResolver()
: undefined;
const localUserRegistry = deps?.localUserRegistry ?? configuredLocalRegistry;
const resolveLocalUserRegistry = (loaded: LoadedAuthConfig) => {
return deps?.localUserRegistry ?? localRegistryResolver?.resolve(loaded);
};
const authSessionStore = deps?.authSessionStore ?? (config.authMode === "local" || config.authMode === "oidc"
? createFileAuthSessionStore(config.authStateRoot, {
currentAuthConfigRevision: () => config.authentication?.current().revision ?? "",
findLocalUser: async (subject) => {
if (config.authentication?.current().value.mode !== "local") return undefined;
const user = await localUserRegistry?.findBySubject(subject);
return user === undefined ? undefined : {
enabled: user.enabled,
authRevision: user.authRevision,
roles: user.roles,
};
currentAuthConfigRevision: () => {
try {
return config.authentication?.current().revision ?? "";
} catch {
throw new AuthSessionOperationalError();
}
},
currentLocalUser: async (subject) => {
try {
const loaded = config.authentication?.current();
if (!loaded || loaded.value.mode !== "local") return { revision: "", user: undefined };
const registry = resolveLocalUserRegistry(loaded);
if (!registry) throw new AuthSessionOperationalError();
const user = await registry.findBySubject(subject);
return {
revision: loaded.revision,
user: user === undefined ? undefined : {
enabled: user.enabled,
authRevision: user.authRevision,
roles: user.roles,
},
};
} catch (error) {
if (error instanceof AuthSessionOperationalError) throw error;
throw new AuthSessionOperationalError();
}
},
})
: undefined);
@@ -204,7 +233,8 @@ export function buildApp(config: AppConfig, deps?: BuildAppDeps): FastifyInstanc
authMode: config.authMode,
authentication: config.authentication,
sessionStore: authSessionStore,
localUserRegistry,
localUserRegistry: deps?.localUserRegistry,
resolveLocalUserRegistry,
});
sessionRoutes(app, {
mgr, tht: tht as ThtRunner, hub, getSettings, readiness, listModels, workspaceRegistry,