fix(auth): fail closed configuration compatibility
This commit is contained in:
@@ -1,12 +1,11 @@
|
||||
import type { FastifyRequest, FastifyReply } from "fastify";
|
||||
import { localPrincipal, type PrincipalContext, upstreamPrincipal } from "./principal.js";
|
||||
import type { AuthMode } from "./types.js";
|
||||
|
||||
declare module "fastify" {
|
||||
interface FastifyRequest { principal?: PrincipalContext }
|
||||
}
|
||||
|
||||
export function authPreHandler(mode: AuthMode) {
|
||||
export function authPreHandler(mode: "none" | "mock" | "upstream") {
|
||||
return async (req: FastifyRequest, reply: FastifyReply) => {
|
||||
if (mode === "none") {
|
||||
req.principal = localPrincipal();
|
||||
|
||||
+23
-10
@@ -59,7 +59,9 @@ const oidcSchema = z.strictObject({
|
||||
authorization: z.strictObject({ groupRoles: groupRolesSchema }),
|
||||
});
|
||||
|
||||
function readBoundedConfig(path: string): string {
|
||||
interface FileIdentity { dev: number; ino: number; size: number; mtimeMs: number }
|
||||
|
||||
function readBoundedConfig(path: string): { source: string; identity: FileIdentity } {
|
||||
let fd: number | undefined;
|
||||
try {
|
||||
fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
||||
@@ -68,7 +70,10 @@ function readBoundedConfig(path: string): string {
|
||||
const buffer = Buffer.allocUnsafe(MAX_AUTH_CONFIG_BYTES + 1);
|
||||
const bytesRead = readSync(fd, buffer, 0, buffer.length, 0);
|
||||
if (bytesRead > MAX_AUTH_CONFIG_BYTES) throw invalid();
|
||||
return new TextDecoder("utf-8", { fatal: true }).decode(buffer.subarray(0, bytesRead));
|
||||
return {
|
||||
source: new TextDecoder("utf-8", { fatal: true }).decode(buffer.subarray(0, bytesRead)),
|
||||
identity: { dev: info.dev, ino: info.ino, size: info.size, mtimeMs: info.mtimeMs },
|
||||
};
|
||||
} catch {
|
||||
throw invalid();
|
||||
} finally {
|
||||
@@ -105,7 +110,7 @@ function canonicalize(value: unknown): unknown {
|
||||
if (Array.isArray(value)) return value.map(canonicalize);
|
||||
if (value && typeof value === "object") {
|
||||
return Object.fromEntries(Object.entries(value as Record<string, unknown>)
|
||||
.sort(([left], [right]) => left.localeCompare(right))
|
||||
.sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)
|
||||
.map(([key, nested]) => [key, canonicalize(nested)]));
|
||||
}
|
||||
return value;
|
||||
@@ -139,13 +144,16 @@ function parseAuthenticationConfig(source: string): AuthenticationConfig {
|
||||
} catch { throw invalid(); }
|
||||
}
|
||||
|
||||
export function loadAuthenticationConfig(path: string): LoadedAuthConfig {
|
||||
function loadAuthenticationConfigWithIdentity(path: string): { loaded: LoadedAuthConfig; identity: FileIdentity } {
|
||||
if (typeof path !== "string" || path.length === 0 || path.trim() !== path || path.includes("\0")) throw invalid();
|
||||
const value = parseAuthenticationConfig(readBoundedConfig(path));
|
||||
return { value, revision: canonicalRevision(value), sourcePath: path };
|
||||
const read = readBoundedConfig(path);
|
||||
const value = parseAuthenticationConfig(read.source);
|
||||
return { loaded: { value, revision: canonicalRevision(value), sourcePath: path }, identity: read.identity };
|
||||
}
|
||||
|
||||
interface FileIdentity { dev: number; ino: number; size: number; mtimeMs: number }
|
||||
export function loadAuthenticationConfig(path: string): LoadedAuthConfig {
|
||||
return loadAuthenticationConfigWithIdentity(path).loaded;
|
||||
}
|
||||
|
||||
function fileIdentity(path: string): FileIdentity {
|
||||
try {
|
||||
@@ -164,9 +172,14 @@ export function createAuthenticationConfigProvider(path: string): Authentication
|
||||
return { current(): LoadedAuthConfig {
|
||||
const before = fileIdentity(path);
|
||||
if (cached && sameIdentity(cached.identity, before)) return cached.loaded;
|
||||
const loaded = loadAuthenticationConfig(path);
|
||||
cached = { identity: fileIdentity(path), loaded };
|
||||
return loaded;
|
||||
for (let attempt = 0; attempt < 2; attempt += 1) {
|
||||
const { loaded, identity } = loadAuthenticationConfigWithIdentity(path);
|
||||
if (sameIdentity(identity, fileIdentity(path))) {
|
||||
cached = { identity, loaded };
|
||||
return loaded;
|
||||
}
|
||||
}
|
||||
throw invalid();
|
||||
} };
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user