UNPKG

agentfootprint

Version:

The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.

161 lines • 6.93 kB
"use strict"; /** * strategies/config — the identity config object, its boot refusal, and the * mapping from environment variables onto it. * * One prefix, `IDENTITY_`. The library reads a plain object * ({@link IdentityConfig}); {@link identityConfigFromEnv} is the one mapping * from `process.env`-shaped input onto it, so an app that keeps its settings * elsewhere builds the object itself and loses nothing. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.readStrategy = exports.FOREIGN_PLATFORM_KEYS = exports.identityConfigFromEnv = exports.IdentityConfigError = void 0; const vocabulary_js_1 = require("./vocabulary.js"); /** * The deployment's identity configuration cannot be started. Raised at BOOT, * never per request: a wrong or missing setting must stop the install, not * turn into a door that is quietly open or permanently 503. * * Names the key, says why, says what to set. Never prints a secret or a claim * value — only names and URLs the operator wrote. */ class IdentityConfigError extends Error { code = 'ERR_IDENTITY_CONFIG'; /** The env name of the key at fault, when one key is. */ key; constructor(sentence, key) { super(`[identity] ${sentence}`); this.name = 'IdentityConfigError'; if (key !== undefined) this.key = key; } } exports.IdentityConfigError = IdentityConfigError; /** * Map `IDENTITY_*` environment variables onto an {@link IdentityConfig}. * * An empty value counts as unset (an env file's `KEY=`). Values are trimmed — * they are configuration, not ids. Refused here, by name: * * - a lower-case `identity_*` name (a likely typo that would otherwise be ignored); * - a key a LATER release reads (`IDENTITY_LDAP_URL`, `IDENTITY_CLIENT_ID`, …) * — "not in this release", never silently ignored; * - any other `IDENTITY_*` name — a typo must not become a default; * - a strategy name that is not one of the five; * - a value that does not parse (`IDENTITY_CLOCK_TOLERANCE_SECONDS=soon`). * * Skipped, never read or printed: the {@link FOREIGN_PLATFORM_KEYS} a hosting * platform injects (`IDENTITY_ENDPOINT`, `IDENTITY_HEADER`, …). * * @example * const choice = await identityFromConfig(identityConfigFromEnv(process.env), { * production: process.env.NODE_ENV === 'production', * }); */ function identityConfigFromEnv(env) { const config = {}; for (const name of Object.keys(env).sort()) { if (!name.toUpperCase().startsWith('IDENTITY_')) continue; // Another platform's own variables, never read and never printed. if (exports.FOREIGN_PLATFORM_KEYS.includes(name)) continue; if (!name.startsWith('IDENTITY_')) { throw new IdentityConfigError(`${name} looks like an identity setting in the wrong case. Settings are upper-case ` + `(${name.toUpperCase()}); a lower-case one would be silently ignored, so it is refused.`, name); } const raw = env[name]; if (raw === undefined || raw.trim().length === 0) continue; const key = vocabulary_js_1.KEYS_IN_THIS_RELEASE.find((k) => k.env === name); if (key === undefined) refuseUnread(name); else config[key.field] = readValue(key, raw.trim()); } return config; } exports.identityConfigFromEnv = identityConfigFromEnv; /** * `IDENTITY_*` names another platform injects into every process it hosts, and * which therefore are not this library's settings: Azure App Service, Functions * and Container Apps set `IDENTITY_ENDPOINT` and `IDENTITY_HEADER` (the managed * identity endpoint and its secret header) and may set `IDENTITY_API_VERSION`; * Service Fabric sets `IDENTITY_SERVER_THUMBPRINT`. They are skipped — never * read, never printed in a refusal or a banner — so * `identityConfigFromEnv(process.env)` works on those hosts. */ exports.FOREIGN_PLATFORM_KEYS = [ 'IDENTITY_ENDPOINT', 'IDENTITY_HEADER', 'IDENTITY_API_VERSION', 'IDENTITY_SERVER_THUMBPRINT', ]; function refuseUnread(name) { const later = vocabulary_js_1.KEYS_IN_A_LATER_RELEASE[name]; if (later !== undefined) { throw new IdentityConfigError(`${name} belongs to ${later}, which is not in this release. Remove it: a setting that ` + `does nothing would let this deployment believe it is doing something.`, name); } throw new IdentityConfigError(`${name} is not a setting this release reads — a misspelling must not become a default. ` + `The settings are: ${vocabulary_js_1.KEYS_IN_THIS_RELEASE.map((k) => k.env).join(', ')}.`, name); } function readValue(key, value) { switch (key.reading) { case 'text': return value; case 'strategy': return readStrategy(value); case 'clients': return readClients(value); case 'list': return value.split(/[\s,]+/).filter((v) => v.length > 0); case 'claim-path': return readClaimPath(value); case 'whole-number': return readWholeNumber(key.env, value); } } /** Refuse anything but the five names, listing them. */ function readStrategy(value) { if (typeof value === 'string' && vocabulary_js_1.IDENTITY_STRATEGIES.includes(value)) { return value; } throw new IdentityConfigError(`IDENTITY_STRATEGY is ${JSON.stringify(value)}, which is not a strategy. It is one of: ` + `${vocabulary_js_1.IDENTITY_STRATEGIES.join(', ')}. A typo must not become 'open'.`, 'IDENTITY_STRATEGY'); } exports.readStrategy = readStrategy; function readClients(value) { const clients = value.split(/[\s,]+/).filter((c) => c.length > 0); if (clients.includes('any')) { if (clients.length === 1) return 'any'; throw new IdentityConfigError(`IDENTITY_ALLOWED_CLIENTS lists 'any' beside client ids. Say 'any' alone (the client ` + `check is off) or list the clients.`, 'IDENTITY_ALLOWED_CLIENTS'); } return clients; } function readClaimPath(value) { if (!value.startsWith('[')) return value; let parsed; try { parsed = JSON.parse(value); } catch { parsed = undefined; } if (Array.isArray(parsed) && parsed.length > 0 && parsed.every((p) => typeof p === 'string' && p.length > 0)) { return parsed; } throw new IdentityConfigError(`IDENTITY_ROLES_CLAIM starts with '[' but is not a JSON array of claim names. Write a ` + `nested claim as a path, e.g. ["realm_access","roles"]; a plain name is written bare.`, 'IDENTITY_ROLES_CLAIM'); } function readWholeNumber(name, value) { if (/^\d{1,6}$/.test(value)) return Number(value); throw new IdentityConfigError(`${name} is a whole number (got '${value}').`, name); } //# sourceMappingURL=config.js.map