@wildo-ai/saas-models 1.1.0 → 1.1.2
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/applications/applications-context.schemas.d.ts +2 -0
- package/dist/esm/applications/applications-context.schemas.d.ts.map +1 -1
- package/dist/esm/billing/billing-account.shared.resources-config.schemas.d.ts +1 -1
- package/dist/esm/billing/billing-account.shared.resources-config.schemas.d.ts.map +1 -1
- package/dist/esm/billing/billing-account.shared.resources-config.schemas.js +36 -0
- package/dist/esm/billing/billing-account.shared.resources-config.schemas.js.map +1 -1
- package/dist/esm/billing/billing-account.shared.schemas.d.ts.map +1 -1
- package/dist/esm/billing/billing-account.shared.schemas.js +8 -1
- package/dist/esm/billing/billing-account.shared.schemas.js.map +1 -1
- package/dist/esm/billing/billing-types.shared.schemas.d.ts +2 -2
- package/dist/esm/billing/credit-pool.shared.resources-config.schemas.d.ts +1 -1
- package/dist/esm/billing/credit-pool.shared.resources-config.schemas.d.ts.map +1 -1
- package/dist/esm/billing/credit-pool.shared.resources-config.schemas.js +39 -0
- package/dist/esm/billing/credit-pool.shared.resources-config.schemas.js.map +1 -1
- package/dist/esm/billing/credit-pool.shared.schemas.d.ts +6 -6
- package/dist/esm/billing/credit-pool.shared.schemas.d.ts.map +1 -1
- package/dist/esm/billing/credit-pool.shared.schemas.js +8 -4
- package/dist/esm/billing/credit-pool.shared.schemas.js.map +1 -1
- package/dist/esm/billing/invoice-ref.shared.resources-config.schemas.d.ts +1 -1
- package/dist/esm/billing/invoice-ref.shared.resources-config.schemas.d.ts.map +1 -1
- package/dist/esm/billing/invoice-ref.shared.resources-config.schemas.js +35 -0
- package/dist/esm/billing/invoice-ref.shared.resources-config.schemas.js.map +1 -1
- package/dist/esm/billing/invoice-ref.shared.schemas.d.ts.map +1 -1
- package/dist/esm/billing/invoice-ref.shared.schemas.js +8 -3
- package/dist/esm/billing/invoice-ref.shared.schemas.js.map +1 -1
- package/dist/esm/billing/subscription.shared.resources-config.schemas.d.ts +1 -1
- package/dist/esm/billing/subscription.shared.resources-config.schemas.d.ts.map +1 -1
- package/dist/esm/billing/subscription.shared.resources-config.schemas.js +39 -0
- package/dist/esm/billing/subscription.shared.resources-config.schemas.js.map +1 -1
- package/dist/esm/billing/subscription.shared.schemas.d.ts +4 -4
- package/dist/esm/billing/subscription.shared.schemas.d.ts.map +1 -1
- package/dist/esm/billing/subscription.shared.schemas.js +6 -2
- package/dist/esm/billing/subscription.shared.schemas.js.map +1 -1
- package/dist/esm/billing/usage-record.shared.resources-config.schemas.d.ts +1 -1
- package/dist/esm/billing/usage-record.shared.resources-config.schemas.d.ts.map +1 -1
- package/dist/esm/billing/usage-record.shared.resources-config.schemas.js +39 -0
- package/dist/esm/billing/usage-record.shared.resources-config.schemas.js.map +1 -1
- package/dist/esm/billing/usage-record.shared.schemas.d.ts +4 -4
- package/dist/esm/billing/usage-record.shared.schemas.d.ts.map +1 -1
- package/dist/esm/billing/usage-record.shared.schemas.js +7 -3
- package/dist/esm/billing/usage-record.shared.schemas.js.map +1 -1
- package/dist/esm/compliance/audit-trails/audit-logs.shared.resources-config.schemas.d.ts.map +1 -1
- package/dist/esm/compliance/audit-trails/audit-logs.shared.resources-config.schemas.js +35 -2
- package/dist/esm/compliance/audit-trails/audit-logs.shared.resources-config.schemas.js.map +1 -1
- package/dist/esm/compliance/audit-trails/auditable-events.shared.schema.d.ts +61 -0
- package/dist/esm/compliance/audit-trails/auditable-events.shared.schema.d.ts.map +1 -1
- package/dist/esm/compliance/audit-trails/auditable-events.shared.schema.js +58 -0
- package/dist/esm/compliance/audit-trails/auditable-events.shared.schema.js.map +1 -1
- package/dist/esm/compliance/audit-trails/security-audit-event-envelope.shared.schema.d.ts.map +1 -1
- package/dist/esm/compliance/audit-trails/security-audit-event-envelope.shared.schema.js +14 -0
- package/dist/esm/compliance/audit-trails/security-audit-event-envelope.shared.schema.js.map +1 -1
- package/dist/esm/compliance/privacy/impersonalization-scrub.shared.utils.d.ts +49 -0
- package/dist/esm/compliance/privacy/impersonalization-scrub.shared.utils.d.ts.map +1 -1
- package/dist/esm/compliance/privacy/impersonalization-scrub.shared.utils.js +124 -1
- package/dist/esm/compliance/privacy/impersonalization-scrub.shared.utils.js.map +1 -1
- package/dist/esm/compliance/privacy/impersonalization.shared.schemas.d.ts +11 -3
- package/dist/esm/compliance/privacy/impersonalization.shared.schemas.d.ts.map +1 -1
- package/dist/esm/compliance/privacy/impersonalization.shared.schemas.js +11 -3
- package/dist/esm/compliance/privacy/impersonalization.shared.schemas.js.map +1 -1
- package/dist/esm/compliance/privacy/operating-jurisdictions.shared.schemas.d.ts +93 -0
- package/dist/esm/compliance/privacy/operating-jurisdictions.shared.schemas.d.ts.map +1 -0
- package/dist/esm/compliance/privacy/operating-jurisdictions.shared.schemas.js +116 -0
- package/dist/esm/compliance/privacy/operating-jurisdictions.shared.schemas.js.map +1 -0
- package/dist/esm/compliance/privacy/operator-compliance-identity.shared.schemas.d.ts +67 -12
- package/dist/esm/compliance/privacy/operator-compliance-identity.shared.schemas.d.ts.map +1 -1
- package/dist/esm/compliance/privacy/operator-compliance-identity.shared.schemas.js +95 -8
- package/dist/esm/compliance/privacy/operator-compliance-identity.shared.schemas.js.map +1 -1
- package/dist/esm/compliance/privacy/redaction.shared.schemas.d.ts +21 -1
- package/dist/esm/compliance/privacy/redaction.shared.schemas.d.ts.map +1 -1
- package/dist/esm/compliance/privacy/redaction.shared.schemas.js +20 -0
- package/dist/esm/compliance/privacy/redaction.shared.schemas.js.map +1 -1
- package/dist/esm/config/app-configuration-shared.shared.schemas.d.ts +2 -0
- package/dist/esm/config/app-configuration-shared.shared.schemas.d.ts.map +1 -1
- package/dist/esm/config/app-configuration-shared.shared.schemas.js +29 -0
- package/dist/esm/config/app-configuration-shared.shared.schemas.js.map +1 -1
- package/dist/esm/config/frontend/navigation.shared.schemas.d.ts +33 -0
- package/dist/esm/config/frontend/navigation.shared.schemas.d.ts.map +1 -1
- package/dist/esm/config/frontend/navigation.shared.schemas.js +32 -0
- package/dist/esm/config/frontend/navigation.shared.schemas.js.map +1 -1
- package/dist/esm/errors/errors.custom-message-ref.shared.definitions.d.ts +12 -1
- package/dist/esm/errors/errors.custom-message-ref.shared.definitions.d.ts.map +1 -1
- package/dist/esm/errors/errors.custom-message-ref.shared.definitions.js +12 -1
- package/dist/esm/errors/errors.custom-message-ref.shared.definitions.js.map +1 -1
- package/dist/esm/external-data/external-data-pipeline.shared.schemas.d.ts +13 -0
- package/dist/esm/external-data/external-data-pipeline.shared.schemas.d.ts.map +1 -1
- package/dist/esm/external-data/external-data-pipeline.shared.schemas.js.map +1 -1
- package/dist/esm/external-providers/engine-capabilities.shared.schemas.d.ts +6 -6
- package/dist/esm/external-providers/engine-capabilities.shared.schemas.d.ts.map +1 -1
- package/dist/esm/external-providers/engine-capabilities.shared.schemas.js +26 -6
- package/dist/esm/external-providers/engine-capabilities.shared.schemas.js.map +1 -1
- package/dist/esm/external-providers/engine-capability-resolution-mode.shared.d.ts.map +1 -1
- package/dist/esm/external-providers/engine-capability-resolution-mode.shared.js +0 -2
- package/dist/esm/external-providers/engine-capability-resolution-mode.shared.js.map +1 -1
- package/dist/esm/external-providers/frontend-bootstrap.shared.schemas.d.ts +1 -0
- package/dist/esm/external-providers/frontend-bootstrap.shared.schemas.d.ts.map +1 -1
- package/dist/esm/external-providers/provider-capability.shared.schemas.d.ts +4 -3
- package/dist/esm/external-providers/provider-capability.shared.schemas.d.ts.map +1 -1
- package/dist/esm/external-providers/provider-capability.shared.schemas.js +4 -3
- package/dist/esm/external-providers/provider-capability.shared.schemas.js.map +1 -1
- package/dist/esm/features/user-features.shared.resources-config.schemas.d.ts +1 -1
- package/dist/esm/features/user-features.shared.resources-config.schemas.d.ts.map +1 -1
- package/dist/esm/features/user-features.shared.resources-config.schemas.js +48 -0
- package/dist/esm/features/user-features.shared.resources-config.schemas.js.map +1 -1
- package/dist/esm/files/file-upload-grant.shared.schemas.d.ts +144 -0
- package/dist/esm/files/file-upload-grant.shared.schemas.d.ts.map +1 -0
- package/dist/esm/files/file-upload-grant.shared.schemas.js +136 -0
- package/dist/esm/files/file-upload-grant.shared.schemas.js.map +1 -0
- package/dist/esm/files/files.shared.schemas.d.ts +45 -4
- package/dist/esm/files/files.shared.schemas.d.ts.map +1 -1
- package/dist/esm/files/files.shared.schemas.js +48 -4
- package/dist/esm/files/files.shared.schemas.js.map +1 -1
- package/dist/esm/flows-actors/a2a-push-delivery-log.shared.resources-config.schemas.d.ts +1 -1
- package/dist/esm/flows-actors/a2a-push-delivery-log.shared.resources-config.schemas.d.ts.map +1 -1
- package/dist/esm/flows-actors/a2a-push-delivery-log.shared.resources-config.schemas.js +33 -0
- package/dist/esm/flows-actors/a2a-push-delivery-log.shared.resources-config.schemas.js.map +1 -1
- package/dist/esm/flows-actors/a2a-push-delivery-log.shared.schemas.d.ts.map +1 -1
- package/dist/esm/flows-actors/a2a-push-delivery-log.shared.schemas.js +6 -2
- package/dist/esm/flows-actors/a2a-push-delivery-log.shared.schemas.js.map +1 -1
- package/dist/esm/flows-actors/a2a-token-budget-state.shared.resources-config.schemas.d.ts +1 -1
- package/dist/esm/flows-actors/a2a-token-budget-state.shared.resources-config.schemas.d.ts.map +1 -1
- package/dist/esm/flows-actors/a2a-token-budget-state.shared.resources-config.schemas.js +53 -0
- package/dist/esm/flows-actors/a2a-token-budget-state.shared.resources-config.schemas.js.map +1 -1
- package/dist/esm/flows-actors/flows-actors-execution.shared.resources-config.schemas.d.ts +1 -1
- package/dist/esm/flows-actors/flows-actors-execution.shared.resources-config.schemas.d.ts.map +1 -1
- package/dist/esm/flows-actors/flows-actors-execution.shared.resources-config.schemas.js +43 -0
- package/dist/esm/flows-actors/flows-actors-execution.shared.resources-config.schemas.js.map +1 -1
- package/dist/esm/flows-actors/flows-actors-execution.shared.schemas.d.ts.map +1 -1
- package/dist/esm/flows-actors/flows-actors-execution.shared.schemas.js +8 -3
- package/dist/esm/flows-actors/flows-actors-execution.shared.schemas.js.map +1 -1
- package/dist/esm/flows-actors/flows-actors-task.shared.resources-config.schemas.d.ts +1 -1
- package/dist/esm/flows-actors/flows-actors-task.shared.resources-config.schemas.d.ts.map +1 -1
- package/dist/esm/flows-actors/flows-actors-task.shared.resources-config.schemas.js +41 -0
- package/dist/esm/flows-actors/flows-actors-task.shared.resources-config.schemas.js.map +1 -1
- package/dist/esm/flows-actors/flows-actors-task.shared.schemas.d.ts +0 -7
- package/dist/esm/flows-actors/flows-actors-task.shared.schemas.d.ts.map +1 -1
- package/dist/esm/flows-actors/flows-actors-task.shared.schemas.js +23 -13
- package/dist/esm/flows-actors/flows-actors-task.shared.schemas.js.map +1 -1
- package/dist/esm/guidance/guidance-state.shared.resources-config.schemas.d.ts +1 -1
- package/dist/esm/guidance/guidance-state.shared.resources-config.schemas.d.ts.map +1 -1
- package/dist/esm/guidance/guidance-state.shared.resources-config.schemas.js +51 -0
- package/dist/esm/guidance/guidance-state.shared.resources-config.schemas.js.map +1 -1
- package/dist/esm/guidance/guidance-state.shared.schemas.d.ts.map +1 -1
- package/dist/esm/guidance/guidance-state.shared.schemas.js +8 -2
- package/dist/esm/guidance/guidance-state.shared.schemas.js.map +1 -1
- package/dist/esm/guidance/lifecycle-state.shared.resources-config.schemas.d.ts +1 -1
- package/dist/esm/guidance/lifecycle-state.shared.resources-config.schemas.d.ts.map +1 -1
- package/dist/esm/guidance/lifecycle-state.shared.resources-config.schemas.js +51 -0
- package/dist/esm/guidance/lifecycle-state.shared.resources-config.schemas.js.map +1 -1
- package/dist/esm/guidance/lifecycle-state.shared.schemas.d.ts.map +1 -1
- package/dist/esm/guidance/lifecycle-state.shared.schemas.js +28 -3
- package/dist/esm/guidance/lifecycle-state.shared.schemas.js.map +1 -1
- package/dist/esm/guidance/progression-state.shared.resources-config.schemas.d.ts +1 -1
- package/dist/esm/guidance/progression-state.shared.resources-config.schemas.d.ts.map +1 -1
- package/dist/esm/guidance/progression-state.shared.resources-config.schemas.js +51 -0
- package/dist/esm/guidance/progression-state.shared.resources-config.schemas.js.map +1 -1
- package/dist/esm/guidance/progression-state.shared.schemas.d.ts.map +1 -1
- package/dist/esm/guidance/progression-state.shared.schemas.js +18 -4
- package/dist/esm/guidance/progression-state.shared.schemas.js.map +1 -1
- package/dist/esm/http-api-binding/http-api-binding.shared.schemas.d.ts +37 -3
- package/dist/esm/http-api-binding/http-api-binding.shared.schemas.d.ts.map +1 -1
- package/dist/esm/http-api-binding/http-api-binding.shared.schemas.js +29 -3
- package/dist/esm/http-api-binding/http-api-binding.shared.schemas.js.map +1 -1
- package/dist/esm/index.d.ts +6 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +6 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/notifications/m2m/m2m-webhook-delivery-contract.shared.d.ts +70 -0
- package/dist/esm/notifications/m2m/m2m-webhook-delivery-contract.shared.d.ts.map +1 -0
- package/dist/esm/notifications/m2m/m2m-webhook-delivery-contract.shared.js +71 -0
- package/dist/esm/notifications/m2m/m2m-webhook-delivery-contract.shared.js.map +1 -0
- package/dist/esm/notifications/notification-badges.shared.resources-config.schemas.d.ts +1 -1
- package/dist/esm/notifications/notification-badges.shared.resources-config.schemas.d.ts.map +1 -1
- package/dist/esm/notifications/notification-badges.shared.resources-config.schemas.js +36 -0
- package/dist/esm/notifications/notification-badges.shared.resources-config.schemas.js.map +1 -1
- package/dist/esm/organizations/organization-members.shared.resources-config.schemas.d.ts +1 -1
- package/dist/esm/organizations/organization-members.shared.resources-config.schemas.d.ts.map +1 -1
- package/dist/esm/organizations/organization-members.shared.resources-config.schemas.js +44 -0
- package/dist/esm/organizations/organization-members.shared.resources-config.schemas.js.map +1 -1
- package/dist/esm/organizations/organization-members.shared.schemas.d.ts.map +1 -1
- package/dist/esm/organizations/organization-members.shared.schemas.js +15 -4
- package/dist/esm/organizations/organization-members.shared.schemas.js.map +1 -1
- package/dist/esm/organizations/organizations.shared.resources-config.schemas.d.ts.map +1 -1
- package/dist/esm/organizations/organizations.shared.resources-config.schemas.js +18 -10
- package/dist/esm/organizations/organizations.shared.resources-config.schemas.js.map +1 -1
- package/dist/esm/queue/jobs.shared.resources-config.schemas.d.ts.map +1 -1
- package/dist/esm/queue/jobs.shared.resources-config.schemas.js +17 -0
- package/dist/esm/queue/jobs.shared.resources-config.schemas.js.map +1 -1
- package/dist/esm/requests/websocket.shared.schemas.d.ts +59 -0
- package/dist/esm/requests/websocket.shared.schemas.d.ts.map +1 -1
- package/dist/esm/requests/websocket.shared.schemas.js +103 -0
- package/dist/esm/requests/websocket.shared.schemas.js.map +1 -1
- package/dist/esm/resources/collection-query-parameters.shared.d.ts +22 -0
- package/dist/esm/resources/collection-query-parameters.shared.d.ts.map +1 -0
- package/dist/esm/resources/collection-query-parameters.shared.js +22 -0
- package/dist/esm/resources/collection-query-parameters.shared.js.map +1 -0
- package/dist/esm/resources/resources-config.shared.factory.d.ts.map +1 -1
- package/dist/esm/resources/resources-config.shared.factory.js +86 -13
- package/dist/esm/resources/resources-config.shared.factory.js.map +1 -1
- package/dist/esm/resources/resources-config.shared.schemas.d.ts +162 -41
- package/dist/esm/resources/resources-config.shared.schemas.d.ts.map +1 -1
- package/dist/esm/resources/resources-config.shared.schemas.js +109 -20
- package/dist/esm/resources/resources-config.shared.schemas.js.map +1 -1
- package/dist/esm/resources/utils/resources-config-operations.dtos-builder.utils.d.ts.map +1 -1
- package/dist/esm/resources/utils/resources-config-operations.dtos-builder.utils.js +21 -6
- package/dist/esm/resources/utils/resources-config-operations.dtos-builder.utils.js.map +1 -1
- package/dist/esm/security/authentications/authentication.shared.schemas.d.ts +57 -0
- package/dist/esm/security/authentications/authentication.shared.schemas.d.ts.map +1 -1
- package/dist/esm/security/authentications/authentication.shared.schemas.js +64 -0
- package/dist/esm/security/authentications/authentication.shared.schemas.js.map +1 -1
- package/dist/esm/security/authentications/consumable-token.shared.schemas.d.ts +17 -1
- package/dist/esm/security/authentications/consumable-token.shared.schemas.d.ts.map +1 -1
- package/dist/esm/security/authentications/consumable-token.shared.schemas.js +16 -0
- package/dist/esm/security/authentications/consumable-token.shared.schemas.js.map +1 -1
- package/dist/esm/security/authentications/mfa-backup-code-contract.shared.d.ts +28 -0
- package/dist/esm/security/authentications/mfa-backup-code-contract.shared.d.ts.map +1 -0
- package/dist/esm/security/authentications/mfa-backup-code-contract.shared.js +28 -0
- package/dist/esm/security/authentications/mfa-backup-code-contract.shared.js.map +1 -0
- package/dist/esm/security/authentications/siem/siem-delivery-reliability-contract.shared.d.ts +21 -0
- package/dist/esm/security/authentications/siem/siem-delivery-reliability-contract.shared.d.ts.map +1 -0
- package/dist/esm/security/authentications/siem/siem-delivery-reliability-contract.shared.js +21 -0
- package/dist/esm/security/authentications/siem/siem-delivery-reliability-contract.shared.js.map +1 -0
- package/dist/esm/security/authorizations/platform-access-grants.shared.resources-config.schemas.d.ts +1 -1
- package/dist/esm/security/authorizations/platform-access-grants.shared.resources-config.schemas.d.ts.map +1 -1
- package/dist/esm/security/authorizations/platform-access-grants.shared.resources-config.schemas.js +43 -0
- package/dist/esm/security/authorizations/platform-access-grants.shared.resources-config.schemas.js.map +1 -1
- package/dist/esm/security/authorizations/platform-access-grants.shared.schemas.d.ts +0 -8
- package/dist/esm/security/authorizations/platform-access-grants.shared.schemas.d.ts.map +1 -1
- package/dist/esm/security/authorizations/platform-access-grants.shared.schemas.js +13 -4
- package/dist/esm/security/authorizations/platform-access-grants.shared.schemas.js.map +1 -1
- package/dist/esm/security/authorizations/roles.shared.schemas.d.ts +16 -1
- package/dist/esm/security/authorizations/roles.shared.schemas.d.ts.map +1 -1
- package/dist/esm/security/authorizations/roles.shared.schemas.js +16 -1
- package/dist/esm/security/authorizations/roles.shared.schemas.js.map +1 -1
- package/dist/esm/security/authorizations/roles.shared.utils.d.ts +54 -0
- package/dist/esm/security/authorizations/roles.shared.utils.d.ts.map +1 -1
- package/dist/esm/security/authorizations/roles.shared.utils.js +59 -0
- package/dist/esm/security/authorizations/roles.shared.utils.js.map +1 -1
- package/dist/esm/security/oauth-clients/oauth-clients.shared.schemas.d.ts +61 -0
- package/dist/esm/security/oauth-clients/oauth-clients.shared.schemas.d.ts.map +1 -1
- package/dist/esm/security/oauth-clients/oauth-clients.shared.schemas.js +13 -1
- package/dist/esm/security/oauth-clients/oauth-clients.shared.schemas.js.map +1 -1
- package/dist/esm/security/oauth-clients/oauth-consent-grants.shared.resources-config.schemas.d.ts +1 -1
- package/dist/esm/security/oauth-clients/oauth-consent-grants.shared.resources-config.schemas.d.ts.map +1 -1
- package/dist/esm/security/oauth-clients/oauth-consent-grants.shared.resources-config.schemas.js +35 -0
- package/dist/esm/security/oauth-clients/oauth-consent-grants.shared.resources-config.schemas.js.map +1 -1
- package/dist/esm/security/oauth-clients/oauth-consent-grants.shared.schemas.d.ts.map +1 -1
- package/dist/esm/security/oauth-clients/oauth-consent-grants.shared.schemas.js +7 -1
- package/dist/esm/security/oauth-clients/oauth-consent-grants.shared.schemas.js.map +1 -1
- package/dist/esm/users/inbound-contacts.shared.resources-config.schemas.d.ts +1 -1
- package/dist/esm/users/inbound-contacts.shared.resources-config.schemas.d.ts.map +1 -1
- package/dist/esm/users/inbound-contacts.shared.resources-config.schemas.js +34 -0
- package/dist/esm/users/inbound-contacts.shared.resources-config.schemas.js.map +1 -1
- package/dist/esm/users/inbound-contacts.shared.schemas.d.ts.map +1 -1
- package/dist/esm/users/inbound-contacts.shared.schemas.js +7 -4
- package/dist/esm/users/inbound-contacts.shared.schemas.js.map +1 -1
- package/dist/esm/users/user-credentials.shared.resources-config.schemas.d.ts +1 -1
- package/dist/esm/users/user-credentials.shared.resources-config.schemas.d.ts.map +1 -1
- package/dist/esm/users/user-credentials.shared.resources-config.schemas.js +37 -0
- package/dist/esm/users/user-credentials.shared.resources-config.schemas.js.map +1 -1
- package/dist/esm/users/user-credentials.shared.schemas.d.ts.map +1 -1
- package/dist/esm/users/user-credentials.shared.schemas.js +17 -2
- package/dist/esm/users/user-credentials.shared.schemas.js.map +1 -1
- package/dist/esm/users/user-identity-links.shared.resources-config.schemas.d.ts +1 -1
- package/dist/esm/users/user-identity-links.shared.resources-config.schemas.d.ts.map +1 -1
- package/dist/esm/users/user-identity-links.shared.resources-config.schemas.js +36 -0
- package/dist/esm/users/user-identity-links.shared.resources-config.schemas.js.map +1 -1
- package/dist/esm/users/user-identity-links.shared.schemas.d.ts.map +1 -1
- package/dist/esm/users/user-identity-links.shared.schemas.js +15 -5
- package/dist/esm/users/user-identity-links.shared.schemas.js.map +1 -1
- package/dist/esm/users/user-preferences.shared.resources-config.schemas.d.ts +1 -1
- package/dist/esm/users/user-preferences.shared.resources-config.schemas.d.ts.map +1 -1
- package/dist/esm/users/user-preferences.shared.resources-config.schemas.js +53 -0
- package/dist/esm/users/user-preferences.shared.resources-config.schemas.js.map +1 -1
- package/dist/esm/users/user-profiles.shared.resources-config.schemas.d.ts +1 -1
- package/dist/esm/users/user-profiles.shared.resources-config.schemas.d.ts.map +1 -1
- package/dist/esm/users/user-profiles.shared.resources-config.schemas.js +62 -0
- package/dist/esm/users/user-profiles.shared.resources-config.schemas.js.map +1 -1
- package/dist/esm/users/user-self-preferences.shared.resources-config.schemas.d.ts +1 -1
- package/dist/esm/users/user-self-preferences.shared.resources-config.schemas.d.ts.map +1 -1
- package/dist/esm/users/user-self-preferences.shared.resources-config.schemas.js +53 -0
- package/dist/esm/users/user-self-preferences.shared.resources-config.schemas.js.map +1 -1
- package/dist/esm/users/user-self-profiles.shared.resources-config.schemas.d.ts +1 -1
- package/dist/esm/users/user-self-profiles.shared.resources-config.schemas.d.ts.map +1 -1
- package/dist/esm/users/user-self-profiles.shared.resources-config.schemas.js +62 -0
- package/dist/esm/users/user-self-profiles.shared.resources-config.schemas.js.map +1 -1
- package/dist/esm/users/users.shared.schemas.d.ts +19 -0
- package/dist/esm/users/users.shared.schemas.d.ts.map +1 -1
- package/dist/esm/users/users.shared.schemas.js +41 -16
- package/dist/esm/users/users.shared.schemas.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +5 -6
- package/dist/esm/.builder.pid +0 -9
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { addImpersonalizeWith } from '@wildo-ai/zod-decorators';
|
|
3
|
+
import { RedactionType } from '../compliance/privacy/redaction.shared.schemas.js';
|
|
4
|
+
import { ERASED_USER_EMAIL } from './users.shared.schemas.js';
|
|
2
5
|
import { InboundContactPrivacySchema } from "../compliance/privacy/privacy.shared.schemas.js";
|
|
3
6
|
import { PersonalDataCategory } from '../compliance/privacy/personal-data-category.shared.schemas.js';
|
|
4
7
|
/**
|
|
@@ -38,10 +41,10 @@ export var InboundContactEmailUserMatch;
|
|
|
38
41
|
*/
|
|
39
42
|
export const InboundContactSchema = z.object({
|
|
40
43
|
_id: z.string().isPrimaryKey().isSummaryField(),
|
|
41
|
-
email: z.email().isSummaryField().dataCategories(PersonalDataCategory.CONTACT_DETAILS),
|
|
42
|
-
firstName: z.string().optional().isSummaryField().dataCategories(PersonalDataCategory.IDENTITY),
|
|
43
|
-
lastName: z.string().optional().isSummaryField().dataCategories(PersonalDataCategory.IDENTITY),
|
|
44
|
-
phone: z.string().optional().dataCategories(PersonalDataCategory.CONTACT_DETAILS),
|
|
44
|
+
email: addImpersonalizeWith(z.email().isSummaryField().dataCategories(PersonalDataCategory.CONTACT_DETAILS), RedactionType.MASK, { maskValue: ERASED_USER_EMAIL }),
|
|
45
|
+
firstName: addImpersonalizeWith(z.string().optional().isSummaryField().dataCategories(PersonalDataCategory.IDENTITY), RedactionType.REMOVE),
|
|
46
|
+
lastName: addImpersonalizeWith(z.string().optional().isSummaryField().dataCategories(PersonalDataCategory.IDENTITY), RedactionType.REMOVE),
|
|
47
|
+
phone: addImpersonalizeWith(z.string().optional().dataCategories(PersonalDataCategory.CONTACT_DETAILS), RedactionType.REMOVE),
|
|
45
48
|
/**
|
|
46
49
|
* Anonymous session that created this lead. FK to `AnonymousUser`.
|
|
47
50
|
* Carries first-touch marketing attribution (utm, referrer,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inbound-contacts.shared.schemas.js","sourceRoot":"","sources":["../../../../src/users/inbound-contacts.shared.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,2BAA2B,EAAE,MAAM,8CAA8C,CAAC;AAC3F,OAAO,EAAE,oBAAoB,EAAE,MAAM,6DAA6D,CAAC;AAGnG;;;;;;;;;;GAUG;AACH,MAAM,CAAN,IAAY,4BAUX;AAVD,WAAY,4BAA4B;IACtC,kEAAkE;IAClE,6CAAa,CAAA;IACb,2EAA2E;IAC3E,2DAA2B,CAAA;IAC3B;;;OAGG;IACH,+DAA+B,CAAA;AACjC,CAAC,EAVW,4BAA4B,KAA5B,4BAA4B,QAUvC;AAGD;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IAEzC,GAAG,EAAG,CAAC,CAAC,MAAM,EAAE,CAAC,YAAY,EAAE,CAAC,cAAc,EAAE;IAChD,KAAK,EAAG,CAAC,CAAC,KAAK,EAAE,CAAC,cAAc,EAAE,CAAC,cAAc,CAAC,oBAAoB,CAAC,eAAe,CAAC;IACvF,SAAS,EAAG,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,cAAc,EAAE,CAAC,cAAc,CAAC,oBAAoB,CAAC,QAAQ,CAAC;IAChG,QAAQ,EAAG,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,cAAc,EAAE,CAAC,cAAc,CAAC,oBAAoB,CAAC,QAAQ,CAAC;IAC/F,KAAK,EAAG,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,cAAc,CAAC,oBAAoB,CAAC,eAAe,CAAC;IAElF;;;;;;;;;;;;;;;;;OAiBG;IACH,eAAe,EAAG,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,WAAW,EAAE,CAAC,iBAAiB,EAAE,CAAC,aAAa,EAAE;IAEzF;;;;;;;OAOG;IACH,MAAM,EAAG,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,WAAW,EAAE,CAAC,iBAAiB,EAAE,CAAC,aAAa,EAAE;IAEhF;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,aAAa,EAAG,CAAC,CAAC,MAAM,EAAE,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE,CAAC,iBAAiB,EAAE;IAEvE;;;;;;;;;;;;;;;;OAgBG;IACH,cAAc,EAAG,CAAC,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC,OAAO,CAAC,4BAA4B,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC,iBAAiB,EAAE,CAAC,iBAAiB,EAAE,CAAC,aAAa,EAAE;IAEtK;;;;;;;;OAQG;IACH,SAAS,EAAG,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,cAAc,EAAE,CAAC,iBAAiB,EAAE,CAAC,iBAAiB,EAAE;IAC3F,SAAS,EAAG,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,iBAAiB,EAAE,CAAC,iBAAiB,EAAE;IAC1E,OAAO,EAAG,2BAA2B;CACxC,CAAC,CAAC","sourcesContent":["import { z } from \"zod\";\nimport { InboundContactPrivacySchema } from \"../compliance/privacy/privacy.shared.schemas\";\nimport { PersonalDataCategory } from '../compliance/privacy/personal-data-category.shared.schemas';\n\n\n/**\n * Server-computed lead-triage signal: whether the lead's email corresponded to\n * an existing `users` account at capture time, and — because the caller only\n * flagged \"active, or not\" — whether that account was ACTIVE.\n *\n * This is a TRIAGE / dedup HINT, **not** an ownership link. It NEVER sets\n * `userId`/`relatedUserId`: an anonymous visitor typing an email does not prove\n * they own it, so attributing the lead to a real account on an unverified string\n * would be an account-takeover hole. Ownership is established only by proven\n * authentication (the transposition hook) or a manual sales tag (`relatedUserId`).\n */\nexport enum InboundContactEmailUserMatch {\n /** No existing `users` row carried this email at capture time. */\n NONE = 'NONE',\n /** Email matched an existing user whose lifecycle `status` is `ACTIVE`. */\n ACTIVE_USER = 'ACTIVE_USER',\n /**\n * Email matched an existing user that is NOT active — any of `INACTIVE`,\n * `SUSPENDED`, `PENDING_VERIFICATION`, `PENDING_INVITATION`, `DELETED`.\n */\n INACTIVE_USER = 'INACTIVE_USER',\n}\n\n\n/**\n * InboundContact — lead / contact-form submission captured from the\n * public marketing site (Phase 0.5). APPLICATION-scoped (Q-E=B), no\n * `isAnonymizable: true` — transposition is handled by a focused\n * manual hook in `AnonymousSessionBackendService.transposeOwnership`\n * that updates `{ anonymousUserId, userId }` (see\n * .cursor/plans/saas-website-slice.md § 0.5.a).\n *\n * Attribution data (UTM, referrer, landing path, IP fingerprint) does\n * NOT live here. It lives ONCE on `AnonymousUser` (Q-D=β first-touch\n * attribution); analytics joins via the `anonymousUserId` FK.\n */\nexport const InboundContactSchema = z.object({\n\n _id : z.string().isPrimaryKey().isSummaryField(),\n email : z.email().isSummaryField().dataCategories(PersonalDataCategory.CONTACT_DETAILS),\n firstName : z.string().optional().isSummaryField().dataCategories(PersonalDataCategory.IDENTITY),\n lastName : z.string().optional().isSummaryField().dataCategories(PersonalDataCategory.IDENTITY),\n phone : z.string().optional().dataCategories(PersonalDataCategory.CONTACT_DETAILS),\n\n /**\n * Anonymous session that created this lead. FK to `AnonymousUser`.\n * Carries first-touch marketing attribution (utm, referrer,\n * landingPath) via `AnonymousUser.marketingMetadata` (Q-D=β —\n * attribution lives on the session, not the conversion row).\n * Cleared to `null` by the manual transposition hook on\n * `AnonymousSessionBackendService` once the user converts and\n * `userId` is set.\n *\n * Server-set on anonymous-session creates by the write-path context\n * enrichment (`ResourceOperationHelperBackendService.enrichDataWithContext`),\n * which injects `executionContext.initiatorIds.anonymousUserId` — the client\n * `uuidRef` (the AnonymousUser's relational identity), NOT its server-minted\n * `_id`. This resource is APPLICATION-scoped and standalone-created (no nested\n * `/anonymous-users/{uuidRef}/…` URL segment carries the FK, and it is\n * deliberately NOT `isAnonymizable`), so the enrichment resolves the FK from the\n * anonymous initiator identity rather than the URL scope map. Never client-authored.\n */\n anonymousUserId : z.string().nullable().isDBIndexed().excludeFromCreate().isBackendOnly(),\n\n /**\n * Authenticated user AFTER anonymous-session transposition (W7.3a).\n * Auto-set by the manual transposition hook on\n * `AnonymousSessionBackendService` when the anonymous user signs up\n * or logs in. `null` for leads that never converted to an\n * authenticated user. Distinct from `relatedUserId` below — see\n * `relatedUserId` JSDoc for the semantic boundary.\n */\n userId : z.string().nullable().isDBIndexed().excludeFromCreate().isBackendOnly(),\n\n /**\n * Manually-linked existing customer profile by sales / support\n * (e.g. a salesperson tags an inbound lead as \"this is the same\n * human as CRM contact X\"). **Not** auto-populated by\n * anonymous-session transposition — that slot is `userId` above.\n *\n * A lead can legitimately have both populated:\n * - `userId` = the user who eventually signed up from this\n * lead session (auto-set by transposition).\n * - `relatedUserId` = the user a human rep manually associated\n * (may be a different person, e.g. the lead is\n * for a colleague of an existing customer).\n *\n * Kept on the schema for zero-cost preservation (no migration);\n * the disambiguating JSDoc here exists so future readers do not\n * conflate it with `userId`.\n *\n * `.excludeFromCreate()` is a HARD requirement, not polish: the public\n * INBOUND_CONTACTS CREATE is anonymous (`APP_ANONYMOUS`), so a client-\n * authorable `relatedUserId` would let an unauthenticated visitor tag a\n * lead as \"related to\" an arbitrary existing user (a data-integrity /\n * attribution-forgery hole). It is set ONLY post-create by a sales/support\n * admin via a privileged update, never on the public capture request.\n */\n relatedUserId : z.string().isDBIndexed().optional().excludeFromCreate(),\n\n /**\n * Lead-triage signal (see `InboundContactEmailUserMatch`): did this lead's\n * email match an existing user at capture time, and was that user active?\n *\n * Server-computed by the INBOUND_CONTACTS CREATE custom implementation\n * (`prefixCoreOperations`) via a privileged users-by-email lookup — the\n * anonymous create context cannot read `users`, so the lookup runs in a\n * dedicated internal super-admin context and the result is stamped here.\n *\n * `.isBackendOnly()` is a HARD requirement, not polish: the CREATE response\n * is returned to the anonymous submitter, so exposing the match would turn\n * lead capture into an account-enumeration oracle. Backend-only keeps it out\n * of every client response; admin / analytics surfaces read it through the\n * internal DTO. `.excludeFromCreate()` (server-set, never client-authored)\n * and `.excludeFromUpdate()` (immutable capture-time snapshot); `.default`\n * `NONE` is the fail-safe when the email is absent or the lookup fails.\n */\n emailUserMatch : z.enum(InboundContactEmailUserMatch).default(InboundContactEmailUserMatch.NONE).isDBIndexed().excludeFromCreate().excludeFromUpdate().isBackendOnly(),\n\n /**\n * Mongoose-managed timestamps. Declared `z.date()` (NOT `z.iso.datetime()`)\n * because the persistence layer stores them as BSON `Date`s and the DTO\n * builder's network-boundary coercion (`applyNetworkBoundaryCoercion`)\n * rewrites `z.date()` → `z.coerce.date()`. A `z.iso.datetime()` string validator would\n * reject the stored `Date` on the output/internalDto parse → HTTP 500\n * `service_resource_transformation_failed` on every create/read. Matches the\n * framework-wide timestamp convention (35+ resources, e.g. `AnonymousUser`).\n */\n createdAt : z.date().isDBIndexed().isSummaryField().excludeFromCreate().excludeFromUpdate(),\n updatedAt : z.date().isDBIndexed().excludeFromCreate().excludeFromUpdate(),\n privacy : InboundContactPrivacySchema,\n});\n\n\nexport type InboundContact = z.infer<typeof InboundContactSchema>;\n"]}
|
|
1
|
+
{"version":3,"file":"inbound-contacts.shared.schemas.js","sourceRoot":"","sources":["../../../../src/users/inbound-contacts.shared.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,gDAAgD,CAAC;AAC/E,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,2BAA2B,EAAE,MAAM,8CAA8C,CAAC;AAC3F,OAAO,EAAE,oBAAoB,EAAE,MAAM,6DAA6D,CAAC;AAGnG;;;;;;;;;;GAUG;AACH,MAAM,CAAN,IAAY,4BAUX;AAVD,WAAY,4BAA4B;IACtC,kEAAkE;IAClE,6CAAa,CAAA;IACb,2EAA2E;IAC3E,2DAA2B,CAAA;IAC3B;;;OAGG;IACH,+DAA+B,CAAA;AACjC,CAAC,EAVW,4BAA4B,KAA5B,4BAA4B,QAUvC;AAGD;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IAEzC,GAAG,EAAG,CAAC,CAAC,MAAM,EAAE,CAAC,YAAY,EAAE,CAAC,cAAc,EAAE;IAChD,KAAK,EAAG,oBAAoB,CAC1B,CAAC,CAAC,KAAK,EAAE,CAAC,cAAc,EAAE,CAAC,cAAc,CAAC,oBAAoB,CAAC,eAAe,CAAC,EAC/E,aAAa,CAAC,IAAI,EAClB,EAAE,SAAS,EAAE,iBAAiB,EAAE,CACjC;IACD,SAAS,EAAG,oBAAoB,CAC9B,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,cAAc,EAAE,CAAC,cAAc,CAAC,oBAAoB,CAAC,QAAQ,CAAC,EACpF,aAAa,CAAC,MAAM,CACrB;IACD,QAAQ,EAAG,oBAAoB,CAC7B,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,cAAc,EAAE,CAAC,cAAc,CAAC,oBAAoB,CAAC,QAAQ,CAAC,EACpF,aAAa,CAAC,MAAM,CACrB;IACD,KAAK,EAAG,oBAAoB,CAC1B,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,cAAc,CAAC,oBAAoB,CAAC,eAAe,CAAC,EAC1E,aAAa,CAAC,MAAM,CACrB;IAED;;;;;;;;;;;;;;;;;OAiBG;IACH,eAAe,EAAG,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,WAAW,EAAE,CAAC,iBAAiB,EAAE,CAAC,aAAa,EAAE;IAEzF;;;;;;;OAOG;IACH,MAAM,EAAG,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,WAAW,EAAE,CAAC,iBAAiB,EAAE,CAAC,aAAa,EAAE;IAEhF;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,aAAa,EAAG,CAAC,CAAC,MAAM,EAAE,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE,CAAC,iBAAiB,EAAE;IAEvE;;;;;;;;;;;;;;;;OAgBG;IACH,cAAc,EAAG,CAAC,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC,OAAO,CAAC,4BAA4B,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC,iBAAiB,EAAE,CAAC,iBAAiB,EAAE,CAAC,aAAa,EAAE;IAEtK;;;;;;;;OAQG;IACH,SAAS,EAAG,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,cAAc,EAAE,CAAC,iBAAiB,EAAE,CAAC,iBAAiB,EAAE;IAC3F,SAAS,EAAG,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,iBAAiB,EAAE,CAAC,iBAAiB,EAAE;IAC1E,OAAO,EAAG,2BAA2B;CACxC,CAAC,CAAC","sourcesContent":["import { z } from \"zod\";\nimport { addImpersonalizeWith } from '@wildo-ai/zod-decorators';\nimport { RedactionType } from '../compliance/privacy/redaction.shared.schemas';\nimport { ERASED_USER_EMAIL } from './users.shared.schemas';\nimport { InboundContactPrivacySchema } from \"../compliance/privacy/privacy.shared.schemas\";\nimport { PersonalDataCategory } from '../compliance/privacy/personal-data-category.shared.schemas';\n\n\n/**\n * Server-computed lead-triage signal: whether the lead's email corresponded to\n * an existing `users` account at capture time, and — because the caller only\n * flagged \"active, or not\" — whether that account was ACTIVE.\n *\n * This is a TRIAGE / dedup HINT, **not** an ownership link. It NEVER sets\n * `userId`/`relatedUserId`: an anonymous visitor typing an email does not prove\n * they own it, so attributing the lead to a real account on an unverified string\n * would be an account-takeover hole. Ownership is established only by proven\n * authentication (the transposition hook) or a manual sales tag (`relatedUserId`).\n */\nexport enum InboundContactEmailUserMatch {\n /** No existing `users` row carried this email at capture time. */\n NONE = 'NONE',\n /** Email matched an existing user whose lifecycle `status` is `ACTIVE`. */\n ACTIVE_USER = 'ACTIVE_USER',\n /**\n * Email matched an existing user that is NOT active — any of `INACTIVE`,\n * `SUSPENDED`, `PENDING_VERIFICATION`, `PENDING_INVITATION`, `DELETED`.\n */\n INACTIVE_USER = 'INACTIVE_USER',\n}\n\n\n/**\n * InboundContact — lead / contact-form submission captured from the\n * public marketing site (Phase 0.5). APPLICATION-scoped (Q-E=B), no\n * `isAnonymizable: true` — transposition is handled by a focused\n * manual hook in `AnonymousSessionBackendService.transposeOwnership`\n * that updates `{ anonymousUserId, userId }` (see\n * .cursor/plans/saas-website-slice.md § 0.5.a).\n *\n * Attribution data (UTM, referrer, landing path, IP fingerprint) does\n * NOT live here. It lives ONCE on `AnonymousUser` (Q-D=β first-touch\n * attribution); analytics joins via the `anonymousUserId` FK.\n */\nexport const InboundContactSchema = z.object({\n\n _id : z.string().isPrimaryKey().isSummaryField(),\n email : addImpersonalizeWith(\n z.email().isSummaryField().dataCategories(PersonalDataCategory.CONTACT_DETAILS),\n RedactionType.MASK,\n { maskValue: ERASED_USER_EMAIL },\n ),\n firstName : addImpersonalizeWith(\n z.string().optional().isSummaryField().dataCategories(PersonalDataCategory.IDENTITY),\n RedactionType.REMOVE,\n ),\n lastName : addImpersonalizeWith(\n z.string().optional().isSummaryField().dataCategories(PersonalDataCategory.IDENTITY),\n RedactionType.REMOVE,\n ),\n phone : addImpersonalizeWith(\n z.string().optional().dataCategories(PersonalDataCategory.CONTACT_DETAILS),\n RedactionType.REMOVE,\n ),\n\n /**\n * Anonymous session that created this lead. FK to `AnonymousUser`.\n * Carries first-touch marketing attribution (utm, referrer,\n * landingPath) via `AnonymousUser.marketingMetadata` (Q-D=β —\n * attribution lives on the session, not the conversion row).\n * Cleared to `null` by the manual transposition hook on\n * `AnonymousSessionBackendService` once the user converts and\n * `userId` is set.\n *\n * Server-set on anonymous-session creates by the write-path context\n * enrichment (`ResourceOperationHelperBackendService.enrichDataWithContext`),\n * which injects `executionContext.initiatorIds.anonymousUserId` — the client\n * `uuidRef` (the AnonymousUser's relational identity), NOT its server-minted\n * `_id`. This resource is APPLICATION-scoped and standalone-created (no nested\n * `/anonymous-users/{uuidRef}/…` URL segment carries the FK, and it is\n * deliberately NOT `isAnonymizable`), so the enrichment resolves the FK from the\n * anonymous initiator identity rather than the URL scope map. Never client-authored.\n */\n anonymousUserId : z.string().nullable().isDBIndexed().excludeFromCreate().isBackendOnly(),\n\n /**\n * Authenticated user AFTER anonymous-session transposition (W7.3a).\n * Auto-set by the manual transposition hook on\n * `AnonymousSessionBackendService` when the anonymous user signs up\n * or logs in. `null` for leads that never converted to an\n * authenticated user. Distinct from `relatedUserId` below — see\n * `relatedUserId` JSDoc for the semantic boundary.\n */\n userId : z.string().nullable().isDBIndexed().excludeFromCreate().isBackendOnly(),\n\n /**\n * Manually-linked existing customer profile by sales / support\n * (e.g. a salesperson tags an inbound lead as \"this is the same\n * human as CRM contact X\"). **Not** auto-populated by\n * anonymous-session transposition — that slot is `userId` above.\n *\n * A lead can legitimately have both populated:\n * - `userId` = the user who eventually signed up from this\n * lead session (auto-set by transposition).\n * - `relatedUserId` = the user a human rep manually associated\n * (may be a different person, e.g. the lead is\n * for a colleague of an existing customer).\n *\n * Kept on the schema for zero-cost preservation (no migration);\n * the disambiguating JSDoc here exists so future readers do not\n * conflate it with `userId`.\n *\n * `.excludeFromCreate()` is a HARD requirement, not polish: the public\n * INBOUND_CONTACTS CREATE is anonymous (`APP_ANONYMOUS`), so a client-\n * authorable `relatedUserId` would let an unauthenticated visitor tag a\n * lead as \"related to\" an arbitrary existing user (a data-integrity /\n * attribution-forgery hole). It is set ONLY post-create by a sales/support\n * admin via a privileged update, never on the public capture request.\n */\n relatedUserId : z.string().isDBIndexed().optional().excludeFromCreate(),\n\n /**\n * Lead-triage signal (see `InboundContactEmailUserMatch`): did this lead's\n * email match an existing user at capture time, and was that user active?\n *\n * Server-computed by the INBOUND_CONTACTS CREATE custom implementation\n * (`prefixCoreOperations`) via a privileged users-by-email lookup — the\n * anonymous create context cannot read `users`, so the lookup runs in a\n * dedicated internal super-admin context and the result is stamped here.\n *\n * `.isBackendOnly()` is a HARD requirement, not polish: the CREATE response\n * is returned to the anonymous submitter, so exposing the match would turn\n * lead capture into an account-enumeration oracle. Backend-only keeps it out\n * of every client response; admin / analytics surfaces read it through the\n * internal DTO. `.excludeFromCreate()` (server-set, never client-authored)\n * and `.excludeFromUpdate()` (immutable capture-time snapshot); `.default`\n * `NONE` is the fail-safe when the email is absent or the lookup fails.\n */\n emailUserMatch : z.enum(InboundContactEmailUserMatch).default(InboundContactEmailUserMatch.NONE).isDBIndexed().excludeFromCreate().excludeFromUpdate().isBackendOnly(),\n\n /**\n * Mongoose-managed timestamps. Declared `z.date()` (NOT `z.iso.datetime()`)\n * because the persistence layer stores them as BSON `Date`s and the DTO\n * builder's network-boundary coercion (`applyNetworkBoundaryCoercion`)\n * rewrites `z.date()` → `z.coerce.date()`. A `z.iso.datetime()` string validator would\n * reject the stored `Date` on the output/internalDto parse → HTTP 500\n * `service_resource_transformation_failed` on every create/read. Matches the\n * framework-wide timestamp convention (35+ resources, e.g. `AnonymousUser`).\n */\n createdAt : z.date().isDBIndexed().isSummaryField().excludeFromCreate().excludeFromUpdate(),\n updatedAt : z.date().isDBIndexed().excludeFromCreate().excludeFromUpdate(),\n privacy : InboundContactPrivacySchema,\n});\n\n\nexport type InboundContact = z.infer<typeof InboundContactSchema>;\n"]}
|
|
@@ -8,7 +8,7 @@ import { ResourceRelationship } from '../resources/resources.shared.schemas';
|
|
|
8
8
|
* Operations are INTERNAL_CALL: accessible through the service layer pipeline
|
|
9
9
|
* by trusted internal code, but not exposed as HTTP endpoints.
|
|
10
10
|
*/
|
|
11
|
-
export declare const userCredentialsResourceConfiguration_InitializationFactory: (resourcesRelationships: ResourceRelationship[]) => import("..").ResourceConfiguration<undefined, CoreResourceOperation.READ | CoreResourceOperation.UPDATE | CoreResourceOperation.DELETE | CoreResourceOperation.CREATE | CoreResourceOperation.LIST, z.ZodObject<{
|
|
11
|
+
export declare const userCredentialsResourceConfiguration_InitializationFactory: (resourcesRelationships: ResourceRelationship[]) => import("..").ResourceConfiguration<undefined, CoreResourceOperation.READ | CoreResourceOperation.UPDATE | CoreResourceOperation.UPDATE_MANY | CoreResourceOperation.DELETE | CoreResourceOperation.CREATE | CoreResourceOperation.LIST, z.ZodObject<{
|
|
12
12
|
_id: import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<z.ZodString>>;
|
|
13
13
|
userId: import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<z.ZodString>>>>;
|
|
14
14
|
passwordHash: import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<z.ZodOptional<z.ZodNullable<z.ZodString>>>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-credentials.shared.resources-config.schemas.d.ts","sourceRoot":"","sources":["../../../../src/users/user-credentials.shared.resources-config.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"user-credentials.shared.resources-config.schemas.d.ts","sourceRoot":"","sources":["../../../../src/users/user-credentials.shared.resources-config.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,OAAO,EAAE,qBAAqB,EAAwD,MAAM,gDAAgD,CAAC;AAC7I,OAAO,EAA4D,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;AAGvI;;;;;;GAMG;AACH,eAAO,MAAM,0DAA0D,GACrE,wBAAwB,oBAAoB,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAsH9C,CAAC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ErasureRetentionMode, ResourceDataSubjectKind } from '../compliance/privacy/impersonalization.shared.schemas.js';
|
|
1
2
|
import { UserCredentialsSchema } from './user-credentials.shared.schemas.js';
|
|
2
3
|
import { createResourceConfiguration_Initialization } from '../resources/resources-config.shared.factory.js';
|
|
3
4
|
import { ResourceSystemAccessMode } from '../resources/resources-config.shared.schemas.js';
|
|
@@ -26,14 +27,50 @@ export const userCredentialsResourceConfiguration_InitializationFactory = (resou
|
|
|
26
27
|
systemAccessPolicy: {
|
|
27
28
|
exportSubject: ResourceSystemAccessMode.FORBIDDEN, create: ResourceSystemAccessMode.ALLOWED, update: ResourceSystemAccessMode.ALLOWED
|
|
28
29
|
},
|
|
30
|
+
/**
|
|
31
|
+
* Subject erasure scrubs-and-RETAINS the credentials row.
|
|
32
|
+
*
|
|
33
|
+
* `RETAIN_AND_IMPERSONALIZE`: the row holds the password hash and the hashed backup codes, and both
|
|
34
|
+
* are REMOVEd. Retaining rather than deleting keeps the row's foreign key to the retained identity
|
|
35
|
+
* row resolvable — the same reason `USERS` is retained — while the secrets themselves are provably
|
|
36
|
+
* gone, which is the statement a notice can actually make.
|
|
37
|
+
*
|
|
38
|
+
* A credential outliving its subject is the worst case here, so both fields are nulled rather than
|
|
39
|
+
* masked: a masked hash is still a string a verifier will attempt, and "it never matches" is a
|
|
40
|
+
* property of the value, not a guarantee.
|
|
41
|
+
*/
|
|
42
|
+
retentionPolicy: {
|
|
43
|
+
mode: ErasureRetentionMode.RETAIN_AND_IMPERSONALIZE,
|
|
44
|
+
},
|
|
45
|
+
/** The subject is the person these credentials authenticate, reached through `userId`. */
|
|
46
|
+
dataSubjectPolicy: { kind: ResourceDataSubjectKind.SELF_PRINCIPAL },
|
|
29
47
|
coreOperations: [
|
|
30
48
|
CoreResourceOperation.READ,
|
|
31
49
|
CoreResourceOperation.LIST,
|
|
32
50
|
CoreResourceOperation.CREATE,
|
|
33
51
|
CoreResourceOperation.UPDATE,
|
|
34
52
|
CoreResourceOperation.DELETE,
|
|
53
|
+
// Route-less; required by `retentionPolicy` (the in-place scrub).
|
|
54
|
+
CoreResourceOperation.UPDATE_MANY,
|
|
35
55
|
],
|
|
36
56
|
operationsConfiguration: {
|
|
57
|
+
/*
|
|
58
|
+
* Route-less retention plumbing. `retentionPolicy` requires UPDATE_MANY — the impersonalize writer
|
|
59
|
+
* scrubs rows in place through `updateManyAsSystem`. INTERNAL_CALL registers NO route, and must not
|
|
60
|
+
* be REPOSITORY_ONLY: that satisfies the factory's boot-time guard and then fails to resolve when an
|
|
61
|
+
* erasure actually runs, because the retention primitives resolve through
|
|
62
|
+
* `getServiceOperationPathDefault`, which excludes it.
|
|
63
|
+
*/
|
|
64
|
+
[CoreResourceOperation.UPDATE_MANY]: {
|
|
65
|
+
variants: [
|
|
66
|
+
{
|
|
67
|
+
variantType: ResourceOperationVariantType.INTERNAL_CALL,
|
|
68
|
+
isDefault: true,
|
|
69
|
+
roles: [CORE_APP_ROLES.APP_ADMIN_SUPER_ADMIN],
|
|
70
|
+
riskLevel: ResourceOperationRiskLevel.CRITICAL,
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
},
|
|
37
74
|
[CoreResourceOperation.READ]: {
|
|
38
75
|
variants: [
|
|
39
76
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-credentials.shared.resources-config.schemas.js","sourceRoot":"","sources":["../../../../src/users/user-credentials.shared.resources-config.schemas.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,mCAAmC,CAAC;AAC1E,OAAO,EAAE,0CAA0C,EAAE,MAAM,8CAA8C,CAAC;AAC1G,OAAO,EAAE,wBAAwB,EAAE,MAAM,8CAA8C,CAAC;AACxF,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,kCAAkC,EAAE,MAAM,gDAAgD,CAAC;AAC7I,OAAO,EAAE,0BAA0B,EAAE,4BAA4B,EAAwB,MAAM,uCAAuC,CAAC;AACvI,OAAO,EAAE,cAAc,EAAE,MAAM,iDAAiD,CAAC;AAEjF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,0DAA0D,GAAG,CACxE,sBAA8C,EAC9C,EAAE,CAAC,0CAA0C,CAG7C;IACA,UAAU,EAAE,qBAAqB;IACjC,kBAAkB,EAAE,gBAAgB,CAAC,gBAAgB;IACrD,uBAAuB,EAAE,kCAAkC,CAAC,gBAAgB,CAAC,gBAAgB,CAAC;IAC9F,qBAAqB,EAAE,sBAAsB;IAC7C,gBAAgB,EAAE,IAAI;IACtB,8EAA8E;IAC9E,gFAAgF;IAChF,qEAAqE;IACrE,yEAAyE;IACzE,4EAA4E;IAC5E,qFAAqF;IACrF,kBAAkB,EAAE;QAClB,aAAa,EAAE,wBAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,wBAAwB,CAAC,OAAO,EAAE,MAAM,EAAE,wBAAwB,CAAC,OAAO;KAAE;
|
|
1
|
+
{"version":3,"file":"user-credentials.shared.resources-config.schemas.js","sourceRoot":"","sources":["../../../../src/users/user-credentials.shared.resources-config.schemas.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from 'zod';\nimport { ErasureRetentionMode, ResourceDataSubjectKind } from '../compliance/privacy/impersonalization.shared.schemas';\nimport { UserCredentialsSchema } from './user-credentials.shared.schemas';\nimport { createResourceConfiguration_Initialization } from '../resources/resources-config.shared.factory';\nimport { ResourceSystemAccessMode } from '../resources/resources-config.shared.schemas';\nimport { CoreResourceOperation, CoreResourceType, coreResourceShared_FieldIdentifier } from '../resources/resources.shared.core.definitions';\nimport { ResourceOperationRiskLevel, ResourceOperationVariantType, ResourceRelationship } from '../resources/resources.shared.schemas';\nimport { CORE_APP_ROLES } from '../security/authorizations/roles.shared.schemas';\n\n/**\n * User Credentials Resource Configuration (Internal-only)\n *\n * System resource — no auto-generated API controller.\n * Operations are INTERNAL_CALL: accessible through the service layer pipeline\n * by trusted internal code, but not exposed as HTTP endpoints.\n */\nexport const userCredentialsResourceConfiguration_InitializationFactory = (\n resourcesRelationships: ResourceRelationship[]\n) => createResourceConfiguration_Initialization<undefined,\n CoreResourceOperation.READ | CoreResourceOperation.LIST | CoreResourceOperation.CREATE | CoreResourceOperation.UPDATE | CoreResourceOperation.DELETE | CoreResourceOperation.UPDATE_MANY,\n typeof UserCredentialsSchema\n>({\n mainSchema: UserCredentialsSchema,\n resourceIdentifier: CoreResourceType.USER_CREDENTIALS,\n resourceFieldIdentifier: coreResourceShared_FieldIdentifier[CoreResourceType.USER_CREDENTIALS],\n resourceRelationships: resourcesRelationships,\n isSystemResource: true,\n // Registration / password-reset / MFA flows write credentials in-process on a\n // pre-auth (or system) EC that carries no request context, so the client-shaped\n // request DTO would strip `userId` (context FK) and `passwordHash` /\n // `secretBase32` / … (backendOnly). Opt CREATE + UPDATE into the trusted\n // `createAsSystem` / `updateAsSystem` path, which re-attaches exactly those\n // fields after the strict parse. INTERNAL_CALL-only resource → never HTTP-reachable.\n systemAccessPolicy: {\n exportSubject: ResourceSystemAccessMode.FORBIDDEN, create: ResourceSystemAccessMode.ALLOWED, update: ResourceSystemAccessMode.ALLOWED },\n\n /**\n * Subject erasure scrubs-and-RETAINS the credentials row.\n *\n * `RETAIN_AND_IMPERSONALIZE`: the row holds the password hash and the hashed backup codes, and both\n * are REMOVEd. Retaining rather than deleting keeps the row's foreign key to the retained identity\n * row resolvable — the same reason `USERS` is retained — while the secrets themselves are provably\n * gone, which is the statement a notice can actually make.\n *\n * A credential outliving its subject is the worst case here, so both fields are nulled rather than\n * masked: a masked hash is still a string a verifier will attempt, and \"it never matches\" is a\n * property of the value, not a guarantee.\n */\n retentionPolicy: {\n mode: ErasureRetentionMode.RETAIN_AND_IMPERSONALIZE,\n },\n\n /** The subject is the person these credentials authenticate, reached through `userId`. */\n dataSubjectPolicy: { kind: ResourceDataSubjectKind.SELF_PRINCIPAL },\n\n coreOperations: [\n CoreResourceOperation.READ,\n CoreResourceOperation.LIST,\n CoreResourceOperation.CREATE,\n CoreResourceOperation.UPDATE,\n CoreResourceOperation.DELETE,\n // Route-less; required by `retentionPolicy` (the in-place scrub).\n CoreResourceOperation.UPDATE_MANY,\n ],\n operationsConfiguration: {\n /*\n * Route-less retention plumbing. `retentionPolicy` requires UPDATE_MANY — the impersonalize writer\n * scrubs rows in place through `updateManyAsSystem`. INTERNAL_CALL registers NO route, and must not\n * be REPOSITORY_ONLY: that satisfies the factory's boot-time guard and then fails to resolve when an\n * erasure actually runs, because the retention primitives resolve through\n * `getServiceOperationPathDefault`, which excludes it.\n */\n [CoreResourceOperation.UPDATE_MANY]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.INTERNAL_CALL,\n isDefault: true,\n roles: [CORE_APP_ROLES.APP_ADMIN_SUPER_ADMIN],\n riskLevel: ResourceOperationRiskLevel.CRITICAL,\n },\n ],\n },\n [CoreResourceOperation.READ]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.INTERNAL_CALL,\n isDefault: true,\n roles: [CORE_APP_ROLES.APP_ADMIN_SUPER_ADMIN],\n riskLevel: ResourceOperationRiskLevel.LOW,\n },\n ],\n },\n [CoreResourceOperation.LIST]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.INTERNAL_CALL,\n isDefault: true,\n roles: [CORE_APP_ROLES.APP_ADMIN_SUPER_ADMIN],\n riskLevel: ResourceOperationRiskLevel.LOW,\n },\n ],\n },\n [CoreResourceOperation.CREATE]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.INTERNAL_CALL,\n isDefault: true,\n roles: [CORE_APP_ROLES.APP_ADMIN_SUPER_ADMIN],\n // requestDto auto-derived from decorators\n riskLevel: ResourceOperationRiskLevel.HIGH,\n },\n ],\n },\n [CoreResourceOperation.UPDATE]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.INTERNAL_CALL,\n isDefault: true,\n roles: [CORE_APP_ROLES.APP_ADMIN_SUPER_ADMIN],\n // requestDto auto-derived from decorators\n riskLevel: ResourceOperationRiskLevel.MEDIUM,\n },\n ],\n },\n [CoreResourceOperation.DELETE]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.INTERNAL_CALL,\n isDefault: true,\n roles: [CORE_APP_ROLES.APP_ADMIN_SUPER_ADMIN],\n riskLevel: ResourceOperationRiskLevel.CRITICAL,\n },\n ],\n },\n },\n});\n\n\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-credentials.shared.schemas.d.ts","sourceRoot":"","sources":["../../../../src/users/user-credentials.shared.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,kBAAkB,EAAE,MAAM,2DAA2D,CAAC;
|
|
1
|
+
{"version":3,"file":"user-credentials.shared.schemas.d.ts","sourceRoot":"","sources":["../../../../src/users/user-credentials.shared.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,kBAAkB,EAAE,MAAM,2DAA2D,CAAC;AAK/F;;;GAGG;AACH,eAAO,MAAM,4BAA4B;;;;;;;;;;;iBAWvC,CAAC;AAGH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAElF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA2FhC,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { MfaEnrollmentState } from '../security/authentications/authentication.shared.schemas.js';
|
|
3
|
+
import { addImpersonalizeWith } from '@wildo-ai/zod-decorators';
|
|
4
|
+
import { RedactionType } from '../compliance/privacy/redaction.shared.schemas.js';
|
|
3
5
|
import { PersonalDataCategory } from '../compliance/privacy/personal-data-category.shared.schemas.js';
|
|
4
6
|
/**
|
|
5
7
|
* Individual WebAuthn/Passkey credential entry.
|
|
@@ -59,7 +61,10 @@ export const UserCredentialsSchema = z.object({
|
|
|
59
61
|
// secrets that must never leave the backend, not editable password INPUTS. So the derivation
|
|
60
62
|
// cannot see them and the declaration has to be explicit. Measured: before this, `userCredentials`
|
|
61
63
|
// reported zero categories while holding every secret the application has.
|
|
62
|
-
|
|
64
|
+
// REMOVE, not MASK. A credential is the one field class where a placeholder would be worse than a
|
|
65
|
+
// null: a masked hash is still a string the verifier will attempt, and "it never matches" is a
|
|
66
|
+
// property of the value rather than a guarantee. Nulling it makes the absence structural.
|
|
67
|
+
passwordHash: addImpersonalizeWith(z.string().min(1).nullish().isBackendOnly().dataCategories(PersonalDataCategory.CREDENTIALS), RedactionType.REMOVE),
|
|
63
68
|
/**
|
|
64
69
|
* TOTP secret encoded as base32 — used with otpauth library for code generation/verification.
|
|
65
70
|
* NULLISH: absent when TOTP was never set up, or explicitly `null` after the
|
|
@@ -71,7 +76,17 @@ export const UserCredentialsSchema = z.object({
|
|
|
71
76
|
/** Whether the user has confirmed TOTP setup by entering a valid code */
|
|
72
77
|
totpVerified: z.boolean().default(false).isDBIndexed(),
|
|
73
78
|
/** SHA-256 hashes of backup codes — consumed on use (removed from array) */
|
|
74
|
-
|
|
79
|
+
// RESET, not REMOVE, and the field is back to non-nullable (issue #195).
|
|
80
|
+
//
|
|
81
|
+
// It was widened to `.nullish()` on 2026-08-31 purely so REMOVE could reach it: no treatment could
|
|
82
|
+
// then scrub a non-nullable field with a default, so widening the READ contract of a credential
|
|
83
|
+
// field was the only way to declare an erasure posture here. `RESET` landed on 2026-09-01 and the
|
|
84
|
+
// workaround is no longer needed.
|
|
85
|
+
//
|
|
86
|
+
// Reverting is also the more truthful scrub. RESET writes `[]` — *there are no backup codes* —
|
|
87
|
+
// while REMOVE wrote `null`, which reads as *unknown*. For a credential those are different
|
|
88
|
+
// statements to anyone reading the row, and only one of them is what erasure actually achieved.
|
|
89
|
+
backupCodesHashed: addImpersonalizeWith(z.array(z.string()).default([]).isBackendOnly().dataCategories(PersonalDataCategory.CREDENTIALS), RedactionType.RESET),
|
|
75
90
|
/**
|
|
76
91
|
* When the user verified their TOTP setup. NULLISH: absent before verification, a `Date` once
|
|
77
92
|
* verified, or explicitly `null` after the TOTP-disable clear. Unlike the backend-only TOTP
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-credentials.shared.schemas.js","sourceRoot":"","sources":["../../../../src/users/user-credentials.shared.schemas.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from 'zod';\nimport { MfaEnrollmentState } from '../security/authentications/authentication.shared.schemas';\nimport { PersonalDataCategory } from '../compliance/privacy/personal-data-category.shared.schemas';\n\n/**\n * Individual WebAuthn/Passkey credential entry.\n * Stores the data returned by @simplewebauthn/server after registration verification.\n */\nexport const PasskeyCredentialEntrySchema = z.object({\n credentialId: z.string().min(1),\n publicKey: z.string().min(1),\n counter: z.number().int().min(0).default(0),\n transports: z.array(z.string()).default([]),\n credentialDeviceType: z.string().default('singleDevice'),\n credentialBackedUp: z.boolean().default(false),\n name: z.string().max(100).default('Passkey'),\n aaguid: z.string().optional(),\n createdAt: z.date().default(() => new Date()),\n lastUsedAt: z.date().optional(),\n});\n\n\nexport type PasskeyCredentialEntry = z.infer<typeof PasskeyCredentialEntrySchema>;\n\n/**\n * User Credentials Schema\n * Stores all authentication secrets for a user: password hash + optional TOTP.\n * All fields are compatible with MongoDB via zod-to-mongoose converter.\n *\n * The local first-factor / TOTP secret fields (`passwordHash`, `secretBase32`,\n * `totpVerifiedAt`) are `.nullish()` — absent when never set, or explicitly `null`\n * once a compliance clear physically destroys them (directory-managed reconciliation,\n * TOTP-disable). See each field's JSDoc for the null-vs-absent contract.\n *\n * TOTP fields (optional — user may have password-only auth):\n * - `secretBase32`: base32-encoded TOTP secret (backendOnly). Used with otpauth library.\n * NOTE: stored as plaintext base32. For production hardening, add AES-256-GCM encryption at rest.\n * - `totpVerified`: whether the user has confirmed TOTP setup by entering a valid code\n * - `backupCodesHashed`: SHA-256 hashes of backup codes (consumed on use)\n * - `totpVerifiedAt`: timestamp when TOTP setup was confirmed\n */\nexport const UserCredentialsSchema = z.object({\n _id: z.string().min(1).isPrimaryKey().isSummaryField(),\n userId: z.string().min(1).isDBIndexed().isForeignKey().isSummaryField().excludeFromUpdate(),\n // NULLISH by design — the hash is a non-empty `string` when set, ABSENT for a PASSWORD-LESS\n // account (first factor is a passkey, magic link, or enterprise SSO — the row must be\n // creatable with no `passwordHash`, e.g. passkey-enroll-at-invitation-accept), or explicitly\n // `null` once a clear physically DESTROYS it. The AUTH-24 directory-managed reconciliation\n // (`_reconcileLocalCredentialsForDirectoryManaged`) writes `passwordHash: null` to neutralise a\n // local first factor that would otherwise survive IdP deprovisioning. `null` MUST be accepted\n // here: the write re-attaches this backend-only field past the strict request-DTO strip\n // (`updateAsSystem`), but the SERVICE-LAYER OUTPUT PROJECTION (`transformOutput` →\n // `internalDto.parse`) re-validates the persisted document against the FULL schema (backend-only\n // fields included). Were this merely `.optional()` (absent-only, not null), the destroy-write\n // would persist the `null` and THEN throw `service_resource_transformation_failed` on the\n // after-image — the credential is gone but the operation errors (misleading \"reconcile failed\"\n // log). Every consumer treats the hash as falsy-when-absent (`!credentials?.passwordHash`\n // guards, `enrolled: !!passwordHash`, last-method-removal counting), so `null` and absent are\n // handled identically. `.min(1)` still guarantees a NON-EMPTY hash whenever one IS present.\n // ── CREDENTIALS is declared, not derived, on this resource ──\n //\n // The producer DERIVES `credentials` from `.isPassword()` / `.isOtp()` / `.isWriteOnlySecret()`.\n // These fields carry `.isBackendOnly()` instead — correctly, since they are stored HASHES and\n // secrets that must never leave the backend, not editable password INPUTS. So the derivation\n // cannot see them and the declaration has to be explicit. Measured: before this, `userCredentials`\n // reported zero categories while holding every secret the application has.\n passwordHash: z.string().min(1).nullish().isBackendOnly().dataCategories(PersonalDataCategory.CREDENTIALS),\n\n /**\n * TOTP secret encoded as base32 — used with otpauth library for code generation/verification.\n * NULLISH: absent when TOTP was never set up, or explicitly `null` after the\n * auth-method-management TOTP-disable clear physically destroys it. `null` must be accepted so\n * that disable-clear's null-write round-trips through the output projection — same contract as\n * `passwordHash` above (backend-only field, re-validated by `transformOutput`).\n */\n secretBase32: z.string().nullish().isBackendOnly().dataCategories(PersonalDataCategory.CREDENTIALS),\n /** Whether the user has confirmed TOTP setup by entering a valid code */\n totpVerified: z.boolean().default(false).isDBIndexed(),\n /** SHA-256 hashes of backup codes — consumed on use (removed from array) */\n backupCodesHashed: z.array(z.string()).default([]).isBackendOnly().dataCategories(PersonalDataCategory.CREDENTIALS),\n /**\n * When the user verified their TOTP setup. NULLISH: absent before verification, a `Date` once\n * verified, or explicitly `null` after the TOTP-disable clear. Unlike the backend-only TOTP\n * secret this field is NOT stripped from the request DTO, so `null` must be accepted on BOTH\n * the write-side parse (`validateAndTransformInput`, which loose-parses the in-shape field) AND\n * the output projection — otherwise the disable-clear throws before/after persisting. Readers\n * use `?? undefined`, so `null` and absent behave identically.\n */\n totpVerifiedAt: z.date().nullish(),\n /** Timestamp of the last password change. Refresh tokens issued before this are rejected (C4 fix). */\n passwordChangedAt: z.date().optional().isBackendOnly(),\n /** WebAuthn/Passkey credentials — array of registered passkeys */\n passkeyCredentials: z.array(PasskeyCredentialEntrySchema).default([]).isBackendOnly().dataCategories(PersonalDataCategory.CREDENTIALS),\n\n /** W2.1: timestamp of the first login where MFA was required but not yet enrolled. Used with enrollmentGracePeriodDays. */\n firstMfaPromptAt: z.date().optional().isBackendOnly(),\n /** W4.6: MFA enrollment lifecycle state. Computed at user creation and updated on enrollment completion. */\n mfaEnrollmentState: z.enum(MfaEnrollmentState).default(MfaEnrollmentState.NOT_REQUIRED).isBackendOnly(),\n /** W2.3: tracks consecutive account lockouts for progressive lockout duration (2^n multiplier). Reset on successful login. */\n consecutiveLockouts: z.number().default(0).isBackendOnly(),\n\n /** Phone number in E.164 format for SMS OTP authentication (Phase G2) */\n // CONTACT_DETAILS, not CREDENTIALS. It is the SMS OTP delivery ADDRESS — a way to reach the\n // person, which they hold independently of this application — not a secret this application\n // stores. Merging the two would make a notice say the phone number is a credential.\n phoneNumber: z.string().regex(/^\\+?[1-9]\\d{1,14}$/).optional().dataCategories(PersonalDataCategory.CONTACT_DETAILS),\n /** Whether the phone number has been verified via OTP confirmation */\n phoneNumberVerified: z.boolean().default(false),\n /** When the phone number was verified */\n phoneVerifiedAt: z.date().optional(),\n\n createdAt: z.date().default(() => new Date()).isDBIndexed().excludeFromCreate().excludeFromUpdate(),\n updatedAt: z.date().default(() => new Date()).isDBIndexed().excludeFromCreate().excludeFromUpdate(),\n});\n\nexport type UserCredentials = z.infer<typeof UserCredentialsSchema>;\n\n\n"]}
|
|
1
|
+
{"version":3,"file":"user-credentials.shared.schemas.js","sourceRoot":"","sources":["../../../../src/users/user-credentials.shared.schemas.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from 'zod';\nimport { MfaEnrollmentState } from '../security/authentications/authentication.shared.schemas';\nimport { addImpersonalizeWith } from '@wildo-ai/zod-decorators';\nimport { RedactionType } from '../compliance/privacy/redaction.shared.schemas';\nimport { PersonalDataCategory } from '../compliance/privacy/personal-data-category.shared.schemas';\n\n/**\n * Individual WebAuthn/Passkey credential entry.\n * Stores the data returned by @simplewebauthn/server after registration verification.\n */\nexport const PasskeyCredentialEntrySchema = z.object({\n credentialId: z.string().min(1),\n publicKey: z.string().min(1),\n counter: z.number().int().min(0).default(0),\n transports: z.array(z.string()).default([]),\n credentialDeviceType: z.string().default('singleDevice'),\n credentialBackedUp: z.boolean().default(false),\n name: z.string().max(100).default('Passkey'),\n aaguid: z.string().optional(),\n createdAt: z.date().default(() => new Date()),\n lastUsedAt: z.date().optional(),\n});\n\n\nexport type PasskeyCredentialEntry = z.infer<typeof PasskeyCredentialEntrySchema>;\n\n/**\n * User Credentials Schema\n * Stores all authentication secrets for a user: password hash + optional TOTP.\n * All fields are compatible with MongoDB via zod-to-mongoose converter.\n *\n * The local first-factor / TOTP secret fields (`passwordHash`, `secretBase32`,\n * `totpVerifiedAt`) are `.nullish()` — absent when never set, or explicitly `null`\n * once a compliance clear physically destroys them (directory-managed reconciliation,\n * TOTP-disable). See each field's JSDoc for the null-vs-absent contract.\n *\n * TOTP fields (optional — user may have password-only auth):\n * - `secretBase32`: base32-encoded TOTP secret (backendOnly). Used with otpauth library.\n * NOTE: stored as plaintext base32. For production hardening, add AES-256-GCM encryption at rest.\n * - `totpVerified`: whether the user has confirmed TOTP setup by entering a valid code\n * - `backupCodesHashed`: SHA-256 hashes of backup codes (consumed on use)\n * - `totpVerifiedAt`: timestamp when TOTP setup was confirmed\n */\nexport const UserCredentialsSchema = z.object({\n _id: z.string().min(1).isPrimaryKey().isSummaryField(),\n userId: z.string().min(1).isDBIndexed().isForeignKey().isSummaryField().excludeFromUpdate(),\n // NULLISH by design — the hash is a non-empty `string` when set, ABSENT for a PASSWORD-LESS\n // account (first factor is a passkey, magic link, or enterprise SSO — the row must be\n // creatable with no `passwordHash`, e.g. passkey-enroll-at-invitation-accept), or explicitly\n // `null` once a clear physically DESTROYS it. The AUTH-24 directory-managed reconciliation\n // (`_reconcileLocalCredentialsForDirectoryManaged`) writes `passwordHash: null` to neutralise a\n // local first factor that would otherwise survive IdP deprovisioning. `null` MUST be accepted\n // here: the write re-attaches this backend-only field past the strict request-DTO strip\n // (`updateAsSystem`), but the SERVICE-LAYER OUTPUT PROJECTION (`transformOutput` →\n // `internalDto.parse`) re-validates the persisted document against the FULL schema (backend-only\n // fields included). Were this merely `.optional()` (absent-only, not null), the destroy-write\n // would persist the `null` and THEN throw `service_resource_transformation_failed` on the\n // after-image — the credential is gone but the operation errors (misleading \"reconcile failed\"\n // log). Every consumer treats the hash as falsy-when-absent (`!credentials?.passwordHash`\n // guards, `enrolled: !!passwordHash`, last-method-removal counting), so `null` and absent are\n // handled identically. `.min(1)` still guarantees a NON-EMPTY hash whenever one IS present.\n // ── CREDENTIALS is declared, not derived, on this resource ──\n //\n // The producer DERIVES `credentials` from `.isPassword()` / `.isOtp()` / `.isWriteOnlySecret()`.\n // These fields carry `.isBackendOnly()` instead — correctly, since they are stored HASHES and\n // secrets that must never leave the backend, not editable password INPUTS. So the derivation\n // cannot see them and the declaration has to be explicit. Measured: before this, `userCredentials`\n // reported zero categories while holding every secret the application has.\n // REMOVE, not MASK. A credential is the one field class where a placeholder would be worse than a\n // null: a masked hash is still a string the verifier will attempt, and \"it never matches\" is a\n // property of the value rather than a guarantee. Nulling it makes the absence structural.\n passwordHash: addImpersonalizeWith(\n z.string().min(1).nullish().isBackendOnly().dataCategories(PersonalDataCategory.CREDENTIALS),\n RedactionType.REMOVE,\n ),\n\n /**\n * TOTP secret encoded as base32 — used with otpauth library for code generation/verification.\n * NULLISH: absent when TOTP was never set up, or explicitly `null` after the\n * auth-method-management TOTP-disable clear physically destroys it. `null` must be accepted so\n * that disable-clear's null-write round-trips through the output projection — same contract as\n * `passwordHash` above (backend-only field, re-validated by `transformOutput`).\n */\n secretBase32: z.string().nullish().isBackendOnly().dataCategories(PersonalDataCategory.CREDENTIALS),\n /** Whether the user has confirmed TOTP setup by entering a valid code */\n totpVerified: z.boolean().default(false).isDBIndexed(),\n /** SHA-256 hashes of backup codes — consumed on use (removed from array) */\n // RESET, not REMOVE, and the field is back to non-nullable (issue #195).\n //\n // It was widened to `.nullish()` on 2026-08-31 purely so REMOVE could reach it: no treatment could\n // then scrub a non-nullable field with a default, so widening the READ contract of a credential\n // field was the only way to declare an erasure posture here. `RESET` landed on 2026-09-01 and the\n // workaround is no longer needed.\n //\n // Reverting is also the more truthful scrub. RESET writes `[]` — *there are no backup codes* —\n // while REMOVE wrote `null`, which reads as *unknown*. For a credential those are different\n // statements to anyone reading the row, and only one of them is what erasure actually achieved.\n backupCodesHashed: addImpersonalizeWith(\n z.array(z.string()).default([]).isBackendOnly().dataCategories(PersonalDataCategory.CREDENTIALS),\n RedactionType.RESET,\n ),\n /**\n * When the user verified their TOTP setup. NULLISH: absent before verification, a `Date` once\n * verified, or explicitly `null` after the TOTP-disable clear. Unlike the backend-only TOTP\n * secret this field is NOT stripped from the request DTO, so `null` must be accepted on BOTH\n * the write-side parse (`validateAndTransformInput`, which loose-parses the in-shape field) AND\n * the output projection — otherwise the disable-clear throws before/after persisting. Readers\n * use `?? undefined`, so `null` and absent behave identically.\n */\n totpVerifiedAt: z.date().nullish(),\n /** Timestamp of the last password change. Refresh tokens issued before this are rejected (C4 fix). */\n passwordChangedAt: z.date().optional().isBackendOnly(),\n /** WebAuthn/Passkey credentials — array of registered passkeys */\n passkeyCredentials: z.array(PasskeyCredentialEntrySchema).default([]).isBackendOnly().dataCategories(PersonalDataCategory.CREDENTIALS),\n\n /** W2.1: timestamp of the first login where MFA was required but not yet enrolled. Used with enrollmentGracePeriodDays. */\n firstMfaPromptAt: z.date().optional().isBackendOnly(),\n /** W4.6: MFA enrollment lifecycle state. Computed at user creation and updated on enrollment completion. */\n mfaEnrollmentState: z.enum(MfaEnrollmentState).default(MfaEnrollmentState.NOT_REQUIRED).isBackendOnly(),\n /** W2.3: tracks consecutive account lockouts for progressive lockout duration (2^n multiplier). Reset on successful login. */\n consecutiveLockouts: z.number().default(0).isBackendOnly(),\n\n /** Phone number in E.164 format for SMS OTP authentication (Phase G2) */\n // CONTACT_DETAILS, not CREDENTIALS. It is the SMS OTP delivery ADDRESS — a way to reach the\n // person, which they hold independently of this application — not a secret this application\n // stores. Merging the two would make a notice say the phone number is a credential.\n phoneNumber: z.string().regex(/^\\+?[1-9]\\d{1,14}$/).optional().dataCategories(PersonalDataCategory.CONTACT_DETAILS),\n /** Whether the phone number has been verified via OTP confirmation */\n phoneNumberVerified: z.boolean().default(false),\n /** When the phone number was verified */\n phoneVerifiedAt: z.date().optional(),\n\n createdAt: z.date().default(() => new Date()).isDBIndexed().excludeFromCreate().excludeFromUpdate(),\n updatedAt: z.date().default(() => new Date()).isDBIndexed().excludeFromCreate().excludeFromUpdate(),\n});\n\nexport type UserCredentials = z.infer<typeof UserCredentialsSchema>;\n\n\n"]}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { CoreResourceOperation } from '../resources/resources.shared.core.definitions';
|
|
12
12
|
import { ResourceRelationship } from '../resources/resources.shared.schemas';
|
|
13
|
-
export declare const userIdentityLinksResourceConfiguration_InitializationFactory: (resourcesRelationships: ResourceRelationship[]) => import("..").ResourceConfiguration<undefined, CoreResourceOperation.READ | CoreResourceOperation.UPDATE | CoreResourceOperation.DELETE | CoreResourceOperation.CREATE | CoreResourceOperation.LIST, import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<import("zod").ZodObject<{
|
|
13
|
+
export declare const userIdentityLinksResourceConfiguration_InitializationFactory: (resourcesRelationships: ResourceRelationship[]) => import("..").ResourceConfiguration<undefined, CoreResourceOperation.READ | CoreResourceOperation.UPDATE | CoreResourceOperation.UPDATE_MANY | CoreResourceOperation.DELETE | CoreResourceOperation.CREATE | CoreResourceOperation.LIST, import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<import("zod").ZodObject<{
|
|
14
14
|
_id: import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<import("zod").ZodString>>;
|
|
15
15
|
userId: import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<import("zod").ZodString>>>>;
|
|
16
16
|
connectionType: import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<import("zod").ZodEnum<typeof import("..").SsoConnectionType>>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-identity-links.shared.resources-config.schemas.d.ts","sourceRoot":"","sources":["../../../../src/users/user-identity-links.shared.resources-config.schemas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;
|
|
1
|
+
{"version":3,"file":"user-identity-links.shared.resources-config.schemas.d.ts","sourceRoot":"","sources":["../../../../src/users/user-identity-links.shared.resources-config.schemas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH,OAAO,EAAE,qBAAqB,EAAwD,MAAM,gDAAgD,CAAC;AAC7I,OAAO,EAA4D,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;AAIvI,eAAO,MAAM,4DAA4D,GACvE,wBAAwB,oBAAoB,EAAE;;;;;;;;;;;;;;;;;kCAkI9C,CAAC"}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* but not exposed as HTTP endpoints.
|
|
10
10
|
*/
|
|
11
11
|
import { UserIdentityLinkSchema } from './user-identity-links.shared.schemas.js';
|
|
12
|
+
import { ErasureRetentionMode, ResourceDataSubjectKind } from '../compliance/privacy/impersonalization.shared.schemas.js';
|
|
12
13
|
import { createResourceConfiguration_Initialization } from '../resources/resources-config.shared.factory.js';
|
|
13
14
|
import { ResourceSystemAccessMode } from '../resources/resources-config.shared.schemas.js';
|
|
14
15
|
import { CoreResourceOperation, CoreResourceType, coreResourceShared_FieldIdentifier } from '../resources/resources.shared.core.definitions.js';
|
|
@@ -42,14 +43,49 @@ export const userIdentityLinksResourceConfiguration_InitializationFactory = (res
|
|
|
42
43
|
list: ResourceSystemAccessMode.ALLOWED,
|
|
43
44
|
},
|
|
44
45
|
portabilityPolicy: { defaultProvenance: DataPortabilityProvenance.OBSERVED },
|
|
46
|
+
/**
|
|
47
|
+
* Subject erasure scrubs-and-RETAINS the identity link.
|
|
48
|
+
*
|
|
49
|
+
* `RETAIN_AND_IMPERSONALIZE`: the row holds the provider's own copy of the person — their id at that
|
|
50
|
+
* provider, their email, display name, avatar URL and the raw profile payload. All are scrubbed.
|
|
51
|
+
*
|
|
52
|
+
* `providerUserId` participates in the compound business key, so masking it would collide across two
|
|
53
|
+
* erased links for one connection under a plain unique index. It does not, because declaring a
|
|
54
|
+
* retention policy makes the planner emit that index RETENTION-SCOPED — retained rows fall outside
|
|
55
|
+
* the constraint. Verify that in the generated migration rather than assuming it.
|
|
56
|
+
*/
|
|
57
|
+
retentionPolicy: {
|
|
58
|
+
mode: ErasureRetentionMode.RETAIN_AND_IMPERSONALIZE,
|
|
59
|
+
},
|
|
60
|
+
/** The subject is the person the external identity belongs to, reached through `userId`. */
|
|
61
|
+
dataSubjectPolicy: { kind: ResourceDataSubjectKind.SELF_PRINCIPAL },
|
|
45
62
|
coreOperations: [
|
|
46
63
|
CoreResourceOperation.READ,
|
|
47
64
|
CoreResourceOperation.LIST,
|
|
48
65
|
CoreResourceOperation.CREATE,
|
|
49
66
|
CoreResourceOperation.UPDATE,
|
|
50
67
|
CoreResourceOperation.DELETE,
|
|
68
|
+
// Route-less; required by `retentionPolicy` (the in-place scrub).
|
|
69
|
+
CoreResourceOperation.UPDATE_MANY,
|
|
51
70
|
],
|
|
52
71
|
operationsConfiguration: {
|
|
72
|
+
/*
|
|
73
|
+
* Route-less retention plumbing. `retentionPolicy` requires UPDATE_MANY — the impersonalize writer
|
|
74
|
+
* scrubs rows in place through `updateManyAsSystem`. INTERNAL_CALL registers NO route, and must not
|
|
75
|
+
* be REPOSITORY_ONLY: that satisfies the factory's boot-time guard and then fails to resolve when an
|
|
76
|
+
* erasure actually runs, because the retention primitives resolve through
|
|
77
|
+
* `getServiceOperationPathDefault`, which excludes it.
|
|
78
|
+
*/
|
|
79
|
+
[CoreResourceOperation.UPDATE_MANY]: {
|
|
80
|
+
variants: [
|
|
81
|
+
{
|
|
82
|
+
variantType: ResourceOperationVariantType.INTERNAL_CALL,
|
|
83
|
+
isDefault: true,
|
|
84
|
+
roles: [CORE_APP_ROLES.APP_ADMIN_SUPER_ADMIN],
|
|
85
|
+
riskLevel: ResourceOperationRiskLevel.CRITICAL,
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
},
|
|
53
89
|
[CoreResourceOperation.READ]: {
|
|
54
90
|
variants: [
|
|
55
91
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-identity-links.shared.resources-config.schemas.js","sourceRoot":"","sources":["../../../../src/users/user-identity-links.shared.resources-config.schemas.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * User Identity Links Resource Configuration (Internal-only)\n *\n * System resource — no auto-generated API controller.\n * Frontend access via custom auth controller endpoint: GET /auth/identity-links\n *\n * Operations are INTERNAL_CALL: accessible through the service layer pipeline\n * by trusted internal code (OAuth2 JIT provisioning, auth method management),\n * but not exposed as HTTP endpoints.\n */\n\nimport { UserIdentityLinkSchema } from './user-identity-links.shared.schemas';\nimport { createResourceConfiguration_Initialization } from '../resources/resources-config.shared.factory';\nimport { ResourceSystemAccessMode } from '../resources/resources-config.shared.schemas';\nimport { CoreResourceOperation, CoreResourceType, coreResourceShared_FieldIdentifier } from '../resources/resources.shared.core.definitions';\nimport { ResourceOperationRiskLevel, ResourceOperationVariantType, ResourceRelationship } from '../resources/resources.shared.schemas';\nimport { CORE_APP_ROLES } from '../security/authorizations/roles.shared.schemas';\nimport { DataPortabilityProvenance } from '../compliance/privacy/subject-export.shared.schemas';\n\nexport const userIdentityLinksResourceConfiguration_InitializationFactory = (\n resourcesRelationships: ResourceRelationship[]\n) => createResourceConfiguration_Initialization<undefined,\n CoreResourceOperation.READ | CoreResourceOperation.LIST | CoreResourceOperation.CREATE | CoreResourceOperation.UPDATE | CoreResourceOperation.DELETE,\n typeof UserIdentityLinkSchema\n>({\n mainSchema: UserIdentityLinkSchema,\n resourceIdentifier: CoreResourceType.USER_IDENTITY_LINKS,\n resourceFieldIdentifier: coreResourceShared_FieldIdentifier[CoreResourceType.USER_IDENTITY_LINKS],\n resourceRelationships: resourcesRelationships,\n isSystemResource: true,\n\n // OAuth2 JIT + enterprise SSO / SCIM provisioning create and update identity links\n // in-process on a request-context-less EC. The client-shaped CREATE DTO strips the\n // context FKs by name (`userId`, `organizationId`, `applicationId`), and both CREATE and UPDATE strip\n // the backend-only `providerRawProfile`. This resource has no HTTP surface (all\n // variants are INTERNAL_CALL), so the trusted `createAsSystem` / `updateAsSystem`\n // primitives are the ONLY writers — opt both verbs in so the stripped fields survive\n // the strict-DTO parse (re-attached from the schema-derived allowlist).\n systemAccessPolicy: {\n exportSubject: ResourceSystemAccessMode.ALLOWED,\n create: ResourceSystemAccessMode.ALLOWED,\n update: ResourceSystemAccessMode.ALLOWED,\n // AUTH-24: the auth-policy resolver READS a user's identity link during LOGIN to decide whether they are\n // IdP-PROVISIONED in an actively directory-managed org — only THEN are their local first-factor\n // credentials derived off (and reconciled). The owner/admin who registered locally has no link and keeps\n // a break-glass credential (user decision 2026-07-21). The bulk sweep on SCIM activation LISTS an org's\n // links to reconcile all already-provisioned members. Both run under a pre-/being-authenticated or\n // token-mint EC, so they resolve via the system door.\n read: ResourceSystemAccessMode.ALLOWED,\n list: ResourceSystemAccessMode.ALLOWED,\n },\n portabilityPolicy: { defaultProvenance: DataPortabilityProvenance.OBSERVED },\n\n coreOperations: [\n CoreResourceOperation.READ,\n CoreResourceOperation.LIST,\n CoreResourceOperation.CREATE,\n CoreResourceOperation.UPDATE,\n CoreResourceOperation.DELETE,\n ],\n operationsConfiguration: {\n [CoreResourceOperation.READ]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.INTERNAL_CALL,\n isDefault: true,\n roles: [CORE_APP_ROLES.APP_ADMIN_SUPER_ADMIN],\n riskLevel: ResourceOperationRiskLevel.LOW,\n },\n ],\n },\n [CoreResourceOperation.LIST]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.INTERNAL_CALL,\n isDefault: true,\n roles: [CORE_APP_ROLES.APP_ADMIN_SUPER_ADMIN],\n riskLevel: ResourceOperationRiskLevel.LOW,\n },\n ],\n },\n [CoreResourceOperation.CREATE]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.INTERNAL_CALL,\n isDefault: true,\n roles: [CORE_APP_ROLES.APP_ADMIN_SUPER_ADMIN],\n riskLevel: ResourceOperationRiskLevel.MEDIUM,\n },\n ],\n },\n [CoreResourceOperation.UPDATE]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.INTERNAL_CALL,\n isDefault: true,\n roles: [CORE_APP_ROLES.APP_ADMIN_SUPER_ADMIN],\n riskLevel: ResourceOperationRiskLevel.MEDIUM,\n },\n ],\n },\n [CoreResourceOperation.DELETE]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.INTERNAL_CALL,\n isDefault: true,\n roles: [CORE_APP_ROLES.APP_ADMIN_SUPER_ADMIN],\n riskLevel: ResourceOperationRiskLevel.HIGH,\n },\n ],\n },\n },\n});\n"]}
|
|
1
|
+
{"version":3,"file":"user-identity-links.shared.resources-config.schemas.js","sourceRoot":"","sources":["../../../../src/users/user-identity-links.shared.resources-config.schemas.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * User Identity Links Resource Configuration (Internal-only)\n *\n * System resource — no auto-generated API controller.\n * Frontend access via custom auth controller endpoint: GET /auth/identity-links\n *\n * Operations are INTERNAL_CALL: accessible through the service layer pipeline\n * by trusted internal code (OAuth2 JIT provisioning, auth method management),\n * but not exposed as HTTP endpoints.\n */\n\nimport { UserIdentityLinkSchema } from './user-identity-links.shared.schemas';\nimport { ErasureRetentionMode, ResourceDataSubjectKind } from '../compliance/privacy/impersonalization.shared.schemas';\nimport { createResourceConfiguration_Initialization } from '../resources/resources-config.shared.factory';\nimport { ResourceSystemAccessMode } from '../resources/resources-config.shared.schemas';\nimport { CoreResourceOperation, CoreResourceType, coreResourceShared_FieldIdentifier } from '../resources/resources.shared.core.definitions';\nimport { ResourceOperationRiskLevel, ResourceOperationVariantType, ResourceRelationship } from '../resources/resources.shared.schemas';\nimport { CORE_APP_ROLES } from '../security/authorizations/roles.shared.schemas';\nimport { DataPortabilityProvenance } from '../compliance/privacy/subject-export.shared.schemas';\n\nexport const userIdentityLinksResourceConfiguration_InitializationFactory = (\n resourcesRelationships: ResourceRelationship[]\n) => createResourceConfiguration_Initialization<undefined,\n CoreResourceOperation.READ | CoreResourceOperation.LIST | CoreResourceOperation.CREATE | CoreResourceOperation.UPDATE | CoreResourceOperation.DELETE | CoreResourceOperation.UPDATE_MANY,\n typeof UserIdentityLinkSchema\n>({\n mainSchema: UserIdentityLinkSchema,\n resourceIdentifier: CoreResourceType.USER_IDENTITY_LINKS,\n resourceFieldIdentifier: coreResourceShared_FieldIdentifier[CoreResourceType.USER_IDENTITY_LINKS],\n resourceRelationships: resourcesRelationships,\n isSystemResource: true,\n\n // OAuth2 JIT + enterprise SSO / SCIM provisioning create and update identity links\n // in-process on a request-context-less EC. The client-shaped CREATE DTO strips the\n // context FKs by name (`userId`, `organizationId`, `applicationId`), and both CREATE and UPDATE strip\n // the backend-only `providerRawProfile`. This resource has no HTTP surface (all\n // variants are INTERNAL_CALL), so the trusted `createAsSystem` / `updateAsSystem`\n // primitives are the ONLY writers — opt both verbs in so the stripped fields survive\n // the strict-DTO parse (re-attached from the schema-derived allowlist).\n systemAccessPolicy: {\n exportSubject: ResourceSystemAccessMode.ALLOWED,\n create: ResourceSystemAccessMode.ALLOWED,\n update: ResourceSystemAccessMode.ALLOWED,\n // AUTH-24: the auth-policy resolver READS a user's identity link during LOGIN to decide whether they are\n // IdP-PROVISIONED in an actively directory-managed org — only THEN are their local first-factor\n // credentials derived off (and reconciled). The owner/admin who registered locally has no link and keeps\n // a break-glass credential (user decision 2026-07-21). The bulk sweep on SCIM activation LISTS an org's\n // links to reconcile all already-provisioned members. Both run under a pre-/being-authenticated or\n // token-mint EC, so they resolve via the system door.\n read: ResourceSystemAccessMode.ALLOWED,\n list: ResourceSystemAccessMode.ALLOWED,\n },\n portabilityPolicy: { defaultProvenance: DataPortabilityProvenance.OBSERVED },\n\n\n /**\n * Subject erasure scrubs-and-RETAINS the identity link.\n *\n * `RETAIN_AND_IMPERSONALIZE`: the row holds the provider's own copy of the person — their id at that\n * provider, their email, display name, avatar URL and the raw profile payload. All are scrubbed.\n *\n * `providerUserId` participates in the compound business key, so masking it would collide across two\n * erased links for one connection under a plain unique index. It does not, because declaring a\n * retention policy makes the planner emit that index RETENTION-SCOPED — retained rows fall outside\n * the constraint. Verify that in the generated migration rather than assuming it.\n */\n retentionPolicy: {\n mode: ErasureRetentionMode.RETAIN_AND_IMPERSONALIZE,\n },\n\n /** The subject is the person the external identity belongs to, reached through `userId`. */\n dataSubjectPolicy: { kind: ResourceDataSubjectKind.SELF_PRINCIPAL },\n\n coreOperations: [\n CoreResourceOperation.READ,\n CoreResourceOperation.LIST,\n CoreResourceOperation.CREATE,\n CoreResourceOperation.UPDATE,\n CoreResourceOperation.DELETE,\n // Route-less; required by `retentionPolicy` (the in-place scrub).\n CoreResourceOperation.UPDATE_MANY,\n ],\n operationsConfiguration: {\n /*\n * Route-less retention plumbing. `retentionPolicy` requires UPDATE_MANY — the impersonalize writer\n * scrubs rows in place through `updateManyAsSystem`. INTERNAL_CALL registers NO route, and must not\n * be REPOSITORY_ONLY: that satisfies the factory's boot-time guard and then fails to resolve when an\n * erasure actually runs, because the retention primitives resolve through\n * `getServiceOperationPathDefault`, which excludes it.\n */\n [CoreResourceOperation.UPDATE_MANY]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.INTERNAL_CALL,\n isDefault: true,\n roles: [CORE_APP_ROLES.APP_ADMIN_SUPER_ADMIN],\n riskLevel: ResourceOperationRiskLevel.CRITICAL,\n },\n ],\n },\n [CoreResourceOperation.READ]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.INTERNAL_CALL,\n isDefault: true,\n roles: [CORE_APP_ROLES.APP_ADMIN_SUPER_ADMIN],\n riskLevel: ResourceOperationRiskLevel.LOW,\n },\n ],\n },\n [CoreResourceOperation.LIST]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.INTERNAL_CALL,\n isDefault: true,\n roles: [CORE_APP_ROLES.APP_ADMIN_SUPER_ADMIN],\n riskLevel: ResourceOperationRiskLevel.LOW,\n },\n ],\n },\n [CoreResourceOperation.CREATE]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.INTERNAL_CALL,\n isDefault: true,\n roles: [CORE_APP_ROLES.APP_ADMIN_SUPER_ADMIN],\n riskLevel: ResourceOperationRiskLevel.MEDIUM,\n },\n ],\n },\n [CoreResourceOperation.UPDATE]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.INTERNAL_CALL,\n isDefault: true,\n roles: [CORE_APP_ROLES.APP_ADMIN_SUPER_ADMIN],\n riskLevel: ResourceOperationRiskLevel.MEDIUM,\n },\n ],\n },\n [CoreResourceOperation.DELETE]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.INTERNAL_CALL,\n isDefault: true,\n roles: [CORE_APP_ROLES.APP_ADMIN_SUPER_ADMIN],\n riskLevel: ResourceOperationRiskLevel.HIGH,\n },\n ],\n },\n },\n});\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-identity-links.shared.schemas.d.ts","sourceRoot":"","sources":["../../../../src/users/user-identity-links.shared.schemas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"user-identity-links.shared.schemas.d.ts","sourceRoot":"","sources":["../../../../src/users/user-identity-links.shared.schemas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,OAAO,EACL,aAAa,EACb,sBAAsB,EACtB,iBAAiB,EAClB,MAAM,2DAA2D,CAAC;AAEnE;;;;;;GAMG;AACH,eAAO,MAAM,mCAAmC,EAAE,QAAQ,CACxD,MAAM,CAAC,iBAAiB,EAAE,WAAW,CAAC,sBAAsB,CAAC,CAAC,CAW/D,CAAC;AAEF,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;kBAmJjC,CAAC;AAGH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC"}
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
* external identity from being bound to two Wildo users inside one tenant connection.
|
|
10
10
|
*/
|
|
11
11
|
import { z } from 'zod';
|
|
12
|
+
import { addImpersonalizeWith } from '@wildo-ai/zod-decorators';
|
|
13
|
+
import { RedactionType } from '../compliance/privacy/redaction.shared.schemas.js';
|
|
14
|
+
import { ERASED_USER_EMAIL, ERASED_IDENTITY_VALUE } from './users.shared.schemas.js';
|
|
12
15
|
import { PersonalDataCategory } from '../compliance/privacy/personal-data-category.shared.schemas.js';
|
|
13
16
|
import { AuthScopeType, ExternalIdentitySource, SsoConnectionType, } from '../security/authentications/authentication.shared.schemas.js';
|
|
14
17
|
/**
|
|
@@ -38,13 +41,17 @@ export const UserIdentityLinkSchema = z.object({
|
|
|
38
41
|
/** Matches external connector ref (e.g., "google", "microsoft", "github", "oidc") */
|
|
39
42
|
providerRef: z.string().min(1).isDBIndexed().isSummaryField(),
|
|
40
43
|
/** The user's ID at the IdP */
|
|
41
|
-
|
|
44
|
+
// MASK, not REMOVE: `min(1)` and part of the compound business key, so a null fails the re-parse.
|
|
45
|
+
// Two erased links for one connection would share this masked value — which is safe because a
|
|
46
|
+
// declared retention policy makes the planner emit that unique index RETENTION-SCOPED, so retained
|
|
47
|
+
// rows fall outside the constraint. Confirmed in the generated migration, not assumed.
|
|
48
|
+
providerUserId: addImpersonalizeWith(z.string().min(1).isDBIndexed().dataCategories(PersonalDataCategory.IDENTITY), RedactionType.MASK, { maskValue: ERASED_IDENTITY_VALUE }),
|
|
42
49
|
/** Email from the IdP */
|
|
43
|
-
providerEmail: z.string().isDBIndexed().dataCategories(PersonalDataCategory.CONTACT_DETAILS),
|
|
50
|
+
providerEmail: addImpersonalizeWith(z.string().isDBIndexed().dataCategories(PersonalDataCategory.CONTACT_DETAILS), RedactionType.MASK, { maskValue: ERASED_USER_EMAIL }),
|
|
44
51
|
/** Display name from IdP */
|
|
45
|
-
providerDisplayName: z.string().optional().dataCategories(PersonalDataCategory.IDENTITY),
|
|
52
|
+
providerDisplayName: addImpersonalizeWith(z.string().optional().dataCategories(PersonalDataCategory.IDENTITY), RedactionType.REMOVE),
|
|
46
53
|
/** Avatar URL from IdP */
|
|
47
|
-
providerAvatarUrl: z.string().optional().dataCategories(PersonalDataCategory.IDENTITY),
|
|
54
|
+
providerAvatarUrl: addImpersonalizeWith(z.string().optional().dataCategories(PersonalDataCategory.IDENTITY), RedactionType.REMOVE),
|
|
48
55
|
/**
|
|
49
56
|
* Raw claims from the IdP for debugging — backend only.
|
|
50
57
|
*
|
|
@@ -53,7 +60,10 @@ export const UserIdentityLinkSchema = z.object({
|
|
|
53
60
|
* is FOR; it is not a certification of what a given IdP will send. An application whose IdP is
|
|
54
61
|
* known to emit a special-category claim should say so on its own copy.
|
|
55
62
|
*/
|
|
56
|
-
|
|
63
|
+
// The provider's own payload about the person, unbounded in shape — whatever that provider chose
|
|
64
|
+
// to send. It carries no `dataCategories` because nothing can classify it in advance, which is
|
|
65
|
+
// exactly why it must be scrubbed: an unclassifiable blob is not an empty one.
|
|
66
|
+
providerRawProfile: addImpersonalizeWith(z.record(z.string(), z.unknown()).optional().isBackendOnly(), RedactionType.REMOVE)
|
|
57
67
|
.dataCategories([PersonalDataCategory.IDENTITY, PersonalDataCategory.USER_CONTENT]),
|
|
58
68
|
/** Enterprise tenant kind. Absent for social links. */
|
|
59
69
|
scopeType: z.enum(AuthScopeType).optional().isDBIndexed(),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-identity-links.shared.schemas.js","sourceRoot":"","sources":["../../../../src/users/user-identity-links.shared.schemas.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * User Identity Links Schema\n *\n * Tracks OAuth2/SSO provider connections per user.\n * A user can have multiple identity links (e.g., Google social + Microsoft enterprise).\n *\n * Canonical identity key: connection type + provider + provider user + exact enterprise\n * scope/connection coordinates. The database compound unique index prevents the same\n * external identity from being bound to two Wildo users inside one tenant connection.\n */\n\nimport { z } from 'zod';\nimport { PersonalDataCategory } from '../compliance/privacy/personal-data-category.shared.schemas';\nimport {\n AuthScopeType,\n ExternalIdentitySource,\n SsoConnectionType,\n} from '../security/authentications/authentication.shared.schemas';\n\n/**\n * Valid provenance mechanisms for each connection ownership class.\n *\n * OAuth2 is social-only; SAML and SCIM are enterprise-only; OIDC legitimately\n * spans both. Keeping this relationship beside the persisted link schema makes\n * invalid cross-products impossible at every repository boundary.\n */\nexport const IDENTITY_SOURCES_BY_CONNECTION_TYPE: Readonly<\n Record<SsoConnectionType, ReadonlySet<ExternalIdentitySource>>\n> = {\n [SsoConnectionType.SOCIAL]: new Set([\n ExternalIdentitySource.OAUTH2,\n ExternalIdentitySource.OIDC,\n ]),\n [SsoConnectionType.ENTERPRISE]: new Set([\n ExternalIdentitySource.OIDC,\n ExternalIdentitySource.SAML,\n ExternalIdentitySource.SCIM,\n ]),\n};\n\nexport const UserIdentityLinkSchema = z.object({\n _id: z.string().min(1).isPrimaryKey().isSummaryField(),\n\n /** FK → USERS — the user who owns this identity link */\n userId: z.string().min(1).isDBIndexed().isForeignKey().isSummaryField().excludeFromUpdate(),\n\n /** Distinguishes social vs enterprise links */\n connectionType: z.enum(SsoConnectionType).isDBIndexed().isSummaryField(),\n\n /** Matches external connector ref (e.g., \"google\", \"microsoft\", \"github\", \"oidc\") */\n providerRef: z.string().min(1).isDBIndexed().isSummaryField(),\n\n /** The user's ID at the IdP */\n providerUserId: z.string().min(1).isDBIndexed().dataCategories(PersonalDataCategory.IDENTITY),\n\n /** Email from the IdP */\n providerEmail: z.string().isDBIndexed().dataCategories(PersonalDataCategory.CONTACT_DETAILS),\n\n /** Display name from IdP */\n providerDisplayName: z.string().optional().dataCategories(PersonalDataCategory.IDENTITY),\n\n /** Avatar URL from IdP */\n providerAvatarUrl: z.string().optional().dataCategories(PersonalDataCategory.IDENTITY),\n\n /**\n * Raw claims from the IdP for debugging — backend only.\n *\n * The one field on this resource whose contents the framework does not control: an IdP may put\n * anything in a claim set, including a special category. IDENTITY + USER_CONTENT is what the field\n * is FOR; it is not a certification of what a given IdP will send. An application whose IdP is\n * known to emit a special-category claim should say so on its own copy.\n */\n providerRawProfile: z.record(z.string(), z.unknown()).optional().isBackendOnly()\n .dataCategories([PersonalDataCategory.IDENTITY, PersonalDataCategory.USER_CONTENT]),\n\n /** Enterprise tenant kind. Absent for social links. */\n scopeType: z.enum(AuthScopeType).optional().isDBIndexed(),\n\n /** Organization tenant identifier. Present only for organization-scoped enterprise links. */\n organizationId: z.string().optional().isForeignKey(),\n\n /** Application tenant identifier. Present only for application-scoped enterprise links. */\n applicationId: z.string().optional().isForeignKey(),\n\n /** Enterprise connection identifier within the owning scope. */\n connectionId: z.string().optional(),\n\n /** Authentication source that created this link. */\n source: z.enum(ExternalIdentitySource),\n\n /** When this identity link was created */\n linkedAt: z.date().default(() => new Date()).isDBIndexed().excludeFromCreate().excludeFromUpdate(),\n\n /** Last time this identity link was used for authentication */\n lastUsedAt: z.date().default(() => new Date()).isDBIndexed().excludeFromCreate().excludeFromUpdate(),\n}).businessKeySet([\n 'connectionType',\n 'providerRef',\n 'providerUserId',\n 'scopeType',\n 'organizationId',\n 'applicationId',\n 'connectionId',\n]).superRefine((link, ctx) => {\n if (!IDENTITY_SOURCES_BY_CONNECTION_TYPE[link.connectionType].has(link.source)) {\n ctx.addIssue({\n code: 'custom',\n path: ['source'],\n message: `Identity source ${link.source} is incompatible with ${link.connectionType} connections`,\n });\n }\n\n if (link.connectionType === SsoConnectionType.SOCIAL) {\n if (\n link.scopeType !== undefined\n || link.organizationId !== undefined\n || link.applicationId !== undefined\n || link.connectionId !== undefined\n ) {\n ctx.addIssue({\n code: 'custom',\n path: ['scopeType'],\n message: 'Social identity links must not carry enterprise scope fields',\n });\n }\n return;\n }\n\n if (!link.connectionId) {\n ctx.addIssue({\n code: 'custom',\n path: ['connectionId'],\n message: 'Enterprise identity links require connectionId',\n });\n }\n\n if (link.scopeType === AuthScopeType.ORGANIZATION) {\n if (!link.organizationId || link.applicationId !== undefined) {\n ctx.addIssue({\n code: 'custom',\n path: ['organizationId'],\n message: 'Organization-scoped identity links require only organizationId',\n });\n }\n return;\n }\n\n if (link.scopeType === AuthScopeType.APPLICATION) {\n if (!link.applicationId || link.organizationId !== undefined) {\n ctx.addIssue({\n code: 'custom',\n path: ['applicationId'],\n message: 'Application-scoped identity links require only applicationId',\n });\n }\n return;\n }\n\n ctx.addIssue({\n code: 'custom',\n path: ['scopeType'],\n message: 'Enterprise identity links require an explicit scopeType',\n });\n});\n\n\nexport type UserIdentityLink = z.infer<typeof UserIdentityLinkSchema>;\n"]}
|
|
1
|
+
{"version":3,"file":"user-identity-links.shared.schemas.js","sourceRoot":"","sources":["../../../../src/users/user-identity-links.shared.schemas.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * User Identity Links Schema\n *\n * Tracks OAuth2/SSO provider connections per user.\n * A user can have multiple identity links (e.g., Google social + Microsoft enterprise).\n *\n * Canonical identity key: connection type + provider + provider user + exact enterprise\n * scope/connection coordinates. The database compound unique index prevents the same\n * external identity from being bound to two Wildo users inside one tenant connection.\n */\n\nimport { z } from 'zod';\nimport { addImpersonalizeWith } from '@wildo-ai/zod-decorators';\nimport { RedactionType } from '../compliance/privacy/redaction.shared.schemas';\nimport { ERASED_USER_EMAIL, ERASED_IDENTITY_VALUE } from './users.shared.schemas';\nimport { PersonalDataCategory } from '../compliance/privacy/personal-data-category.shared.schemas';\nimport {\n AuthScopeType,\n ExternalIdentitySource,\n SsoConnectionType,\n} from '../security/authentications/authentication.shared.schemas';\n\n/**\n * Valid provenance mechanisms for each connection ownership class.\n *\n * OAuth2 is social-only; SAML and SCIM are enterprise-only; OIDC legitimately\n * spans both. Keeping this relationship beside the persisted link schema makes\n * invalid cross-products impossible at every repository boundary.\n */\nexport const IDENTITY_SOURCES_BY_CONNECTION_TYPE: Readonly<\n Record<SsoConnectionType, ReadonlySet<ExternalIdentitySource>>\n> = {\n [SsoConnectionType.SOCIAL]: new Set([\n ExternalIdentitySource.OAUTH2,\n ExternalIdentitySource.OIDC,\n ]),\n [SsoConnectionType.ENTERPRISE]: new Set([\n ExternalIdentitySource.OIDC,\n ExternalIdentitySource.SAML,\n ExternalIdentitySource.SCIM,\n ]),\n};\n\nexport const UserIdentityLinkSchema = z.object({\n _id: z.string().min(1).isPrimaryKey().isSummaryField(),\n\n /** FK → USERS — the user who owns this identity link */\n userId: z.string().min(1).isDBIndexed().isForeignKey().isSummaryField().excludeFromUpdate(),\n\n /** Distinguishes social vs enterprise links */\n connectionType: z.enum(SsoConnectionType).isDBIndexed().isSummaryField(),\n\n /** Matches external connector ref (e.g., \"google\", \"microsoft\", \"github\", \"oidc\") */\n providerRef: z.string().min(1).isDBIndexed().isSummaryField(),\n\n /** The user's ID at the IdP */\n // MASK, not REMOVE: `min(1)` and part of the compound business key, so a null fails the re-parse.\n // Two erased links for one connection would share this masked value — which is safe because a\n // declared retention policy makes the planner emit that unique index RETENTION-SCOPED, so retained\n // rows fall outside the constraint. Confirmed in the generated migration, not assumed.\n providerUserId: addImpersonalizeWith(\n z.string().min(1).isDBIndexed().dataCategories(PersonalDataCategory.IDENTITY),\n RedactionType.MASK,\n { maskValue: ERASED_IDENTITY_VALUE },\n ),\n\n /** Email from the IdP */\n providerEmail: addImpersonalizeWith(\n z.string().isDBIndexed().dataCategories(PersonalDataCategory.CONTACT_DETAILS),\n RedactionType.MASK,\n { maskValue: ERASED_USER_EMAIL },\n ),\n\n /** Display name from IdP */\n providerDisplayName: addImpersonalizeWith(\n z.string().optional().dataCategories(PersonalDataCategory.IDENTITY),\n RedactionType.REMOVE,\n ),\n\n /** Avatar URL from IdP */\n providerAvatarUrl: addImpersonalizeWith(\n z.string().optional().dataCategories(PersonalDataCategory.IDENTITY),\n RedactionType.REMOVE,\n ),\n\n /**\n * Raw claims from the IdP for debugging — backend only.\n *\n * The one field on this resource whose contents the framework does not control: an IdP may put\n * anything in a claim set, including a special category. IDENTITY + USER_CONTENT is what the field\n * is FOR; it is not a certification of what a given IdP will send. An application whose IdP is\n * known to emit a special-category claim should say so on its own copy.\n */\n // The provider's own payload about the person, unbounded in shape — whatever that provider chose\n // to send. It carries no `dataCategories` because nothing can classify it in advance, which is\n // exactly why it must be scrubbed: an unclassifiable blob is not an empty one.\n providerRawProfile: addImpersonalizeWith(\n z.record(z.string(), z.unknown()).optional().isBackendOnly(),\n RedactionType.REMOVE,\n )\n .dataCategories([PersonalDataCategory.IDENTITY, PersonalDataCategory.USER_CONTENT]),\n\n /** Enterprise tenant kind. Absent for social links. */\n scopeType: z.enum(AuthScopeType).optional().isDBIndexed(),\n\n /** Organization tenant identifier. Present only for organization-scoped enterprise links. */\n organizationId: z.string().optional().isForeignKey(),\n\n /** Application tenant identifier. Present only for application-scoped enterprise links. */\n applicationId: z.string().optional().isForeignKey(),\n\n /** Enterprise connection identifier within the owning scope. */\n connectionId: z.string().optional(),\n\n /** Authentication source that created this link. */\n source: z.enum(ExternalIdentitySource),\n\n /** When this identity link was created */\n linkedAt: z.date().default(() => new Date()).isDBIndexed().excludeFromCreate().excludeFromUpdate(),\n\n /** Last time this identity link was used for authentication */\n lastUsedAt: z.date().default(() => new Date()).isDBIndexed().excludeFromCreate().excludeFromUpdate(),\n}).businessKeySet([\n 'connectionType',\n 'providerRef',\n 'providerUserId',\n 'scopeType',\n 'organizationId',\n 'applicationId',\n 'connectionId',\n]).superRefine((link, ctx) => {\n if (!IDENTITY_SOURCES_BY_CONNECTION_TYPE[link.connectionType].has(link.source)) {\n ctx.addIssue({\n code: 'custom',\n path: ['source'],\n message: `Identity source ${link.source} is incompatible with ${link.connectionType} connections`,\n });\n }\n\n if (link.connectionType === SsoConnectionType.SOCIAL) {\n if (\n link.scopeType !== undefined\n || link.organizationId !== undefined\n || link.applicationId !== undefined\n || link.connectionId !== undefined\n ) {\n ctx.addIssue({\n code: 'custom',\n path: ['scopeType'],\n message: 'Social identity links must not carry enterprise scope fields',\n });\n }\n return;\n }\n\n if (!link.connectionId) {\n ctx.addIssue({\n code: 'custom',\n path: ['connectionId'],\n message: 'Enterprise identity links require connectionId',\n });\n }\n\n if (link.scopeType === AuthScopeType.ORGANIZATION) {\n if (!link.organizationId || link.applicationId !== undefined) {\n ctx.addIssue({\n code: 'custom',\n path: ['organizationId'],\n message: 'Organization-scoped identity links require only organizationId',\n });\n }\n return;\n }\n\n if (link.scopeType === AuthScopeType.APPLICATION) {\n if (!link.applicationId || link.organizationId !== undefined) {\n ctx.addIssue({\n code: 'custom',\n path: ['applicationId'],\n message: 'Application-scoped identity links require only applicationId',\n });\n }\n return;\n }\n\n ctx.addIssue({\n code: 'custom',\n path: ['scopeType'],\n message: 'Enterprise identity links require an explicit scopeType',\n });\n});\n\n\nexport type UserIdentityLink = z.infer<typeof UserIdentityLinkSchema>;\n"]}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { CoreResourceOperation } from '../resources/resources.shared.core.definitions';
|
|
8
8
|
import { ResourceRelationship } from '../resources/resources.shared.schemas';
|
|
9
|
-
type UserPreferencesCoreOperations = CoreResourceOperation.READ | CoreResourceOperation.UPDATE | CoreResourceOperation.CREATE | CoreResourceOperation.DELETE;
|
|
9
|
+
type UserPreferencesCoreOperations = CoreResourceOperation.READ | CoreResourceOperation.UPDATE | CoreResourceOperation.CREATE | CoreResourceOperation.LIST | CoreResourceOperation.UPDATE_MANY | CoreResourceOperation.DELETE;
|
|
10
10
|
export declare const userPreferencesResourceConfiguration_InitializationFactory: (resourcesRelationships: ResourceRelationship[]) => import("..").ResourceConfiguration<undefined, UserPreferencesCoreOperations, import("zod").ZodObject<{
|
|
11
11
|
_id: import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<import("zod").ZodString>>;
|
|
12
12
|
userId: import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<import("zod").ZodString>>>>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-preferences.shared.resources-config.schemas.d.ts","sourceRoot":"","sources":["../../../../src/users/user-preferences.shared.resources-config.schemas.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"user-preferences.shared.resources-config.schemas.d.ts","sourceRoot":"","sources":["../../../../src/users/user-preferences.shared.resources-config.schemas.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAAE,qBAAqB,EAAE,MAAM,gDAAgD,CAAC;AACvF,OAAO,EAA8B,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;AAWzG,KAAK,6BAA6B,GAC9B,qBAAqB,CAAC,IAAI,GAC1B,qBAAqB,CAAC,MAAM,GAC5B,qBAAqB,CAAC,MAAM,GAC5B,qBAAqB,CAAC,IAAI,GAC1B,qBAAqB,CAAC,WAAW,GACjC,qBAAqB,CAAC,MAAM,CAAC;AAKjC,eAAO,MAAM,0DAA0D,GAAI,wBAAwB,oBAAoB,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;iCAyJvH,CAAC"}
|