fix(auth): address Task 13 deployment review findings

This commit is contained in:
2026-08-17 21:31:25 +02:00
parent 9558eaa508
commit 7e52df2702
22 changed files with 1279 additions and 82 deletions
+15
View File
@@ -82,6 +82,12 @@ export function authenticateSession(deps: AuthDependencies): preHandlerHookHandl
const snapshot = captureAuthConfigSnapshot(request, deps.authentication);
if (isPublicRoute(request)) return;
const operator = loopbackMaintenancePrincipal(request);
if (operator) {
request.principal = operator;
return;
}
if (legacy) {
await legacy(request, reply);
if (reply.sent || !STATE_CHANGING_METHODS.has(request.method)) return;
@@ -138,6 +144,15 @@ export function authenticateSession(deps: AuthDependencies): preHandlerHookHandl
};
}
function loopbackMaintenancePrincipal(request: FastifyRequest): PrincipalContext | undefined {
if (request.ip !== "127.0.0.1" && request.ip !== "::1" && request.ip !== "::ffff:127.0.0.1") return undefined;
if (singleHeader(request.headers["x-thoth-principal-issuer"]) !== "tht"
|| singleHeader(request.headers["x-thoth-principal-subject"]) !== "tht-maintenance"
|| singleHeader(request.headers["x-thoth-principal-display-name"]) !== "Tht maintenance"
|| singleHeader(request.headers["x-thoth-is-admin"]) !== "1") return undefined;
return upstreamPrincipal(request.headers);
}
export function requireCsrf(request: FastifyRequest, reply: FastifyReply): true | FastifyReply {
const expectedOrigin = request.authPublicOrigin;
const token = request.authSessionToken;
+1 -1
View File
@@ -173,7 +173,7 @@ function validateFilename(filename: string, allowClaim = false, allowOidcSlot =
}
function safeThtExecutable(value: string | undefined, pathStyle: AuthStoragePathStyle): string {
const executable = value ?? process.env.THT_BIN ?? "tht";
const executable = value ?? process.env.THT_AUTH_STORAGE_BIN ?? process.env.THT_BIN ?? "tht";
if (typeof executable !== "string" || executable.length === 0 || /[\u0000-\u001f\u007f]/.test(executable)) throw invalid();
if (executable === "tht" || (pathStyle === "windows" && executable === "tht.exe")) return executable;
const paths = pathStyle === "windows" ? win32 : posix;
+3 -2
View File
@@ -1,5 +1,6 @@
import { buildApp, type AppWithAuthSessionStore } from "./app.js";
import { loadConfig } from "./config.js";
import { formatStartupFailure } from "./startup-error.js";
const config = loadConfig(process.env);
const app = buildApp(config) as AppWithAuthSessionStore;
@@ -17,7 +18,7 @@ async function start(): Promise<void> {
console.log(`backend listening on ${address}`);
}
void start().catch(() => {
console.error("backend startup failed");
void start().catch((error: unknown) => {
console.error(formatStartupFailure(error));
process.exitCode = 1;
});
+12
View File
@@ -0,0 +1,12 @@
const STARTUP_CAUSES = new Set([
"auth_config_invalid",
"auth_session_store_invalid",
"workspace_registry_invalid",
]);
/** Return one bounded machine cause; never include the original error text or stack. */
export function formatStartupFailure(error: unknown): string {
const message = error instanceof Error ? error.message : "";
const cause = STARTUP_CAUSES.has(message) ? message : "startup_unknown";
return `backend startup failed: ${cause}`;
}