fix(workspace): isolate maintenance authentication surface
This commit is contained in:
@@ -176,7 +176,11 @@ function positiveDimension(value: string | undefined, fallback: number): number
|
|||||||
return parsed;
|
return parsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loadConfig(env: Record<string, string | undefined>): AppConfig {
|
export function loadConfig(
|
||||||
|
env: Record<string, string | undefined>,
|
||||||
|
options: { surface?: "application" | "workspace-maintenance" } = {},
|
||||||
|
): AppConfig {
|
||||||
|
const applicationSurface = options.surface !== "workspace-maintenance";
|
||||||
const defaultAuthConfigFile = "/run/thothii-auth/auth.yaml";
|
const defaultAuthConfigFile = "/run/thothii-auth/auth.yaml";
|
||||||
const authConfigFile = absoluteAuthPath(env.THT_AUTH_CONFIG_FILE ?? defaultAuthConfigFile, "file");
|
const authConfigFile = absoluteAuthPath(env.THT_AUTH_CONFIG_FILE ?? defaultAuthConfigFile, "file");
|
||||||
const authStateRoot = absoluteAuthPath(env.THT_AUTH_STATE_ROOT ?? "/data/auth", "state root");
|
const authStateRoot = absoluteAuthPath(env.THT_AUTH_STATE_ROOT ?? "/data/auth", "state root");
|
||||||
@@ -211,13 +215,13 @@ export function loadConfig(env: Record<string, string | undefined>): AppConfig {
|
|||||||
throw new Error(`unsupported AUTH_MODE=${requestedMode}; use none, mock, or upstream`);
|
throw new Error(`unsupported AUTH_MODE=${requestedMode}; use none, mock, or upstream`);
|
||||||
}
|
}
|
||||||
const nodeEnvironment = env.NODE_ENV ?? process.env.NODE_ENV;
|
const nodeEnvironment = env.NODE_ENV ?? process.env.NODE_ENV;
|
||||||
if ((requestedMode === "none" || requestedMode === "mock")
|
if (applicationSurface && (requestedMode === "none" || requestedMode === "mock")
|
||||||
&& nodeEnvironment !== "development" && nodeEnvironment !== "test") {
|
&& nodeEnvironment !== "development" && nodeEnvironment !== "test") {
|
||||||
throw new Error("production requires auth.yaml or AUTH_MODE=upstream");
|
throw new Error("production requires auth.yaml or AUTH_MODE=upstream");
|
||||||
}
|
}
|
||||||
authMode = requestedMode as "none" | "mock" | "upstream";
|
authMode = requestedMode as "none" | "mock" | "upstream";
|
||||||
}
|
}
|
||||||
const publicExposure = env.THOTH_PUBLIC_EXPOSURE === "true";
|
const publicExposure = applicationSurface && env.THOTH_PUBLIC_EXPOSURE === "true";
|
||||||
if (publicExposure && authMode !== "oidc" && authMode !== "upstream") {
|
if (publicExposure && authMode !== "oidc" && authMode !== "upstream") {
|
||||||
throw new Error("public exposure requires AUTH_MODE=upstream or configured OIDC behind a trusted proxy");
|
throw new Error("public exposure requires AUTH_MODE=upstream or configured OIDC behind a trusted proxy");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ async function readSessionInventory(dataRoot: string, workspaceId: string): Prom
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createProductionService(): WorkspacePreprocessingService {
|
function createProductionService(): WorkspacePreprocessingService {
|
||||||
const config = loadConfig(process.env);
|
const config = loadConfig(process.env, { surface: "workspace-maintenance" });
|
||||||
const registry = new WorkspaceRegistry(config.workspaceRegistry);
|
const registry = new WorkspaceRegistry(config.workspaceRegistry);
|
||||||
const workspaceSecretStore = new WorkspaceSecretStore({
|
const workspaceSecretStore = new WorkspaceSecretStore({
|
||||||
root: config.workspaceSecretStoreRoot,
|
root: config.workspaceSecretStoreRoot,
|
||||||
|
|||||||
@@ -97,6 +97,17 @@ test("loadConfig allows none and mock only outside production when auth.yaml is
|
|||||||
.toThrow("production requires auth.yaml or AUTH_MODE=upstream");
|
.toThrow("production requires auth.yaml or AUTH_MODE=upstream");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("workspace maintenance loads production configuration without an authentication surface", () => {
|
||||||
|
expect(loadConfig(
|
||||||
|
{ NODE_ENV: "production", THOTH_PUBLIC_EXPOSURE: "true" },
|
||||||
|
{ surface: "workspace-maintenance" },
|
||||||
|
)).toMatchObject({
|
||||||
|
authMode: "none",
|
||||||
|
authentication: undefined,
|
||||||
|
publicExposure: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test("local Compose profiles explicitly select the development auth environment", () => {
|
test("local Compose profiles explicitly select the development auth environment", () => {
|
||||||
for (const profile of ["../../deploy/compose.local.yaml", "../../docker-compose.dev.yml"]) {
|
for (const profile of ["../../deploy/compose.local.yaml", "../../docker-compose.dev.yml"]) {
|
||||||
expect(readFileSync(new URL(profile, import.meta.url), "utf8")).toMatch(/NODE_ENV:\s*development/);
|
expect(readFileSync(new URL(profile, import.meta.url), "utf8")).toMatch(/NODE_ENV:\s*development/);
|
||||||
|
|||||||
Reference in New Issue
Block a user