sc-idp/lib/constants.js
2026-06-01 16:40:54 -05:00

169 lines
6.8 KiB
JavaScript

// 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=<tenant>,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 <issuer>/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
};