@contember/engine-tenant-api 2.2.0-alpha.6 → 2.2.0-alpha.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/development/src/TenantContainer.cjs +5 -2
- package/dist/development/src/TenantContainer.cjs.map +1 -1
- package/dist/development/src/TenantContainer.js +5 -2
- package/dist/development/src/TenantContainer.js.map +1 -1
- package/dist/development/src/model/commands/apiKey/ProlongApiKeyCommand.cjs +5 -8
- package/dist/development/src/model/commands/apiKey/ProlongApiKeyCommand.cjs.map +1 -1
- package/dist/development/src/model/commands/apiKey/ProlongApiKeyCommand.js +5 -8
- package/dist/development/src/model/commands/apiKey/ProlongApiKeyCommand.js.map +1 -1
- package/dist/development/src/model/commands/authLog/CreateAuthLogEntryCommand.cjs.map +1 -1
- package/dist/development/src/model/commands/authLog/CreateAuthLogEntryCommand.js.map +1 -1
- package/dist/development/src/model/commands/idp/ClaimIdpRevalidationCommand.cjs +3 -7
- package/dist/development/src/model/commands/idp/ClaimIdpRevalidationCommand.cjs.map +1 -1
- package/dist/development/src/model/commands/idp/ClaimIdpRevalidationCommand.js +3 -7
- package/dist/development/src/model/commands/idp/ClaimIdpRevalidationCommand.js.map +1 -1
- package/dist/development/src/model/service/IDPSignInManager.cjs +23 -15
- package/dist/development/src/model/service/IDPSignInManager.cjs.map +1 -1
- package/dist/development/src/model/service/IDPSignInManager.js +23 -15
- package/dist/development/src/model/service/IDPSignInManager.js.map +1 -1
- package/dist/development/src/model/service/idp/IdpSessionRevalidator.cjs +42 -31
- package/dist/development/src/model/service/idp/IdpSessionRevalidator.cjs.map +1 -1
- package/dist/development/src/model/service/idp/IdpSessionRevalidator.js +42 -31
- package/dist/development/src/model/service/idp/IdpSessionRevalidator.js.map +1 -1
- package/dist/production/src/TenantContainer.cjs +5 -2
- package/dist/production/src/TenantContainer.cjs.map +1 -1
- package/dist/production/src/TenantContainer.js +5 -2
- package/dist/production/src/TenantContainer.js.map +1 -1
- package/dist/production/src/model/commands/apiKey/ProlongApiKeyCommand.cjs +5 -8
- package/dist/production/src/model/commands/apiKey/ProlongApiKeyCommand.cjs.map +1 -1
- package/dist/production/src/model/commands/apiKey/ProlongApiKeyCommand.js +5 -8
- package/dist/production/src/model/commands/apiKey/ProlongApiKeyCommand.js.map +1 -1
- package/dist/production/src/model/commands/authLog/CreateAuthLogEntryCommand.cjs.map +1 -1
- package/dist/production/src/model/commands/authLog/CreateAuthLogEntryCommand.js.map +1 -1
- package/dist/production/src/model/commands/idp/ClaimIdpRevalidationCommand.cjs +3 -7
- package/dist/production/src/model/commands/idp/ClaimIdpRevalidationCommand.cjs.map +1 -1
- package/dist/production/src/model/commands/idp/ClaimIdpRevalidationCommand.js +3 -7
- package/dist/production/src/model/commands/idp/ClaimIdpRevalidationCommand.js.map +1 -1
- package/dist/production/src/model/service/IDPSignInManager.cjs +23 -15
- package/dist/production/src/model/service/IDPSignInManager.cjs.map +1 -1
- package/dist/production/src/model/service/IDPSignInManager.js +23 -15
- package/dist/production/src/model/service/IDPSignInManager.js.map +1 -1
- package/dist/production/src/model/service/idp/IdpSessionRevalidator.cjs +42 -31
- package/dist/production/src/model/service/idp/IdpSessionRevalidator.cjs.map +1 -1
- package/dist/production/src/model/service/idp/IdpSessionRevalidator.js +42 -31
- package/dist/production/src/model/service/idp/IdpSessionRevalidator.js.map +1 -1
- package/dist/tests/cases/integration/mocked/sql/sqlTransaction.d.ts +5 -0
- package/dist/tests/cases/integration/mocked/sql/sqlTransaction.d.ts.map +1 -1
- package/dist/tests/cases/unit/idp/claimIdpRevalidation.test.d.ts +2 -0
- package/dist/tests/cases/unit/idp/claimIdpRevalidation.test.d.ts.map +1 -0
- package/dist/tests/src/IdPMock.d.ts.map +1 -1
- package/dist/tests/src/testTenant.d.ts.map +1 -1
- package/dist/tests/tsconfig.tsbuildinfo +1 -1
- package/dist/types/TenantContainer.d.ts +2 -0
- package/dist/types/TenantContainer.d.ts.map +1 -1
- package/dist/types/model/commands/apiKey/ProlongApiKeyCommand.d.ts +5 -3
- package/dist/types/model/commands/apiKey/ProlongApiKeyCommand.d.ts.map +1 -1
- package/dist/types/model/commands/authLog/CreateAuthLogEntryCommand.d.ts +1 -0
- package/dist/types/model/commands/authLog/CreateAuthLogEntryCommand.d.ts.map +1 -1
- package/dist/types/model/commands/idp/ClaimIdpRevalidationCommand.d.ts.map +1 -1
- package/dist/types/model/service/IDPSignInManager.d.ts +3 -1
- package/dist/types/model/service/IDPSignInManager.d.ts.map +1 -1
- package/dist/types/model/service/idp/IdpSessionRevalidator.d.ts +3 -1
- package/dist/types/model/service/idp/IdpSessionRevalidator.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +12 -12
- package/src/TenantContainer.ts +7 -2
- package/src/model/commands/apiKey/ProlongApiKeyCommand.ts +10 -11
- package/src/model/commands/authLog/CreateAuthLogEntryCommand.ts +1 -0
- package/src/model/commands/idp/ClaimIdpRevalidationCommand.ts +8 -7
- package/src/model/service/IDPSignInManager.ts +26 -14
- package/src/model/service/idp/IdpSessionRevalidator.ts +43 -28
- package/tests/cases/integration/mocked/signInIdpMutation.test.ts +100 -1
- package/tests/cases/integration/mocked/sql/sqlTransaction.ts +16 -0
- package/tests/cases/unit/apiKey/prolongApiKeyCommand.test.ts +19 -9
- package/tests/cases/unit/idp/claimIdpRevalidation.test.ts +50 -0
- package/tests/cases/unit/idp/idpSessionRevalidator.test.ts +37 -4
- package/tests/src/IdPMock.ts +2 -0
- package/tests/src/testTenant.ts +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IdpSessionRevalidator.js","sources":["../../../../../../../packages/engine-tenant-api/src/model/service/idp/IdpSessionRevalidator.ts"],"sourcesContent":["import { DatabaseContext } from '../../utils/index.js'\nimport { ApiKeyRequestInfo } from '../../commands/index.js'\nimport { ApiKeyRow } from '../../queries/apiKey/ApiKeyQuery.js'\nimport { IdpSessionByApiKeyQuery, IdpSessionRow } from '../../queries/idp/IdpSessionByApiKeyQuery.js'\nimport { IDPHandlerRegistry } from './IDPHandlerRegistry.js'\nimport { ClaimIdpRevalidationCommand } from '../../commands/idp/ClaimIdpRevalidationCommand.js'\nimport { UpdateIdpSessionCommand } from '../../commands/idp/UpdateIdpSessionCommand.js'\nimport { DisableApiKeyCommand } from '../../commands/index.js'\nimport { CreateAuthLogEntryCommand } from '../../commands/authLog/CreateAuthLogEntryCommand.js'\nimport { REVALIDATION_DEFAULT_FALLBACK_INTERVAL, REVALIDATION_DEFAULT_MIN_INTERVAL, REVALIDATION_DEFAULT_SOFT_THRESHOLD } from './IDPRevalidation.js'\nimport { IdentityProviderHandler, RevalidationResult } from './IdentityProviderHandler.js'\nimport { IDPClaimSyncService } from './IDPClaimSyncService.js'\nimport { parseClaimMapping } from './ClaimMapping.js'\nimport { JSONValue } from '@contember/schema'\n\nexport type RevalidationOutcome = 'valid' | 'revoked'\n\ntype ResolvedRevalidationConfig = {\n\tenabled: boolean\n\tsoftThreshold: number\n\tminInterval: string\n\tfallbackInterval: string\n\tmode: 'auto' | 'blocking'\n}\n\n/**\n * Decision for the current request: whether to attempt a revalidation, and if so whether it\n * must block the request. `claimInterval` is the single-flight floor for the atomic claim.\n */\ntype Decision = null | { blocking: boolean; claimInterval: string }\n\n/**\n * Re-validates IdP-backed sessions against their identity provider on the verify hot path\n * (A24). Invoked from `ApiKeyManager.verifyAndProlong`. Operates entirely through the supplied\n * database contexts and the command bus, so it does not depend on `ApiKeyManager` (no DI cycle).\n *\n * The cadence is driven by the **access-token lifetime**, not a fixed interval:\n * - while the token is fresh (before `softRefreshThreshold` of its lifetime) → skip, free;\n * - past the threshold but not yet expired → a proactive **background (SWR)** refresh, so the\n * request keeps zero added latency and the token is renewed before it dies;\n * - once the token has actually expired → a **blocking** refresh (we can't serve a dead token).\n * `mode: 'blocking'` forces every revalidation synchronous (zero revocation lag). When the IdP\n * returns no token expiry, falls back to a plain `fallbackInterval` background throttle.\n */\nexport class IdpSessionRevalidator {\n\tconstructor(\n\t\tprivate readonly idpRegistry: IDPHandlerRegistry,\n\t\tprivate readonly claimSyncService: IDPClaimSyncService,\n\t) {\n\t}\n\n\tasync revalidate(\n\t\tdbContext: DatabaseContext,\n\t\treadDbContext: DatabaseContext,\n\t\tapiKeyRow: ApiKeyRow,\n\t\trequestInfo?: ApiKeyRequestInfo,\n\t): Promise<RevalidationOutcome> {\n\t\tconst row = await readDbContext.queryHandler.fetch(new IdpSessionByApiKeyQuery(apiKeyRow.id, readDbContext.providers))\n\t\tif (!row) {\n\t\t\t// not a federated session (password / IdP without revalidation) — fast no-op\n\t\t\treturn 'valid'\n\t\t}\n\n\t\tconst config = this.resolveConfig(row.providerConfiguration)\n\t\tif (!config.enabled) {\n\t\t\treturn 'valid'\n\t\t}\n\n\t\t// A disabled IdP no longer vouches for its sessions.\n\t\tif (row.providerDisabledAt) {\n\t\t\tawait this.revoke(dbContext, apiKeyRow, row, 'idp_disabled', requestInfo)\n\t\t\treturn 'revoked'\n\t\t}\n\n\t\tconst handler = this.idpRegistry.getHandlerOrNull(row.providerType)\n\t\tif (!handler?.revalidate) {\n\t\t\t// provider can't actively re-validate (e.g. SAML) — rely on expiry / SLO\n\t\t\treturn 'valid'\n\t\t}\n\n\t\tconst decision = this.decide(row, config, dbContext.providers.now())\n\t\tif (!decision) {\n\t\t\t// token still fresh — vouched for, no IdP call\n\t\t\treturn 'valid'\n\t\t}\n\n\t\t// Atomic claim = single-flight. Loser (within the floor window, or lost the race) serves stale.\n\t\tlet claimed: boolean\n\t\ttry {\n\t\t\tclaimed = await dbContext.commandBus.execute(new ClaimIdpRevalidationCommand(row.id, decision.claimInterval))\n\t\t} catch {\n\t\t\t// invalid interval string or transient DB error — fail open, retry next request\n\t\t\treturn 'valid'\n\t\t}\n\t\tif (!claimed) {\n\t\t\treturn 'valid'\n\t\t}\n\n\t\tif (decision.blocking) {\n\t\t\treturn await this.runRevalidation(dbContext, handler, row, apiKeyRow, requestInfo)\n\t\t}\n\n\t\t// SWR: serve this request from the (still valid) token, refresh after the response.\n\t\t// The work runs detached (after the response is sent), so any rejection — a transient\n\t\t// DB error in the revoke / persist writes, a corrupt-config throw — must be swallowed\n\t\t// here; otherwise it becomes an unhandledRejection and can take the worker down. The\n\t\t// session is simply kept and re-tried on a later request (the claim floor throttles).\n\t\tsetImmediate(() => {\n\t\t\tthis.runRevalidation(dbContext, handler, row, apiKeyRow, requestInfo).catch(() => {})\n\t\t})\n\t\treturn 'valid'\n\t}\n\n\t/**\n\t * Decide whether (and how) to revalidate, purely from timestamps — no interval parsing in\n\t * JS (the claim handles intervals in SQL). Returns null to skip.\n\t */\n\tprivate decide(row: IdpSessionRow, config: ResolvedRevalidationConfig, now: Date): Decision {\n\t\tconst expiresAt = row.session.expiresAt\n\t\tconst obtainedAt = row.tokenObtainedAt\n\n\t\t// No lifetime info from the IdP → plain background throttle on fallbackInterval.\n\t\tif (!expiresAt || !obtainedAt) {\n\t\t\treturn { blocking: config.mode === 'blocking', claimInterval: config.fallbackInterval }\n\t\t}\n\n\t\tconst lifetimeMs = expiresAt.getTime() - obtainedAt.getTime()\n\t\tconst softAtMs = obtainedAt.getTime() + lifetimeMs * config.softThreshold\n\t\tconst nowMs = now.getTime()\n\n\t\tif (nowMs < softAtMs) {\n\t\t\treturn null // still within the trusted window\n\t\t}\n\n\t\tconst expired = nowMs >= expiresAt.getTime()\n\t\treturn { blocking: config.mode === 'blocking' || expired, claimInterval: config.minInterval }\n\t}\n\n\tprivate async runRevalidation(\n\t\tdbContext: DatabaseContext,\n\t\thandler: IdentityProviderHandler<{}>,\n\t\trow: IdpSessionRow,\n\t\tapiKeyRow: ApiKeyRow,\n\t\trequestInfo?: ApiKeyRequestInfo,\n\t): Promise<RevalidationOutcome> {\n\t\tlet config: {}\n\t\ttry {\n\t\t\tconfig = handler.validateConfiguration(row.providerConfiguration)\n\t\t} catch {\n\t\t\t// corrupt stored config — must NOT revoke; keep the session and retry later. Audit\n\t\t\t// it so the operator can tell \"vouched for\" apart from \"silently broken\" (without an\n\t\t\t// entry, a config typo disables re-validation invisibly for the life of every session).\n\t\t\tawait this.logRevalidationFailed(dbContext, apiKeyRow, row, 'config_invalid', requestInfo)\n\t\t\treturn 'valid'\n\t\t}\n\n\t\tlet result: RevalidationResult\n\t\ttry {\n\t\t\tresult = await handler.revalidate!(config, row.session)\n\t\t} catch {\n\t\t\t// transient failure (network / IdP down) — must NOT revoke; keep the session and retry\n\t\t\t// on a later request (the claim floor prevents hammering the IdP). Audited (throttled\n\t\t\t// to one entry per claim window) so a prolonged IdP outage is visible rather than a\n\t\t\t// silent fail-open.\n\t\t\tawait this.logRevalidationFailed(dbContext, apiKeyRow, row, 'revalidation_error', requestInfo)\n\t\t\treturn 'valid'\n\t\t}\n\n\t\tif (result.status === 'revoked') {\n\t\t\tawait this.revoke(dbContext, apiKeyRow, row, result.reason, requestInfo)\n\t\t\treturn 'revoked'\n\t\t}\n\n\t\t// Still valid — persist the rotated token (resets the lifetime reference) / refreshed\n\t\t// expiry. A rotation is a real, low-frequency event (token-lifetime cadence), so we\n\t\t// audit it as `idp_session_revalidated`; the no-op userinfo/introspection probes\n\t\t// (no rotated session) are intentionally NOT logged to keep the audit log usable.\n\t\t//\n\t\t// The IdP has already vouched for the session, so these post-success writes are pure\n\t\t// best-effort bookkeeping (they only reset the SWR cadence reference). A transient DB\n\t\t// error or an `encrypt()` throw here must NOT fail the request — otherwise a blocking\n\t\t// revalidation fails closed on a successful refresh, contradicting \"fail open\" (the SWR\n\t\t// path already swallows this). Keep the existing row; the claim floor throttles a retry.\n\t\tif (result.idpSession) {\n\t\t\ttry {\n\t\t\t\tawait dbContext.commandBus.execute(new UpdateIdpSessionCommand(row.id, result.idpSession))\n\t\t\t\tawait this.logRevalidated(dbContext, apiKeyRow, row, requestInfo)\n\t\t\t} catch {\n\t\t\t\t// fall through — session stays valid, persist retried on a later request\n\t\t\t}\n\t\t}\n\n\t\t// A09 — re-apply the IdP's claim mapping when the refresh returned fresh claims, so an `always`\n\t\t// mapping keeps an established session's project memberships in sync with the IdP's current claims\n\t\t// without requiring a full re-login. `sticky` mappings self-skip (the sync passes isNewPerson=false).\n\t\t// `claimsComplete` tells the sync whether the refresh rebuilt sign-in's full claim surface, so it can\n\t\t// reconcile (incl. `unmatched: \"remove\"`) exactly like sign-in. Strictly fail-open — see `syncClaims`.\n\t\tif (result.claims) {\n\t\t\tawait this.syncClaims(dbContext, row, apiKeyRow, result.claims, result.claimsComplete === true, requestInfo)\n\t\t}\n\t\treturn 'valid'\n\t}\n\n\t/**\n\t * Re-apply the claim mapping on a successful refresh. Runs in its own transaction (atomic apply of\n\t * the membership changes) and never throws: the IdP has already vouched for the session, so a\n\t * malformed mapping or a transient apply error must not revoke or fail it. A real membership change\n\t * is audited as `idp_role_mapped`; a failure is audited as `idp_role_mapping_failed` (fail-open\n\t * marker), mirroring the sign-in path. `claimsComplete` carries whether the refresh rebuilt sign-in's\n\t * full claim surface (so removal reconciliation is safe) — see `allowRemoval` below.\n\t */\n\tprivate async syncClaims(\n\t\tdbContext: DatabaseContext,\n\t\trow: IdpSessionRow,\n\t\tapiKeyRow: ApiKeyRow,\n\t\tclaims: Record<string, unknown>,\n\t\tclaimsComplete: boolean,\n\t\trequestInfo?: ApiKeyRequestInfo,\n\t): Promise<void> {\n\t\ttry {\n\t\t\tconst mapping = parseClaimMapping(row.providerConfiguration)\n\t\t\tif (!mapping) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// Atomic apply in its own transaction (the verify path has no surrounding one). `always`-only:\n\t\t\t// the sync passes isNewPerson=false, so a `sticky` mapping self-skips and returns a null audit.\n\t\t\t// Refresh mirrors sign-in as closely as the claim surface allows: when the provider returned its\n\t\t\t// COMPLETE surface (`claimsComplete` — `method: \"refresh\"` rebuilding sign-in's id-token + userinfo\n\t\t\t// merge), `unmatched: \"remove\"` reconciliation runs here too. A partial surface (a userinfo-only\n\t\t\t// probe, or a refresh whose userinfo fetch failed) passes `allowRemoval: false`, so refresh stays\n\t\t\t// additive-only and a membership the IdP still asserts is never stripped off an incomplete surface.\n\t\t\tconst { audit, droppedUnsafeRules } = await dbContext.transaction(db =>\n\t\t\t\tthis.claimSyncService.sync(db, mapping, claims, { id: apiKeyRow.identity_id }, false, { allowRemoval: claimsComplete })\n\t\t\t)\n\t\t\tif (audit) {\n\t\t\t\tawait this.logRoleMapping(dbContext, apiKeyRow, row, 'idp_role_mapped', audit, requestInfo)\n\t\t\t}\n\t\t\t// The apply-time safety backstop dropped a configured rule — emit the fail-open marker so a broken\n\t\t\t// mapping is visible on the refresh path too, mirroring sign-in (the marker carries no claim values).\n\t\t\tif (droppedUnsafeRules) {\n\t\t\t\tawait this.logRoleMapping(dbContext, apiKeyRow, row, 'idp_role_mapping_failed', undefined, requestInfo)\n\t\t\t}\n\t\t} catch {\n\t\t\t// Strictly fail-open: a malformed mapping or a transient apply error must never revoke or\n\t\t\t// fail an already-vouched-for session. The transaction rolls back any partial apply.\n\t\t\tawait this.logRoleMapping(dbContext, apiKeyRow, row, 'idp_role_mapping_failed', undefined, requestInfo)\n\t\t}\n\t}\n\n\tprivate async logRoleMapping(\n\t\tdbContext: DatabaseContext,\n\t\tapiKeyRow: ApiKeyRow,\n\t\trow: IdpSessionRow,\n\t\ttype: 'idp_role_mapped' | 'idp_role_mapping_failed',\n\t\teventData: JSONValue | undefined,\n\t\trequestInfo?: ApiKeyRequestInfo,\n\t): Promise<void> {\n\t\ttry {\n\t\t\tawait dbContext.commandBus.execute(\n\t\t\t\tnew CreateAuthLogEntryCommand({\n\t\t\t\t\ttype,\n\t\t\t\t\tinvokedById: apiKeyRow.identity_id,\n\t\t\t\t\tpersonId: apiKeyRow.person_id ?? undefined,\n\t\t\t\t\t// The membership change affects this session's own person — record it as the target too,\n\t\t\t\t\t// so an admin querying membership changes by target_person_id sees IdP-driven grants/revokes\n\t\t\t\t\t// (consistent with the project_membership_* audit events).\n\t\t\t\t\ttargetPersonId: apiKeyRow.person_id ?? undefined,\n\t\t\t\t\tidentityProviderId: row.identityProviderId,\n\t\t\t\t\tpersonTokenId: apiKeyRow.id,\n\t\t\t\t\tsuccess: true,\n\t\t\t\t\teventData,\n\t\t\t\t\tipAddress: requestInfo?.ip,\n\t\t\t\t\tuserAgent: requestInfo?.userAgent,\n\t\t\t\t}),\n\t\t\t)\n\t\t} catch {\n\t\t\t// best-effort audit — never fail the request because the audit write failed\n\t\t}\n\t}\n\n\tprivate async logRevalidated(dbContext: DatabaseContext, apiKeyRow: ApiKeyRow, row: IdpSessionRow, requestInfo?: ApiKeyRequestInfo): Promise<void> {\n\t\tawait dbContext.commandBus.execute(\n\t\t\tnew CreateAuthLogEntryCommand({\n\t\t\t\ttype: 'idp_session_revalidated',\n\t\t\t\tinvokedById: apiKeyRow.identity_id,\n\t\t\t\tpersonId: apiKeyRow.person_id ?? undefined,\n\t\t\t\tidentityProviderId: row.identityProviderId,\n\t\t\t\tpersonTokenId: apiKeyRow.id,\n\t\t\t\tsuccess: true,\n\t\t\t\tipAddress: requestInfo?.ip,\n\t\t\t\tuserAgent: requestInfo?.userAgent,\n\t\t\t}),\n\t\t)\n\t}\n\n\t/**\n\t * Audit a fail-open: the IdP couldn't be consulted (transient outage) or the stored config is\n\t * corrupt, so we kept the session rather than revoking it. Naturally throttled to one entry\n\t * per claim window (we only reach here after a successful claim, which bumps\n\t * `last_validated_at`). The write is best-effort and self-contained — a failure to audit must\n\t * never propagate and fail a blocking verify request (the whole point of failing open).\n\t */\n\tprivate async logRevalidationFailed(\n\t\tdbContext: DatabaseContext,\n\t\tapiKeyRow: ApiKeyRow,\n\t\trow: IdpSessionRow,\n\t\terrorCode: 'config_invalid' | 'revalidation_error',\n\t\trequestInfo?: ApiKeyRequestInfo,\n\t): Promise<void> {\n\t\ttry {\n\t\t\tawait dbContext.commandBus.execute(\n\t\t\t\tnew CreateAuthLogEntryCommand({\n\t\t\t\t\ttype: 'idp_session_revalidation_failed',\n\t\t\t\t\tinvokedById: apiKeyRow.identity_id,\n\t\t\t\t\tpersonId: apiKeyRow.person_id ?? undefined,\n\t\t\t\t\tidentityProviderId: row.identityProviderId,\n\t\t\t\t\tpersonTokenId: apiKeyRow.id,\n\t\t\t\t\t// not a security failure (the session is kept) — a fail-open marker the operator\n\t\t\t\t\t// can alert on. `success: true` keeps it out of the failed-login funnels.\n\t\t\t\t\tsuccess: true,\n\t\t\t\t\terrorCode,\n\t\t\t\t\tipAddress: requestInfo?.ip,\n\t\t\t\t\tuserAgent: requestInfo?.userAgent,\n\t\t\t\t}),\n\t\t\t)\n\t\t} catch {\n\t\t\t// best-effort observability — never fail the request because the audit write failed\n\t\t}\n\t}\n\n\tprivate async revoke(\n\t\tdbContext: DatabaseContext,\n\t\tapiKeyRow: ApiKeyRow,\n\t\trow: IdpSessionRow,\n\t\treason: string,\n\t\trequestInfo?: ApiKeyRequestInfo,\n\t): Promise<void> {\n\t\tawait dbContext.commandBus.execute(new DisableApiKeyCommand(apiKeyRow.id))\n\t\tawait dbContext.commandBus.execute(\n\t\t\tnew CreateAuthLogEntryCommand({\n\t\t\t\ttype: 'idp_session_revoked',\n\t\t\t\tinvokedById: apiKeyRow.identity_id,\n\t\t\t\tpersonId: apiKeyRow.person_id ?? undefined,\n\t\t\t\tidentityProviderId: row.identityProviderId,\n\t\t\t\tpersonTokenId: apiKeyRow.id,\n\t\t\t\tsuccess: false,\n\t\t\t\terrorCode: reason,\n\t\t\t\tipAddress: requestInfo?.ip,\n\t\t\t\tuserAgent: requestInfo?.userAgent,\n\t\t\t\t// the action payload belongs in `event_data` (where every other audit event puts\n\t\t\t\t// it and where the admin UI / AuthLogQueryResolver reads it); `metadata` is for\n\t\t\t\t// transport context (forwarder IP/UA).\n\t\t\t\teventData: { reason },\n\t\t\t}),\n\t\t)\n\t}\n\n\tprivate resolveConfig(configuration: Record<string, unknown>): ResolvedRevalidationConfig {\n\t\tconst raw = (configuration.revalidation ?? {}) as Record<string, unknown>\n\t\tconst threshold = typeof raw.softRefreshThreshold === 'number' && raw.softRefreshThreshold > 0 && raw.softRefreshThreshold <= 1\n\t\t\t? raw.softRefreshThreshold\n\t\t\t: REVALIDATION_DEFAULT_SOFT_THRESHOLD\n\t\treturn {\n\t\t\tenabled: raw.enabled === true,\n\t\t\tsoftThreshold: threshold,\n\t\t\tminInterval: typeof raw.minInterval === 'string' ? raw.minInterval : REVALIDATION_DEFAULT_MIN_INTERVAL,\n\t\t\tfallbackInterval: typeof raw.fallbackInterval === 'string' ? raw.fallbackInterval : REVALIDATION_DEFAULT_FALLBACK_INTERVAL,\n\t\t\tmode: raw.mode === 'blocking' ? 'blocking' : 'auto',\n\t\t}\n\t}\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"IdpSessionRevalidator.js","sources":["../../../../../../../packages/engine-tenant-api/src/model/service/idp/IdpSessionRevalidator.ts"],"sourcesContent":["import { DatabaseContext } from '../../utils/index.js'\nimport { ApiKeyRequestInfo } from '../../commands/index.js'\nimport { ApiKeyRow } from '../../queries/apiKey/ApiKeyQuery.js'\nimport { IdpSessionByApiKeyQuery, IdpSessionRow } from '../../queries/idp/IdpSessionByApiKeyQuery.js'\nimport { IDPHandlerRegistry } from './IDPHandlerRegistry.js'\nimport { ClaimIdpRevalidationCommand } from '../../commands/idp/ClaimIdpRevalidationCommand.js'\nimport { UpdateIdpSessionCommand } from '../../commands/idp/UpdateIdpSessionCommand.js'\nimport { DisableApiKeyCommand } from '../../commands/index.js'\nimport { CreateAuthLogEntryCommand } from '../../commands/authLog/CreateAuthLogEntryCommand.js'\nimport { REVALIDATION_DEFAULT_FALLBACK_INTERVAL, REVALIDATION_DEFAULT_MIN_INTERVAL, REVALIDATION_DEFAULT_SOFT_THRESHOLD } from './IDPRevalidation.js'\nimport { IdentityProviderHandler, RevalidationResult } from './IdentityProviderHandler.js'\nimport { IDPClaimSyncService } from './IDPClaimSyncService.js'\nimport { Logger } from '@contember/logger'\nimport { parseClaimMapping } from './ClaimMapping.js'\nimport { JSONValue } from '@contember/schema'\n\nexport type RevalidationOutcome = 'valid' | 'revoked'\n\ntype ResolvedRevalidationConfig = {\n\tenabled: boolean\n\tsoftThreshold: number\n\tminInterval: string\n\tfallbackInterval: string\n\tmode: 'auto' | 'blocking'\n}\n\n/**\n * Decision for the current request: whether to attempt a revalidation, and if so whether it\n * must block the request. `claimInterval` is the single-flight floor for the atomic claim.\n */\ntype Decision = null | { blocking: boolean; claimInterval: string }\n\n/**\n * Re-validates IdP-backed sessions against their identity provider on the verify hot path\n * (A24). Invoked from `ApiKeyManager.verifyAndProlong`. Operates entirely through the supplied\n * database contexts and the command bus, so it does not depend on `ApiKeyManager` (no DI cycle).\n *\n * The cadence is driven by the **access-token lifetime**, not a fixed interval:\n * - while the token is fresh (before `softRefreshThreshold` of its lifetime) → skip, free;\n * - past the threshold but not yet expired → a proactive **background (SWR)** refresh, so the\n * request keeps zero added latency and the token is renewed before it dies;\n * - once the token has actually expired → a **blocking** refresh (we can't serve a dead token).\n * `mode: 'blocking'` forces every revalidation synchronous (zero revocation lag). When the IdP\n * returns no token expiry, falls back to a plain `fallbackInterval` background throttle.\n */\nexport class IdpSessionRevalidator {\n\tconstructor(\n\t\tprivate readonly idpRegistry: IDPHandlerRegistry,\n\t\tprivate readonly claimSyncService: IDPClaimSyncService,\n\t\tprivate readonly logger: Logger,\n\t) {\n\t}\n\n\tasync revalidate(\n\t\tdbContext: DatabaseContext,\n\t\treadDbContext: DatabaseContext,\n\t\tapiKeyRow: ApiKeyRow,\n\t\trequestInfo?: ApiKeyRequestInfo,\n\t): Promise<RevalidationOutcome> {\n\t\tconst row = await readDbContext.queryHandler.fetch(new IdpSessionByApiKeyQuery(apiKeyRow.id, readDbContext.providers))\n\t\tif (!row) {\n\t\t\t// not a federated session (password / IdP without revalidation) — fast no-op\n\t\t\treturn 'valid'\n\t\t}\n\n\t\tconst config = this.resolveConfig(row.providerConfiguration)\n\t\tif (!config.enabled) {\n\t\t\treturn 'valid'\n\t\t}\n\n\t\t// A disabled IdP no longer vouches for its sessions.\n\t\tif (row.providerDisabledAt) {\n\t\t\tawait this.revoke(dbContext, apiKeyRow, row, 'idp_disabled', requestInfo)\n\t\t\treturn 'revoked'\n\t\t}\n\n\t\tconst handler = this.idpRegistry.getHandlerOrNull(row.providerType)\n\t\tif (!handler?.revalidate) {\n\t\t\t// provider can't actively re-validate (e.g. SAML) — rely on expiry / SLO\n\t\t\treturn 'valid'\n\t\t}\n\n\t\tconst decision = this.decide(row, config, dbContext.providers.now())\n\t\tif (!decision) {\n\t\t\t// token still fresh — vouched for, no IdP call\n\t\t\treturn 'valid'\n\t\t}\n\n\t\t// Atomic claim = single-flight. Loser (within the floor window, or lost the race) serves stale.\n\t\tlet claimed: boolean\n\t\ttry {\n\t\t\tclaimed = await dbContext.commandBus.execute(new ClaimIdpRevalidationCommand(row.id, decision.claimInterval))\n\t\t} catch (e) {\n\t\t\tthis.logger.warn(e, { message: 'IdP session revalidation: claim failed', idpSessionId: row.id })\n\t\t\t// invalid interval string or transient DB error — fail open, retry next request\n\t\t\treturn 'valid'\n\t\t}\n\t\tif (!claimed) {\n\t\t\treturn 'valid'\n\t\t}\n\n\t\tif (decision.blocking) {\n\t\t\treturn await this.runRevalidation(dbContext, handler, row, apiKeyRow, requestInfo)\n\t\t}\n\n\t\t// SWR: serve this request from the (still valid) token, refresh after the response.\n\t\t// The work runs detached (after the response is sent), so any rejection — a transient\n\t\t// DB error in the revoke / persist writes, a corrupt-config throw — must be swallowed\n\t\t// here; otherwise it becomes an unhandledRejection and can take the worker down. The\n\t\t// session is simply kept and re-tried on a later request (the claim floor throttles).\n\t\tsetImmediate(() => {\n\t\t\tthis.runRevalidation(dbContext, handler, row, apiKeyRow, requestInfo).catch(e => {\n\t\t\t\tthis.logger.error(e, { message: 'IdP session revalidation: background run failed', idpSessionId: row.id })\n\t\t\t})\n\t\t})\n\t\treturn 'valid'\n\t}\n\n\t/**\n\t * Decide whether (and how) to revalidate, purely from timestamps — no interval parsing in\n\t * JS (the claim handles intervals in SQL). Returns null to skip.\n\t */\n\tprivate decide(row: IdpSessionRow, config: ResolvedRevalidationConfig, now: Date): Decision {\n\t\tconst expiresAt = row.session.expiresAt\n\t\tconst obtainedAt = row.tokenObtainedAt\n\n\t\t// No lifetime info from the IdP → plain background throttle on fallbackInterval.\n\t\tif (!expiresAt || !obtainedAt) {\n\t\t\treturn { blocking: config.mode === 'blocking', claimInterval: config.fallbackInterval }\n\t\t}\n\n\t\tconst lifetimeMs = expiresAt.getTime() - obtainedAt.getTime()\n\t\tconst softAtMs = obtainedAt.getTime() + lifetimeMs * config.softThreshold\n\t\tconst nowMs = now.getTime()\n\n\t\tif (nowMs < softAtMs) {\n\t\t\treturn null // still within the trusted window\n\t\t}\n\n\t\tconst expired = nowMs >= expiresAt.getTime()\n\t\treturn { blocking: config.mode === 'blocking' || expired, claimInterval: config.minInterval }\n\t}\n\n\tprivate async runRevalidation(\n\t\tdbContext: DatabaseContext,\n\t\thandler: IdentityProviderHandler<{}>,\n\t\trow: IdpSessionRow,\n\t\tapiKeyRow: ApiKeyRow,\n\t\trequestInfo?: ApiKeyRequestInfo,\n\t): Promise<RevalidationOutcome> {\n\t\tlet config: {}\n\t\ttry {\n\t\t\tconfig = handler.validateConfiguration(row.providerConfiguration)\n\t\t} catch (e) {\n\t\t\tthis.logger.warn(e, { message: 'IdP session revalidation: stored configuration is invalid', idpSessionId: row.id })\n\t\t\t// corrupt stored config — must NOT revoke; keep the session and retry later. Audit\n\t\t\t// it so the operator can tell \"vouched for\" apart from \"silently broken\" (without an\n\t\t\t// entry, a config typo disables re-validation invisibly for the life of every session).\n\t\t\tawait this.logRevalidationFailed(dbContext, apiKeyRow, row, 'config_invalid', requestInfo)\n\t\t\treturn 'valid'\n\t\t}\n\n\t\tlet result: RevalidationResult\n\t\ttry {\n\t\t\tresult = await handler.revalidate!(config, row.session)\n\t\t} catch (e) {\n\t\t\tthis.logger.warn(e, { message: 'IdP session revalidation: identity provider call failed', idpSessionId: row.id })\n\t\t\t// transient failure (network / IdP down) — must NOT revoke; keep the session and retry\n\t\t\t// on a later request (the claim floor prevents hammering the IdP). Audited (throttled\n\t\t\t// to one entry per claim window) so a prolonged IdP outage is visible rather than a\n\t\t\t// silent fail-open.\n\t\t\tawait this.logRevalidationFailed(dbContext, apiKeyRow, row, 'revalidation_error', requestInfo)\n\t\t\treturn 'valid'\n\t\t}\n\n\t\tif (result.status === 'revoked') {\n\t\t\tawait this.revoke(dbContext, apiKeyRow, row, result.reason, requestInfo)\n\t\t\treturn 'revoked'\n\t\t}\n\n\t\t// Still valid — persist the rotated token (resets the lifetime reference) / refreshed\n\t\t// expiry. A rotation is a real, low-frequency event (token-lifetime cadence), so we\n\t\t// audit it as `idp_session_revalidated`; the no-op userinfo/introspection probes\n\t\t// (no rotated session) are intentionally NOT logged to keep the audit log usable.\n\t\t//\n\t\t// The IdP has already vouched for the session, so these post-success writes are pure\n\t\t// best-effort bookkeeping (they only reset the SWR cadence reference). A transient DB\n\t\t// error or an `encrypt()` throw here must NOT fail the request — otherwise a blocking\n\t\t// revalidation fails closed on a successful refresh, contradicting \"fail open\" (the SWR\n\t\t// path already swallows this). Keep the existing row; the claim floor throttles a retry.\n\t\tif (result.idpSession) {\n\t\t\ttry {\n\t\t\t\tawait dbContext.commandBus.execute(new UpdateIdpSessionCommand(row.id, result.idpSession))\n\t\t\t\tawait this.logRevalidated(dbContext, apiKeyRow, row, requestInfo)\n\t\t\t} catch (e) {\n\t\t\t\tthis.logger.error(e, { message: 'IdP session revalidation: persisting the rotated session failed', idpSessionId: row.id })\n\t\t\t\t// fall through — session stays valid, persist retried on a later request\n\t\t\t}\n\t\t}\n\n\t\t// A09 — re-apply the IdP's claim mapping when the refresh returned fresh claims, so an `always`\n\t\t// mapping keeps an established session's project memberships in sync with the IdP's current claims\n\t\t// without requiring a full re-login. `sticky` mappings self-skip (the sync passes isNewPerson=false).\n\t\t// `claimsComplete` tells the sync whether the refresh rebuilt sign-in's full claim surface, so it can\n\t\t// reconcile (incl. `unmatched: \"remove\"`) exactly like sign-in. Strictly fail-open — see `syncClaims`.\n\t\tif (result.claims) {\n\t\t\tawait this.syncClaims(dbContext, row, apiKeyRow, result.claims, result.claimsComplete === true, requestInfo)\n\t\t}\n\t\treturn 'valid'\n\t}\n\n\t/**\n\t * Re-apply the claim mapping on a successful refresh. Runs in its own transaction (atomic apply of\n\t * the membership changes) and never throws: the IdP has already vouched for the session, so a\n\t * malformed mapping or a transient apply error must not revoke or fail it. A real membership change\n\t * is audited as `idp_role_mapped`; a failure is audited as `idp_role_mapping_failed` (fail-open\n\t * marker), mirroring the sign-in path. `claimsComplete` carries whether the refresh rebuilt sign-in's\n\t * full claim surface (so removal reconciliation is safe) — see `allowRemoval` below.\n\t */\n\tprivate async syncClaims(\n\t\tdbContext: DatabaseContext,\n\t\trow: IdpSessionRow,\n\t\tapiKeyRow: ApiKeyRow,\n\t\tclaims: Record<string, unknown>,\n\t\tclaimsComplete: boolean,\n\t\trequestInfo?: ApiKeyRequestInfo,\n\t): Promise<void> {\n\t\ttry {\n\t\t\tconst mapping = parseClaimMapping(row.providerConfiguration)\n\t\t\tif (!mapping) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// Atomic apply in its own transaction (the verify path has no surrounding one). `always`-only:\n\t\t\t// the sync passes isNewPerson=false, so a `sticky` mapping self-skips and returns a null audit.\n\t\t\t// Refresh mirrors sign-in as closely as the claim surface allows: when the provider returned its\n\t\t\t// COMPLETE surface (`claimsComplete` — `method: \"refresh\"` rebuilding sign-in's id-token + userinfo\n\t\t\t// merge), `unmatched: \"remove\"` reconciliation runs here too. A partial surface (a userinfo-only\n\t\t\t// probe, or a refresh whose userinfo fetch failed) passes `allowRemoval: false`, so refresh stays\n\t\t\t// additive-only and a membership the IdP still asserts is never stripped off an incomplete surface.\n\t\t\tconst { audit, droppedUnsafeRules } = await dbContext.transaction(db =>\n\t\t\t\tthis.claimSyncService.sync(db, mapping, claims, { id: apiKeyRow.identity_id }, false, { allowRemoval: claimsComplete })\n\t\t\t)\n\t\t\tif (audit) {\n\t\t\t\tawait this.logRoleMapping(dbContext, apiKeyRow, row, 'idp_role_mapped', audit, requestInfo)\n\t\t\t}\n\t\t\t// The apply-time safety backstop dropped a configured rule — emit the fail-open marker so a broken\n\t\t\t// mapping is visible on the refresh path too, mirroring sign-in (the marker carries no claim values).\n\t\t\tif (droppedUnsafeRules) {\n\t\t\t\tawait this.logRoleMapping(dbContext, apiKeyRow, row, 'idp_role_mapping_failed', undefined, requestInfo)\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tthis.logger.warn(e, { message: 'IdP session revalidation: claim mapping sync failed', idpSessionId: row.id })\n\t\t\t// Strictly fail-open: a malformed mapping or a transient apply error must never revoke or\n\t\t\t// fail an already-vouched-for session. The transaction rolls back any partial apply.\n\t\t\tawait this.logRoleMapping(dbContext, apiKeyRow, row, 'idp_role_mapping_failed', undefined, requestInfo)\n\t\t}\n\t}\n\n\tprivate async logRoleMapping(\n\t\tdbContext: DatabaseContext,\n\t\tapiKeyRow: ApiKeyRow,\n\t\trow: IdpSessionRow,\n\t\ttype: 'idp_role_mapped' | 'idp_role_mapping_failed',\n\t\teventData: JSONValue | undefined,\n\t\trequestInfo?: ApiKeyRequestInfo,\n\t): Promise<void> {\n\t\ttry {\n\t\t\tawait dbContext.commandBus.execute(\n\t\t\t\tnew CreateAuthLogEntryCommand({\n\t\t\t\t\ttype,\n\t\t\t\t\tinvokedById: apiKeyRow.identity_id,\n\t\t\t\t\tpersonId: apiKeyRow.person_id ?? undefined,\n\t\t\t\t\t// The membership change affects this session's own person — record it as the target too,\n\t\t\t\t\t// so an admin querying membership changes by target_person_id sees IdP-driven grants/revokes\n\t\t\t\t\t// (consistent with the project_membership_* audit events).\n\t\t\t\t\ttargetPersonId: apiKeyRow.person_id ?? undefined,\n\t\t\t\t\tidentityProviderId: row.identityProviderId,\n\t\t\t\t\tsuccess: true,\n\t\t\t\t\teventData,\n\t\t\t\t\tipAddress: requestInfo?.ip,\n\t\t\t\t\tuserAgent: requestInfo?.userAgent,\n\t\t\t\t}),\n\t\t\t)\n\t\t} catch (e) {\n\t\t\tthis.logger.error(e, { message: `IdP session revalidation: writing the ${type} audit entry failed`, idpSessionId: row.id })\n\t\t\t// best-effort audit — never fail the request because the audit write failed\n\t\t}\n\t}\n\n\tprivate async logRevalidated(dbContext: DatabaseContext, apiKeyRow: ApiKeyRow, row: IdpSessionRow, requestInfo?: ApiKeyRequestInfo): Promise<void> {\n\t\tawait dbContext.commandBus.execute(\n\t\t\tnew CreateAuthLogEntryCommand({\n\t\t\t\ttype: 'idp_session_revalidated',\n\t\t\t\tinvokedById: apiKeyRow.identity_id,\n\t\t\t\tpersonId: apiKeyRow.person_id ?? undefined,\n\t\t\t\tidentityProviderId: row.identityProviderId,\n\t\t\t\tsuccess: true,\n\t\t\t\tipAddress: requestInfo?.ip,\n\t\t\t\tuserAgent: requestInfo?.userAgent,\n\t\t\t\teventData: { apiKeyId: apiKeyRow.id },\n\t\t\t}),\n\t\t)\n\t}\n\n\t/**\n\t * Audit a fail-open: the IdP couldn't be consulted (transient outage) or the stored config is\n\t * corrupt, so we kept the session rather than revoking it. Naturally throttled to one entry\n\t * per claim window (we only reach here after a successful claim, which bumps\n\t * `last_validated_at`). The write is best-effort and self-contained — a failure to audit must\n\t * never propagate and fail a blocking verify request (the whole point of failing open).\n\t */\n\tprivate async logRevalidationFailed(\n\t\tdbContext: DatabaseContext,\n\t\tapiKeyRow: ApiKeyRow,\n\t\trow: IdpSessionRow,\n\t\terrorCode: 'config_invalid' | 'revalidation_error',\n\t\trequestInfo?: ApiKeyRequestInfo,\n\t): Promise<void> {\n\t\ttry {\n\t\t\tawait dbContext.commandBus.execute(\n\t\t\t\tnew CreateAuthLogEntryCommand({\n\t\t\t\t\ttype: 'idp_session_revalidation_failed',\n\t\t\t\t\tinvokedById: apiKeyRow.identity_id,\n\t\t\t\t\tpersonId: apiKeyRow.person_id ?? undefined,\n\t\t\t\t\tidentityProviderId: row.identityProviderId,\n\t\t\t\t\t// not a security failure (the session is kept) — a fail-open marker the operator\n\t\t\t\t\t// can alert on. `success: true` keeps it out of the failed-login funnels.\n\t\t\t\t\tsuccess: true,\n\t\t\t\t\terrorCode,\n\t\t\t\t\tipAddress: requestInfo?.ip,\n\t\t\t\t\tuserAgent: requestInfo?.userAgent,\n\t\t\t\t\teventData: { apiKeyId: apiKeyRow.id },\n\t\t\t\t}),\n\t\t\t)\n\t\t} catch (e) {\n\t\t\tthis.logger.error(e, { message: 'IdP session revalidation: writing the idp_session_revalidation_failed audit entry failed', idpSessionId: row.id })\n\t\t\t// best-effort observability — never fail the request because the audit write failed\n\t\t}\n\t}\n\n\tprivate async revoke(\n\t\tdbContext: DatabaseContext,\n\t\tapiKeyRow: ApiKeyRow,\n\t\trow: IdpSessionRow,\n\t\treason: string,\n\t\trequestInfo?: ApiKeyRequestInfo,\n\t): Promise<void> {\n\t\tawait dbContext.commandBus.execute(new DisableApiKeyCommand(apiKeyRow.id))\n\t\ttry {\n\t\t\tawait dbContext.commandBus.execute(\n\t\t\t\tnew CreateAuthLogEntryCommand({\n\t\t\t\t\ttype: 'idp_session_revoked',\n\t\t\t\t\tinvokedById: apiKeyRow.identity_id,\n\t\t\t\t\tpersonId: apiKeyRow.person_id ?? undefined,\n\t\t\t\t\tidentityProviderId: row.identityProviderId,\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terrorCode: reason,\n\t\t\t\t\tipAddress: requestInfo?.ip,\n\t\t\t\t\tuserAgent: requestInfo?.userAgent,\n\t\t\t\t\t// the action payload belongs in `event_data` (where every other audit event puts\n\t\t\t\t\t// it and where the admin UI / AuthLogQueryResolver reads it); `metadata` is for\n\t\t\t\t\t// transport context (forwarder IP/UA).\n\t\t\t\t\teventData: { reason, apiKeyId: apiKeyRow.id },\n\t\t\t\t}),\n\t\t\t)\n\t\t} catch (e) {\n\t\t\tthis.logger.error(e, { message: 'IdP session revalidation: writing the idp_session_revoked audit entry failed', idpSessionId: row.id })\n\t\t\t// best-effort audit: the key is already disabled, so the request must still be answered\n\t\t\t// as revoked rather than fail on the audit write\n\t\t}\n\t}\n\n\tprivate resolveConfig(configuration: Record<string, unknown>): ResolvedRevalidationConfig {\n\t\tconst raw = (configuration.revalidation ?? {}) as Record<string, unknown>\n\t\tconst threshold = typeof raw.softRefreshThreshold === 'number' && raw.softRefreshThreshold > 0 && raw.softRefreshThreshold <= 1\n\t\t\t? raw.softRefreshThreshold\n\t\t\t: REVALIDATION_DEFAULT_SOFT_THRESHOLD\n\t\treturn {\n\t\t\tenabled: raw.enabled === true,\n\t\t\tsoftThreshold: threshold,\n\t\t\tminInterval: typeof raw.minInterval === 'string' ? raw.minInterval : REVALIDATION_DEFAULT_MIN_INTERVAL,\n\t\t\tfallbackInterval: typeof raw.fallbackInterval === 'string' ? raw.fallbackInterval : REVALIDATION_DEFAULT_FALLBACK_INTERVAL,\n\t\t\tmode: raw.mode === 'blocking' ? 'blocking' : 'auto',\n\t\t}\n\t}\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
import { ExpectedQuery } from '@contember/database-tester';
|
|
2
2
|
export declare const sqlTransaction: (...queries: ExpectedQuery[]) => ExpectedQuery[];
|
|
3
|
+
/**
|
|
4
|
+
* A nested `client.transaction()` inside an open transaction. Postgres runs it as a savepoint; the
|
|
5
|
+
* connection mock renders every transaction as BEGIN/COMMIT and sets no isolation level for it.
|
|
6
|
+
*/
|
|
7
|
+
export declare const sqlNestedTransaction: (...queries: ExpectedQuery[]) => ExpectedQuery[];
|
|
3
8
|
//# sourceMappingURL=sqlTransaction.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlTransaction.d.ts","sourceRoot":"","sources":["../../../../../../tests/cases/integration/mocked/sql/sqlTransaction.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAE1D,eAAO,MAAM,cAAc,eAAgB,aAAa,EAAE,KAAG,aAAa,EAczE,CAAA"}
|
|
1
|
+
{"version":3,"file":"sqlTransaction.d.ts","sourceRoot":"","sources":["../../../../../../tests/cases/integration/mocked/sql/sqlTransaction.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAE1D,eAAO,MAAM,cAAc,eAAgB,aAAa,EAAE,KAAG,aAAa,EAczE,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,oBAAoB,eAAgB,aAAa,EAAE,KAAG,aAAa,EAU/E,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claimIdpRevalidation.test.d.ts","sourceRoot":"","sources":["../../../../../tests/cases/unit/idp/claimIdpRevalidation.test.ts"],"names":[],"mappings":""}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IdPMock.d.ts","sourceRoot":"","sources":["../../../tests/src/IdPMock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,WAAW,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAEjI,qBAAa,OAAQ,YAAW,uBAAuB,CAAC,GAAG,CAAC;IAC3D,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAKtE;IAED,eAAe,CAAC,aAAa,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC,
|
|
1
|
+
{"version":3,"file":"IdPMock.d.ts","sourceRoot":"","sources":["../../../tests/src/IdPMock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,WAAW,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAEjI,qBAAa,OAAQ,YAAW,uBAAuB,CAAC,GAAG,CAAC;IAC3D,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAKtE;IAED,eAAe,CAAC,aAAa,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC,CAY3E;IAED,qBAAqB,CAAC,MAAM,EAAE,OAAO,GAAG,GAAG,CAE1C;IAED;;;OAGG;IACH,cAAc,CAAC,aAAa,EAAE,GAAG,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAapF;IAED;;;OAGG;IACH,mBAAmB,CAAC,aAAa,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAQvF;CACD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testTenant.d.ts","sourceRoot":"","sources":["../../../tests/src/testTenant.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAA;AAE3E,OAAO,EAGN,QAAQ,EAER,qBAAqB,EAErB,SAAS,EAKT,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAIvD,OAAO,EAAwB,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAEhF,OAAO,EAAsB,eAAe,EAAE,MAAM,aAAa,CAAA;AAMjE,OAAO,EAAE,cAAc,EAAE,MAAM,2CAA2C,CAAA;AAE1E,MAAM,WAAW,IAAI;IACpB,KAAK,EAAE,gBAAgB,CAAA;IACvB,QAAQ,EAAE,aAAa,EAAE,CAAA;IACzB,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,eAAe,EAAE,CAAA;IAC7B,+FAA+F;IAC/F,eAAe,CAAC,EAAE,cAAc,CAAC,OAAO,GAAG,cAAc,CAAC,OAAO,EAAE,CAAA;IACnE,wBAAwB,CAAC,EAAE,OAAO,CAAA;IAClC,QAAQ,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACnE,kFAAkF;IAClF,SAAS,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,CAAA;IAC9B,kEAAkE;IAClE,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB;;;OAGG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAA;IACrC;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,qBAAqB,CAAA;CAC7C;AAED,eAAO,MAAM,mBAAmB,oBAG/B,CAAA;AAED,eAAO,MAAM,GAAG,MAA+B,CAAA;AA+C/C,eAAO,MAAM,uBAAuB,QAAgB,CAAA;AACpD,eAAO,MAAM,qBAAqB,QAAgB,CAAA;AAElD,eAAO,MAAM,iBAAiB,SAAgB,IAAI,
|
|
1
|
+
{"version":3,"file":"testTenant.d.ts","sourceRoot":"","sources":["../../../tests/src/testTenant.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAA;AAE3E,OAAO,EAGN,QAAQ,EAER,qBAAqB,EAErB,SAAS,EAKT,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAIvD,OAAO,EAAwB,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAEhF,OAAO,EAAsB,eAAe,EAAE,MAAM,aAAa,CAAA;AAMjE,OAAO,EAAE,cAAc,EAAE,MAAM,2CAA2C,CAAA;AAE1E,MAAM,WAAW,IAAI;IACpB,KAAK,EAAE,gBAAgB,CAAA;IACvB,QAAQ,EAAE,aAAa,EAAE,CAAA;IACzB,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,eAAe,EAAE,CAAA;IAC7B,+FAA+F;IAC/F,eAAe,CAAC,EAAE,cAAc,CAAC,OAAO,GAAG,cAAc,CAAC,OAAO,EAAE,CAAA;IACnE,wBAAwB,CAAC,EAAE,OAAO,CAAA;IAClC,QAAQ,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACnE,kFAAkF;IAClF,SAAS,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,CAAA;IAC9B,kEAAkE;IAClE,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB;;;OAGG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAA;IACrC;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,qBAAqB,CAAA;CAC7C;AAED,eAAO,MAAM,mBAAmB,oBAG/B,CAAA;AAED,eAAO,MAAM,GAAG,MAA+B,CAAA;AA+C/C,eAAO,MAAM,uBAAuB,QAAgB,CAAA;AACpD,eAAO,MAAM,qBAAqB,QAAgB,CAAA;AAElD,eAAO,MAAM,iBAAiB,SAAgB,IAAI,kBAiGjD,CAAA"}
|