feat(auth): add generic OIDC login with mandatory groups

This commit is contained in:
2026-08-17 05:44:10 +02:00
parent 202822f3ba
commit 4fe51cbeb1
20 changed files with 1144 additions and 40 deletions
+23
View File
@@ -14,6 +14,7 @@ import type { LoadedAuthConfig } from "./auth/types.js";
import { createCurrentLocalUserRegistryResolver, type LocalUserRegistry } from "./auth/local-registry.js";
import { AuthSessionOperationalError, createFileAuthSessionStore, type AuthSessionStore, type AuthSessionValidity } from "./auth/session-store.js";
import { registerAuthRoutes } from "./auth/routes.js";
import { createOidcProtocol, type OidcProtocol } from "./auth/oidc-client.js";
import { sessionRoutes } from "./routes/sessions.js";
import { sqlRoutes } from "./routes/sql.js";
import { metaRoutes, type ListModelsFn } from "./routes/meta.js";
@@ -48,6 +49,7 @@ export interface BuildAppDeps {
piManagement?: PiManagementService;
localUserRegistry?: LocalUserRegistry;
authSessionStore?: AuthSessionStore;
oidcProtocol?: OidcProtocol;
}
export interface AppWithAuthSessionStore extends FastifyInstance {
@@ -192,6 +194,26 @@ export function buildApp(config: AppConfig, deps?: BuildAppDeps): FastifyInstanc
currentAuthConfigRevision: () => loaded.revision,
currentLocalUser: (subject) => localUserForSnapshot(loaded, subject),
});
const resolveOidcProtocol = (loaded: LoadedAuthConfig): OidcProtocol | undefined => {
if (deps?.oidcProtocol) return deps.oidcProtocol;
if (loaded.value.mode !== "oidc") return undefined;
const clientSecret = process.env.THT_OIDC_CLIENT_SECRET;
if (typeof clientSecret !== "string" || clientSecret.length === 0 || clientSecret.length > 4096 || /\p{Cc}/u.test(clientSecret)) {
return undefined;
}
try {
return createOidcProtocol({
issuer: loaded.value.oidc.issuer,
clientId: loaded.value.oidc.clientId,
clientSecret,
callbackUrl: new URL("/api/auth/oidc/callback", loaded.value.publicUrl).href,
scopes: loaded.value.oidc.scopes,
groupsClaim: loaded.value.oidc.groupsClaim,
});
} catch {
return undefined;
}
};
const authSessionStore = deps?.authSessionStore ?? (config.authMode === "local" || config.authMode === "oidc"
? createFileAuthSessionStore(config.authStateRoot, {
currentAuthConfigRevision: () => {
@@ -251,6 +273,7 @@ export function buildApp(config: AppConfig, deps?: BuildAppDeps): FastifyInstanc
sessionStore: authSessionStore,
localUserRegistry: deps?.localUserRegistry,
resolveLocalUserRegistry,
resolveOidcProtocol,
});
sessionRoutes(app, {
mgr, tht: tht as ThtRunner, hub, getSettings, readiness, listModels, workspaceRegistry,