// Constants for the saltcorn-idp plugin. // // One source of truth for plugin metadata, route base paths, table names, and // signing parameters shared across the lib/ modules. Crypto byte-sizes live in // crypto.js; protocol/policy values live here. const PLUGIN_NAME = "saltcorn-idp"; const PLUGIN_VERSION = "0.0.1"; // Public OIDC/OAuth2 + machine endpoints live under this path and are // CSRF-exempt. Admin (browser, CSRF-protected) pages live under ADMIN_BASE_PATH. const IDP_BASE_PATH = "/idp"; const ADMIN_BASE_PATH = "/admin/idp"; // Defence-in-depth rate limit on admin mutation POSTs (on top of the role gate): // a generous per-session sliding window -- normal admin use / the gates never // approach it, but it caps automated abuse of a compromised admin session. const ADMIN_RATE_MAX = 200; const ADMIN_RATE_WINDOW_MS = 60 * 1000; // Well-known discovery + JWKS endpoints (relative to the server root). const WELL_KNOWN_OPENID = IDP_BASE_PATH + "/.well-known/openid-configuration"; // oidc-provider serves JWKS at the mount-relative /jwks (not under /.well-known); // the discovery document advertises this as jwks_uri. const JWKS_PATH = IDP_BASE_PATH + "/jwks"; // OIDC flow endpoints (served by oidc-provider via delegation) + the interaction // (login/consent) endpoint we host ourselves. const AUTH_PATH = IDP_BASE_PATH + "/auth"; const AUTH_RESUME_PATH = IDP_BASE_PATH + "/auth/:uid"; const TOKEN_PATH = IDP_BASE_PATH + "/token"; const USERINFO_PATH = IDP_BASE_PATH + "/me"; const INTERACTION_PATH = IDP_BASE_PATH + "/interaction/:uid"; const INTERACTION_CONFIRM_PATH = IDP_BASE_PATH + "/interaction/:uid/confirm"; // Plugin tables (all prefixed _idp_, created idempotently in onLoad). const TABLE_ENV = "_idp_env"; const TABLE_KEYS = "_idp_keys"; const TABLE_OIDC_STORE = "_idp_oidc_store"; const TABLE_GROUPS = "_idp_groups"; const TABLE_GROUP_MEMBERS = "_idp_group_members"; const TABLE_CLIENTS = "_idp_clients"; // Signing. const SIGNING_ALG = "RS256"; const RSA_MODULUS_BITS = 2048; // Key lifecycle states. const KEY_STATUS = { ACTIVE: "active", RETIRING: "retiring", RETIRED: "retired" }; // Signing-key rotation grace window. On rotation the prior ACTIVE key is marked // RETIRING with retire_after = now + this window; it stays in JWKS (so tokens it // signed still verify) until the window elapses, after which retireExpiredKeys() // flips it RETIRED (dropped from JWKS). Sized to comfortably exceed the longest // id_token lifetime so no live token is ever orphaned by rotation. const KEY_RETIRE_GRACE_MS = 24 * 60 * 60 * 1000; // LDAP (Phase 4). An instance enables LDAP by setting the port env var (so the // dev instances don't collide on one port); the directory tree mirrors Saltcorn // users (ou=people) and groups (ou=groups). const LDAP_BASE_DN = "dc=saltcorn,dc=local"; const LDAP_PEOPLE_OU = "ou=people," + LDAP_BASE_DN; const LDAP_GROUPS_OU = "ou=groups," + LDAP_BASE_DN; const LDAP_PORT_ENV = "SALTCORN_IDP_LDAP_PORT"; // Interface the LDAPS listener binds to. Defaults to loopback so LDAP is NOT // network-exposed unless an operator explicitly opts in (e.g. "0.0.0.0" to serve // LDAP clients on other hosts). const LDAP_HOST_ENV = "SALTCORN_IDP_LDAP_HOST"; const LDAP_DEFAULT_HOST = "127.0.0.1"; // LDAP DoS guards: cap total inbound bytes per connection (the BER parser has no // size limit) and the search-filter nesting depth (the filter parser recurses // without a depth bound). A multi-tenant deployment encodes the tenant as an // extra dc component (dc=,dc=saltcorn,dc=local); the bare base = default. const LDAP_MAX_MSG_BYTES = 256 * 1024; const LDAP_MAX_FILTER_DEPTH = 32; const TABLE_LDAP_SERVICE = "_idp_ldap_service"; // LDAP bind-retry: only the cluster primary binds the listener, so a transient // EADDRINUSE (e.g. the prior process's socket lingering across a fast restart) // would otherwise leave LDAP silently down with no fallback. Retry a bounded // number of times with linear backoff, then warn LOUDLY. Delay = base * attempt. const LDAP_BIND_MAX_ATTEMPTS = 5; const LDAP_BIND_RETRY_BASE_MS = 500; // Per-connection idle timeout: a connection that sends no data for this long is // destroyed, so an attacker cannot hoard the connection pool with idle/slow-loris // sockets (works with LDAP_MAX_MSG_BYTES, which bounds a never-completing message). const LDAP_IDLE_TIMEOUT_MS = 30 * 1000; // Cap the number of directory entries a single search loads/returns, bounding // memory; past this the search returns sizeLimitExceeded. (MVP loads users into // memory and filters there; a production build would push the filter to SQL.) const LDAP_MAX_SEARCH_RESULTS = 2000; // SAML (Phase 5). The IdP entityID is /saml; SSO + metadata under /idp/saml. const TABLE_SAML = "_idp_saml"; const TABLE_SAML_SPS = "_idp_saml_sps"; const SAML_METADATA_PATH = IDP_BASE_PATH + "/saml/metadata"; const SAML_SSO_PATH = IDP_BASE_PATH + "/saml/sso"; const SAML_SLO_PATH = IDP_BASE_PATH + "/saml/slo"; const SAML_INIT_PATH = IDP_BASE_PATH + "/saml/init"; // SAML DoS guards: cap the base64 SAMLRequest/SAMLResponse we accept (before // decoding) and the inflated XML size (deflate can expand ~1000:1, so an // unbounded inflateRawSync on an unauthenticated endpoint is a memory bomb). const SAML_MAX_MSG_B64_BYTES = 64 * 1024; const SAML_MAX_XML_BYTES = 256 * 1024; // AuthnContext class for password login + the signature algorithm we require on // signed SAML messages (RSA-SHA256). One source of truth for these URNs. const SAML_AUTHN_CONTEXT = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"; const SAML_SIG_ALG = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"; // Minimum acceptable xml-crypto version (2025 SAML-signature CVEs patched). const XML_CRYPTO_MIN = "6.1.2"; module.exports = { PLUGIN_NAME, PLUGIN_VERSION, IDP_BASE_PATH, ADMIN_BASE_PATH, ADMIN_RATE_MAX, ADMIN_RATE_WINDOW_MS, WELL_KNOWN_OPENID, JWKS_PATH, AUTH_PATH, AUTH_RESUME_PATH, TOKEN_PATH, USERINFO_PATH, INTERACTION_PATH, INTERACTION_CONFIRM_PATH, TABLE_ENV, TABLE_KEYS, TABLE_OIDC_STORE, TABLE_GROUPS, TABLE_GROUP_MEMBERS, TABLE_CLIENTS, SIGNING_ALG, RSA_MODULUS_BITS, KEY_STATUS, KEY_RETIRE_GRACE_MS, LDAP_BASE_DN, LDAP_PEOPLE_OU, LDAP_GROUPS_OU, LDAP_PORT_ENV, LDAP_HOST_ENV, LDAP_DEFAULT_HOST, LDAP_MAX_MSG_BYTES, LDAP_MAX_FILTER_DEPTH, TABLE_LDAP_SERVICE, LDAP_BIND_MAX_ATTEMPTS, LDAP_BIND_RETRY_BASE_MS, LDAP_IDLE_TIMEOUT_MS, LDAP_MAX_SEARCH_RESULTS, TABLE_SAML, TABLE_SAML_SPS, SAML_METADATA_PATH, SAML_SSO_PATH, SAML_SLO_PATH, SAML_INIT_PATH, SAML_MAX_MSG_B64_BYTES, SAML_MAX_XML_BYTES, SAML_AUTHN_CONTEXT, SAML_SIG_ALG, XML_CRYPTO_MIN };