@wildo-ai/saas-backend-lib 1.1.12 → 1.1.13
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/esm/base-classes/models/manual-controllers.backend.definition.d.ts.map +1 -1
- package/dist/esm/base-classes/models/manual-controllers.backend.definition.js +2 -0
- package/dist/esm/base-classes/models/manual-controllers.backend.definition.js.map +1 -1
- package/dist/esm/base-classes/services/repositories/stored-field-omission.backend.d.ts +4 -0
- package/dist/esm/base-classes/services/repositories/stored-field-omission.backend.d.ts.map +1 -1
- package/dist/esm/base-classes/services/repositories/stored-field-omission.backend.js +20 -5
- package/dist/esm/base-classes/services/repositories/stored-field-omission.backend.js.map +1 -1
- package/dist/esm/base-classes/services/services/resource-operation-custom-implementation-registry.backend.service.d.ts +3 -0
- package/dist/esm/base-classes/services/services/resource-operation-custom-implementation-registry.backend.service.d.ts.map +1 -1
- package/dist/esm/base-classes/services/services/resource-operation-custom-implementation-registry.backend.service.js +10 -1
- package/dist/esm/base-classes/services/services/resource-operation-custom-implementation-registry.backend.service.js.map +1 -1
- package/dist/esm/base-classes/services/services/resource-operation-custom-implementation.factory.backend.d.ts +22 -0
- package/dist/esm/base-classes/services/services/resource-operation-custom-implementation.factory.backend.d.ts.map +1 -1
- package/dist/esm/base-classes/services/services/resource-operation-custom-implementation.factory.backend.js +25 -1
- package/dist/esm/base-classes/services/services/resource-operation-custom-implementation.factory.backend.js.map +1 -1
- package/dist/esm/base-classes/services/services/resource-operation-transaction-persistence.backend.utils.d.ts +38 -3
- package/dist/esm/base-classes/services/services/resource-operation-transaction-persistence.backend.utils.d.ts.map +1 -1
- package/dist/esm/base-classes/services/services/resource-operation-transaction-persistence.backend.utils.js +68 -5
- package/dist/esm/base-classes/services/services/resource-operation-transaction-persistence.backend.utils.js.map +1 -1
- package/dist/esm/base-classes/services/services/resource-status-transition.custom-impl.backend.service.d.ts +2 -2
- package/dist/esm/base-classes/services/services/resource-status-transition.custom-impl.backend.service.d.ts.map +1 -1
- package/dist/esm/base-classes/services/services/resource-status-transition.custom-impl.backend.service.js +6 -3
- package/dist/esm/base-classes/services/services/resource-status-transition.custom-impl.backend.service.js.map +1 -1
- package/dist/esm/base-classes/services/services/services-registry-handler.backend.service.d.ts +83 -0
- package/dist/esm/base-classes/services/services/services-registry-handler.backend.service.d.ts.map +1 -1
- package/dist/esm/base-classes/services/services/services-registry-handler.backend.service.js +217 -14
- package/dist/esm/base-classes/services/services/services-registry-handler.backend.service.js.map +1 -1
- package/dist/esm/compliance/approval-seals/approval-seal-currency.backend.d.ts +32 -0
- package/dist/esm/compliance/approval-seals/approval-seal-currency.backend.d.ts.map +1 -0
- package/dist/esm/compliance/approval-seals/approval-seal-currency.backend.js +36 -0
- package/dist/esm/compliance/approval-seals/approval-seal-currency.backend.js.map +1 -0
- package/dist/esm/compliance/approval-seals/approval-seal-on-record.backend.d.ts +38 -0
- package/dist/esm/compliance/approval-seals/approval-seal-on-record.backend.d.ts.map +1 -0
- package/dist/esm/compliance/approval-seals/approval-seal-on-record.backend.js +83 -0
- package/dist/esm/compliance/approval-seals/approval-seal-on-record.backend.js.map +1 -0
- package/dist/esm/compliance/approval-seals/approval-seal-presentation-read.backend.d.ts +36 -0
- package/dist/esm/compliance/approval-seals/approval-seal-presentation-read.backend.d.ts.map +1 -0
- package/dist/esm/compliance/approval-seals/approval-seal-presentation-read.backend.js +51 -0
- package/dist/esm/compliance/approval-seals/approval-seal-presentation-read.backend.js.map +1 -0
- package/dist/esm/compliance/approval-seals/approval-seal-relay-store.backend.d.ts +46 -0
- package/dist/esm/compliance/approval-seals/approval-seal-relay-store.backend.d.ts.map +1 -1
- package/dist/esm/compliance/approval-seals/approval-seal-relay-store.backend.js +68 -0
- package/dist/esm/compliance/approval-seals/approval-seal-relay-store.backend.js.map +1 -1
- package/dist/esm/compliance/approval-seals/approval-seal-store.backend.d.ts +27 -0
- package/dist/esm/compliance/approval-seals/approval-seal-store.backend.d.ts.map +1 -1
- package/dist/esm/compliance/approval-seals/approval-seal-store.backend.js +42 -3
- package/dist/esm/compliance/approval-seals/approval-seal-store.backend.js.map +1 -1
- package/dist/esm/compliance/approval-seals/approval-seals-admin.backend.contracts.d.ts +26 -1
- package/dist/esm/compliance/approval-seals/approval-seals-admin.backend.contracts.d.ts.map +1 -1
- package/dist/esm/compliance/approval-seals/approval-seals-admin.backend.contracts.js +10 -0
- package/dist/esm/compliance/approval-seals/approval-seals-admin.backend.contracts.js.map +1 -1
- package/dist/esm/compliance/approval-seals/approval-seals-admin.backend.controller.d.ts +10 -1
- package/dist/esm/compliance/approval-seals/approval-seals-admin.backend.controller.d.ts.map +1 -1
- package/dist/esm/compliance/approval-seals/approval-seals-admin.backend.controller.js +60 -28
- package/dist/esm/compliance/approval-seals/approval-seals-admin.backend.controller.js.map +1 -1
- package/dist/esm/compliance/approval-seals/approval-seals-on-record.backend.controller.d.ts +90 -0
- package/dist/esm/compliance/approval-seals/approval-seals-on-record.backend.controller.d.ts.map +1 -0
- package/dist/esm/compliance/approval-seals/approval-seals-on-record.backend.controller.js +373 -0
- package/dist/esm/compliance/approval-seals/approval-seals-on-record.backend.controller.js.map +1 -0
- package/dist/esm/compliance/approval-seals/decision-presentation.backend.d.ts +29 -2
- package/dist/esm/compliance/approval-seals/decision-presentation.backend.d.ts.map +1 -1
- package/dist/esm/compliance/approval-seals/decision-presentation.backend.js +40 -3
- package/dist/esm/compliance/approval-seals/decision-presentation.backend.js.map +1 -1
- package/dist/esm/compliance/electronic-signatures/pdf-signature-structure.backend.d.ts.map +1 -1
- package/dist/esm/compliance/electronic-signatures/pdf-signature-structure.backend.js +34 -6
- package/dist/esm/compliance/electronic-signatures/pdf-signature-structure.backend.js.map +1 -1
- package/dist/esm/compliance/electronic-signatures/signature-request-evidence-files.backend.d.ts +30 -6
- package/dist/esm/compliance/electronic-signatures/signature-request-evidence-files.backend.d.ts.map +1 -1
- package/dist/esm/compliance/electronic-signatures/signature-request-evidence-files.backend.js +67 -22
- package/dist/esm/compliance/electronic-signatures/signature-request-evidence-files.backend.js.map +1 -1
- package/dist/esm/compliance/electronic-signatures/signature-request-poll.batch.backend.service.d.ts.map +1 -1
- package/dist/esm/compliance/electronic-signatures/signature-request-poll.batch.backend.service.js +18 -1
- package/dist/esm/compliance/electronic-signatures/signature-request-poll.batch.backend.service.js.map +1 -1
- package/dist/esm/compliance/electronic-signatures/signature-requests.backend.service.d.ts +66 -1
- package/dist/esm/compliance/electronic-signatures/signature-requests.backend.service.d.ts.map +1 -1
- package/dist/esm/compliance/electronic-signatures/signature-requests.backend.service.js +161 -14
- package/dist/esm/compliance/electronic-signatures/signature-requests.backend.service.js.map +1 -1
- package/dist/esm/compliance/electronic-signatures/signed-document-validation.backend.d.ts +47 -0
- package/dist/esm/compliance/electronic-signatures/signed-document-validation.backend.d.ts.map +1 -1
- package/dist/esm/compliance/electronic-signatures/signed-document-validation.backend.js.map +1 -1
- package/dist/esm/external-providers/services/content-safety.backend.service.d.ts +27 -2
- package/dist/esm/external-providers/services/content-safety.backend.service.d.ts.map +1 -1
- package/dist/esm/external-providers/services/content-safety.backend.service.js +46 -1
- package/dist/esm/external-providers/services/content-safety.backend.service.js.map +1 -1
- package/dist/esm/external-providers/services/embeddings.backend.service.d.ts.map +1 -1
- package/dist/esm/external-providers/services/embeddings.backend.service.js +2 -0
- package/dist/esm/external-providers/services/embeddings.backend.service.js.map +1 -1
- package/dist/esm/external-providers/services/external-providers-registry.backend.service.d.ts +11 -0
- package/dist/esm/external-providers/services/external-providers-registry.backend.service.d.ts.map +1 -1
- package/dist/esm/external-providers/services/external-providers-registry.backend.service.js +23 -0
- package/dist/esm/external-providers/services/external-providers-registry.backend.service.js.map +1 -1
- package/dist/esm/external-providers/services/provider-service-account-token.backend.d.ts +19 -1
- package/dist/esm/external-providers/services/provider-service-account-token.backend.d.ts.map +1 -1
- package/dist/esm/external-providers/services/provider-service-account-token.backend.js +41 -1
- package/dist/esm/external-providers/services/provider-service-account-token.backend.js.map +1 -1
- package/dist/esm/files/utils/file-parent-read-context.backend.d.ts +27 -2
- package/dist/esm/files/utils/file-parent-read-context.backend.d.ts.map +1 -1
- package/dist/esm/files/utils/file-parent-read-context.backend.js +23 -4
- package/dist/esm/files/utils/file-parent-read-context.backend.js.map +1 -1
- package/dist/esm/flows-actors/content-guardrails/classifier-availability.backend.utils.d.ts +26 -0
- package/dist/esm/flows-actors/content-guardrails/classifier-availability.backend.utils.d.ts.map +1 -1
- package/dist/esm/flows-actors/content-guardrails/classifier-availability.backend.utils.js +100 -8
- package/dist/esm/flows-actors/content-guardrails/classifier-availability.backend.utils.js.map +1 -1
- package/dist/esm/flows-actors/content-guardrails/record-owner-policy.backend.d.ts +3 -1
- package/dist/esm/flows-actors/content-guardrails/record-owner-policy.backend.d.ts.map +1 -1
- package/dist/esm/flows-actors/content-guardrails/record-owner-policy.backend.js +8 -2
- package/dist/esm/flows-actors/content-guardrails/record-owner-policy.backend.js.map +1 -1
- package/dist/esm/flows-actors/models/function-tools.backend.definitions.d.ts +43 -0
- package/dist/esm/flows-actors/models/function-tools.backend.definitions.d.ts.map +1 -1
- package/dist/esm/flows-actors/models/function-tools.backend.definitions.js +37 -0
- package/dist/esm/flows-actors/models/function-tools.backend.definitions.js.map +1 -1
- package/dist/esm/flows-actors/services/agent-work-decisions.backend.service.d.ts +7 -0
- package/dist/esm/flows-actors/services/agent-work-decisions.backend.service.d.ts.map +1 -1
- package/dist/esm/flows-actors/services/agent-work-decisions.backend.service.js +8 -3
- package/dist/esm/flows-actors/services/agent-work-decisions.backend.service.js.map +1 -1
- package/dist/esm/flows-actors/services/agents.backend.service.d.ts +31 -6
- package/dist/esm/flows-actors/services/agents.backend.service.d.ts.map +1 -1
- package/dist/esm/flows-actors/services/agents.backend.service.js +78 -30
- package/dist/esm/flows-actors/services/agents.backend.service.js.map +1 -1
- package/dist/esm/flows-actors/services/autonomous-agent-turn.backend.service.d.ts.map +1 -1
- package/dist/esm/flows-actors/services/autonomous-agent-turn.backend.service.js +4 -0
- package/dist/esm/flows-actors/services/autonomous-agent-turn.backend.service.js.map +1 -1
- package/dist/esm/flows-actors/services/conversational-execution.backend.service.d.ts +28 -2
- package/dist/esm/flows-actors/services/conversational-execution.backend.service.d.ts.map +1 -1
- package/dist/esm/flows-actors/services/conversational-execution.backend.service.js +132 -22
- package/dist/esm/flows-actors/services/conversational-execution.backend.service.js.map +1 -1
- package/dist/esm/flows-actors/services/flow-actor-executions.custom-impl.backend.service.d.ts.map +1 -1
- package/dist/esm/flows-actors/services/flow-actor-executions.custom-impl.backend.service.js +12 -8
- package/dist/esm/flows-actors/services/flow-actor-executions.custom-impl.backend.service.js.map +1 -1
- package/dist/esm/flows-actors/services/flows-actors-registry.service.d.ts +5 -0
- package/dist/esm/flows-actors/services/flows-actors-registry.service.d.ts.map +1 -1
- package/dist/esm/flows-actors/services/flows-actors-registry.service.js +18 -2
- package/dist/esm/flows-actors/services/flows-actors-registry.service.js.map +1 -1
- package/dist/esm/flows-actors/utils/acp-session-runner.backend.utils.d.ts +33 -0
- package/dist/esm/flows-actors/utils/acp-session-runner.backend.utils.d.ts.map +1 -1
- package/dist/esm/flows-actors/utils/acp-session-runner.backend.utils.js +58 -0
- package/dist/esm/flows-actors/utils/acp-session-runner.backend.utils.js.map +1 -1
- package/dist/esm/inbound/services/inbound-delivery-dispatch.backend.service.d.ts +12 -1
- package/dist/esm/inbound/services/inbound-delivery-dispatch.backend.service.d.ts.map +1 -1
- package/dist/esm/inbound/services/inbound-delivery-dispatch.backend.service.js +23 -4
- package/dist/esm/inbound/services/inbound-delivery-dispatch.backend.service.js.map +1 -1
- package/dist/esm/inbound/services/inbound-delivery-receiver.backend.service.d.ts +12 -1
- package/dist/esm/inbound/services/inbound-delivery-receiver.backend.service.d.ts.map +1 -1
- package/dist/esm/inbound/services/inbound-delivery-receiver.backend.service.js +55 -17
- package/dist/esm/inbound/services/inbound-delivery-receiver.backend.service.js.map +1 -1
- package/dist/esm/inbound/services/inbound-delivery-store.backend.service.d.ts +22 -1
- package/dist/esm/inbound/services/inbound-delivery-store.backend.service.d.ts.map +1 -1
- package/dist/esm/inbound/services/inbound-delivery-store.backend.service.js +52 -9
- package/dist/esm/inbound/services/inbound-delivery-store.backend.service.js.map +1 -1
- package/dist/esm/inbound/services/inbound-delivery-stored-body.backend.service.d.ts +79 -0
- package/dist/esm/inbound/services/inbound-delivery-stored-body.backend.service.d.ts.map +1 -0
- package/dist/esm/inbound/services/inbound-delivery-stored-body.backend.service.js +211 -0
- package/dist/esm/inbound/services/inbound-delivery-stored-body.backend.service.js.map +1 -0
- package/dist/esm/inbound/services/inbound-delivery-subject-erasure-severance.backend.d.ts +2 -2
- package/dist/esm/inbound/services/inbound-delivery-subject-erasure-severance.backend.d.ts.map +1 -1
- package/dist/esm/inbound/services/inbound-delivery-subject-erasure-severance.backend.js +9 -1
- package/dist/esm/inbound/services/inbound-delivery-subject-erasure-severance.backend.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/inject/models/container.d.ts.map +1 -1
- package/dist/esm/inject/models/container.js +6 -0
- package/dist/esm/inject/models/container.js.map +1 -1
- package/dist/esm/inject/models/service-types.d.ts +5 -0
- package/dist/esm/inject/models/service-types.d.ts.map +1 -1
- package/dist/esm/inject/models/service-types.js +3 -0
- package/dist/esm/inject/models/service-types.js.map +1 -1
- package/dist/esm/monitoring/observability/models/observability.backend.schemas.d.ts +5 -0
- package/dist/esm/monitoring/observability/models/observability.backend.schemas.d.ts.map +1 -1
- package/dist/esm/monitoring/observability/models/observability.backend.schemas.js.map +1 -1
- package/dist/esm/monitoring/observability/services/metrics.service.d.ts +34 -1
- package/dist/esm/monitoring/observability/services/metrics.service.d.ts.map +1 -1
- package/dist/esm/monitoring/observability/services/metrics.service.js +49 -0
- package/dist/esm/monitoring/observability/services/metrics.service.js.map +1 -1
- package/dist/esm/notifications/webhooks/controllers/webhook-auto-controller.backend.service.d.ts +6 -1
- package/dist/esm/notifications/webhooks/controllers/webhook-auto-controller.backend.service.d.ts.map +1 -1
- package/dist/esm/notifications/webhooks/controllers/webhook-auto-controller.backend.service.js +15 -4
- package/dist/esm/notifications/webhooks/controllers/webhook-auto-controller.backend.service.js.map +1 -1
- package/dist/esm/notifications/webhooks/outbound/services/m2m-webhook-delivery-log.custom-impl.backend.service.d.ts.map +1 -1
- package/dist/esm/notifications/webhooks/outbound/services/m2m-webhook-delivery-log.custom-impl.backend.service.js +3 -1
- package/dist/esm/notifications/webhooks/outbound/services/m2m-webhook-delivery-log.custom-impl.backend.service.js.map +1 -1
- package/dist/esm/runtime/services/http-server.backend.service.d.ts +3 -1
- package/dist/esm/runtime/services/http-server.backend.service.d.ts.map +1 -1
- package/dist/esm/runtime/services/http-server.backend.service.js +11 -3
- package/dist/esm/runtime/services/http-server.backend.service.js.map +1 -1
- package/dist/esm/runtime/services/utils/inbound-endpoint-raw-body.backend.utils.d.ts +22 -0
- package/dist/esm/runtime/services/utils/inbound-endpoint-raw-body.backend.utils.d.ts.map +1 -1
- package/dist/esm/runtime/services/utils/inbound-endpoint-raw-body.backend.utils.js +54 -0
- package/dist/esm/runtime/services/utils/inbound-endpoint-raw-body.backend.utils.js.map +1 -1
- package/dist/esm/security/authentications/services/auth-methods/auth-password-reset.backend.service.d.ts +51 -0
- package/dist/esm/security/authentications/services/auth-methods/auth-password-reset.backend.service.d.ts.map +1 -1
- package/dist/esm/security/authentications/services/auth-methods/auth-password-reset.backend.service.js +87 -15
- package/dist/esm/security/authentications/services/auth-methods/auth-password-reset.backend.service.js.map +1 -1
- package/dist/esm/security/authentications/services/custom-impls/siem-delivery-dlq.custom-impl.backend.service.d.ts.map +1 -1
- package/dist/esm/security/authentications/services/custom-impls/siem-delivery-dlq.custom-impl.backend.service.js +5 -2
- package/dist/esm/security/authentications/services/custom-impls/siem-delivery-dlq.custom-impl.backend.service.js.map +1 -1
- package/dist/esm/security/authentications/services/custom-impls/sso-config-domain-verification.custom-impl.backend.service.d.ts.map +1 -1
- package/dist/esm/security/authentications/services/custom-impls/sso-config-domain-verification.custom-impl.backend.service.js +65 -24
- package/dist/esm/security/authentications/services/custom-impls/sso-config-domain-verification.custom-impl.backend.service.js.map +1 -1
- package/dist/esm/security/authorizations/super-admin-floor.backend.utils.d.ts +64 -1
- package/dist/esm/security/authorizations/super-admin-floor.backend.utils.d.ts.map +1 -1
- package/dist/esm/security/authorizations/super-admin-floor.backend.utils.js +84 -2
- package/dist/esm/security/authorizations/super-admin-floor.backend.utils.js.map +1 -1
- package/dist/esm/security/oauth-clients/oauth-clients.custom-impl.backend.service.d.ts +14 -0
- package/dist/esm/security/oauth-clients/oauth-clients.custom-impl.backend.service.d.ts.map +1 -1
- package/dist/esm/security/oauth-clients/oauth-clients.custom-impl.backend.service.js +13 -5
- package/dist/esm/security/oauth-clients/oauth-clients.custom-impl.backend.service.js.map +1 -1
- package/dist/esm/users/services/anonymous-consent.custom-impl.backend.service.d.ts.map +1 -1
- package/dist/esm/users/services/anonymous-consent.custom-impl.backend.service.js +6 -2
- package/dist/esm/users/services/anonymous-consent.custom-impl.backend.service.js.map +1 -1
- package/dist/esm/users/services/user-force-password-reset.custom-impl.backend.service.d.ts +7 -0
- package/dist/esm/users/services/user-force-password-reset.custom-impl.backend.service.d.ts.map +1 -1
- package/dist/esm/users/services/user-force-password-reset.custom-impl.backend.service.js +65 -20
- package/dist/esm/users/services/user-force-password-reset.custom-impl.backend.service.js.map +1 -1
- package/dist/esm/users/services/user-role-administration.custom-impl.backend.service.d.ts.map +1 -1
- package/dist/esm/users/services/user-role-administration.custom-impl.backend.service.js +11 -4
- package/dist/esm/users/services/user-role-administration.custom-impl.backend.service.js.map +1 -1
- package/dist/esm/users/services/user-self-custom-implementations.backend.d.ts.map +1 -1
- package/dist/esm/users/services/user-self-custom-implementations.backend.js +4 -2
- package/dist/esm/users/services/user-self-custom-implementations.backend.js.map +1 -1
- package/dist/esm/users/services/user-super-admin-floor.custom-impl.backend.service.d.ts.map +1 -1
- package/dist/esm/users/services/user-super-admin-floor.custom-impl.backend.service.js +16 -0
- package/dist/esm/users/services/user-super-admin-floor.custom-impl.backend.service.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +10 -10
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-self-custom-implementations.backend.js","sourceRoot":"","sources":["../../../../src/users/services/user-self-custom-implementations.backend.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * User Custom Service Implementations\n *\n * SEC-W5.13-FIX-6: Validates userTypes on Users.UPDATE to prevent assigning\n * non-existent user types via admin CRUD. #2668 / #2677: refuses an unjustified membership-bound grant, and ends sessions\n * and records a removal.\n */\n\nimport {\n CoreResourceOperation,\n CoreResourceType,\n ErrorType,\n ErrorCustomMessageReference,\n MembershipBoundUserTypeErrorCode,\n RegistrationAcquisitionChannel,\n UserTypeRevocationBasis,\n evaluateUserTypeAcquisition,\n resolveMembershipBoundUserTypes,\n ResourceOperationVariantType,\n} from '@wildo-ai/saas-models';\nimport type { ExecutionContext_RoleInfo, User, UserTypeDefinition } from '@wildo-ai/saas-models';\nimport {\n createResourceCustomServiceImplementation,\n ResourceCustomServiceImplementationHookTransactionPosture,\n type ResourceCustomServiceImplementationFactory,\n} from '~wildo-backend-lib/base-classes/services/services/resource-operation-custom-implementation.factory.backend';\nimport type { ResourcesRegistryBackendService } from '~wildo-backend-lib/resources/services/resources-registry.backend.service';\nimport type { AuthorizationsBackendService } from '~wildo-backend-lib/security/authorizations/services/authorizations.backend.service';\nimport { SAAS_SERVICE_TYPES } from '~wildo-backend-lib/inject/models/service-types';\nimport type { ResourceOperation_CustomImplementation_Utils } from '~wildo-backend-lib/base-classes/services/services/resource-operation-custom-implementation-base.backend.service';\nimport type { ExecutionContext } from '~wildo-backend-lib/runtime/models/execution-context.backend.schemas';\nimport { describeUnaddressableShape, resolveSingleSubjectId } from '~wildo-backend-lib/base-classes/utils/single-subject-addressing.backend.utils';\nimport type { AuditLogsBackendService } from '~wildo-backend-lib/compliance/audit-trails/services/audit-logs.backend.service';\nimport type { AuthTokenIssuerBackendService } from '~wildo-backend-lib/security/authentications/services/auth-token-issuer.backend.service';\nimport { emitAuthorityChangeAuditWhenDurable } from '~wildo-backend-lib/organizations/services/authority-change-audit.backend.utils';\nimport { unjustifiedMembershipBoundUserTypes } from '~wildo-backend-lib/organizations/services/user-type-revocation-on-org-leave.backend.utils';\nimport type { Container } from 'inversify';\nimport { engineRepositoriesRegistry } from '~wildo-backend-lib/base-classes/services/services/engine-repositories-registry.backend.utils';\n\n/** The one authorizer question this module asks. */\ntype SuperAdministrationAuthority = Pick<AuthorizationsBackendService, 'initiatorHoldsApplicationSuperAdministration'>;\n\nfunction assertAdministrativeUserTypeAcquisition(\n resourcesRegistryService: ResourcesRegistryBackendService,\n authorizations: SuperAdministrationAuthority,\n requestedUserTypes: string[],\n currentUserTypes: string[],\n executionContext: {\n readonly initiatorRoles: readonly ExecutionContext_RoleInfo[];\n isUserExecution(): boolean;\n isMachineExecution(): boolean;\n },\n errorBuilder: {\n buildError: (type: ErrorType, executionContext?: unknown, options?: {\n customMessageReference?: ErrorCustomMessageReference;\n context?: Record<string, unknown>;\n }) => unknown;\n },\n): void {\n // Only explicit external administrator CRUD owns this channel. Internal/public provisioning\n // paths are governed at their semantic gateways (registration, invitation, SSO, or SCIM).\n //\n // Through the role hierarchy, never by string (#2448). This check SELECTS a safeguard rather than opening a\n // door, so the string compare failed OPEN: a role inheriting the super-administrator passed the operation's\n // roles gate (which resolves the hierarchy) and then skipped the acquisition rule below.\n const isExternalAdministrator =\n (executionContext.isUserExecution() || executionContext.isMachineExecution())\n && authorizations.initiatorHoldsApplicationSuperAdministration(executionContext.initiatorRoles);\n if (!isExternalAdministrator) return;\n\n const userTypeDefinitions = resourcesRegistryService.getUserTypesConfig() as\n | Record<string, UserTypeDefinition>\n | undefined;\n const acquisition = evaluateUserTypeAcquisition(\n userTypeDefinitions,\n requestedUserTypes,\n currentUserTypes,\n RegistrationAcquisitionChannel.ADMINISTRATIVE_PROVISIONING,\n );\n if (!acquisition.allowed) {\n throw errorBuilder.buildError(ErrorType.AUTHORIZATION, executionContext, {\n customMessageReference: ErrorCustomMessageReference.REGISTRATION_DISABLED,\n context: {\n channel: RegistrationAcquisitionChannel.ADMINISTRATIVE_PROVISIONING,\n deniedUserTypes: acquisition.deniedUserTypes,\n },\n });\n }\n}\n\nfunction createUsersCreateFactory(): ResourceCustomServiceImplementationFactory {\n return (resourcesRegistryService: ResourcesRegistryBackendService, container: Container) => {\n return createResourceCustomServiceImplementation({\n operationPath: {\n resourceIdentifier: CoreResourceType.USERS,\n operationIdentifier: CoreResourceOperation.CREATE,\n variantType: ResourceOperationVariantType.API_CALL,\n isOperationDefault: true,\n },\n // #2297: the user-type acquisition check reads configuration and the caller's authority, no I/O.\n hookTransactionPosture: ResourceCustomServiceImplementationHookTransactionPosture.RUNS_INSIDE_TRANSACTION,\n handlers: {\n prefixCoreOperations: async (_id, input, executionContext, _operationPath, utils) => {\n const userTypes = (input as { userTypes?: string[] })?.userTypes ?? [];\n assertAdministrativeUserTypeAcquisition(\n resourcesRegistryService,\n container.get<SuperAdministrationAuthority>(SAAS_SERVICE_TYPES.AuthorizationsService),\n userTypes,\n [],\n executionContext,\n utils.errorBuilder,\n );\n return input;\n },\n },\n });\n };\n}\n\n/**\n * The caller-authored `userTypes`, or `undefined` when the patch does not mention them. An authored EMPTY array is a real\n * change (it removes every type), so it is kept, not read as \"not authored\" (#2677). Non-string members are dropped: a\n * prefix sees the payload before the strict parse, which refuses malformed input further down.\n */\nfunction readAuthoredUserTypes(input: unknown): string[] | undefined {\n const value = (input as { userTypes?: unknown } | null | undefined)?.userTypes;\n if (!Array.isArray(value)) return undefined;\n return value.filter((userType): userType is string => typeof userType === 'string' && userType.length > 0);\n}\n\n/**\n * What an administrator's UPDATE changed in a user's types, staged by the prefix for the postfix: only the prefix sees the\n * previous types, only the postfix knows the write ran. Both halves are recorded (#2677, #2668 review F2).\n */\ntype PendingUserTypeChange = { userId: string; revokedUserTypes: string[]; grantedUserTypes: string[]; resultingUserTypes: string[] };\nconst PENDING_USER_TYPE_CHANGE_BY_EC = new WeakMap<ExecutionContext<any>, PendingUserTypeChange>();\n\n/**\n * Refuse a grant of a membership-bound user type that the subject's organizations do not justify (#2668).\n *\n * The same question the start-time re-check asks (#2623), through the same predicate: a user who belongs to organizations,\n * none of a type the declaration lists for the type, would lose it at the next start. A user who belongs nowhere is not\n * refused, because the declaration says nothing about them (a registrant whose organization comes at onboarding).\n * Read through the infrastructure door: this is an invariant about the subject's memberships, not a question the\n * administrator's own reach should answer.\n */\nasync function assertBoundUserTypeGrantsJustified(params: {\n subjectUserId: string;\n addedUserTypes: readonly string[];\n userTypesConfig: Parameters<typeof resolveMembershipBoundUserTypes>[0];\n executionContext: ExecutionContext<any>;\n utils: ResourceOperation_CustomImplementation_Utils;\n}): Promise<void> {\n const repositories = engineRepositoriesRegistry(params.utils);\n const unjustified = await unjustifiedMembershipBoundUserTypes({\n userId: params.subjectUserId,\n grantedUserTypes: params.addedUserTypes,\n userTypesConfig: params.userTypesConfig,\n readers: {\n listMemberships: (userId) => repositories.findManyForInfrastructure<Record<string, unknown>>(CoreResourceType.ORGANIZATION_MEMBERS, { userId }),\n organizationTypesOf: async (organizationIds) => {\n const organizations = await repositories.findManyForInfrastructure<Record<string, unknown>>(CoreResourceType.ORGANIZATIONS, { _id: { $in: [...organizationIds] } });\n return new Map(organizations.map((organization) => [String(organization._id), typeof organization.type === 'string' ? organization.type : undefined]));\n },\n },\n });\n if (unjustified.length === 0) return;\n const boundUserTypes = resolveMembershipBoundUserTypes(params.userTypesConfig);\n\n throw params.utils.errorBuilder.buildError(ErrorType.CONFLICT, params.executionContext, {\n customMessageReference: ErrorCustomMessageReference.CONFLICT,\n context: {\n code: MembershipBoundUserTypeErrorCode.MEMBERSHIP_BOUND_USER_TYPE_NOT_JUSTIFIED,\n reason: 'membership_bound_user_type_not_justified',\n userId: params.subjectUserId,\n unjustifiedUserTypes: unjustified,\n listedOrganizationTypes: Object.fromEntries(unjustified.map((userType) => [userType, boundUserTypes.get(userType) ?? []])),\n message: 'This user belongs to organizations, none of a type the declaration lists for the user type(s) being granted '\n + '(`userTypes.<type>.requiresOrgMembership.orgTypes`). The next start would revoke them again. Add the user to an '\n + 'organization of a listed type, or list their organization\\'s type in the declaration.',\n },\n });\n}\n\n/**\n * USERS UPDATE prefix — user-type validation, administrative-type acquisition control, and what removing a type owes.\n *\n * Registered on API_CALL only, the single UPDATE variant the resource configuration\n * declares. A second registration on REPOSITORY_ONLY used to sit alongside it to cover\n * \"in-process\" updates; it never ran and never could. Custom implementations resolve by an\n * exact operation-path key, no REPOSITORY_ONLY UPDATE variant is configured, and\n * `doOperation` throws on REPOSITORY_ONLY operations outright — the service tier is not\n * reachable for them, so a service-tier custom implementation there is unreachable by\n * construction rather than merely unmatched.\n *\n * The UPDATE replaces the whole `userTypes` array, so it can REMOVE a type as well as grant one (#2677). A user type sets\n * the sign-in policy, so a removal ends the user's sessions here, before the write, as every other revocation does\n * (member removal, a tenant's suspension, SCIM, the start-time re-check); and it is recorded as `USER_TYPES_REVOKED`\n * with the administrator as actor once the write is durable. The variant is MEDIUM risk, below the automatic audit floor,\n * so without this the removal left no row at all. A grant is recorded the same way, as `USER_TYPES_GRANTED` (#2668 review\n * F2), and a grant of a membership-bound type is refused when the subject's organizations do not justify it (#2668).\n */\nfunction createUsersUpdateFactory(): ResourceCustomServiceImplementationFactory {\n return (resourcesRegistryService: ResourcesRegistryBackendService, container: Container) => {\n return createResourceCustomServiceImplementation({\n operationPath: {\n resourceIdentifier: CoreResourceType.USERS,\n operationIdentifier: CoreResourceOperation.UPDATE,\n variantType: ResourceOperationVariantType.API_CALL,\n isOperationDefault: true,\n },\n handlers: {\n prefixCoreOperations: async (_id, input, executionContext, _operationPath, utils) => {\n const userTypes = readAuthoredUserTypes(input);\n if (userTypes === undefined) return input;\n\n const userTypesConfig = resourcesRegistryService.getUserTypesConfig();\n if (userTypesConfig && userTypes.length > 0) {\n const validTypes = Object.keys(userTypesConfig);\n const invalidTypes = userTypes.filter(t => !validTypes.includes(t));\n\n if (invalidTypes.length > 0) {\n throw utils.errorBuilder.buildError(ErrorType.VALIDATION, executionContext, {\n customMessageReference: ErrorCustomMessageReference.SERVICE_RESOURCE_VALIDATION_FAILED,\n context: {\n reason: 'invalid_user_types',\n invalidTypes,\n validTypes,\n },\n });\n }\n }\n\n // A join's grant, a leave's or a declaration's revocation, an SSO or SCIM reconciliation: the server decided this\n // write and owns what it owes (its own sessions and record, with its own basis). Only an administrator's request\n // is answered below. Not keyed on the execution type: member removal revokes under the remover's own context.\n if (utils.userTypesChangedByServer === true) return input;\n\n // One addressed subject: a removal must end ONE user's sessions and name them in the record.\n const subjectUserId = resolveSingleSubjectId(_id);\n if (subjectUserId === undefined) {\n throw utils.errorBuilder.buildError(ErrorType.VALIDATION, executionContext, {\n context: {\n message: 'Users UPDATE carrying userTypes requires a single addressed subject',\n reason: 'user_types_update_unaddressed',\n receivedAddressing: describeUnaddressableShape(_id),\n },\n });\n }\n // Read fresh, through the caller's own reach: a user outside it is absent and answered 404.\n const currentUser = await engineRepositoriesRegistry(utils).getRepositoryOrThrow(CoreResourceType.USERS, executionContext)\n .read<Pick<User, '_id' | 'userTypes'>>(executionContext, subjectUserId);\n if (!currentUser) {\n throw utils.errorBuilder.buildError(ErrorType.NOT_FOUND, executionContext, {\n customMessageReference: ErrorCustomMessageReference.NOT_FOUND,\n context: { reason: 'user_not_found', userId: subjectUserId },\n });\n }\n const currentUserTypes = Array.isArray(currentUser.userTypes) ? currentUser.userTypes.filter((userType): userType is string => typeof userType === 'string') : [];\n\n assertAdministrativeUserTypeAcquisition(\n resourcesRegistryService,\n container.get<SuperAdministrationAuthority>(SAAS_SERVICE_TYPES.AuthorizationsService),\n userTypes,\n currentUserTypes,\n executionContext,\n utils.errorBuilder,\n );\n\n const grantedUserTypes = [...new Set(userTypes.filter((userType) => !currentUserTypes.includes(userType)))];\n await assertBoundUserTypeGrantsJustified({\n subjectUserId,\n addedUserTypes: grantedUserTypes,\n userTypesConfig: userTypesConfig as never,\n executionContext,\n utils,\n });\n\n const revokedUserTypes = currentUserTypes.filter((userType) => !userTypes.includes(userType));\n if (revokedUserTypes.length > 0) {\n // Before the write: a failure here leaves the type in place and the request refused, never a removal with\n // live sessions still running under the old policy. A refusal LATER in the same request (the super-\n // administrator floor on an `appRoles` rewrite, say) leaves the sessions ended and the types in place: the\n // fail-closed direction, costing the subject a sign-in, and deliberate (review F8 of #2677).\n await container.get<AuthTokenIssuerBackendService>(SAAS_SERVICE_TYPES.AuthTokenIssuer).invalidateAllTokensForUser(subjectUserId);\n }\n if (revokedUserTypes.length > 0 || grantedUserTypes.length > 0) {\n PENDING_USER_TYPE_CHANGE_BY_EC.set(executionContext, { userId: subjectUserId, revokedUserTypes, grantedUserTypes, resultingUserTypes: [...userTypes] });\n }\n\n return input;\n },\n postfixCoreOperations: async (_id, result, executionContext, _operationPath, utils) => {\n const pending = PENDING_USER_TYPE_CHANGE_BY_EC.get(executionContext);\n if (!pending) return result;\n PENDING_USER_TYPE_CHANGE_BY_EC.delete(executionContext);\n const auditLogs = () => container.get<AuditLogsBackendService>(SAAS_SERVICE_TYPES.AuditLogsService);\n // After commit, so a rolled-back update records nothing.\n if (pending.revokedUserTypes.length > 0) {\n await emitAuthorityChangeAuditWhenDurable({\n serviceOptions: utils.serviceOptions,\n effectKey: `user-types-revoked:${pending.userId}`,\n logger: utils.logger,\n failureContext: { userId: pending.userId, revokedUserTypes: pending.revokedUserTypes },\n emit: () => auditLogs().logUserTypesRevoked({\n userId: pending.userId,\n revokedUserTypes: pending.revokedUserTypes,\n remainingUserTypes: pending.resultingUserTypes,\n basis: UserTypeRevocationBasis.ADMINISTRATOR_DECISION,\n }, executionContext),\n });\n }\n if (pending.grantedUserTypes.length > 0) {\n await emitAuthorityChangeAuditWhenDurable({\n serviceOptions: utils.serviceOptions,\n effectKey: `user-types-granted:${pending.userId}`,\n logger: utils.logger,\n failureContext: { userId: pending.userId, grantedUserTypes: pending.grantedUserTypes },\n emit: () => auditLogs().logUserTypesGranted({\n userId: pending.userId,\n grantedUserTypes: pending.grantedUserTypes,\n resultingUserTypes: pending.resultingUserTypes,\n }, executionContext),\n });\n }\n return result;\n },\n },\n });\n };\n}\n\nexport const userSelfCustomImplementations: ResourceCustomServiceImplementationFactory[] = [\n createUsersCreateFactory(),\n createUsersUpdateFactory(),\n];\n"]}
|
|
1
|
+
{"version":3,"file":"user-self-custom-implementations.backend.js","sourceRoot":"","sources":["../../../../src/users/services/user-self-custom-implementations.backend.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * User Custom Service Implementations\n *\n * SEC-W5.13-FIX-6: Validates userTypes on Users.UPDATE to prevent assigning\n * non-existent user types via admin CRUD. #2668 / #2677: refuses an unjustified membership-bound grant, and ends sessions\n * and records a removal.\n */\n\nimport {\n CoreResourceOperation,\n CoreResourceType,\n ErrorType,\n ErrorCustomMessageReference,\n MembershipBoundUserTypeErrorCode,\n RegistrationAcquisitionChannel,\n UserTypeRevocationBasis,\n evaluateUserTypeAcquisition,\n resolveMembershipBoundUserTypes,\n ResourceOperationVariantType,\n} from '@wildo-ai/saas-models';\nimport type { ExecutionContext_RoleInfo, User, UserTypeDefinition } from '@wildo-ai/saas-models';\nimport {\n createResourceCustomServiceImplementation,\n ResourceCustomServiceImplementationHookTransactionPosture,\n type ResourceCustomServiceImplementationFactory,\n} from '~wildo-backend-lib/base-classes/services/services/resource-operation-custom-implementation.factory.backend';\nimport type { ResourcesRegistryBackendService } from '~wildo-backend-lib/resources/services/resources-registry.backend.service';\nimport type { AuthorizationsBackendService } from '~wildo-backend-lib/security/authorizations/services/authorizations.backend.service';\nimport { SAAS_SERVICE_TYPES } from '~wildo-backend-lib/inject/models/service-types';\nimport type { ResourceOperation_CustomImplementation_Utils } from '~wildo-backend-lib/base-classes/services/services/resource-operation-custom-implementation-base.backend.service';\nimport type { ExecutionContext } from '~wildo-backend-lib/runtime/models/execution-context.backend.schemas';\nimport { describeUnaddressableShape, resolveSingleSubjectId } from '~wildo-backend-lib/base-classes/utils/single-subject-addressing.backend.utils';\nimport type { AuditLogsBackendService } from '~wildo-backend-lib/compliance/audit-trails/services/audit-logs.backend.service';\nimport type { AuthTokenIssuerBackendService } from '~wildo-backend-lib/security/authentications/services/auth-token-issuer.backend.service';\nimport { emitAuthorityChangeAuditWhenDurable } from '~wildo-backend-lib/organizations/services/authority-change-audit.backend.utils';\nimport { unjustifiedMembershipBoundUserTypes } from '~wildo-backend-lib/organizations/services/user-type-revocation-on-org-leave.backend.utils';\nimport type { Container } from 'inversify';\nimport { engineRepositoriesRegistry } from '~wildo-backend-lib/base-classes/services/services/engine-repositories-registry.backend.utils';\n\n/** The one authorizer question this module asks. */\ntype SuperAdministrationAuthority = Pick<AuthorizationsBackendService, 'initiatorHoldsApplicationSuperAdministration'>;\n\nfunction assertAdministrativeUserTypeAcquisition(\n resourcesRegistryService: ResourcesRegistryBackendService,\n authorizations: SuperAdministrationAuthority,\n requestedUserTypes: string[],\n currentUserTypes: string[],\n executionContext: {\n readonly initiatorRoles: readonly ExecutionContext_RoleInfo[];\n isUserExecution(): boolean;\n isMachineExecution(): boolean;\n },\n errorBuilder: {\n buildError: (type: ErrorType, executionContext?: unknown, options?: {\n customMessageReference?: ErrorCustomMessageReference;\n context?: Record<string, unknown>;\n }) => unknown;\n },\n): void {\n // Only explicit external administrator CRUD owns this channel. Internal/public provisioning\n // paths are governed at their semantic gateways (registration, invitation, SSO, or SCIM).\n //\n // Through the role hierarchy, never by string (#2448). This check SELECTS a safeguard rather than opening a\n // door, so the string compare failed OPEN: a role inheriting the super-administrator passed the operation's\n // roles gate (which resolves the hierarchy) and then skipped the acquisition rule below.\n const isExternalAdministrator =\n (executionContext.isUserExecution() || executionContext.isMachineExecution())\n && authorizations.initiatorHoldsApplicationSuperAdministration(executionContext.initiatorRoles);\n if (!isExternalAdministrator) return;\n\n const userTypeDefinitions = resourcesRegistryService.getUserTypesConfig() as\n | Record<string, UserTypeDefinition>\n | undefined;\n const acquisition = evaluateUserTypeAcquisition(\n userTypeDefinitions,\n requestedUserTypes,\n currentUserTypes,\n RegistrationAcquisitionChannel.ADMINISTRATIVE_PROVISIONING,\n );\n if (!acquisition.allowed) {\n throw errorBuilder.buildError(ErrorType.AUTHORIZATION, executionContext, {\n customMessageReference: ErrorCustomMessageReference.REGISTRATION_DISABLED,\n context: {\n channel: RegistrationAcquisitionChannel.ADMINISTRATIVE_PROVISIONING,\n deniedUserTypes: acquisition.deniedUserTypes,\n },\n });\n }\n}\n\nfunction createUsersCreateFactory(): ResourceCustomServiceImplementationFactory {\n return (resourcesRegistryService: ResourcesRegistryBackendService, container: Container) => {\n return createResourceCustomServiceImplementation({\n operationPath: {\n resourceIdentifier: CoreResourceType.USERS,\n operationIdentifier: CoreResourceOperation.CREATE,\n variantType: ResourceOperationVariantType.API_CALL,\n isOperationDefault: true,\n },\n // #2297: the user-type acquisition check reads configuration and the caller's authority, no I/O.\n hookTransactionPosture: ResourceCustomServiceImplementationHookTransactionPosture.RUNS_INSIDE_TRANSACTION,\n handlers: {\n prefixCoreOperations: async (_id, input, executionContext, _operationPath, utils) => {\n const userTypes = (input as { userTypes?: string[] })?.userTypes ?? [];\n assertAdministrativeUserTypeAcquisition(\n resourcesRegistryService,\n container.get<SuperAdministrationAuthority>(SAAS_SERVICE_TYPES.AuthorizationsService),\n userTypes,\n [],\n executionContext,\n utils.errorBuilder,\n );\n return input;\n },\n },\n });\n };\n}\n\n/**\n * The caller-authored `userTypes`, or `undefined` when the patch does not mention them. An authored EMPTY array is a real\n * change (it removes every type), so it is kept, not read as \"not authored\" (#2677). Non-string members are dropped: a\n * prefix sees the payload before the strict parse, which refuses malformed input further down.\n */\nfunction readAuthoredUserTypes(input: unknown): string[] | undefined {\n const value = (input as { userTypes?: unknown } | null | undefined)?.userTypes;\n if (!Array.isArray(value)) return undefined;\n return value.filter((userType): userType is string => typeof userType === 'string' && userType.length > 0);\n}\n\n/**\n * What an administrator's UPDATE changed in a user's types, staged by the prefix for the postfix: only the prefix sees the\n * previous types, only the postfix knows the write ran. Both halves are recorded (#2677, #2668 review F2).\n */\ntype PendingUserTypeChange = { userId: string; revokedUserTypes: string[]; grantedUserTypes: string[]; resultingUserTypes: string[] };\nconst PENDING_USER_TYPE_CHANGE_BY_EC = new WeakMap<ExecutionContext<any>, PendingUserTypeChange>();\n\n/**\n * Refuse a grant of a membership-bound user type that the subject's organizations do not justify (#2668).\n *\n * The same question the start-time re-check asks (#2623), through the same predicate: a user who belongs to organizations,\n * none of a type the declaration lists for the type, would lose it at the next start. A user who belongs nowhere is not\n * refused, because the declaration says nothing about them (a registrant whose organization comes at onboarding).\n * Read through the infrastructure door: this is an invariant about the subject's memberships, not a question the\n * administrator's own reach should answer.\n */\nasync function assertBoundUserTypeGrantsJustified(params: {\n subjectUserId: string;\n addedUserTypes: readonly string[];\n userTypesConfig: Parameters<typeof resolveMembershipBoundUserTypes>[0];\n executionContext: ExecutionContext<any>;\n utils: ResourceOperation_CustomImplementation_Utils;\n}): Promise<void> {\n const repositories = engineRepositoriesRegistry(params.utils);\n const unjustified = await unjustifiedMembershipBoundUserTypes({\n userId: params.subjectUserId,\n grantedUserTypes: params.addedUserTypes,\n userTypesConfig: params.userTypesConfig,\n readers: {\n listMemberships: (userId) => repositories.findManyForInfrastructure<Record<string, unknown>>(CoreResourceType.ORGANIZATION_MEMBERS, { userId }),\n organizationTypesOf: async (organizationIds) => {\n const organizations = await repositories.findManyForInfrastructure<Record<string, unknown>>(CoreResourceType.ORGANIZATIONS, { _id: { $in: [...organizationIds] } });\n return new Map(organizations.map((organization) => [String(organization._id), typeof organization.type === 'string' ? organization.type : undefined]));\n },\n },\n });\n if (unjustified.length === 0) return;\n const boundUserTypes = resolveMembershipBoundUserTypes(params.userTypesConfig);\n\n throw params.utils.errorBuilder.buildError(ErrorType.CONFLICT, params.executionContext, {\n customMessageReference: ErrorCustomMessageReference.CONFLICT,\n context: {\n code: MembershipBoundUserTypeErrorCode.MEMBERSHIP_BOUND_USER_TYPE_NOT_JUSTIFIED,\n reason: 'membership_bound_user_type_not_justified',\n userId: params.subjectUserId,\n unjustifiedUserTypes: unjustified,\n listedOrganizationTypes: Object.fromEntries(unjustified.map((userType) => [userType, boundUserTypes.get(userType) ?? []])),\n message: 'This user belongs to organizations, none of a type the declaration lists for the user type(s) being granted '\n + '(`userTypes.<type>.requiresOrgMembership.orgTypes`). The next start would revoke them again. Add the user to an '\n + 'organization of a listed type, or list their organization\\'s type in the declaration.',\n },\n });\n}\n\n/**\n * USERS UPDATE prefix — user-type validation, administrative-type acquisition control, and what removing a type owes.\n *\n * Registered on API_CALL only, the single UPDATE variant the resource configuration\n * declares. A second registration on REPOSITORY_ONLY used to sit alongside it to cover\n * \"in-process\" updates; it never ran and never could. Custom implementations resolve by an\n * exact operation-path key, no REPOSITORY_ONLY UPDATE variant is configured, and\n * `doOperation` throws on REPOSITORY_ONLY operations outright — the service tier is not\n * reachable for them, so a service-tier custom implementation there is unreachable by\n * construction rather than merely unmatched.\n *\n * The UPDATE replaces the whole `userTypes` array, so it can REMOVE a type as well as grant one (#2677). A user type sets\n * the sign-in policy, so a removal ends the user's sessions here, before the write, as every other revocation does\n * (member removal, a tenant's suspension, SCIM, the start-time re-check); and it is recorded as `USER_TYPES_REVOKED`\n * with the administrator as actor once the write is durable. The variant is MEDIUM risk, below the automatic audit floor,\n * so without this the removal left no row at all. A grant is recorded the same way, as `USER_TYPES_GRANTED` (#2668 review\n * F2), and a grant of a membership-bound type is refused when the subject's organizations do not justify it (#2668).\n */\nfunction createUsersUpdateFactory(): ResourceCustomServiceImplementationFactory {\n return (resourcesRegistryService: ResourcesRegistryBackendService, container: Container) => {\n return createResourceCustomServiceImplementation({\n operationPath: {\n resourceIdentifier: CoreResourceType.USERS,\n operationIdentifier: CoreResourceOperation.UPDATE,\n variantType: ResourceOperationVariantType.API_CALL,\n isOperationDefault: true,\n },\n handlers: {\n prefixCoreOperations: async (_id, input, executionContext, _operationPath, utils) => {\n const userTypes = readAuthoredUserTypes(input);\n if (userTypes === undefined) return input;\n\n const userTypesConfig = resourcesRegistryService.getUserTypesConfig();\n if (userTypesConfig && userTypes.length > 0) {\n const validTypes = Object.keys(userTypesConfig);\n const invalidTypes = userTypes.filter(t => !validTypes.includes(t));\n\n if (invalidTypes.length > 0) {\n throw utils.errorBuilder.buildError(ErrorType.VALIDATION, executionContext, {\n customMessageReference: ErrorCustomMessageReference.SERVICE_RESOURCE_VALIDATION_FAILED,\n context: {\n reason: 'invalid_user_types',\n invalidTypes,\n validTypes,\n },\n });\n }\n }\n\n // A join's grant, a leave's or a declaration's revocation, an SSO or SCIM reconciliation: the server decided this\n // write and owns what it owes (its own sessions and record, with its own basis). Only an administrator's request\n // is answered below. Not keyed on the execution type: member removal revokes under the remover's own context.\n if (utils.userTypesChangedByServer === true) return input;\n\n // One addressed subject: a removal must end ONE user's sessions and name them in the record.\n const subjectUserId = resolveSingleSubjectId(_id);\n if (subjectUserId === undefined) {\n throw utils.errorBuilder.buildError(ErrorType.VALIDATION, executionContext, {\n context: {\n message: 'Users UPDATE carrying userTypes requires a single addressed subject',\n reason: 'user_types_update_unaddressed',\n receivedAddressing: describeUnaddressableShape(_id),\n },\n });\n }\n // Read fresh, through the caller's own reach: a user outside it is absent and answered 404. Inside the UPDATE's\n // transaction (owned by the super-administrator floor's registration since #3670), so the row is read where the\n // write will land rather than from another connection.\n const currentUser = await engineRepositoriesRegistry(utils).getRepositoryOrThrow(CoreResourceType.USERS, executionContext)\n .read<Pick<User, '_id' | 'userTypes'>>(executionContext, subjectUserId, utils.serviceOptions);\n if (!currentUser) {\n throw utils.errorBuilder.buildError(ErrorType.NOT_FOUND, executionContext, {\n customMessageReference: ErrorCustomMessageReference.NOT_FOUND,\n context: { reason: 'user_not_found', userId: subjectUserId },\n });\n }\n const currentUserTypes = Array.isArray(currentUser.userTypes) ? currentUser.userTypes.filter((userType): userType is string => typeof userType === 'string') : [];\n\n assertAdministrativeUserTypeAcquisition(\n resourcesRegistryService,\n container.get<SuperAdministrationAuthority>(SAAS_SERVICE_TYPES.AuthorizationsService),\n userTypes,\n currentUserTypes,\n executionContext,\n utils.errorBuilder,\n );\n\n const grantedUserTypes = [...new Set(userTypes.filter((userType) => !currentUserTypes.includes(userType)))];\n await assertBoundUserTypeGrantsJustified({\n subjectUserId,\n addedUserTypes: grantedUserTypes,\n userTypesConfig: userTypesConfig as never,\n executionContext,\n utils,\n });\n\n const revokedUserTypes = currentUserTypes.filter((userType) => !userTypes.includes(userType));\n if (revokedUserTypes.length > 0) {\n // Before the write: a failure here leaves the type in place and the request refused, never a removal with\n // live sessions still running under the old policy. A refusal LATER in the same request (the super-\n // administrator floor on an `appRoles` rewrite, say) leaves the sessions ended and the types in place: the\n // fail-closed direction, costing the subject a sign-in, and deliberate (review F8 of #2677).\n await container.get<AuthTokenIssuerBackendService>(SAAS_SERVICE_TYPES.AuthTokenIssuer).invalidateAllTokensForUser(subjectUserId);\n }\n if (revokedUserTypes.length > 0 || grantedUserTypes.length > 0) {\n PENDING_USER_TYPE_CHANGE_BY_EC.set(executionContext, { userId: subjectUserId, revokedUserTypes, grantedUserTypes, resultingUserTypes: [...userTypes] });\n }\n\n return input;\n },\n postfixCoreOperations: async (_id, result, executionContext, _operationPath, utils) => {\n const pending = PENDING_USER_TYPE_CHANGE_BY_EC.get(executionContext);\n if (!pending) return result;\n PENDING_USER_TYPE_CHANGE_BY_EC.delete(executionContext);\n const auditLogs = () => container.get<AuditLogsBackendService>(SAAS_SERVICE_TYPES.AuditLogsService);\n // After commit, so a rolled-back update records nothing.\n if (pending.revokedUserTypes.length > 0) {\n await emitAuthorityChangeAuditWhenDurable({\n serviceOptions: utils.serviceOptions,\n effectKey: `user-types-revoked:${pending.userId}`,\n logger: utils.logger,\n failureContext: { userId: pending.userId, revokedUserTypes: pending.revokedUserTypes },\n emit: () => auditLogs().logUserTypesRevoked({\n userId: pending.userId,\n revokedUserTypes: pending.revokedUserTypes,\n remainingUserTypes: pending.resultingUserTypes,\n basis: UserTypeRevocationBasis.ADMINISTRATOR_DECISION,\n }, executionContext),\n });\n }\n if (pending.grantedUserTypes.length > 0) {\n await emitAuthorityChangeAuditWhenDurable({\n serviceOptions: utils.serviceOptions,\n effectKey: `user-types-granted:${pending.userId}`,\n logger: utils.logger,\n failureContext: { userId: pending.userId, grantedUserTypes: pending.grantedUserTypes },\n emit: () => auditLogs().logUserTypesGranted({\n userId: pending.userId,\n grantedUserTypes: pending.grantedUserTypes,\n resultingUserTypes: pending.resultingUserTypes,\n }, executionContext),\n });\n }\n return result;\n },\n },\n });\n };\n}\n\nexport const userSelfCustomImplementations: ResourceCustomServiceImplementationFactory[] = [\n createUsersCreateFactory(),\n createUsersUpdateFactory(),\n];\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-super-admin-floor.custom-impl.backend.service.d.ts","sourceRoot":"","sources":["../../../../src/users/services/user-super-admin-floor.custom-impl.backend.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAUH,OAAO,EAGL,KAAK,0CAA0C,EAChD,MAAM,4GAA4G,CAAC;AA+CpH;;GAEG;AACH,wBAAgB,sCAAsC,IAAI,0CAA0C,
|
|
1
|
+
{"version":3,"file":"user-super-admin-floor.custom-impl.backend.service.d.ts","sourceRoot":"","sources":["../../../../src/users/services/user-super-admin-floor.custom-impl.backend.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAUH,OAAO,EAGL,KAAK,0CAA0C,EAChD,MAAM,4GAA4G,CAAC;AA+CpH;;GAEG;AACH,wBAAgB,sCAAsC,IAAI,0CAA0C,CAuEnG;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,sCAAsC,IAAI,0CAA0C,CA2EnG;AAED,yDAAyD;AACzD,wBAAgB,kCAAkC,IAAI,0CAA0C,EAAE,CAEjG"}
|
|
@@ -123,6 +123,10 @@ export function createUserDeleteSuperAdminFloorFactory() {
|
|
|
123
123
|
errorBuilder: utils.errorBuilder,
|
|
124
124
|
conferringRoles: superAdminConferralResolver(),
|
|
125
125
|
operationIdentifier: CoreResourceOperation.DELETE,
|
|
126
|
+
// #3670: the delete's own transaction (the audited write's, #2297), where the floor serializes concurrent
|
|
127
|
+
// removals. Not owned here as `transactionalCore`: the delete's cascade reaches child resources this
|
|
128
|
+
// registration cannot name as participants, and the audited write already runs it in one transaction.
|
|
129
|
+
transaction: utils.serviceOptions?.transaction,
|
|
126
130
|
});
|
|
127
131
|
// A PREFIX returns the (unmodified) input: this registration guards, it does not transform.
|
|
128
132
|
return input;
|
|
@@ -175,6 +179,17 @@ export function createUserUpdateSuperAdminFloorFactory() {
|
|
|
175
179
|
},
|
|
176
180
|
debugLabel: 'users.update.super-admin-floor',
|
|
177
181
|
priority: SUPER_ADMIN_FLOOR_GUARD_PRIORITY,
|
|
182
|
+
/*
|
|
183
|
+
* #3670: `users | update` is MEDIUM risk, so no audited-write transaction is opened for it (#2297), and without one
|
|
184
|
+
* the floor cannot serialize two concurrent `appRoles` rewrites that each demote the OTHER administrator. This
|
|
185
|
+
* registration therefore owns the transaction for the whole UPDATE pipeline: the floor bumps the APPLICATION row
|
|
186
|
+
* and measures inside it. The other UPDATE hooks were read for it: the role ceiling is in memory and reads the user
|
|
187
|
+
* through utils.serviceOptions, its audit and the user-type change records are post-commit effects, and the
|
|
188
|
+
* user-type revocation ends sessions BEFORE the write on purpose (fail closed: a later refusal leaves the sessions
|
|
189
|
+
* ended, #2677 review F8), which a retried attempt only repeats.
|
|
190
|
+
*/
|
|
191
|
+
transactionalCore: true,
|
|
192
|
+
transactionParticipants: { application: CoreResourceType.APPLICATION },
|
|
178
193
|
handlers: {
|
|
179
194
|
prefixCoreOperations: async (_id, input, executionContext, _operationPath, utils) => {
|
|
180
195
|
const payload = input;
|
|
@@ -212,6 +227,7 @@ export function createUserUpdateSuperAdminFloorFactory() {
|
|
|
212
227
|
errorBuilder: utils.errorBuilder,
|
|
213
228
|
conferringRoles: superAdminConferralResolver(),
|
|
214
229
|
operationIdentifier: CoreResourceOperation.UPDATE,
|
|
230
|
+
transaction: utils.serviceOptions?.transaction,
|
|
215
231
|
});
|
|
216
232
|
// Guard only — the payload is passed through untouched.
|
|
217
233
|
return input;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-super-admin-floor.custom-impl.backend.service.js","sourceRoot":"","sources":["../../../../src/users/services/user-super-admin-floor.custom-impl.backend.service.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * USERS `DELETE` — the last-super-administrator floor on the destructive path.\n *\n * Removing the role and destroying the row that HOLDS it produce the identical unadministrable\n * application, so the guard that refuses the first has to refuse the second. `users | DELETE` is\n * `APP_ADMIN_SUPER_ADMIN`-gated and `riskLevel: CRITICAL`, which means the only principal who can\n * reach it is exactly the principal capable of deleting the last one — including their own account.\n *\n * The role-transition guards live in `user-role-administration.custom-impl.backend.service.ts`\n * because they share that file's user load and its audit staging. This one is separate because a\n * deletion has no role transition to inspect: the row simply stops counting, and the check reads\n * the pre-deletion snapshot rather than an authored patch.\n *\n * ## Why a PREFIX, and why priority 50\n *\n * A refusal has to happen before anything is persisted, so this is a `prefixCoreOperations` hook —\n * the mirror image of the sibling `user-deletion-audit` registration on this same operation path,\n * which is deliberately a POSTFIX (its event may only be emitted once the delete and its cascade\n * have committed).\n *\n * Registrations on one operation path chain in ascending priority. This one takes 50 against the\n * default 100 so it runs FIRST: a delete the floor is going to refuse should not have run any other\n * hook's preparation, and nothing downstream should be reasoning about a row that is not going to be\n * removed.\n *\n * ## Fail-closed on an unreadable subject\n *\n * `utils.currentObject` is the framework's pre-operation snapshot, fetched by the service before any\n * custom implementation runs (`fetchCurrentObjectForOperation`), and is the same snapshot the\n * deletion-audit postfix reads. If it is absent or carries no `appRoles`, this refuses rather than\n * proceeding: treating an unreadable subject as \"not an administrator\" would fail OPEN on the exact\n * input the guard exists for, and there is no recovery once the row is gone.\n *\n * assurance-control: WILDO.ACCESS.ADMINISTRATIVE_CONTINUITY — an application must retain an\n * accountable administrative principal; identity destruction must not remove the last one.\n */\n\nimport type { Container } from 'inversify';\nimport {\n CoreResourceOperation,\n CoreResourceType,\n ErrorType,\n ResourceOperationVariantType,\n type User,\n} from '@wildo-ai/saas-models';\nimport {\n createResourceCustomServiceImplementation,\n ResourceCustomServiceImplementationHookTransactionPosture,\n type ResourceCustomServiceImplementationFactory,\n} from '~wildo-backend-lib/base-classes/services/services/resource-operation-custom-implementation.factory.backend';\nimport type { ResourcesRegistryBackendService } from '~wildo-backend-lib/resources/services/resources-registry.backend.service';\nimport {\n assertSuperAdminFloorPreservedForRemoval,\n assertSuperAdminFloorPreservedForTransition,\n createSuperAdminConferralResolver,\n SuperAdminFloorReductionCause,\n} from '~wildo-backend-lib/security/authorizations/super-admin-floor.backend.utils';\nimport {\n describeUnaddressableShape,\n resolveSingleSubjectId,\n} from '~wildo-backend-lib/base-classes/utils/single-subject-addressing.backend.utils';\nimport { engineRepositoriesRegistry } from '~wildo-backend-lib/base-classes/services/services/engine-repositories-registry.backend.utils';\n\n/**\n * Priority for this registration. Lower runs first; the framework default is 100.\n *\n * Named rather than inlined so the ordering relationship with the sibling deletion-audit\n * registration on this operation path is stated where it is chosen, not inferred from a bare number.\n */\nconst SUPER_ADMIN_FLOOR_GUARD_PRIORITY = 50;\n\n/** The pre-deletion snapshot fields the floor reads. */\ntype UserDeletionSubjectRow = Pick<User, '_id' | 'appRoles' | 'status'>;\n\n/**\n * Project the framework's pre-operation snapshot into the row the floor needs, or `undefined` when\n * it cannot be trusted.\n *\n * `appRoles` is required to be an array: it is the field the whole decision turns on, and a snapshot\n * that lacks it cannot answer whether this subject is an administrator. Returning `undefined` routes\n * the caller into an explicit refusal instead of a silent \"not an administrator\" verdict.\n */\nfunction projectDeletionSubject(currentObject: unknown, subjectId: string): UserDeletionSubjectRow | undefined {\n if (currentObject === null || typeof currentObject !== 'object') return undefined;\n const snapshot = currentObject as Partial<User>;\n if (!Array.isArray(snapshot.appRoles)) return undefined;\n\n return {\n // The snapshot's own `_id` is preferred; the addressed id is the fallback so the subject can\n // always be excluded from the population count by SOME stable identity.\n _id: (snapshot._id ?? subjectId) as User['_id'],\n appRoles: snapshot.appRoles,\n status: snapshot.status as User['status'],\n };\n}\n\n/**\n * `users | DELETE` — refuse a deletion that would remove the last usable super-administrator.\n */\nexport function createUserDeleteSuperAdminFloorFactory(): ResourceCustomServiceImplementationFactory {\n const factory = (_resourcesRegistryService: ResourcesRegistryBackendService, container: Container) => {\n const superAdminConferralResolver = createSuperAdminConferralResolver(container);\n\n return createResourceCustomServiceImplementation({\n operationPath: {\n resourceIdentifier: CoreResourceType.USERS,\n operationIdentifier: CoreResourceOperation.DELETE,\n variantType: ResourceOperationVariantType.API_CALL,\n isOperationDefault: true,\n },\n debugLabel: 'users.delete.super-admin-floor',\n priority: SUPER_ADMIN_FLOOR_GUARD_PRIORITY,\n // #2297: the subject is utils.currentObject and the floor counts OTHER users' rows; a refusal is a thrown CONFLICT.\n hookTransactionPosture: ResourceCustomServiceImplementationHookTransactionPosture.RUNS_INSIDE_TRANSACTION,\n handlers: {\n prefixCoreOperations: async (_id, input, executionContext, _operationPath, utils) => {\n // Fail closed on anything but a single addressed user: a POPULATION-addressed delete names no\n // subject set the floor could exclude from its population count, so it could satisfy the floor\n // with a row it is itself about to destroy. A conditional `{ _id, … }` filter is not such a\n // case — `_id` names the one row that can be destroyed, and the extra predicates only decide\n // whether the delete lands at all.\n const subjectUserId = resolveSingleSubjectId(_id);\n if (subjectUserId === undefined) {\n throw utils.errorBuilder.buildError(ErrorType.VALIDATION, executionContext, {\n context: {\n message: 'Users DELETE requires a single addressed subject',\n reason: 'user_super_admin_floor_unaddressed',\n receivedAddressing: describeUnaddressableShape(_id),\n },\n });\n }\n\n const subject = projectDeletionSubject(utils.currentObject, subjectUserId);\n\n if (!subject) {\n // See the header: an unreadable subject must not be assumed harmless.\n throw utils.errorBuilder.buildError(ErrorType.CONFLICT, executionContext, {\n context: {\n message:\n 'Refused: the user about to be deleted could not be read, so it cannot be proven that this '\n + 'is not the last usable super-administrator.',\n reason: 'user_super_admin_floor_subject_unreadable',\n operationIdentifier: CoreResourceOperation.DELETE,\n userId: subjectUserId,\n },\n });\n }\n\n await assertSuperAdminFloorPreservedForRemoval({\n subjects: [subject],\n cause: SuperAdminFloorReductionCause.DELETION,\n executionContext,\n repositoriesRegistry: engineRepositoriesRegistry(utils),\n errorBuilder: utils.errorBuilder,\n conferringRoles: superAdminConferralResolver(),\n operationIdentifier: CoreResourceOperation.DELETE,\n });\n\n // A PREFIX returns the (unmodified) input: this registration guards, it does not transform.\n return input;\n },\n },\n });\n };\n\n return factory as unknown as ResourceCustomServiceImplementationFactory;\n}\n\n/**\n * `users | UPDATE` — refuse a generic field patch that would retire the last usable\n * super-administrator.\n *\n * ## Why this is needed when `suspend_user` is already guarded\n *\n * `users | update` still carries `status` in its request contract, and a custom implementation\n * resolves by an EXACT operation-path key — so the floor enforced inside the shared\n * status-transition implementation covers `suspend_user` and reaches nothing else. A\n * super-administrator could therefore `PUT /users/{id}` with `{\"status\":\"SUSPENDED\"}` and remove the\n * application's last usable administrator through the generic door, while the purpose-built verb\n * one URL away refused exactly that. The floor is only a control if every door to the field\n * enforces it.\n *\n * The narrow fix is deliberate. Removing `status` from the update contract would be the tidier\n * answer, but seven trusted writers (email verification, SCIM provisioning/deprovisioning, SSO JIT\n * activation) reach `status` through this same operation and would be SILENTLY stripped by the\n * repository's strict re-parse — users would simply never activate, with no error. That migration\n * to `updateAsSystem` is real work with a real blast radius; this guard closes the security half\n * now without touching any of it.\n *\n * ## Why it only runs when `status` is actually changing\n *\n * A floor evaluation is a population query. Running it on every user patch — a display-name edit,\n * a `userTypes` change — would put a count query on an unrelated hot path and could refuse writes\n * that take nothing away. The guard measures only when the payload moves `status`, and only when it\n * moves it to a value that stops conferring authority.\n *\n * assurance-control: WILDO.ACCESS.ADMINISTRATIVE_CONTINUITY — every door onto a status that decides\n * administrative usability enforces the same floor.\n */\nexport function createUserUpdateSuperAdminFloorFactory(): ResourceCustomServiceImplementationFactory {\n const factory = (_resourcesRegistryService: ResourcesRegistryBackendService, container: Container) => {\n const superAdminConferralResolver = createSuperAdminConferralResolver(container);\n\n return createResourceCustomServiceImplementation({\n operationPath: {\n resourceIdentifier: CoreResourceType.USERS,\n operationIdentifier: CoreResourceOperation.UPDATE,\n variantType: ResourceOperationVariantType.API_CALL,\n isOperationDefault: true,\n },\n debugLabel: 'users.update.super-admin-floor',\n priority: SUPER_ADMIN_FLOOR_GUARD_PRIORITY,\n handlers: {\n prefixCoreOperations: async (_id, input, executionContext, _operationPath, utils) => {\n const payload = input as { status?: unknown; appRoles?: unknown } | undefined;\n const changesStatus = payload?.status !== undefined;\n const changesRoles = payload?.appRoles !== undefined;\n // Neither axis of administrative usability is moving — nothing this floor governs. Pass\n // through without paying for a population query on an unrelated field edit.\n if (!changesStatus && !changesRoles) return input;\n\n const subjectUserId = resolveSingleSubjectId(_id);\n if (subjectUserId === undefined) {\n // Same fail-closed reasoning as the DELETE guard: a population-addressed write names no\n // subject the floor could exclude from its count, so it could satisfy the floor with a\n // row it is itself about to retire.\n throw utils.errorBuilder.buildError(ErrorType.VALIDATION, executionContext, {\n context: {\n message:\n 'Users UPDATE that changes `status` or `appRoles` requires a single addressed subject',\n reason: 'user_super_admin_floor_unaddressed',\n receivedAddressing: describeUnaddressableShape(_id),\n },\n });\n }\n\n // `utils.currentObject` is the pre-PREFIX row, i.e. the state the floor is measuring\n // FROM. An axis the payload leaves alone keeps its current value, so a status-only patch\n // is not mistaken for a role removal (or vice versa).\n const current = utils.currentObject as { status?: unknown; appRoles?: unknown } | undefined;\n\n await assertSuperAdminFloorPreservedForTransition({\n subjectUserId,\n previousRoles: current?.appRoles,\n previousStatus: current?.status,\n nextRoles: changesRoles ? payload?.appRoles : current?.appRoles,\n nextStatus: changesStatus ? payload?.status : current?.status,\n executionContext,\n repositoriesRegistry: engineRepositoriesRegistry(utils),\n errorBuilder: utils.errorBuilder,\n conferringRoles: superAdminConferralResolver(),\n operationIdentifier: CoreResourceOperation.UPDATE,\n });\n\n // Guard only — the payload is passed through untouched.\n return input;\n },\n },\n });\n };\n\n return factory as unknown as ResourceCustomServiceImplementationFactory;\n}\n\n/** The USERS super-administrator-floor registrations. */\nexport function createUserSuperAdminFloorFactories(): ResourceCustomServiceImplementationFactory[] {\n return [createUserDeleteSuperAdminFloorFactory(), createUserUpdateSuperAdminFloorFactory()];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"user-super-admin-floor.custom-impl.backend.service.js","sourceRoot":"","sources":["../../../../src/users/services/user-super-admin-floor.custom-impl.backend.service.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * USERS `DELETE` — the last-super-administrator floor on the destructive path.\n *\n * Removing the role and destroying the row that HOLDS it produce the identical unadministrable\n * application, so the guard that refuses the first has to refuse the second. `users | DELETE` is\n * `APP_ADMIN_SUPER_ADMIN`-gated and `riskLevel: CRITICAL`, which means the only principal who can\n * reach it is exactly the principal capable of deleting the last one — including their own account.\n *\n * The role-transition guards live in `user-role-administration.custom-impl.backend.service.ts`\n * because they share that file's user load and its audit staging. This one is separate because a\n * deletion has no role transition to inspect: the row simply stops counting, and the check reads\n * the pre-deletion snapshot rather than an authored patch.\n *\n * ## Why a PREFIX, and why priority 50\n *\n * A refusal has to happen before anything is persisted, so this is a `prefixCoreOperations` hook —\n * the mirror image of the sibling `user-deletion-audit` registration on this same operation path,\n * which is deliberately a POSTFIX (its event may only be emitted once the delete and its cascade\n * have committed).\n *\n * Registrations on one operation path chain in ascending priority. This one takes 50 against the\n * default 100 so it runs FIRST: a delete the floor is going to refuse should not have run any other\n * hook's preparation, and nothing downstream should be reasoning about a row that is not going to be\n * removed.\n *\n * ## Fail-closed on an unreadable subject\n *\n * `utils.currentObject` is the framework's pre-operation snapshot, fetched by the service before any\n * custom implementation runs (`fetchCurrentObjectForOperation`), and is the same snapshot the\n * deletion-audit postfix reads. If it is absent or carries no `appRoles`, this refuses rather than\n * proceeding: treating an unreadable subject as \"not an administrator\" would fail OPEN on the exact\n * input the guard exists for, and there is no recovery once the row is gone.\n *\n * assurance-control: WILDO.ACCESS.ADMINISTRATIVE_CONTINUITY — an application must retain an\n * accountable administrative principal; identity destruction must not remove the last one.\n */\n\nimport type { Container } from 'inversify';\nimport {\n CoreResourceOperation,\n CoreResourceType,\n ErrorType,\n ResourceOperationVariantType,\n type User,\n} from '@wildo-ai/saas-models';\nimport {\n createResourceCustomServiceImplementation,\n ResourceCustomServiceImplementationHookTransactionPosture,\n type ResourceCustomServiceImplementationFactory,\n} from '~wildo-backend-lib/base-classes/services/services/resource-operation-custom-implementation.factory.backend';\nimport type { ResourcesRegistryBackendService } from '~wildo-backend-lib/resources/services/resources-registry.backend.service';\nimport {\n assertSuperAdminFloorPreservedForRemoval,\n assertSuperAdminFloorPreservedForTransition,\n createSuperAdminConferralResolver,\n SuperAdminFloorReductionCause,\n} from '~wildo-backend-lib/security/authorizations/super-admin-floor.backend.utils';\nimport {\n describeUnaddressableShape,\n resolveSingleSubjectId,\n} from '~wildo-backend-lib/base-classes/utils/single-subject-addressing.backend.utils';\nimport { engineRepositoriesRegistry } from '~wildo-backend-lib/base-classes/services/services/engine-repositories-registry.backend.utils';\n\n/**\n * Priority for this registration. Lower runs first; the framework default is 100.\n *\n * Named rather than inlined so the ordering relationship with the sibling deletion-audit\n * registration on this operation path is stated where it is chosen, not inferred from a bare number.\n */\nconst SUPER_ADMIN_FLOOR_GUARD_PRIORITY = 50;\n\n/** The pre-deletion snapshot fields the floor reads. */\ntype UserDeletionSubjectRow = Pick<User, '_id' | 'appRoles' | 'status'>;\n\n/**\n * Project the framework's pre-operation snapshot into the row the floor needs, or `undefined` when\n * it cannot be trusted.\n *\n * `appRoles` is required to be an array: it is the field the whole decision turns on, and a snapshot\n * that lacks it cannot answer whether this subject is an administrator. Returning `undefined` routes\n * the caller into an explicit refusal instead of a silent \"not an administrator\" verdict.\n */\nfunction projectDeletionSubject(currentObject: unknown, subjectId: string): UserDeletionSubjectRow | undefined {\n if (currentObject === null || typeof currentObject !== 'object') return undefined;\n const snapshot = currentObject as Partial<User>;\n if (!Array.isArray(snapshot.appRoles)) return undefined;\n\n return {\n // The snapshot's own `_id` is preferred; the addressed id is the fallback so the subject can\n // always be excluded from the population count by SOME stable identity.\n _id: (snapshot._id ?? subjectId) as User['_id'],\n appRoles: snapshot.appRoles,\n status: snapshot.status as User['status'],\n };\n}\n\n/**\n * `users | DELETE` — refuse a deletion that would remove the last usable super-administrator.\n */\nexport function createUserDeleteSuperAdminFloorFactory(): ResourceCustomServiceImplementationFactory {\n const factory = (_resourcesRegistryService: ResourcesRegistryBackendService, container: Container) => {\n const superAdminConferralResolver = createSuperAdminConferralResolver(container);\n\n return createResourceCustomServiceImplementation({\n operationPath: {\n resourceIdentifier: CoreResourceType.USERS,\n operationIdentifier: CoreResourceOperation.DELETE,\n variantType: ResourceOperationVariantType.API_CALL,\n isOperationDefault: true,\n },\n debugLabel: 'users.delete.super-admin-floor',\n priority: SUPER_ADMIN_FLOOR_GUARD_PRIORITY,\n // #2297: the subject is utils.currentObject and the floor counts OTHER users' rows; a refusal is a thrown CONFLICT.\n hookTransactionPosture: ResourceCustomServiceImplementationHookTransactionPosture.RUNS_INSIDE_TRANSACTION,\n handlers: {\n prefixCoreOperations: async (_id, input, executionContext, _operationPath, utils) => {\n // Fail closed on anything but a single addressed user: a POPULATION-addressed delete names no\n // subject set the floor could exclude from its population count, so it could satisfy the floor\n // with a row it is itself about to destroy. A conditional `{ _id, … }` filter is not such a\n // case — `_id` names the one row that can be destroyed, and the extra predicates only decide\n // whether the delete lands at all.\n const subjectUserId = resolveSingleSubjectId(_id);\n if (subjectUserId === undefined) {\n throw utils.errorBuilder.buildError(ErrorType.VALIDATION, executionContext, {\n context: {\n message: 'Users DELETE requires a single addressed subject',\n reason: 'user_super_admin_floor_unaddressed',\n receivedAddressing: describeUnaddressableShape(_id),\n },\n });\n }\n\n const subject = projectDeletionSubject(utils.currentObject, subjectUserId);\n\n if (!subject) {\n // See the header: an unreadable subject must not be assumed harmless.\n throw utils.errorBuilder.buildError(ErrorType.CONFLICT, executionContext, {\n context: {\n message:\n 'Refused: the user about to be deleted could not be read, so it cannot be proven that this '\n + 'is not the last usable super-administrator.',\n reason: 'user_super_admin_floor_subject_unreadable',\n operationIdentifier: CoreResourceOperation.DELETE,\n userId: subjectUserId,\n },\n });\n }\n\n await assertSuperAdminFloorPreservedForRemoval({\n subjects: [subject],\n cause: SuperAdminFloorReductionCause.DELETION,\n executionContext,\n repositoriesRegistry: engineRepositoriesRegistry(utils),\n errorBuilder: utils.errorBuilder,\n conferringRoles: superAdminConferralResolver(),\n operationIdentifier: CoreResourceOperation.DELETE,\n // #3670: the delete's own transaction (the audited write's, #2297), where the floor serializes concurrent\n // removals. Not owned here as `transactionalCore`: the delete's cascade reaches child resources this\n // registration cannot name as participants, and the audited write already runs it in one transaction.\n transaction: utils.serviceOptions?.transaction,\n });\n\n // A PREFIX returns the (unmodified) input: this registration guards, it does not transform.\n return input;\n },\n },\n });\n };\n\n return factory as unknown as ResourceCustomServiceImplementationFactory;\n}\n\n/**\n * `users | UPDATE` — refuse a generic field patch that would retire the last usable\n * super-administrator.\n *\n * ## Why this is needed when `suspend_user` is already guarded\n *\n * `users | update` still carries `status` in its request contract, and a custom implementation\n * resolves by an EXACT operation-path key — so the floor enforced inside the shared\n * status-transition implementation covers `suspend_user` and reaches nothing else. A\n * super-administrator could therefore `PUT /users/{id}` with `{\"status\":\"SUSPENDED\"}` and remove the\n * application's last usable administrator through the generic door, while the purpose-built verb\n * one URL away refused exactly that. The floor is only a control if every door to the field\n * enforces it.\n *\n * The narrow fix is deliberate. Removing `status` from the update contract would be the tidier\n * answer, but seven trusted writers (email verification, SCIM provisioning/deprovisioning, SSO JIT\n * activation) reach `status` through this same operation and would be SILENTLY stripped by the\n * repository's strict re-parse — users would simply never activate, with no error. That migration\n * to `updateAsSystem` is real work with a real blast radius; this guard closes the security half\n * now without touching any of it.\n *\n * ## Why it only runs when `status` is actually changing\n *\n * A floor evaluation is a population query. Running it on every user patch — a display-name edit,\n * a `userTypes` change — would put a count query on an unrelated hot path and could refuse writes\n * that take nothing away. The guard measures only when the payload moves `status`, and only when it\n * moves it to a value that stops conferring authority.\n *\n * assurance-control: WILDO.ACCESS.ADMINISTRATIVE_CONTINUITY — every door onto a status that decides\n * administrative usability enforces the same floor.\n */\nexport function createUserUpdateSuperAdminFloorFactory(): ResourceCustomServiceImplementationFactory {\n const factory = (_resourcesRegistryService: ResourcesRegistryBackendService, container: Container) => {\n const superAdminConferralResolver = createSuperAdminConferralResolver(container);\n\n return createResourceCustomServiceImplementation({\n operationPath: {\n resourceIdentifier: CoreResourceType.USERS,\n operationIdentifier: CoreResourceOperation.UPDATE,\n variantType: ResourceOperationVariantType.API_CALL,\n isOperationDefault: true,\n },\n debugLabel: 'users.update.super-admin-floor',\n priority: SUPER_ADMIN_FLOOR_GUARD_PRIORITY,\n /*\n * #3670: `users | update` is MEDIUM risk, so no audited-write transaction is opened for it (#2297), and without one\n * the floor cannot serialize two concurrent `appRoles` rewrites that each demote the OTHER administrator. This\n * registration therefore owns the transaction for the whole UPDATE pipeline: the floor bumps the APPLICATION row\n * and measures inside it. The other UPDATE hooks were read for it: the role ceiling is in memory and reads the user\n * through utils.serviceOptions, its audit and the user-type change records are post-commit effects, and the\n * user-type revocation ends sessions BEFORE the write on purpose (fail closed: a later refusal leaves the sessions\n * ended, #2677 review F8), which a retried attempt only repeats.\n */\n transactionalCore: true,\n transactionParticipants: { application: CoreResourceType.APPLICATION },\n handlers: {\n prefixCoreOperations: async (_id, input, executionContext, _operationPath, utils) => {\n const payload = input as { status?: unknown; appRoles?: unknown } | undefined;\n const changesStatus = payload?.status !== undefined;\n const changesRoles = payload?.appRoles !== undefined;\n // Neither axis of administrative usability is moving — nothing this floor governs. Pass\n // through without paying for a population query on an unrelated field edit.\n if (!changesStatus && !changesRoles) return input;\n\n const subjectUserId = resolveSingleSubjectId(_id);\n if (subjectUserId === undefined) {\n // Same fail-closed reasoning as the DELETE guard: a population-addressed write names no\n // subject the floor could exclude from its count, so it could satisfy the floor with a\n // row it is itself about to retire.\n throw utils.errorBuilder.buildError(ErrorType.VALIDATION, executionContext, {\n context: {\n message:\n 'Users UPDATE that changes `status` or `appRoles` requires a single addressed subject',\n reason: 'user_super_admin_floor_unaddressed',\n receivedAddressing: describeUnaddressableShape(_id),\n },\n });\n }\n\n // `utils.currentObject` is the pre-PREFIX row, i.e. the state the floor is measuring\n // FROM. An axis the payload leaves alone keeps its current value, so a status-only patch\n // is not mistaken for a role removal (or vice versa).\n const current = utils.currentObject as { status?: unknown; appRoles?: unknown } | undefined;\n\n await assertSuperAdminFloorPreservedForTransition({\n subjectUserId,\n previousRoles: current?.appRoles,\n previousStatus: current?.status,\n nextRoles: changesRoles ? payload?.appRoles : current?.appRoles,\n nextStatus: changesStatus ? payload?.status : current?.status,\n executionContext,\n repositoriesRegistry: engineRepositoriesRegistry(utils),\n errorBuilder: utils.errorBuilder,\n conferringRoles: superAdminConferralResolver(),\n operationIdentifier: CoreResourceOperation.UPDATE,\n transaction: utils.serviceOptions?.transaction,\n });\n\n // Guard only — the payload is passed through untouched.\n return input;\n },\n },\n });\n };\n\n return factory as unknown as ResourceCustomServiceImplementationFactory;\n}\n\n/** The USERS super-administrator-floor registrations. */\nexport function createUserSuperAdminFloorFactories(): ResourceCustomServiceImplementationFactory[] {\n return [createUserDeleteSuperAdminFloorFactory(), createUserUpdateSuperAdminFloorFactory()];\n}\n"]}
|