@portaidentity/server 1.7.0
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/CHANGELOG.md +11 -0
- package/LICENSE +21 -0
- package/README.md +33 -0
- package/dist/applications/cache.d.ts +64 -0
- package/dist/applications/cache.d.ts.map +1 -0
- package/dist/applications/cache.js +161 -0
- package/dist/applications/cache.js.map +1 -0
- package/dist/applications/errors.d.ts +25 -0
- package/dist/applications/errors.d.ts.map +1 -0
- package/dist/applications/errors.js +31 -0
- package/dist/applications/errors.js.map +1 -0
- package/dist/applications/index.d.ts +17 -0
- package/dist/applications/index.d.ts.map +1 -0
- package/dist/applications/index.js +19 -0
- package/dist/applications/index.js.map +1 -0
- package/dist/applications/repository.d.ts +184 -0
- package/dist/applications/repository.d.ts.map +1 -0
- package/dist/applications/repository.js +367 -0
- package/dist/applications/repository.js.map +1 -0
- package/dist/applications/service.d.ts +160 -0
- package/dist/applications/service.d.ts.map +1 -0
- package/dist/applications/service.js +388 -0
- package/dist/applications/service.js.map +1 -0
- package/dist/applications/slugs.d.ts +61 -0
- package/dist/applications/slugs.d.ts.map +1 -0
- package/dist/applications/slugs.js +115 -0
- package/dist/applications/slugs.js.map +1 -0
- package/dist/applications/types.d.ts +129 -0
- package/dist/applications/types.d.ts.map +1 -0
- package/dist/applications/types.js +62 -0
- package/dist/applications/types.js.map +1 -0
- package/dist/auth/csrf.d.ts +71 -0
- package/dist/auth/csrf.d.ts.map +1 -0
- package/dist/auth/csrf.js +112 -0
- package/dist/auth/csrf.js.map +1 -0
- package/dist/auth/email-renderer.d.ts +37 -0
- package/dist/auth/email-renderer.d.ts.map +1 -0
- package/dist/auth/email-renderer.js +116 -0
- package/dist/auth/email-renderer.js.map +1 -0
- package/dist/auth/email-service.d.ts +154 -0
- package/dist/auth/email-service.d.ts.map +1 -0
- package/dist/auth/email-service.js +415 -0
- package/dist/auth/email-service.js.map +1 -0
- package/dist/auth/email-transport.d.ts +65 -0
- package/dist/auth/email-transport.d.ts.map +1 -0
- package/dist/auth/email-transport.js +82 -0
- package/dist/auth/email-transport.js.map +1 -0
- package/dist/auth/i18n.d.ts +81 -0
- package/dist/auth/i18n.d.ts.map +1 -0
- package/dist/auth/i18n.js +395 -0
- package/dist/auth/i18n.js.map +1 -0
- package/dist/auth/index.d.ts +28 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +31 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/interaction-authority.d.ts +39 -0
- package/dist/auth/interaction-authority.d.ts.map +1 -0
- package/dist/auth/interaction-authority.js +30 -0
- package/dist/auth/interaction-authority.js.map +1 -0
- package/dist/auth/magic-link-session.d.ts +81 -0
- package/dist/auth/magic-link-session.d.ts.map +1 -0
- package/dist/auth/magic-link-session.js +183 -0
- package/dist/auth/magic-link-session.js.map +1 -0
- package/dist/auth/rate-limiter.d.ts +143 -0
- package/dist/auth/rate-limiter.d.ts.map +1 -0
- package/dist/auth/rate-limiter.js +220 -0
- package/dist/auth/rate-limiter.js.map +1 -0
- package/dist/auth/recovery-crypto.d.ts +34 -0
- package/dist/auth/recovery-crypto.d.ts.map +1 -0
- package/dist/auth/recovery-crypto.js +103 -0
- package/dist/auth/recovery-crypto.js.map +1 -0
- package/dist/auth/recovery-job-processor.d.ts +24 -0
- package/dist/auth/recovery-job-processor.d.ts.map +1 -0
- package/dist/auth/recovery-job-processor.js +123 -0
- package/dist/auth/recovery-job-processor.js.map +1 -0
- package/dist/auth/recovery-job-repository.d.ts +219 -0
- package/dist/auth/recovery-job-repository.d.ts.map +1 -0
- package/dist/auth/recovery-job-repository.js +340 -0
- package/dist/auth/recovery-job-repository.js.map +1 -0
- package/dist/auth/recovery-service.d.ts +27 -0
- package/dist/auth/recovery-service.d.ts.map +1 -0
- package/dist/auth/recovery-service.js +53 -0
- package/dist/auth/recovery-service.js.map +1 -0
- package/dist/auth/recovery-worker.d.ts +158 -0
- package/dist/auth/recovery-worker.d.ts.map +1 -0
- package/dist/auth/recovery-worker.js +306 -0
- package/dist/auth/recovery-worker.js.map +1 -0
- package/dist/auth/template-engine.d.ts +134 -0
- package/dist/auth/template-engine.d.ts.map +1 -0
- package/dist/auth/template-engine.js +225 -0
- package/dist/auth/template-engine.js.map +1 -0
- package/dist/auth/token-repository.d.ts +238 -0
- package/dist/auth/token-repository.d.ts.map +1 -0
- package/dist/auth/token-repository.js +475 -0
- package/dist/auth/token-repository.js.map +1 -0
- package/dist/auth/tokens.d.ts +45 -0
- package/dist/auth/tokens.d.ts.map +1 -0
- package/dist/auth/tokens.js +51 -0
- package/dist/auth/tokens.js.map +1 -0
- package/dist/cli/bootstrap.d.ts +55 -0
- package/dist/cli/bootstrap.d.ts.map +1 -0
- package/dist/cli/bootstrap.js +90 -0
- package/dist/cli/bootstrap.js.map +1 -0
- package/dist/cli/commands/health.d.ts +20 -0
- package/dist/cli/commands/health.d.ts.map +1 -0
- package/dist/cli/commands/health.js +80 -0
- package/dist/cli/commands/health.js.map +1 -0
- package/dist/cli/commands/init.d.ts +48 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +349 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/migrate.d.ts +19 -0
- package/dist/cli/commands/migrate.d.ts.map +1 -0
- package/dist/cli/commands/migrate.js +88 -0
- package/dist/cli/commands/migrate.js.map +1 -0
- package/dist/cli/commands/seed.d.ts +19 -0
- package/dist/cli/commands/seed.d.ts.map +1 -0
- package/dist/cli/commands/seed.js +71 -0
- package/dist/cli/commands/seed.js.map +1 -0
- package/dist/cli/commands/user-2fa.d.ts +29 -0
- package/dist/cli/commands/user-2fa.d.ts.map +1 -0
- package/dist/cli/commands/user-2fa.js +113 -0
- package/dist/cli/commands/user-2fa.js.map +1 -0
- package/dist/cli/error-handler.d.ts +31 -0
- package/dist/cli/error-handler.d.ts.map +1 -0
- package/dist/cli/error-handler.js +65 -0
- package/dist/cli/error-handler.js.map +1 -0
- package/dist/cli/index.d.ts +48 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +99 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/output.d.ts +94 -0
- package/dist/cli/output.d.ts.map +1 -0
- package/dist/cli/output.js +124 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/cli/prompt.d.ts +48 -0
- package/dist/cli/prompt.d.ts.map +1 -0
- package/dist/cli/prompt.js +96 -0
- package/dist/cli/prompt.js.map +1 -0
- package/dist/clients/cache.d.ts +63 -0
- package/dist/clients/cache.d.ts.map +1 -0
- package/dist/clients/cache.js +160 -0
- package/dist/clients/cache.js.map +1 -0
- package/dist/clients/crypto.d.ts +69 -0
- package/dist/clients/crypto.d.ts.map +1 -0
- package/dist/clients/crypto.js +83 -0
- package/dist/clients/crypto.js.map +1 -0
- package/dist/clients/errors.d.ts +27 -0
- package/dist/clients/errors.d.ts.map +1 -0
- package/dist/clients/errors.js +33 -0
- package/dist/clients/errors.js.map +1 -0
- package/dist/clients/index.d.ts +19 -0
- package/dist/clients/index.d.ts.map +1 -0
- package/dist/clients/index.js +23 -0
- package/dist/clients/index.js.map +1 -0
- package/dist/clients/repository.d.ts +161 -0
- package/dist/clients/repository.d.ts.map +1 -0
- package/dist/clients/repository.js +318 -0
- package/dist/clients/repository.js.map +1 -0
- package/dist/clients/resolve-login-methods.d.ts +74 -0
- package/dist/clients/resolve-login-methods.d.ts.map +1 -0
- package/dist/clients/resolve-login-methods.js +68 -0
- package/dist/clients/resolve-login-methods.js.map +1 -0
- package/dist/clients/secret-repository.d.ts +109 -0
- package/dist/clients/secret-repository.d.ts.map +1 -0
- package/dist/clients/secret-repository.js +166 -0
- package/dist/clients/secret-repository.js.map +1 -0
- package/dist/clients/secret-service.d.ts +87 -0
- package/dist/clients/secret-service.d.ts.map +1 -0
- package/dist/clients/secret-service.js +204 -0
- package/dist/clients/secret-service.js.map +1 -0
- package/dist/clients/service.d.ts +158 -0
- package/dist/clients/service.d.ts.map +1 -0
- package/dist/clients/service.js +523 -0
- package/dist/clients/service.js.map +1 -0
- package/dist/clients/types.d.ts +214 -0
- package/dist/clients/types.d.ts.map +1 -0
- package/dist/clients/types.js +77 -0
- package/dist/clients/types.js.map +1 -0
- package/dist/clients/validators.d.ts +87 -0
- package/dist/clients/validators.d.ts.map +1 -0
- package/dist/clients/validators.js +164 -0
- package/dist/clients/validators.js.map +1 -0
- package/dist/config/index.d.ts +23 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +52 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/schema.d.ts +35 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +167 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/custom-claims/cache.d.ts +48 -0
- package/dist/custom-claims/cache.d.ts.map +1 -0
- package/dist/custom-claims/cache.js +122 -0
- package/dist/custom-claims/cache.js.map +1 -0
- package/dist/custom-claims/errors.d.ts +25 -0
- package/dist/custom-claims/errors.d.ts.map +1 -0
- package/dist/custom-claims/errors.js +31 -0
- package/dist/custom-claims/errors.js.map +1 -0
- package/dist/custom-claims/index.d.ts +14 -0
- package/dist/custom-claims/index.d.ts.map +1 -0
- package/dist/custom-claims/index.js +17 -0
- package/dist/custom-claims/index.js.map +1 -0
- package/dist/custom-claims/repository.d.ts +147 -0
- package/dist/custom-claims/repository.d.ts.map +1 -0
- package/dist/custom-claims/repository.js +338 -0
- package/dist/custom-claims/repository.js.map +1 -0
- package/dist/custom-claims/service.d.ts +129 -0
- package/dist/custom-claims/service.d.ts.map +1 -0
- package/dist/custom-claims/service.js +296 -0
- package/dist/custom-claims/service.js.map +1 -0
- package/dist/custom-claims/types.d.ts +132 -0
- package/dist/custom-claims/types.d.ts.map +1 -0
- package/dist/custom-claims/types.js +61 -0
- package/dist/custom-claims/types.js.map +1 -0
- package/dist/custom-claims/validators.d.ts +61 -0
- package/dist/custom-claims/validators.d.ts.map +1 -0
- package/dist/custom-claims/validators.js +136 -0
- package/dist/custom-claims/validators.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +91 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/admin-permissions.d.ts +147 -0
- package/dist/lib/admin-permissions.d.ts.map +1 -0
- package/dist/lib/admin-permissions.js +261 -0
- package/dist/lib/admin-permissions.js.map +1 -0
- package/dist/lib/audit-log.d.ts +45 -0
- package/dist/lib/audit-log.d.ts.map +1 -0
- package/dist/lib/audit-log.js +79 -0
- package/dist/lib/audit-log.js.map +1 -0
- package/dist/lib/branding-assets.d.ts +47 -0
- package/dist/lib/branding-assets.d.ts.map +1 -0
- package/dist/lib/branding-assets.js +93 -0
- package/dist/lib/branding-assets.js.map +1 -0
- package/dist/lib/bulk-operations.d.ts +54 -0
- package/dist/lib/bulk-operations.d.ts.map +1 -0
- package/dist/lib/bulk-operations.js +201 -0
- package/dist/lib/bulk-operations.js.map +1 -0
- package/dist/lib/cursor.d.ts +110 -0
- package/dist/lib/cursor.d.ts.map +1 -0
- package/dist/lib/cursor.js +115 -0
- package/dist/lib/cursor.js.map +1 -0
- package/dist/lib/data-export.d.ts +67 -0
- package/dist/lib/data-export.d.ts.map +1 -0
- package/dist/lib/data-export.js +272 -0
- package/dist/lib/data-export.js.map +1 -0
- package/dist/lib/data-import-plan.d.ts +50 -0
- package/dist/lib/data-import-plan.d.ts.map +1 -0
- package/dist/lib/data-import-plan.js +235 -0
- package/dist/lib/data-import-plan.js.map +1 -0
- package/dist/lib/data-import.d.ts +276 -0
- package/dist/lib/data-import.d.ts.map +1 -0
- package/dist/lib/data-import.js +1694 -0
- package/dist/lib/data-import.js.map +1 -0
- package/dist/lib/database.d.ts +22 -0
- package/dist/lib/database.d.ts.map +1 -0
- package/dist/lib/database.js +103 -0
- package/dist/lib/database.js.map +1 -0
- package/dist/lib/entity-history.d.ts +52 -0
- package/dist/lib/entity-history.d.ts.map +1 -0
- package/dist/lib/entity-history.js +121 -0
- package/dist/lib/entity-history.js.map +1 -0
- package/dist/lib/etag.d.ts +92 -0
- package/dist/lib/etag.d.ts.map +1 -0
- package/dist/lib/etag.js +155 -0
- package/dist/lib/etag.js.map +1 -0
- package/dist/lib/image-validator.d.ts +44 -0
- package/dist/lib/image-validator.d.ts.map +1 -0
- package/dist/lib/image-validator.js +204 -0
- package/dist/lib/image-validator.js.map +1 -0
- package/dist/lib/logger.d.ts +15 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +84 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/migrator.d.ts +14 -0
- package/dist/lib/migrator.d.ts.map +1 -0
- package/dist/lib/migrator.js +34 -0
- package/dist/lib/migrator.js.map +1 -0
- package/dist/lib/redis.d.ts +5 -0
- package/dist/lib/redis.d.ts.map +1 -0
- package/dist/lib/redis.js +29 -0
- package/dist/lib/redis.js.map +1 -0
- package/dist/lib/runtime-paths.d.ts +38 -0
- package/dist/lib/runtime-paths.d.ts.map +1 -0
- package/dist/lib/runtime-paths.js +50 -0
- package/dist/lib/runtime-paths.js.map +1 -0
- package/dist/lib/session-tracking.d.ts +81 -0
- package/dist/lib/session-tracking.d.ts.map +1 -0
- package/dist/lib/session-tracking.js +145 -0
- package/dist/lib/session-tracking.js.map +1 -0
- package/dist/lib/signing-key-crypto.d.ts +53 -0
- package/dist/lib/signing-key-crypto.d.ts.map +1 -0
- package/dist/lib/signing-key-crypto.js +108 -0
- package/dist/lib/signing-key-crypto.js.map +1 -0
- package/dist/lib/signing-keys.d.ts +141 -0
- package/dist/lib/signing-keys.d.ts.map +1 -0
- package/dist/lib/signing-keys.js +206 -0
- package/dist/lib/signing-keys.js.map +1 -0
- package/dist/lib/stats.d.ts +83 -0
- package/dist/lib/stats.d.ts.map +1 -0
- package/dist/lib/stats.js +187 -0
- package/dist/lib/stats.js.map +1 -0
- package/dist/lib/super-admin-protection.d.ts +69 -0
- package/dist/lib/super-admin-protection.d.ts.map +1 -0
- package/dist/lib/super-admin-protection.js +102 -0
- package/dist/lib/super-admin-protection.js.map +1 -0
- package/dist/lib/system-config.d.ts +79 -0
- package/dist/lib/system-config.d.ts.map +1 -0
- package/dist/lib/system-config.js +159 -0
- package/dist/lib/system-config.js.map +1 -0
- package/dist/middleware/admin-auth.d.ts +100 -0
- package/dist/middleware/admin-auth.d.ts.map +1 -0
- package/dist/middleware/admin-auth.js +241 -0
- package/dist/middleware/admin-auth.js.map +1 -0
- package/dist/middleware/admin-cors.d.ts +41 -0
- package/dist/middleware/admin-cors.d.ts.map +1 -0
- package/dist/middleware/admin-cors.js +83 -0
- package/dist/middleware/admin-cors.js.map +1 -0
- package/dist/middleware/admin-mutation-audit.d.ts +9 -0
- package/dist/middleware/admin-mutation-audit.d.ts.map +1 -0
- package/dist/middleware/admin-mutation-audit.js +76 -0
- package/dist/middleware/admin-mutation-audit.js.map +1 -0
- package/dist/middleware/admin-rate-limiter.d.ts +56 -0
- package/dist/middleware/admin-rate-limiter.d.ts.map +1 -0
- package/dist/middleware/admin-rate-limiter.js +97 -0
- package/dist/middleware/admin-rate-limiter.js.map +1 -0
- package/dist/middleware/client-secret-hash.d.ts +34 -0
- package/dist/middleware/client-secret-hash.d.ts.map +1 -0
- package/dist/middleware/client-secret-hash.js +74 -0
- package/dist/middleware/client-secret-hash.js.map +1 -0
- package/dist/middleware/error-handler.d.ts +3 -0
- package/dist/middleware/error-handler.d.ts.map +1 -0
- package/dist/middleware/error-handler.js +93 -0
- package/dist/middleware/error-handler.js.map +1 -0
- package/dist/middleware/health.d.ts +3 -0
- package/dist/middleware/health.d.ts.map +1 -0
- package/dist/middleware/health.js +35 -0
- package/dist/middleware/health.js.map +1 -0
- package/dist/middleware/metrics.d.ts +40 -0
- package/dist/middleware/metrics.d.ts.map +1 -0
- package/dist/middleware/metrics.js +75 -0
- package/dist/middleware/metrics.js.map +1 -0
- package/dist/middleware/oidc-client-tenant.d.ts +37 -0
- package/dist/middleware/oidc-client-tenant.d.ts.map +1 -0
- package/dist/middleware/oidc-client-tenant.js +156 -0
- package/dist/middleware/oidc-client-tenant.js.map +1 -0
- package/dist/middleware/oidc-cors.d.ts +33 -0
- package/dist/middleware/oidc-cors.d.ts.map +1 -0
- package/dist/middleware/oidc-cors.js +68 -0
- package/dist/middleware/oidc-cors.js.map +1 -0
- package/dist/middleware/oidc-preflight-cors.d.ts +54 -0
- package/dist/middleware/oidc-preflight-cors.d.ts.map +1 -0
- package/dist/middleware/oidc-preflight-cors.js +267 -0
- package/dist/middleware/oidc-preflight-cors.js.map +1 -0
- package/dist/middleware/prompt-login-reset.d.ts +103 -0
- package/dist/middleware/prompt-login-reset.d.ts.map +1 -0
- package/dist/middleware/prompt-login-reset.js +204 -0
- package/dist/middleware/prompt-login-reset.js.map +1 -0
- package/dist/middleware/ready.d.ts +23 -0
- package/dist/middleware/ready.d.ts.map +1 -0
- package/dist/middleware/ready.js +60 -0
- package/dist/middleware/ready.js.map +1 -0
- package/dist/middleware/request-logger.d.ts +27 -0
- package/dist/middleware/request-logger.d.ts.map +1 -0
- package/dist/middleware/request-logger.js +46 -0
- package/dist/middleware/request-logger.js.map +1 -0
- package/dist/middleware/require-permission.d.ts +28 -0
- package/dist/middleware/require-permission.d.ts.map +1 -0
- package/dist/middleware/require-permission.js +63 -0
- package/dist/middleware/require-permission.js.map +1 -0
- package/dist/middleware/require-user-organization.d.ts +18 -0
- package/dist/middleware/require-user-organization.d.ts.map +1 -0
- package/dist/middleware/require-user-organization.js +53 -0
- package/dist/middleware/require-user-organization.js.map +1 -0
- package/dist/middleware/root-page.d.ts +62 -0
- package/dist/middleware/root-page.d.ts.map +1 -0
- package/dist/middleware/root-page.js +134 -0
- package/dist/middleware/root-page.js.map +1 -0
- package/dist/middleware/security-headers.d.ts +98 -0
- package/dist/middleware/security-headers.d.ts.map +1 -0
- package/dist/middleware/security-headers.js +159 -0
- package/dist/middleware/security-headers.js.map +1 -0
- package/dist/middleware/tenant-resolver.d.ts +37 -0
- package/dist/middleware/tenant-resolver.d.ts.map +1 -0
- package/dist/middleware/tenant-resolver.js +71 -0
- package/dist/middleware/tenant-resolver.js.map +1 -0
- package/dist/middleware/token-rate-limiter.d.ts +89 -0
- package/dist/middleware/token-rate-limiter.d.ts.map +1 -0
- package/dist/middleware/token-rate-limiter.js +189 -0
- package/dist/middleware/token-rate-limiter.js.map +1 -0
- package/dist/oidc/account-finder.d.ts +52 -0
- package/dist/oidc/account-finder.d.ts.map +1 -0
- package/dist/oidc/account-finder.js +134 -0
- package/dist/oidc/account-finder.js.map +1 -0
- package/dist/oidc/adapter-factory.d.ts +90 -0
- package/dist/oidc/adapter-factory.d.ts.map +1 -0
- package/dist/oidc/adapter-factory.js +165 -0
- package/dist/oidc/adapter-factory.js.map +1 -0
- package/dist/oidc/configuration.d.ts +155 -0
- package/dist/oidc/configuration.d.ts.map +1 -0
- package/dist/oidc/configuration.js +531 -0
- package/dist/oidc/configuration.js.map +1 -0
- package/dist/oidc/postgres-adapter.d.ts +159 -0
- package/dist/oidc/postgres-adapter.d.ts.map +1 -0
- package/dist/oidc/postgres-adapter.js +222 -0
- package/dist/oidc/postgres-adapter.js.map +1 -0
- package/dist/oidc/protocol-security-observer.d.ts +38 -0
- package/dist/oidc/protocol-security-observer.d.ts.map +1 -0
- package/dist/oidc/protocol-security-observer.js +93 -0
- package/dist/oidc/protocol-security-observer.js.map +1 -0
- package/dist/oidc/provider.d.ts +37 -0
- package/dist/oidc/provider.d.ts.map +1 -0
- package/dist/oidc/provider.js +71 -0
- package/dist/oidc/provider.js.map +1 -0
- package/dist/oidc/redis-adapter.d.ts +140 -0
- package/dist/oidc/redis-adapter.d.ts.map +1 -0
- package/dist/oidc/redis-adapter.js +344 -0
- package/dist/oidc/redis-adapter.js.map +1 -0
- package/dist/organizations/cache.d.ts +60 -0
- package/dist/organizations/cache.d.ts.map +1 -0
- package/dist/organizations/cache.js +157 -0
- package/dist/organizations/cache.js.map +1 -0
- package/dist/organizations/errors.d.ts +25 -0
- package/dist/organizations/errors.d.ts.map +1 -0
- package/dist/organizations/errors.js +31 -0
- package/dist/organizations/errors.js.map +1 -0
- package/dist/organizations/index.d.ts +15 -0
- package/dist/organizations/index.d.ts.map +1 -0
- package/dist/organizations/index.js +15 -0
- package/dist/organizations/index.js.map +1 -0
- package/dist/organizations/repository.d.ts +183 -0
- package/dist/organizations/repository.d.ts.map +1 -0
- package/dist/organizations/repository.js +354 -0
- package/dist/organizations/repository.js.map +1 -0
- package/dist/organizations/service.d.ts +168 -0
- package/dist/organizations/service.d.ts.map +1 -0
- package/dist/organizations/service.js +511 -0
- package/dist/organizations/service.js.map +1 -0
- package/dist/organizations/slugs.d.ts +57 -0
- package/dist/organizations/slugs.d.ts.map +1 -0
- package/dist/organizations/slugs.js +130 -0
- package/dist/organizations/slugs.js.map +1 -0
- package/dist/organizations/types.d.ts +146 -0
- package/dist/organizations/types.d.ts.map +1 -0
- package/dist/organizations/types.js +49 -0
- package/dist/organizations/types.js.map +1 -0
- package/dist/rbac/cache.d.ts +110 -0
- package/dist/rbac/cache.d.ts.map +1 -0
- package/dist/rbac/cache.js +271 -0
- package/dist/rbac/cache.js.map +1 -0
- package/dist/rbac/errors.d.ts +34 -0
- package/dist/rbac/errors.d.ts.map +1 -0
- package/dist/rbac/errors.js +43 -0
- package/dist/rbac/errors.js.map +1 -0
- package/dist/rbac/index.d.ts +17 -0
- package/dist/rbac/index.d.ts.map +1 -0
- package/dist/rbac/index.js +21 -0
- package/dist/rbac/index.js.map +1 -0
- package/dist/rbac/mapping-repository.d.ts +121 -0
- package/dist/rbac/mapping-repository.d.ts.map +1 -0
- package/dist/rbac/mapping-repository.js +224 -0
- package/dist/rbac/mapping-repository.js.map +1 -0
- package/dist/rbac/permission-repository.d.ts +100 -0
- package/dist/rbac/permission-repository.d.ts.map +1 -0
- package/dist/rbac/permission-repository.js +190 -0
- package/dist/rbac/permission-repository.js.map +1 -0
- package/dist/rbac/permission-service.d.ts +85 -0
- package/dist/rbac/permission-service.d.ts.map +1 -0
- package/dist/rbac/permission-service.js +179 -0
- package/dist/rbac/permission-service.js.map +1 -0
- package/dist/rbac/role-repository.d.ts +104 -0
- package/dist/rbac/role-repository.d.ts.map +1 -0
- package/dist/rbac/role-repository.js +202 -0
- package/dist/rbac/role-repository.js.map +1 -0
- package/dist/rbac/role-service.d.ts +111 -0
- package/dist/rbac/role-service.d.ts.map +1 -0
- package/dist/rbac/role-service.js +256 -0
- package/dist/rbac/role-service.js.map +1 -0
- package/dist/rbac/slugs.d.ts +116 -0
- package/dist/rbac/slugs.d.ts.map +1 -0
- package/dist/rbac/slugs.js +178 -0
- package/dist/rbac/slugs.js.map +1 -0
- package/dist/rbac/types.d.ts +187 -0
- package/dist/rbac/types.d.ts.map +1 -0
- package/dist/rbac/types.js +75 -0
- package/dist/rbac/types.js.map +1 -0
- package/dist/rbac/user-role-service.d.ts +101 -0
- package/dist/rbac/user-role-service.d.ts.map +1 -0
- package/dist/rbac/user-role-service.js +166 -0
- package/dist/rbac/user-role-service.js.map +1 -0
- package/dist/routes/applications.d.ts +40 -0
- package/dist/routes/applications.d.ts.map +1 -0
- package/dist/routes/applications.js +284 -0
- package/dist/routes/applications.js.map +1 -0
- package/dist/routes/audit.d.ts +31 -0
- package/dist/routes/audit.d.ts.map +1 -0
- package/dist/routes/audit.js +140 -0
- package/dist/routes/audit.js.map +1 -0
- package/dist/routes/branding.d.ts +17 -0
- package/dist/routes/branding.d.ts.map +1 -0
- package/dist/routes/branding.js +104 -0
- package/dist/routes/branding.js.map +1 -0
- package/dist/routes/bulk.d.ts +37 -0
- package/dist/routes/bulk.d.ts.map +1 -0
- package/dist/routes/bulk.js +94 -0
- package/dist/routes/bulk.js.map +1 -0
- package/dist/routes/clients.d.ts +44 -0
- package/dist/routes/clients.d.ts.map +1 -0
- package/dist/routes/clients.js +353 -0
- package/dist/routes/clients.js.map +1 -0
- package/dist/routes/config.d.ts +35 -0
- package/dist/routes/config.d.ts.map +1 -0
- package/dist/routes/config.js +115 -0
- package/dist/routes/config.js.map +1 -0
- package/dist/routes/custom-claims.d.ts +38 -0
- package/dist/routes/custom-claims.d.ts.map +1 -0
- package/dist/routes/custom-claims.js +208 -0
- package/dist/routes/custom-claims.js.map +1 -0
- package/dist/routes/exports.d.ts +21 -0
- package/dist/routes/exports.d.ts.map +1 -0
- package/dist/routes/exports.js +98 -0
- package/dist/routes/exports.js.map +1 -0
- package/dist/routes/imports.d.ts +18 -0
- package/dist/routes/imports.d.ts.map +1 -0
- package/dist/routes/imports.js +72 -0
- package/dist/routes/imports.js.map +1 -0
- package/dist/routes/interactions.d.ts +111 -0
- package/dist/routes/interactions.d.ts.map +1 -0
- package/dist/routes/interactions.js +1074 -0
- package/dist/routes/interactions.js.map +1 -0
- package/dist/routes/invitation.d.ts +31 -0
- package/dist/routes/invitation.d.ts.map +1 -0
- package/dist/routes/invitation.js +342 -0
- package/dist/routes/invitation.js.map +1 -0
- package/dist/routes/keys.d.ts +28 -0
- package/dist/routes/keys.d.ts.map +1 -0
- package/dist/routes/keys.js +91 -0
- package/dist/routes/keys.js.map +1 -0
- package/dist/routes/magic-link.d.ts +43 -0
- package/dist/routes/magic-link.d.ts.map +1 -0
- package/dist/routes/magic-link.js +264 -0
- package/dist/routes/magic-link.js.map +1 -0
- package/dist/routes/organizations.d.ts +38 -0
- package/dist/routes/organizations.d.ts.map +1 -0
- package/dist/routes/organizations.js +347 -0
- package/dist/routes/organizations.js.map +1 -0
- package/dist/routes/password-reset.d.ts +65 -0
- package/dist/routes/password-reset.d.ts.map +1 -0
- package/dist/routes/password-reset.js +493 -0
- package/dist/routes/password-reset.js.map +1 -0
- package/dist/routes/permissions.d.ts +33 -0
- package/dist/routes/permissions.d.ts.map +1 -0
- package/dist/routes/permissions.js +166 -0
- package/dist/routes/permissions.js.map +1 -0
- package/dist/routes/roles.d.ts +37 -0
- package/dist/routes/roles.d.ts.map +1 -0
- package/dist/routes/roles.js +222 -0
- package/dist/routes/roles.js.map +1 -0
- package/dist/routes/sessions.d.ts +37 -0
- package/dist/routes/sessions.d.ts.map +1 -0
- package/dist/routes/sessions.js +121 -0
- package/dist/routes/sessions.js.map +1 -0
- package/dist/routes/stats.d.ts +28 -0
- package/dist/routes/stats.d.ts.map +1 -0
- package/dist/routes/stats.js +65 -0
- package/dist/routes/stats.js.map +1 -0
- package/dist/routes/two-factor-admin.d.ts +54 -0
- package/dist/routes/two-factor-admin.d.ts.map +1 -0
- package/dist/routes/two-factor-admin.js +344 -0
- package/dist/routes/two-factor-admin.js.map +1 -0
- package/dist/routes/two-factor.d.ts +31 -0
- package/dist/routes/two-factor.d.ts.map +1 -0
- package/dist/routes/two-factor.js +595 -0
- package/dist/routes/two-factor.js.map +1 -0
- package/dist/routes/user-roles.d.ts +33 -0
- package/dist/routes/user-roles.d.ts.map +1 -0
- package/dist/routes/user-roles.js +129 -0
- package/dist/routes/user-roles.js.map +1 -0
- package/dist/routes/users.d.ts +61 -0
- package/dist/routes/users.d.ts.map +1 -0
- package/dist/routes/users.js +868 -0
- package/dist/routes/users.js.map +1 -0
- package/dist/security/decision-context.d.ts +52 -0
- package/dist/security/decision-context.d.ts.map +1 -0
- package/dist/security/decision-context.js +150 -0
- package/dist/security/decision-context.js.map +1 -0
- package/dist/security/decision-event.d.ts +128 -0
- package/dist/security/decision-event.d.ts.map +1 -0
- package/dist/security/decision-event.js +225 -0
- package/dist/security/decision-event.js.map +1 -0
- package/dist/security/transport-decision.d.ts +6 -0
- package/dist/security/transport-decision.d.ts.map +1 -0
- package/dist/security/transport-decision.js +37 -0
- package/dist/security/transport-decision.js.map +1 -0
- package/dist/server.d.ts +50 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +491 -0
- package/dist/server.js.map +1 -0
- package/dist/two-factor/cache.d.ts +47 -0
- package/dist/two-factor/cache.d.ts.map +1 -0
- package/dist/two-factor/cache.js +122 -0
- package/dist/two-factor/cache.js.map +1 -0
- package/dist/two-factor/crypto.d.ts +41 -0
- package/dist/two-factor/crypto.d.ts.map +1 -0
- package/dist/two-factor/crypto.js +94 -0
- package/dist/two-factor/crypto.js.map +1 -0
- package/dist/two-factor/errors.d.ts +82 -0
- package/dist/two-factor/errors.d.ts.map +1 -0
- package/dist/two-factor/errors.js +112 -0
- package/dist/two-factor/errors.js.map +1 -0
- package/dist/two-factor/index.d.ts +18 -0
- package/dist/two-factor/index.d.ts.map +1 -0
- package/dist/two-factor/index.js +25 -0
- package/dist/two-factor/index.js.map +1 -0
- package/dist/two-factor/otp.d.ts +43 -0
- package/dist/two-factor/otp.d.ts.map +1 -0
- package/dist/two-factor/otp.js +57 -0
- package/dist/two-factor/otp.js.map +1 -0
- package/dist/two-factor/recovery.d.ts +45 -0
- package/dist/two-factor/recovery.d.ts.map +1 -0
- package/dist/two-factor/recovery.js +104 -0
- package/dist/two-factor/recovery.js.map +1 -0
- package/dist/two-factor/repository.d.ts +156 -0
- package/dist/two-factor/repository.d.ts.map +1 -0
- package/dist/two-factor/repository.js +251 -0
- package/dist/two-factor/repository.js.map +1 -0
- package/dist/two-factor/service.d.ts +232 -0
- package/dist/two-factor/service.d.ts.map +1 -0
- package/dist/two-factor/service.js +599 -0
- package/dist/two-factor/service.js.map +1 -0
- package/dist/two-factor/totp.d.ts +57 -0
- package/dist/two-factor/totp.d.ts.map +1 -0
- package/dist/two-factor/totp.js +96 -0
- package/dist/two-factor/totp.js.map +1 -0
- package/dist/two-factor/types.d.ts +152 -0
- package/dist/two-factor/types.d.ts.map +1 -0
- package/dist/two-factor/types.js +64 -0
- package/dist/two-factor/types.js.map +1 -0
- package/dist/users/cache.d.ts +49 -0
- package/dist/users/cache.d.ts.map +1 -0
- package/dist/users/cache.js +140 -0
- package/dist/users/cache.js.map +1 -0
- package/dist/users/claims.d.ts +67 -0
- package/dist/users/claims.d.ts.map +1 -0
- package/dist/users/claims.js +104 -0
- package/dist/users/claims.js.map +1 -0
- package/dist/users/errors.d.ts +25 -0
- package/dist/users/errors.d.ts.map +1 -0
- package/dist/users/errors.js +31 -0
- package/dist/users/errors.js.map +1 -0
- package/dist/users/gdpr.d.ts +110 -0
- package/dist/users/gdpr.d.ts.map +1 -0
- package/dist/users/gdpr.js +211 -0
- package/dist/users/gdpr.js.map +1 -0
- package/dist/users/index.d.ts +20 -0
- package/dist/users/index.d.ts.map +1 -0
- package/dist/users/index.js +19 -0
- package/dist/users/index.js.map +1 -0
- package/dist/users/password.d.ts +85 -0
- package/dist/users/password.d.ts.map +1 -0
- package/dist/users/password.js +138 -0
- package/dist/users/password.js.map +1 -0
- package/dist/users/repository.d.ts +263 -0
- package/dist/users/repository.d.ts.map +1 -0
- package/dist/users/repository.js +499 -0
- package/dist/users/repository.js.map +1 -0
- package/dist/users/service.d.ts +299 -0
- package/dist/users/service.d.ts.map +1 -0
- package/dist/users/service.js +698 -0
- package/dist/users/service.js.map +1 -0
- package/dist/users/types.d.ts +220 -0
- package/dist/users/types.d.ts.map +1 -0
- package/dist/users/types.js +77 -0
- package/dist/users/types.js.map +1 -0
- package/locales/default/en/common.json +10 -0
- package/locales/default/en/consent.json +13 -0
- package/locales/default/en/emails.json +17 -0
- package/locales/default/en/errors.json +21 -0
- package/locales/default/en/forgot-password.json +11 -0
- package/locales/default/en/invitation.json +18 -0
- package/locales/default/en/login.json +17 -0
- package/locales/default/en/logout.json +9 -0
- package/locales/default/en/magic-link.json +12 -0
- package/locales/default/en/reset-password.json +16 -0
- package/locales/default/en/two-factor.json +29 -0
- package/migrations/.gitkeep +0 -0
- package/migrations/001_extensions.sql +22 -0
- package/migrations/002_organizations.sql +43 -0
- package/migrations/003_applications.sql +50 -0
- package/migrations/004_clients.sql +74 -0
- package/migrations/005_users.sql +119 -0
- package/migrations/006_roles_permissions.sql +80 -0
- package/migrations/007_custom_claims.sql +55 -0
- package/migrations/008_config.sql +51 -0
- package/migrations/009_audit_log.sql +31 -0
- package/migrations/010_oidc_adapter.sql +34 -0
- package/migrations/011_seed.sql +53 -0
- package/migrations/012_two_factor.sql +88 -0
- package/migrations/013_client_secret_sha256.sql +22 -0
- package/migrations/014_login_methods.sql +34 -0
- package/migrations/015_signing_key_encryption.sql +20 -0
- package/migrations/016_failed_login_count.sql +27 -0
- package/migrations/017_audit_retention.sql +11 -0
- package/migrations/018_admin_api_enhancements.sql +78 -0
- package/migrations/019_invitation_details.sql +27 -0
- package/migrations/020_two_factor_admin_permission.sql +108 -0
- package/migrations/021_auth_recovery_jobs.sql +74 -0
- package/migrations/022_recovery_artifact_ownership.sql +25 -0
- package/migrations/023_magic_link_authority.sql +39 -0
- package/package.json +106 -0
- package/templates/default/emails/invitation.hbs +39 -0
- package/templates/default/emails/invitation.txt.hbs +18 -0
- package/templates/default/emails/magic-link.hbs +30 -0
- package/templates/default/emails/magic-link.txt.hbs +11 -0
- package/templates/default/emails/otp-code.hbs +31 -0
- package/templates/default/emails/otp-code.txt.hbs +15 -0
- package/templates/default/emails/password-changed.hbs +24 -0
- package/templates/default/emails/password-changed.txt.hbs +7 -0
- package/templates/default/emails/password-reset.hbs +30 -0
- package/templates/default/emails/password-reset.txt.hbs +11 -0
- package/templates/default/emails/welcome.hbs +25 -0
- package/templates/default/emails/welcome.txt.hbs +9 -0
- package/templates/default/layouts/main.hbs +49 -0
- package/templates/default/pages/accept-invite.hbs +25 -0
- package/templates/default/pages/consent.hbs +27 -0
- package/templates/default/pages/error.hbs +15 -0
- package/templates/default/pages/forgot-password.hbs +21 -0
- package/templates/default/pages/invite-expired.hbs +6 -0
- package/templates/default/pages/invite-success.hbs +9 -0
- package/templates/default/pages/login.hbs +108 -0
- package/templates/default/pages/logout-success.hbs +5 -0
- package/templates/default/pages/logout.hbs +24 -0
- package/templates/default/pages/magic-link-sent.hbs +10 -0
- package/templates/default/pages/magic-link-success.hbs +17 -0
- package/templates/default/pages/reset-password.hbs +20 -0
- package/templates/default/pages/reset-success.hbs +9 -0
- package/templates/default/pages/two-factor-setup.hbs +65 -0
- package/templates/default/pages/two-factor-verify.hbs +77 -0
- package/templates/default/partials/flash-messages.hbs +10 -0
- package/templates/default/partials/footer.hbs +4 -0
- package/templates/default/partials/header.hbs +8 -0
|
@@ -0,0 +1,698 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User service — business logic orchestrator.
|
|
3
|
+
*
|
|
4
|
+
* Composes the repository, cache, password utilities, and audit log
|
|
5
|
+
* to provide the complete user management API. All write operations
|
|
6
|
+
* follow the pattern:
|
|
7
|
+
* 1. Validate inputs
|
|
8
|
+
* 2. Perform DB operation (via repository)
|
|
9
|
+
* 3. Invalidate + re-cache (via cache)
|
|
10
|
+
* 4. Write audit log (fire-and-forget)
|
|
11
|
+
*
|
|
12
|
+
* Read operations check cache first (by ID), fall back to DB on miss.
|
|
13
|
+
* Email lookups always hit the DB (not cached — see cache design decision).
|
|
14
|
+
*
|
|
15
|
+
* Status lifecycle rules:
|
|
16
|
+
* - deactivate: active → inactive
|
|
17
|
+
* - reactivate: inactive → active
|
|
18
|
+
* - suspend: active → suspended
|
|
19
|
+
* - unsuspend: suspended → active
|
|
20
|
+
* - lock: active → locked (with reason and timestamp)
|
|
21
|
+
* - unlock: locked → active (clears reason and timestamp)
|
|
22
|
+
*/
|
|
23
|
+
import { writeAuditLog } from '../lib/audit-log.js';
|
|
24
|
+
import { getSystemConfigNumber } from '../lib/system-config.js';
|
|
25
|
+
import { cacheUser, getCachedUserById, invalidateUserCache } from './cache.js';
|
|
26
|
+
import { UserNotFoundError, UserValidationError } from './errors.js';
|
|
27
|
+
import { ensureDummyPasswordHash, hashPassword, validatePassword, verifyPassword, } from './password.js';
|
|
28
|
+
import { emailExists, getPasswordHash, incrementFailedLoginCount, insertUser, findUserByEmail as repoFindByEmail, findUserById as repoFindById, listUsers as repoList, listUsersCursor as repoListCursor, updateUser as repoUpdate, resetFailedLoginCount, recordEligiblePasswordFailure, unlockEligiblePasswordAccount, updateLoginStats, } from './repository.js';
|
|
29
|
+
/** Fixed UUID that can never identify a persisted account. */
|
|
30
|
+
const NON_ACCOUNT_USER_ID = '00000000-0000-0000-0000-000000000000';
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Create
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
/**
|
|
35
|
+
* Create a new user in an organization.
|
|
36
|
+
*
|
|
37
|
+
* 1. Validates email uniqueness within the organization
|
|
38
|
+
* 2. Validates and hashes password if provided
|
|
39
|
+
* 3. Inserts user via repository
|
|
40
|
+
* 4. Caches the new user
|
|
41
|
+
* 5. Writes audit log entry
|
|
42
|
+
*
|
|
43
|
+
* @param input - User creation data (password is plaintext, will be hashed)
|
|
44
|
+
* @param actorId - UUID of the user performing the action (for audit)
|
|
45
|
+
* @returns Created user
|
|
46
|
+
* @throws UserValidationError if email exists or password invalid
|
|
47
|
+
*/
|
|
48
|
+
export async function createUser(input, actorId) {
|
|
49
|
+
// Check for duplicate email in the organization
|
|
50
|
+
const exists = await emailExists(input.organizationId, input.email);
|
|
51
|
+
if (exists) {
|
|
52
|
+
throw new UserValidationError('Email already exists in this organization');
|
|
53
|
+
}
|
|
54
|
+
// Hash password if provided
|
|
55
|
+
let passwordHash = null;
|
|
56
|
+
if (input.password) {
|
|
57
|
+
const validation = validatePassword(input.password);
|
|
58
|
+
if (!validation.isValid) {
|
|
59
|
+
throw new UserValidationError(validation.error);
|
|
60
|
+
}
|
|
61
|
+
passwordHash = await hashPassword(input.password);
|
|
62
|
+
}
|
|
63
|
+
// Insert into database
|
|
64
|
+
const user = await insertUser({
|
|
65
|
+
organizationId: input.organizationId,
|
|
66
|
+
email: input.email,
|
|
67
|
+
passwordHash,
|
|
68
|
+
givenName: input.givenName,
|
|
69
|
+
familyName: input.familyName,
|
|
70
|
+
middleName: input.middleName,
|
|
71
|
+
nickname: input.nickname,
|
|
72
|
+
preferredUsername: input.preferredUsername,
|
|
73
|
+
profileUrl: input.profileUrl,
|
|
74
|
+
pictureUrl: input.pictureUrl,
|
|
75
|
+
websiteUrl: input.websiteUrl,
|
|
76
|
+
gender: input.gender,
|
|
77
|
+
birthdate: input.birthdate,
|
|
78
|
+
zoneinfo: input.zoneinfo,
|
|
79
|
+
locale: input.locale,
|
|
80
|
+
phoneNumber: input.phoneNumber,
|
|
81
|
+
addressStreet: input.address?.street,
|
|
82
|
+
addressLocality: input.address?.locality,
|
|
83
|
+
addressRegion: input.address?.region,
|
|
84
|
+
addressPostalCode: input.address?.postalCode,
|
|
85
|
+
addressCountry: input.address?.country,
|
|
86
|
+
});
|
|
87
|
+
// Cache the new user
|
|
88
|
+
await cacheUser(user);
|
|
89
|
+
// Audit log (fire-and-forget)
|
|
90
|
+
await writeAuditLog({
|
|
91
|
+
organizationId: user.organizationId,
|
|
92
|
+
actorId,
|
|
93
|
+
eventType: 'user.created',
|
|
94
|
+
eventCategory: 'admin',
|
|
95
|
+
metadata: { userId: user.id, email: user.email },
|
|
96
|
+
});
|
|
97
|
+
return user;
|
|
98
|
+
}
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
// Read
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
/**
|
|
103
|
+
* Find a user by ID.
|
|
104
|
+
* Checks Redis cache first, falls back to database on miss.
|
|
105
|
+
*
|
|
106
|
+
* @param id - User UUID
|
|
107
|
+
* @returns User or null if not found
|
|
108
|
+
*/
|
|
109
|
+
export async function getUserById(id) {
|
|
110
|
+
// Try cache first
|
|
111
|
+
const cached = await getCachedUserById(id);
|
|
112
|
+
if (cached)
|
|
113
|
+
return cached;
|
|
114
|
+
// Fall back to database
|
|
115
|
+
const user = await repoFindById(id);
|
|
116
|
+
if (user) {
|
|
117
|
+
await cacheUser(user);
|
|
118
|
+
}
|
|
119
|
+
return user;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Find a user by email within an organization.
|
|
123
|
+
* Always hits the database (email lookups are not cached).
|
|
124
|
+
*
|
|
125
|
+
* @param orgId - Organization UUID
|
|
126
|
+
* @param email - User email address (case-insensitive)
|
|
127
|
+
* @returns User or null if not found
|
|
128
|
+
*/
|
|
129
|
+
export async function getUserByEmail(orgId, email) {
|
|
130
|
+
return repoFindByEmail(orgId, email);
|
|
131
|
+
}
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
// Update
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
/**
|
|
136
|
+
* Update a user's profile fields.
|
|
137
|
+
*
|
|
138
|
+
* Builds an update data object from the input, checking for email
|
|
139
|
+
* uniqueness if the email is being changed. Address fields are
|
|
140
|
+
* flattened from the nested AddressInput to individual columns.
|
|
141
|
+
*
|
|
142
|
+
* @param id - User UUID
|
|
143
|
+
* @param input - Fields to update
|
|
144
|
+
* @param actorId - UUID of the user performing the action
|
|
145
|
+
* @returns Updated user
|
|
146
|
+
* @throws UserNotFoundError if user not found
|
|
147
|
+
* @throws UserValidationError if new email already exists in org
|
|
148
|
+
*/
|
|
149
|
+
export async function updateUser(id, input, actorId) {
|
|
150
|
+
// If email is being changed, verify uniqueness
|
|
151
|
+
if (input.email !== undefined) {
|
|
152
|
+
// We need the current user to know the org ID
|
|
153
|
+
const current = await repoFindById(id);
|
|
154
|
+
if (!current)
|
|
155
|
+
throw new UserNotFoundError(id);
|
|
156
|
+
const taken = await emailExists(current.organizationId, input.email, id);
|
|
157
|
+
if (taken) {
|
|
158
|
+
throw new UserValidationError('Email already exists in this organization');
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// Build update data from input
|
|
162
|
+
const updateData = {};
|
|
163
|
+
if (input.email !== undefined)
|
|
164
|
+
updateData.email = input.email;
|
|
165
|
+
if (input.emailVerified !== undefined)
|
|
166
|
+
updateData.emailVerified = input.emailVerified;
|
|
167
|
+
if (input.givenName !== undefined)
|
|
168
|
+
updateData.givenName = input.givenName;
|
|
169
|
+
if (input.familyName !== undefined)
|
|
170
|
+
updateData.familyName = input.familyName;
|
|
171
|
+
if (input.middleName !== undefined)
|
|
172
|
+
updateData.middleName = input.middleName;
|
|
173
|
+
if (input.nickname !== undefined)
|
|
174
|
+
updateData.nickname = input.nickname;
|
|
175
|
+
if (input.preferredUsername !== undefined)
|
|
176
|
+
updateData.preferredUsername = input.preferredUsername;
|
|
177
|
+
if (input.profileUrl !== undefined)
|
|
178
|
+
updateData.profileUrl = input.profileUrl;
|
|
179
|
+
if (input.pictureUrl !== undefined)
|
|
180
|
+
updateData.pictureUrl = input.pictureUrl;
|
|
181
|
+
if (input.websiteUrl !== undefined)
|
|
182
|
+
updateData.websiteUrl = input.websiteUrl;
|
|
183
|
+
if (input.gender !== undefined)
|
|
184
|
+
updateData.gender = input.gender;
|
|
185
|
+
if (input.birthdate !== undefined)
|
|
186
|
+
updateData.birthdate = input.birthdate;
|
|
187
|
+
if (input.zoneinfo !== undefined)
|
|
188
|
+
updateData.zoneinfo = input.zoneinfo;
|
|
189
|
+
if (input.locale !== undefined)
|
|
190
|
+
updateData.locale = input.locale;
|
|
191
|
+
if (input.phoneNumber !== undefined)
|
|
192
|
+
updateData.phoneNumber = input.phoneNumber;
|
|
193
|
+
if (input.phoneNumberVerified !== undefined)
|
|
194
|
+
updateData.phoneNumberVerified = input.phoneNumberVerified;
|
|
195
|
+
// Flatten address fields from nested input
|
|
196
|
+
if (input.address) {
|
|
197
|
+
if (input.address.street !== undefined)
|
|
198
|
+
updateData.addressStreet = input.address.street;
|
|
199
|
+
if (input.address.locality !== undefined)
|
|
200
|
+
updateData.addressLocality = input.address.locality;
|
|
201
|
+
if (input.address.region !== undefined)
|
|
202
|
+
updateData.addressRegion = input.address.region;
|
|
203
|
+
if (input.address.postalCode !== undefined)
|
|
204
|
+
updateData.addressPostalCode = input.address.postalCode;
|
|
205
|
+
if (input.address.country !== undefined)
|
|
206
|
+
updateData.addressCountry = input.address.country;
|
|
207
|
+
}
|
|
208
|
+
let user;
|
|
209
|
+
try {
|
|
210
|
+
user = await repoUpdate(id, updateData);
|
|
211
|
+
}
|
|
212
|
+
catch (err) {
|
|
213
|
+
if (err instanceof Error && err.message === 'User not found') {
|
|
214
|
+
throw new UserNotFoundError(id);
|
|
215
|
+
}
|
|
216
|
+
throw err;
|
|
217
|
+
}
|
|
218
|
+
// Invalidate old cache and re-cache the updated version
|
|
219
|
+
await invalidateUserCache(user.id);
|
|
220
|
+
await cacheUser(user);
|
|
221
|
+
// Audit log (fire-and-forget)
|
|
222
|
+
await writeAuditLog({
|
|
223
|
+
organizationId: user.organizationId,
|
|
224
|
+
actorId,
|
|
225
|
+
eventType: 'user.updated',
|
|
226
|
+
eventCategory: 'admin',
|
|
227
|
+
metadata: { userId: user.id, fields: Object.keys(updateData) },
|
|
228
|
+
});
|
|
229
|
+
return user;
|
|
230
|
+
}
|
|
231
|
+
// ---------------------------------------------------------------------------
|
|
232
|
+
// List
|
|
233
|
+
// ---------------------------------------------------------------------------
|
|
234
|
+
/**
|
|
235
|
+
* List users within an organization with pagination and filtering.
|
|
236
|
+
* Delegates directly to the repository.
|
|
237
|
+
*
|
|
238
|
+
* @param options - Pagination, filter, and sort options (orgId required)
|
|
239
|
+
* @returns Paginated result
|
|
240
|
+
*/
|
|
241
|
+
export async function listUsersByOrganization(options) {
|
|
242
|
+
return repoList(options);
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* List users with cursor-based keyset pagination.
|
|
246
|
+
* Supports all existing filters (org scope, status, search, sort).
|
|
247
|
+
*
|
|
248
|
+
* @param options - Cursor, limit, filter, and sort options (orgId required)
|
|
249
|
+
* @returns Cursor-paginated result with nextCursor/hasMore
|
|
250
|
+
*/
|
|
251
|
+
export async function listUsersCursor(options) {
|
|
252
|
+
return repoListCursor(options);
|
|
253
|
+
}
|
|
254
|
+
// ---------------------------------------------------------------------------
|
|
255
|
+
// Status lifecycle
|
|
256
|
+
// ---------------------------------------------------------------------------
|
|
257
|
+
/**
|
|
258
|
+
* Load a user and validate it exists before a status change.
|
|
259
|
+
* @throws UserNotFoundError if not found
|
|
260
|
+
*/
|
|
261
|
+
async function loadUserForStatusChange(id) {
|
|
262
|
+
const user = await repoFindById(id);
|
|
263
|
+
if (!user)
|
|
264
|
+
throw new UserNotFoundError(id);
|
|
265
|
+
return user;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Deactivate a user (active → inactive).
|
|
269
|
+
*
|
|
270
|
+
* @param id - User UUID
|
|
271
|
+
* @param actorId - UUID of the user performing the action
|
|
272
|
+
* @throws UserNotFoundError if not found
|
|
273
|
+
* @throws UserValidationError if not currently active
|
|
274
|
+
*/
|
|
275
|
+
export async function deactivateUser(id, actorId) {
|
|
276
|
+
const user = await loadUserForStatusChange(id);
|
|
277
|
+
if (user.status !== 'active') {
|
|
278
|
+
throw new UserValidationError(`Cannot deactivate user from status: ${user.status}`);
|
|
279
|
+
}
|
|
280
|
+
await repoUpdate(id, { status: 'inactive' });
|
|
281
|
+
await invalidateUserCache(id);
|
|
282
|
+
await writeAuditLog({
|
|
283
|
+
organizationId: user.organizationId,
|
|
284
|
+
actorId,
|
|
285
|
+
eventType: 'user.deactivated',
|
|
286
|
+
eventCategory: 'admin',
|
|
287
|
+
metadata: { userId: id },
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Reactivate a user (inactive → active).
|
|
292
|
+
*
|
|
293
|
+
* @param id - User UUID
|
|
294
|
+
* @param actorId - UUID of the user performing the action
|
|
295
|
+
* @throws UserNotFoundError if not found
|
|
296
|
+
* @throws UserValidationError if not currently inactive
|
|
297
|
+
*/
|
|
298
|
+
export async function reactivateUser(id, actorId) {
|
|
299
|
+
const user = await loadUserForStatusChange(id);
|
|
300
|
+
if (user.status !== 'inactive') {
|
|
301
|
+
throw new UserValidationError(`Cannot reactivate user from status: ${user.status}`);
|
|
302
|
+
}
|
|
303
|
+
await repoUpdate(id, { status: 'active' });
|
|
304
|
+
await invalidateUserCache(id);
|
|
305
|
+
await writeAuditLog({
|
|
306
|
+
organizationId: user.organizationId,
|
|
307
|
+
actorId,
|
|
308
|
+
eventType: 'user.reactivated',
|
|
309
|
+
eventCategory: 'admin',
|
|
310
|
+
metadata: { userId: id },
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Suspend a user (active → suspended).
|
|
315
|
+
*
|
|
316
|
+
* @param id - User UUID
|
|
317
|
+
* @param reason - Optional reason for suspension
|
|
318
|
+
* @param actorId - UUID of the user performing the action
|
|
319
|
+
* @throws UserNotFoundError if not found
|
|
320
|
+
* @throws UserValidationError if not currently active
|
|
321
|
+
*/
|
|
322
|
+
export async function suspendUser(id, reason, actorId) {
|
|
323
|
+
const user = await loadUserForStatusChange(id);
|
|
324
|
+
if (user.status !== 'active') {
|
|
325
|
+
throw new UserValidationError(`Cannot suspend user from status: ${user.status}`);
|
|
326
|
+
}
|
|
327
|
+
await repoUpdate(id, { status: 'suspended' });
|
|
328
|
+
await invalidateUserCache(id);
|
|
329
|
+
await writeAuditLog({
|
|
330
|
+
organizationId: user.organizationId,
|
|
331
|
+
actorId,
|
|
332
|
+
eventType: 'user.suspended',
|
|
333
|
+
eventCategory: 'admin',
|
|
334
|
+
metadata: { userId: id, reason: reason ?? null },
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Unsuspend a user (suspended → active).
|
|
339
|
+
*
|
|
340
|
+
* @param id - User UUID
|
|
341
|
+
* @param actorId - UUID of the user performing the action
|
|
342
|
+
* @throws UserNotFoundError if not found
|
|
343
|
+
* @throws UserValidationError if not currently suspended
|
|
344
|
+
*/
|
|
345
|
+
export async function unsuspendUser(id, actorId) {
|
|
346
|
+
const user = await loadUserForStatusChange(id);
|
|
347
|
+
if (user.status !== 'suspended') {
|
|
348
|
+
throw new UserValidationError(`Cannot unsuspend user from status: ${user.status}`);
|
|
349
|
+
}
|
|
350
|
+
await repoUpdate(id, { status: 'active' });
|
|
351
|
+
await invalidateUserCache(id);
|
|
352
|
+
await writeAuditLog({
|
|
353
|
+
organizationId: user.organizationId,
|
|
354
|
+
actorId,
|
|
355
|
+
eventType: 'user.activated',
|
|
356
|
+
eventCategory: 'admin',
|
|
357
|
+
metadata: { userId: id },
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Lock a user (active → locked).
|
|
362
|
+
*
|
|
363
|
+
* Sets the locked_at timestamp and locked_reason. Locked users
|
|
364
|
+
* cannot authenticate (getPasswordHash only returns for active users).
|
|
365
|
+
*
|
|
366
|
+
* @param id - User UUID
|
|
367
|
+
* @param reason - Reason for locking the account
|
|
368
|
+
* @param actorId - UUID of the user performing the action
|
|
369
|
+
* @throws UserNotFoundError if not found
|
|
370
|
+
* @throws UserValidationError if not currently active
|
|
371
|
+
*/
|
|
372
|
+
export async function lockUser(id, reason, actorId) {
|
|
373
|
+
const user = await loadUserForStatusChange(id);
|
|
374
|
+
if (user.status !== 'active') {
|
|
375
|
+
throw new UserValidationError(`Cannot lock user from status: ${user.status}`);
|
|
376
|
+
}
|
|
377
|
+
await repoUpdate(id, {
|
|
378
|
+
status: 'locked',
|
|
379
|
+
lockedAt: new Date(),
|
|
380
|
+
lockedReason: reason,
|
|
381
|
+
});
|
|
382
|
+
await invalidateUserCache(id);
|
|
383
|
+
await writeAuditLog({
|
|
384
|
+
organizationId: user.organizationId,
|
|
385
|
+
actorId,
|
|
386
|
+
eventType: 'user.locked',
|
|
387
|
+
eventCategory: 'admin',
|
|
388
|
+
metadata: { userId: id, reason },
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Unlock a user (locked → active).
|
|
393
|
+
*
|
|
394
|
+
* Clears the locked_at timestamp and locked_reason.
|
|
395
|
+
*
|
|
396
|
+
* @param id - User UUID
|
|
397
|
+
* @param actorId - UUID of the user performing the action
|
|
398
|
+
* @throws UserNotFoundError if not found
|
|
399
|
+
* @throws UserValidationError if not currently locked
|
|
400
|
+
*/
|
|
401
|
+
export async function unlockUser(id, actorId) {
|
|
402
|
+
const user = await loadUserForStatusChange(id);
|
|
403
|
+
if (user.status !== 'locked') {
|
|
404
|
+
throw new UserValidationError(`Cannot unlock user from status: ${user.status}`);
|
|
405
|
+
}
|
|
406
|
+
await repoUpdate(id, {
|
|
407
|
+
status: 'active',
|
|
408
|
+
lockedAt: null,
|
|
409
|
+
lockedReason: null,
|
|
410
|
+
});
|
|
411
|
+
await invalidateUserCache(id);
|
|
412
|
+
await writeAuditLog({
|
|
413
|
+
organizationId: user.organizationId,
|
|
414
|
+
actorId,
|
|
415
|
+
eventType: 'user.unlocked',
|
|
416
|
+
eventCategory: 'admin',
|
|
417
|
+
metadata: { userId: id },
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
// ---------------------------------------------------------------------------
|
|
421
|
+
// Password management
|
|
422
|
+
// ---------------------------------------------------------------------------
|
|
423
|
+
/**
|
|
424
|
+
* Set a user's password.
|
|
425
|
+
*
|
|
426
|
+
* Validates the password, hashes it with Argon2id, and stores the hash.
|
|
427
|
+
* Also sets password_changed_at to the current timestamp.
|
|
428
|
+
*
|
|
429
|
+
* @param id - User UUID
|
|
430
|
+
* @param password - New plaintext password
|
|
431
|
+
* @param actorId - UUID of the user performing the action
|
|
432
|
+
* @throws UserValidationError if password fails validation
|
|
433
|
+
*/
|
|
434
|
+
export async function setUserPassword(id, password, actorId) {
|
|
435
|
+
const validation = validatePassword(password);
|
|
436
|
+
if (!validation.isValid) {
|
|
437
|
+
throw new UserValidationError(validation.error);
|
|
438
|
+
}
|
|
439
|
+
const hash = await hashPassword(password);
|
|
440
|
+
await repoUpdate(id, {
|
|
441
|
+
passwordHash: hash,
|
|
442
|
+
passwordChangedAt: new Date(),
|
|
443
|
+
});
|
|
444
|
+
await invalidateUserCache(id);
|
|
445
|
+
await writeAuditLog({
|
|
446
|
+
actorId,
|
|
447
|
+
eventType: 'user.password.set',
|
|
448
|
+
eventCategory: 'admin',
|
|
449
|
+
metadata: { userId: id },
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Verify a user's password.
|
|
454
|
+
*
|
|
455
|
+
* Returns false if the user has no password (passwordless), is not
|
|
456
|
+
* active, or if the password doesn't match. No audit log is written —
|
|
457
|
+
* login tracking is handled separately by recordLogin().
|
|
458
|
+
*
|
|
459
|
+
* @param id - User UUID
|
|
460
|
+
* @param password - Plaintext password to verify
|
|
461
|
+
* @returns true if the password matches
|
|
462
|
+
*/
|
|
463
|
+
export async function verifyUserPassword(id, password) {
|
|
464
|
+
const hash = await getPasswordHash(id);
|
|
465
|
+
if (!hash)
|
|
466
|
+
return false;
|
|
467
|
+
return verifyPassword(hash, password);
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Verify exactly one account or dummy Argon2id hash for an admitted login attempt.
|
|
471
|
+
*
|
|
472
|
+
* @param id - Eligible active account UUID, or null for every non-eligible identity state.
|
|
473
|
+
* @param password - Submitted plaintext password.
|
|
474
|
+
* @param dependencies - Production hash boundaries, replaceable by pass-through observers.
|
|
475
|
+
* @returns True only when an eligible account hash matched.
|
|
476
|
+
*/
|
|
477
|
+
export async function verifyLoginPassword(id, password, dependencies) {
|
|
478
|
+
const boundaries = dependencies ?? {
|
|
479
|
+
loadAccountHash: getPasswordHash,
|
|
480
|
+
loadDummyHash: ensureDummyPasswordHash,
|
|
481
|
+
verifyHash: verifyPassword,
|
|
482
|
+
};
|
|
483
|
+
const hash = await boundaries.loadAccountHash(id ?? NON_ACCOUNT_USER_ID);
|
|
484
|
+
const selectedHash = hash ?? (await boundaries.loadDummyHash());
|
|
485
|
+
const matched = await boundaries.verifyHash(selectedHash, password);
|
|
486
|
+
return id !== null && hash !== null && matched;
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Resolve the password-login account through a fixed sequence of database and cache operations.
|
|
490
|
+
*
|
|
491
|
+
* The initial lookup identifies a possible elapsed automatic lock. The conditional update and
|
|
492
|
+
* cache invalidation execute for every admitted identity, using a non-account UUID when absent.
|
|
493
|
+
* A second lookup then observes the post-cooldown state without an identity-dependent branch.
|
|
494
|
+
*
|
|
495
|
+
* @param organizationId - Resolved tenant authority.
|
|
496
|
+
* @param email - Normalized login address.
|
|
497
|
+
* @returns Current account state, or null when no account exists.
|
|
498
|
+
*/
|
|
499
|
+
export async function prepareUserForPasswordLogin(organizationId, email) {
|
|
500
|
+
const candidate = await getUserByEmail(organizationId, email);
|
|
501
|
+
const cooldownSeconds = await getSystemConfigNumber('lockout_duration_seconds', 900);
|
|
502
|
+
const candidateId = candidate?.id ?? NON_ACCOUNT_USER_ID;
|
|
503
|
+
const unlocked = await unlockEligiblePasswordAccount(candidateId, new Date(Date.now() - cooldownSeconds * 1_000));
|
|
504
|
+
await invalidateUserCache(candidateId);
|
|
505
|
+
if (unlocked && candidate) {
|
|
506
|
+
void writeAuditLog({
|
|
507
|
+
organizationId: candidate.organizationId,
|
|
508
|
+
userId: candidate.id,
|
|
509
|
+
eventType: 'user.auto_unlocked',
|
|
510
|
+
eventCategory: 'security',
|
|
511
|
+
description: 'Account auto-unlocked after configured cooldown',
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
return getUserByEmail(organizationId, email);
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Clear a user's password (convert to passwordless-only).
|
|
518
|
+
*
|
|
519
|
+
* Sets password_hash to NULL and clears password_changed_at.
|
|
520
|
+
*
|
|
521
|
+
* @param id - User UUID
|
|
522
|
+
* @param actorId - UUID of the user performing the action
|
|
523
|
+
*/
|
|
524
|
+
export async function clearUserPassword(id, actorId) {
|
|
525
|
+
await repoUpdate(id, {
|
|
526
|
+
passwordHash: null,
|
|
527
|
+
passwordChangedAt: null,
|
|
528
|
+
});
|
|
529
|
+
await invalidateUserCache(id);
|
|
530
|
+
await writeAuditLog({
|
|
531
|
+
actorId,
|
|
532
|
+
eventType: 'user.password.cleared',
|
|
533
|
+
eventCategory: 'admin',
|
|
534
|
+
metadata: { userId: id },
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
// ---------------------------------------------------------------------------
|
|
538
|
+
// Email verification
|
|
539
|
+
// ---------------------------------------------------------------------------
|
|
540
|
+
/**
|
|
541
|
+
* Mark a user's email as verified.
|
|
542
|
+
*
|
|
543
|
+
* @param id - User UUID
|
|
544
|
+
* @param actorId - UUID of the user performing the action
|
|
545
|
+
*/
|
|
546
|
+
export async function markEmailVerified(id, actorId) {
|
|
547
|
+
await repoUpdate(id, { emailVerified: true });
|
|
548
|
+
await invalidateUserCache(id);
|
|
549
|
+
await writeAuditLog({
|
|
550
|
+
actorId,
|
|
551
|
+
eventType: 'user.email.verified',
|
|
552
|
+
eventCategory: 'admin',
|
|
553
|
+
metadata: { userId: id },
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Mark a user's email as unverified.
|
|
558
|
+
* Typically called when the email address changes.
|
|
559
|
+
*
|
|
560
|
+
* @param id - User UUID
|
|
561
|
+
*/
|
|
562
|
+
export async function markEmailUnverified(id) {
|
|
563
|
+
await repoUpdate(id, { emailVerified: false });
|
|
564
|
+
await invalidateUserCache(id);
|
|
565
|
+
}
|
|
566
|
+
// ---------------------------------------------------------------------------
|
|
567
|
+
// Login tracking
|
|
568
|
+
// ---------------------------------------------------------------------------
|
|
569
|
+
/**
|
|
570
|
+
* Record a successful login for a user.
|
|
571
|
+
*
|
|
572
|
+
* Increments login_count and sets last_login_at. Also resets
|
|
573
|
+
* failed_login_count and last_failed_login_at. Invalidates cache
|
|
574
|
+
* because the login stats have changed.
|
|
575
|
+
*
|
|
576
|
+
* @param id - User UUID
|
|
577
|
+
*/
|
|
578
|
+
export async function recordLogin(id) {
|
|
579
|
+
await updateLoginStats(id);
|
|
580
|
+
await invalidateUserCache(id);
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Record a failed login attempt and auto-lock if threshold exceeded.
|
|
584
|
+
*
|
|
585
|
+
* Uses an atomic SQL UPDATE to increment the counter and conditionally
|
|
586
|
+
* flip status to 'locked' with reason 'auto_lockout' in a single query.
|
|
587
|
+
* Returns whether the account was just locked so the caller can show
|
|
588
|
+
* an appropriate message and write an audit event.
|
|
589
|
+
*
|
|
590
|
+
* The threshold comes from system_config `max_failed_logins` (default 5).
|
|
591
|
+
*
|
|
592
|
+
* @param user - The user whose login failed
|
|
593
|
+
* @returns Object with `locked: true` if the account was just auto-locked
|
|
594
|
+
*/
|
|
595
|
+
export async function recordFailedLogin(user) {
|
|
596
|
+
const maxAttempts = await getSystemConfigNumber('max_failed_logins', 5);
|
|
597
|
+
const result = await incrementFailedLoginCount(user.id, maxAttempts);
|
|
598
|
+
await invalidateUserCache(user.id);
|
|
599
|
+
const justLocked = result.status === 'locked' && user.status === 'active';
|
|
600
|
+
if (justLocked) {
|
|
601
|
+
void writeAuditLog({
|
|
602
|
+
organizationId: user.organizationId,
|
|
603
|
+
userId: user.id,
|
|
604
|
+
eventType: 'user.auto_locked',
|
|
605
|
+
eventCategory: 'security',
|
|
606
|
+
description: `Account auto-locked after ${result.failedLoginCount} failed login attempts`,
|
|
607
|
+
metadata: { failedCount: result.failedLoginCount, threshold: maxAttempts },
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
return { locked: justLocked, failedCount: result.failedLoginCount };
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Execute one fixed-shape failure-accounting operation for an admitted password rejection.
|
|
614
|
+
*
|
|
615
|
+
* @param user - Eligible active account, or null for every other identity state.
|
|
616
|
+
* @param dependencies - Production persistence/cache boundaries, replaceable by pass-through observers.
|
|
617
|
+
* @returns Neutral or real account-lock result without disclosing identity publicly.
|
|
618
|
+
*/
|
|
619
|
+
export async function recordPasswordFailure(user, dependencies) {
|
|
620
|
+
const boundaries = dependencies ?? {
|
|
621
|
+
recordEligibleFailure: recordEligiblePasswordFailure,
|
|
622
|
+
invalidateCache: invalidateUserCache,
|
|
623
|
+
};
|
|
624
|
+
const maxAttempts = await getSystemConfigNumber('max_failed_logins', 5);
|
|
625
|
+
const userId = user?.id ?? NON_ACCOUNT_USER_ID;
|
|
626
|
+
const result = await boundaries.recordEligibleFailure(userId, maxAttempts);
|
|
627
|
+
await boundaries.invalidateCache(userId);
|
|
628
|
+
const locked = result.status === 'locked' && user !== null;
|
|
629
|
+
if (locked && user) {
|
|
630
|
+
void writeAuditLog({
|
|
631
|
+
organizationId: user.organizationId,
|
|
632
|
+
userId: user.id,
|
|
633
|
+
eventType: 'user.auto_locked',
|
|
634
|
+
eventCategory: 'security',
|
|
635
|
+
description: 'Account auto-locked after failed login threshold',
|
|
636
|
+
metadata: { failedCount: result.failedLoginCount, threshold: maxAttempts },
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
return { locked, failedCount: result.failedLoginCount };
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Check whether an auto-locked account should be unlocked (cooldown elapsed).
|
|
643
|
+
*
|
|
644
|
+
* Only applies to users with `status = 'locked'` and
|
|
645
|
+
* `lockedReason = 'auto_lockout'`. Manual locks are never auto-unlocked.
|
|
646
|
+
*
|
|
647
|
+
* The cooldown duration comes from system_config `lockout_duration_seconds`
|
|
648
|
+
* (default 900 = 15 minutes).
|
|
649
|
+
*
|
|
650
|
+
* @param user - User to check
|
|
651
|
+
* @returns `true` if the account was auto-unlocked
|
|
652
|
+
*/
|
|
653
|
+
export async function checkAutoUnlock(user) {
|
|
654
|
+
if (user.status !== 'locked' || user.lockedReason !== 'auto_lockout') {
|
|
655
|
+
return false;
|
|
656
|
+
}
|
|
657
|
+
if (!user.lockedAt)
|
|
658
|
+
return false;
|
|
659
|
+
const cooldownSeconds = await getSystemConfigNumber('lockout_duration_seconds', 900);
|
|
660
|
+
const elapsed = (Date.now() - user.lockedAt.getTime()) / 1000;
|
|
661
|
+
if (elapsed < cooldownSeconds) {
|
|
662
|
+
return false;
|
|
663
|
+
}
|
|
664
|
+
// Cooldown elapsed — unlock and reset counter
|
|
665
|
+
await resetFailedLoginCount(user.id);
|
|
666
|
+
await invalidateUserCache(user.id);
|
|
667
|
+
writeAuditLog({
|
|
668
|
+
organizationId: user.organizationId,
|
|
669
|
+
userId: user.id,
|
|
670
|
+
eventType: 'user.auto_unlocked',
|
|
671
|
+
eventCategory: 'security',
|
|
672
|
+
description: `Account auto-unlocked after ${cooldownSeconds}s cooldown`,
|
|
673
|
+
});
|
|
674
|
+
return true;
|
|
675
|
+
}
|
|
676
|
+
// ---------------------------------------------------------------------------
|
|
677
|
+
// OIDC integration
|
|
678
|
+
// ---------------------------------------------------------------------------
|
|
679
|
+
/**
|
|
680
|
+
* Find a user for OIDC token/userinfo operations.
|
|
681
|
+
*
|
|
682
|
+
* Returns the user only if their status is 'active'. Non-active users
|
|
683
|
+
* should not be able to obtain tokens or access the userinfo endpoint.
|
|
684
|
+
* Used by the upgraded account-finder.ts.
|
|
685
|
+
*
|
|
686
|
+
* @param sub - Subject identifier (user UUID)
|
|
687
|
+
* @returns Active user or null
|
|
688
|
+
*/
|
|
689
|
+
export async function findUserForOidc(sub) {
|
|
690
|
+
const user = await getUserById(sub);
|
|
691
|
+
if (!user)
|
|
692
|
+
return null;
|
|
693
|
+
// Only active users can interact with OIDC endpoints
|
|
694
|
+
if (user.status !== 'active')
|
|
695
|
+
return null;
|
|
696
|
+
return user;
|
|
697
|
+
}
|
|
698
|
+
//# sourceMappingURL=service.js.map
|