fix(auth): make diagnostics bounded and portable
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import type { AuthDiagnostic, GroupCatalog } from "./group-catalog.js";
|
||||
import { isUsableAuthenticationSecret } from "./secret-policy.js";
|
||||
import { parseConfiguredTransportUrl } from "./url-policy.js";
|
||||
|
||||
const MAX_RESPONSE_BYTES = 1024 * 1024;
|
||||
@@ -34,10 +35,6 @@ function ambiguous(name: string): AuthDiagnostic {
|
||||
return diagnostic("oidc_mapped_group_ambiguous", "A configured authorization group is ambiguous.", name);
|
||||
}
|
||||
|
||||
function safeApiToken(value: string): boolean {
|
||||
return typeof value === "string" && value.length > 0 && value.length <= 16 * 1024 && !/\p{Cc}/u.test(value);
|
||||
}
|
||||
|
||||
function stableCompare(left: string, right: string): number {
|
||||
return left < right ? -1 : left > right ? 1 : 0;
|
||||
}
|
||||
@@ -143,10 +140,21 @@ type GroupResult = "present" | "missing" | "ambiguous" | "unauthorized" | "unrea
|
||||
function exactResult(name: string, parsed: unknown): GroupResult {
|
||||
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return "unreachable";
|
||||
const record = parsed as { results?: unknown; pagination?: unknown };
|
||||
if (!Array.isArray(record.results) || !record.pagination || typeof record.pagination !== "object"
|
||||
if (!Array.isArray(record.results) || record.results.length > 2
|
||||
|| !record.pagination || typeof record.pagination !== "object"
|
||||
|| Array.isArray(record.pagination)) return "unreachable";
|
||||
const next = (record.pagination as { next?: unknown }).next;
|
||||
if (next !== null && next !== undefined) return "ambiguous";
|
||||
if (!Object.prototype.hasOwnProperty.call(record.pagination, "next")) return "unreachable";
|
||||
const next = (record.pagination as { next: unknown }).next;
|
||||
if (next !== null) {
|
||||
if (typeof next !== "string" || next.length === 0 || next.length > 2048 || /\p{Cc}/u.test(next)) return "unreachable";
|
||||
try {
|
||||
const continuation = new URL(next);
|
||||
if (continuation.protocol !== "https:" || continuation.username || continuation.password || continuation.hash) return "unreachable";
|
||||
} catch {
|
||||
return "unreachable";
|
||||
}
|
||||
return "ambiguous";
|
||||
}
|
||||
const resultNames: string[] = [];
|
||||
for (const result of record.results) {
|
||||
if (!result || typeof result !== "object" || Array.isArray(result)
|
||||
@@ -161,7 +169,9 @@ function exactResult(name: string, parsed: unknown): GroupResult {
|
||||
export function createAuthentikGroupCatalog(options: AuthentikGroupCatalogOptions): GroupCatalog {
|
||||
const origin = parseConfiguredTransportUrl(options.baseUrl, { allowLoopbackHttp: false, originOnly: true });
|
||||
const fetchImplementation = options.fetch ?? globalThis.fetch;
|
||||
const valid = origin !== undefined && safeApiToken(options.apiToken) && typeof fetchImplementation === "function";
|
||||
const valid = origin !== undefined
|
||||
&& isUsableAuthenticationSecret("THT_AUTHENTIK_API_TOKEN", options.apiToken)
|
||||
&& typeof fetchImplementation === "function";
|
||||
|
||||
async function verify(name: string, signal: AbortSignal): Promise<GroupResult> {
|
||||
if (!origin || !valid || signal.aborted) return "unreachable";
|
||||
|
||||
Reference in New Issue
Block a user