@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,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Magic link verification route handler.
|
|
3
|
+
*
|
|
4
|
+
* Handles the callback when a user clicks the magic link in their email.
|
|
5
|
+
* Verifies the token, marks the user's email as verified, records the login,
|
|
6
|
+
* and creates a short-lived `_ml_session` in Redis. Then redirects to the
|
|
7
|
+
* interaction login page where the session cookie is detected and either:
|
|
8
|
+
* - **Same browser**: OIDC flow completes seamlessly via interactionFinished()
|
|
9
|
+
* - **Different browser**: success page shown ("return to original browser")
|
|
10
|
+
*
|
|
11
|
+
* Route structure:
|
|
12
|
+
* GET /:orgSlug/auth/magic-link/:token → verifyMagicLink
|
|
13
|
+
*
|
|
14
|
+
* The tenant resolver middleware runs before this route, ensuring
|
|
15
|
+
* ctx.state.organization is populated with the resolved organization.
|
|
16
|
+
*
|
|
17
|
+
* Security features:
|
|
18
|
+
* - Token is hashed (SHA-256) before DB lookup — plaintext never stored
|
|
19
|
+
* - Token is single-use (marked as used after verification)
|
|
20
|
+
* - Expired/used tokens show a generic error page
|
|
21
|
+
* - `_ml_session` is HttpOnly, 5-min TTL, single-use (Redis-backed)
|
|
22
|
+
* - Audit logging for all magic link events
|
|
23
|
+
*/
|
|
24
|
+
import Router from '@koa/router';
|
|
25
|
+
import { tenantResolver } from '../middleware/tenant-resolver.js';
|
|
26
|
+
import { hashToken } from '../auth/tokens.js';
|
|
27
|
+
import { consumeAuthorizedMagicLink } from '../auth/token-repository.js';
|
|
28
|
+
import { resolveLocale, getTranslationFunction } from '../auth/i18n.js';
|
|
29
|
+
import { renderPage } from '../auth/template-engine.js';
|
|
30
|
+
import { generateCsrfToken } from '../auth/csrf.js';
|
|
31
|
+
import { invalidateUserCache } from '../users/cache.js';
|
|
32
|
+
import { createMagicLinkSession } from '../auth/magic-link-session.js';
|
|
33
|
+
import { writeAuditLog } from '../lib/audit-log.js';
|
|
34
|
+
import { logger } from '../lib/logger.js';
|
|
35
|
+
import { createInteractionAuthorityResolver, } from '../auth/interaction-authority.js';
|
|
36
|
+
import { buildMagicLinkCallbackRateLimitKey, checkRateLimitStrict, loadMagicLinkRateLimitConfig, } from '../auth/rate-limiter.js';
|
|
37
|
+
import { magicLinkCallbackArtifactDigest } from '../auth/recovery-crypto.js';
|
|
38
|
+
/** Production dependencies used unless a test owns an explicit failure boundary. */
|
|
39
|
+
const DEFAULT_MAGIC_LINK_ROUTE_DEPENDENCIES = {
|
|
40
|
+
checkCallbackRateLimit: checkRateLimitStrict,
|
|
41
|
+
};
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// Helpers
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
/**
|
|
46
|
+
* Build branding context from organization data.
|
|
47
|
+
*
|
|
48
|
+
* @param org - Organization with branding fields
|
|
49
|
+
* @returns Branding context for templates
|
|
50
|
+
*/
|
|
51
|
+
function buildBrandingFromOrg(org) {
|
|
52
|
+
return {
|
|
53
|
+
logoUrl: org.brandingLogoUrl,
|
|
54
|
+
faviconUrl: org.brandingFaviconUrl,
|
|
55
|
+
primaryColor: org.brandingPrimaryColor ?? '#3B82F6',
|
|
56
|
+
companyName: org.brandingCompanyName ?? org.name,
|
|
57
|
+
customCss: org.brandingCustomCss,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Parse the optional interaction query without normalizing malformed input into standalone use.
|
|
62
|
+
*
|
|
63
|
+
* @param value - Raw Koa query value.
|
|
64
|
+
* @returns Exact bounded interaction authority and a fail-closed validity discriminator.
|
|
65
|
+
*/
|
|
66
|
+
function parsePresentedInteraction(value) {
|
|
67
|
+
if (value === undefined)
|
|
68
|
+
return { valid: true, value: null };
|
|
69
|
+
if (typeof value !== 'string' || value.length < 1 || value.length > 128) {
|
|
70
|
+
return { valid: false, value: null };
|
|
71
|
+
}
|
|
72
|
+
return { valid: true, value };
|
|
73
|
+
}
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// Router factory
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
/**
|
|
78
|
+
* Create the magic link auth router.
|
|
79
|
+
*
|
|
80
|
+
* Handles magic link token verification at /:orgSlug/auth/magic-link/:token. Interaction-bound
|
|
81
|
+
* callbacks resolve their current client through the provider before durable authority is used.
|
|
82
|
+
*
|
|
83
|
+
* @param provider - Provider-owned live interaction model. Omit only for standalone callbacks.
|
|
84
|
+
* @param dependencies - Fail-closed callback-limiter boundary.
|
|
85
|
+
* @returns Koa router with magic link routes
|
|
86
|
+
*/
|
|
87
|
+
export function createMagicLinkRouter(provider, dependencies = DEFAULT_MAGIC_LINK_ROUTE_DEPENDENCIES) {
|
|
88
|
+
const router = new Router();
|
|
89
|
+
const interactionAuthority = provider ? createInteractionAuthorityResolver(provider) : null;
|
|
90
|
+
// Tenant resolver — resolves orgSlug to organization and sets ctx.state.organization.
|
|
91
|
+
// Applied at route level because this router is mounted directly on the Koa app
|
|
92
|
+
// (not under the OIDC catch-all router which has its own tenant resolver).
|
|
93
|
+
const resolve = tenantResolver();
|
|
94
|
+
// GET /:orgSlug/auth/magic-link/:token — Verify magic link
|
|
95
|
+
router.get('/:orgSlug/auth/magic-link/:token', resolve, async (ctx) => {
|
|
96
|
+
await verifyMagicLink(ctx, interactionAuthority, dependencies);
|
|
97
|
+
});
|
|
98
|
+
return router;
|
|
99
|
+
}
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
// Route handler
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
/**
|
|
104
|
+
* Verify a magic link token and set up the `_ml_session` for flow completion.
|
|
105
|
+
*
|
|
106
|
+
* 1. Hashes the token from URL params for DB lookup
|
|
107
|
+
* 2. Locks and validates the token, route tenant, account, and persisted interaction authority
|
|
108
|
+
* 3. If invalid/expired: renders error page with "link expired" message
|
|
109
|
+
* 4. If valid:
|
|
110
|
+
* a. Atomically consumes the token, updates the user, and writes the successful audit row
|
|
111
|
+
* e. Creates `_ml_session` in Redis (5-min TTL, single-use)
|
|
112
|
+
* f. Redirects to `/interaction/{uid}` where the interaction handler
|
|
113
|
+
* detects the session cookie and completes the OIDC flow (same
|
|
114
|
+
* browser) or shows a "return to original browser" page (different
|
|
115
|
+
* browser)
|
|
116
|
+
*
|
|
117
|
+
* @param ctx - Koa context with organization state
|
|
118
|
+
*/
|
|
119
|
+
async function verifyMagicLink(ctx, interactionAuthority, dependencies) {
|
|
120
|
+
const org = ctx.state.organization;
|
|
121
|
+
const tokenPlaintext = ctx.params.token;
|
|
122
|
+
const presentedInteraction = parsePresentedInteraction(ctx.query.interaction);
|
|
123
|
+
// Resolve locale for error pages
|
|
124
|
+
const locale = await resolveLocale(undefined, ctx.get('Accept-Language') || undefined, org.defaultLocale);
|
|
125
|
+
const t = getTranslationFunction(locale, org.slug);
|
|
126
|
+
try {
|
|
127
|
+
// Step 1: Hash the token for DB lookup
|
|
128
|
+
const tokenHash = hashToken(tokenPlaintext);
|
|
129
|
+
const socketPeer = ctx.req.socket.remoteAddress ?? 'unavailable';
|
|
130
|
+
const rateLimitKey = buildMagicLinkCallbackRateLimitKey(org.id, socketPeer, magicLinkCallbackArtifactDigest(tokenPlaintext));
|
|
131
|
+
const rateLimit = await dependencies.checkCallbackRateLimit(rateLimitKey, await loadMagicLinkRateLimitConfig());
|
|
132
|
+
if (!rateLimit.allowed) {
|
|
133
|
+
await rejectMagicLink(ctx, org, locale, t);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const liveAuthority = presentedInteraction.valid && presentedInteraction.value !== null && interactionAuthority
|
|
137
|
+
? await interactionAuthority.resolve(presentedInteraction.value)
|
|
138
|
+
: null;
|
|
139
|
+
const interactionAccepted = presentedInteraction.valid &&
|
|
140
|
+
(presentedInteraction.value === null ||
|
|
141
|
+
(liveAuthority !== null && liveAuthority.interactionUid === presentedInteraction.value));
|
|
142
|
+
// Step 2: Lock and validate all durable authority before any successful mutation.
|
|
143
|
+
const authority = interactionAccepted
|
|
144
|
+
? await consumeAuthorizedMagicLink({
|
|
145
|
+
tokenHash,
|
|
146
|
+
organizationId: org.id,
|
|
147
|
+
interactionUid: presentedInteraction.value,
|
|
148
|
+
clientId: liveAuthority?.clientId ?? null,
|
|
149
|
+
ipAddress: ctx.ip,
|
|
150
|
+
})
|
|
151
|
+
: null;
|
|
152
|
+
if (!authority) {
|
|
153
|
+
await rejectMagicLink(ctx, org, locale, t);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
// The durable transaction has committed. Remove any stale cached account representation.
|
|
157
|
+
await invalidateUserCache(authority.userId);
|
|
158
|
+
// Step 8: Complete the OIDC flow via _ml_session cookie.
|
|
159
|
+
//
|
|
160
|
+
// Create a magic link session and redirect to the interaction handler.
|
|
161
|
+
// The interaction handler will detect the session cookie and complete
|
|
162
|
+
// the OIDC flow (same browser) or show a success page (different browser).
|
|
163
|
+
if (authority.interactionUid) {
|
|
164
|
+
await createMagicLinkSession(ctx, {
|
|
165
|
+
userId: authority.userId,
|
|
166
|
+
interactionUid: authority.interactionUid,
|
|
167
|
+
organizationId: org.id,
|
|
168
|
+
});
|
|
169
|
+
ctx.redirect(`/interaction/${authority.interactionUid}`);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
// No interaction UID — magic link opened outside an OIDC flow.
|
|
173
|
+
// Show the magic link success page (standalone authentication confirmation).
|
|
174
|
+
logger.info({ event: 'magic-link-standalone-completed' }, 'Magic link verification completed');
|
|
175
|
+
await renderSuccessPageForAuth(ctx, org, locale, t);
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
logger.error({ event: 'magic-link-verification-failed' }, 'Magic link verification failed');
|
|
179
|
+
await renderErrorPageForAuth(ctx, org, locale, t, t('errors.generic'));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/** Render and audit the one generic callback rejection without retaining bearer authority. */
|
|
183
|
+
async function rejectMagicLink(ctx, org, locale, t) {
|
|
184
|
+
writeAuditLog({
|
|
185
|
+
organizationId: org.id,
|
|
186
|
+
eventType: 'user.magic_link.failed',
|
|
187
|
+
eventCategory: 'security',
|
|
188
|
+
description: 'Magic link verification failed: invalid or expired token',
|
|
189
|
+
ipAddress: ctx.ip,
|
|
190
|
+
});
|
|
191
|
+
await renderErrorPageForAuth(ctx, org, locale, t, t('errors.magic_link_expired'));
|
|
192
|
+
}
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
// Helper: render success page for auth routes
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
/**
|
|
197
|
+
* Render a success page when a magic link is verified outside an OIDC flow.
|
|
198
|
+
*
|
|
199
|
+
* This handles the edge case where a magic link URL doesn't have an
|
|
200
|
+
* interaction UID (e.g., link was malformed or interaction expired).
|
|
201
|
+
* Shows a generic "email verified" confirmation instead of redirecting
|
|
202
|
+
* to the forgot-password page.
|
|
203
|
+
*
|
|
204
|
+
* @param ctx - Koa context
|
|
205
|
+
* @param org - Organization for branding
|
|
206
|
+
* @param locale - Resolved locale
|
|
207
|
+
* @param t - Translation function
|
|
208
|
+
*/
|
|
209
|
+
async function renderSuccessPageForAuth(ctx, org, locale, t) {
|
|
210
|
+
try {
|
|
211
|
+
const csrfToken = generateCsrfToken();
|
|
212
|
+
const context = {
|
|
213
|
+
branding: buildBrandingFromOrg(org),
|
|
214
|
+
locale,
|
|
215
|
+
t,
|
|
216
|
+
csrfToken,
|
|
217
|
+
orgSlug: org.slug,
|
|
218
|
+
};
|
|
219
|
+
const html = await renderPage('magic-link-success', context);
|
|
220
|
+
ctx.status = 200;
|
|
221
|
+
ctx.type = 'text/html';
|
|
222
|
+
ctx.body = html;
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
logger.error({ event: 'magic-link-success-render-failed' }, 'Magic link success page failed');
|
|
226
|
+
ctx.status = 200;
|
|
227
|
+
ctx.body = 'Your email has been verified. You may close this tab.';
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// ---------------------------------------------------------------------------
|
|
231
|
+
// Helper: render error page for auth routes
|
|
232
|
+
// ---------------------------------------------------------------------------
|
|
233
|
+
/**
|
|
234
|
+
* Render an error page for auth routes (outside OIDC interaction flow).
|
|
235
|
+
*
|
|
236
|
+
* @param ctx - Koa context
|
|
237
|
+
* @param org - Organization for branding
|
|
238
|
+
* @param locale - Resolved locale
|
|
239
|
+
* @param t - Translation function
|
|
240
|
+
* @param errorMessage - Error message to display
|
|
241
|
+
*/
|
|
242
|
+
async function renderErrorPageForAuth(ctx, org, locale, t, errorMessage) {
|
|
243
|
+
try {
|
|
244
|
+
const csrfToken = generateCsrfToken();
|
|
245
|
+
const context = {
|
|
246
|
+
branding: buildBrandingFromOrg(org),
|
|
247
|
+
locale,
|
|
248
|
+
t,
|
|
249
|
+
csrfToken,
|
|
250
|
+
orgSlug: org.slug,
|
|
251
|
+
errorMessage,
|
|
252
|
+
};
|
|
253
|
+
const html = await renderPage('error', context);
|
|
254
|
+
ctx.status = 400;
|
|
255
|
+
ctx.type = 'text/html';
|
|
256
|
+
ctx.body = html;
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
logger.error({ event: 'auth-error-render-failed' }, 'Authentication error page failed');
|
|
260
|
+
ctx.status = 500;
|
|
261
|
+
ctx.body = 'An error occurred';
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
//# sourceMappingURL=magic-link.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"magic-link.js","sourceRoot":"","sources":["../../src/routes/magic-link.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Organization management API routes.
|
|
3
|
+
*
|
|
4
|
+
* All routes are under `/api/admin/organizations` and require
|
|
5
|
+
* admin authentication (Bearer JWT). Provides CRUD, status lifecycle,
|
|
6
|
+
* branding management, and slug validation endpoints.
|
|
7
|
+
*
|
|
8
|
+
* Route structure:
|
|
9
|
+
* POST / — Create a new organization
|
|
10
|
+
* GET / — List organizations (paginated)
|
|
11
|
+
* GET /validate-slug — Validate slug availability
|
|
12
|
+
* GET /:id — Get organization by ID
|
|
13
|
+
* PUT /:id — Update organization
|
|
14
|
+
* PUT /:id/branding — Update branding
|
|
15
|
+
* POST /:id/suspend — Suspend organization
|
|
16
|
+
* POST /:id/activate — Activate organization
|
|
17
|
+
* POST /:id/archive — Archive organization
|
|
18
|
+
* POST /:id/restore — Restore organization
|
|
19
|
+
*
|
|
20
|
+
* Error mapping:
|
|
21
|
+
* OrganizationNotFoundError → 404
|
|
22
|
+
* OrganizationValidationError → 400
|
|
23
|
+
* ZodError → 400 with validation details
|
|
24
|
+
*/
|
|
25
|
+
import Router from '@koa/router';
|
|
26
|
+
/**
|
|
27
|
+
* Create the organization management router.
|
|
28
|
+
*
|
|
29
|
+
* All routes require admin authentication (Bearer JWT via requireAdminAuth).
|
|
30
|
+
* The middleware validates the token, checks the user belongs to the
|
|
31
|
+
* super-admin org, and verifies the porta-admin role.
|
|
32
|
+
*
|
|
33
|
+
* Prefix: /api/admin/organizations
|
|
34
|
+
*
|
|
35
|
+
* @returns Configured Koa Router
|
|
36
|
+
*/
|
|
37
|
+
export declare function createOrganizationRouter(): Router;
|
|
38
|
+
//# sourceMappingURL=organizations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"organizations.d.ts","sourceRoot":"","sources":["../../src/routes/organizations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,MAAM,MAAM,aAAa,CAAC;AAqIjC;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,CA2MjD"}
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Organization management API routes.
|
|
3
|
+
*
|
|
4
|
+
* All routes are under `/api/admin/organizations` and require
|
|
5
|
+
* admin authentication (Bearer JWT). Provides CRUD, status lifecycle,
|
|
6
|
+
* branding management, and slug validation endpoints.
|
|
7
|
+
*
|
|
8
|
+
* Route structure:
|
|
9
|
+
* POST / — Create a new organization
|
|
10
|
+
* GET / — List organizations (paginated)
|
|
11
|
+
* GET /validate-slug — Validate slug availability
|
|
12
|
+
* GET /:id — Get organization by ID
|
|
13
|
+
* PUT /:id — Update organization
|
|
14
|
+
* PUT /:id/branding — Update branding
|
|
15
|
+
* POST /:id/suspend — Suspend organization
|
|
16
|
+
* POST /:id/activate — Activate organization
|
|
17
|
+
* POST /:id/archive — Archive organization
|
|
18
|
+
* POST /:id/restore — Restore organization
|
|
19
|
+
*
|
|
20
|
+
* Error mapping:
|
|
21
|
+
* OrganizationNotFoundError → 404
|
|
22
|
+
* OrganizationValidationError → 400
|
|
23
|
+
* ZodError → 400 with validation details
|
|
24
|
+
*/
|
|
25
|
+
import Router from '@koa/router';
|
|
26
|
+
import { z } from 'zod';
|
|
27
|
+
import { requireAdminAuth } from '../middleware/admin-auth.js';
|
|
28
|
+
import { requirePermission } from '../middleware/require-permission.js';
|
|
29
|
+
import { ADMIN_PERMISSIONS } from '../lib/admin-permissions.js';
|
|
30
|
+
import * as organizationService from '../organizations/service.js';
|
|
31
|
+
import { setETagHeader, checkIfMatch } from '../lib/etag.js';
|
|
32
|
+
import { getEntityHistory } from '../lib/entity-history.js';
|
|
33
|
+
import { OrganizationNotFoundError, OrganizationValidationError } from '../organizations/errors.js';
|
|
34
|
+
import { LOGIN_METHODS } from '../clients/types.js';
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Validation schemas
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
/**
|
|
39
|
+
* Login method Zod schema — single source of truth for HTTP payload validation.
|
|
40
|
+
* Uses the runtime `LOGIN_METHODS` const so adding a new method only requires
|
|
41
|
+
* updating the union in `src/clients/types.ts`.
|
|
42
|
+
*/
|
|
43
|
+
const loginMethodSchema = z.enum(LOGIN_METHODS);
|
|
44
|
+
/**
|
|
45
|
+
* Organization-level default login methods — a non-empty array (empty arrays
|
|
46
|
+
* are rejected by the service layer and must be rejected at the HTTP boundary
|
|
47
|
+
* too so the 400 carries a useful validation message).
|
|
48
|
+
*/
|
|
49
|
+
const defaultLoginMethodsSchema = z.array(loginMethodSchema).min(1);
|
|
50
|
+
const createOrganizationSchema = z.object({
|
|
51
|
+
name: z.string().min(1).max(255),
|
|
52
|
+
slug: z.string().min(3).max(100).optional(),
|
|
53
|
+
defaultLocale: z.string().min(2).max(10).optional(),
|
|
54
|
+
defaultLoginMethods: defaultLoginMethodsSchema.optional(),
|
|
55
|
+
branding: z
|
|
56
|
+
.object({
|
|
57
|
+
logoUrl: z.string().url().nullable().optional(),
|
|
58
|
+
faviconUrl: z.string().url().nullable().optional(),
|
|
59
|
+
primaryColor: z
|
|
60
|
+
.string()
|
|
61
|
+
.regex(/^#[0-9A-Fa-f]{6}$/)
|
|
62
|
+
.nullable()
|
|
63
|
+
.optional(),
|
|
64
|
+
companyName: z.string().max(255).nullable().optional(),
|
|
65
|
+
customCss: z.string().max(10000).nullable().optional(),
|
|
66
|
+
})
|
|
67
|
+
.optional(),
|
|
68
|
+
});
|
|
69
|
+
const updateOrganizationSchema = z.object({
|
|
70
|
+
name: z.string().min(1).max(255).optional(),
|
|
71
|
+
defaultLocale: z.string().min(2).max(10).optional(),
|
|
72
|
+
defaultLoginMethods: defaultLoginMethodsSchema.optional(),
|
|
73
|
+
branding: z
|
|
74
|
+
.object({
|
|
75
|
+
logoUrl: z.string().url().nullable().optional(),
|
|
76
|
+
faviconUrl: z.string().url().nullable().optional(),
|
|
77
|
+
primaryColor: z
|
|
78
|
+
.string()
|
|
79
|
+
.regex(/^#[0-9A-Fa-f]{6}$/)
|
|
80
|
+
.nullable()
|
|
81
|
+
.optional(),
|
|
82
|
+
companyName: z.string().max(255).nullable().optional(),
|
|
83
|
+
customCss: z.string().max(10000).nullable().optional(),
|
|
84
|
+
})
|
|
85
|
+
.optional(),
|
|
86
|
+
});
|
|
87
|
+
const updateBrandingSchema = z.object({
|
|
88
|
+
logoUrl: z.string().url().nullable().optional(),
|
|
89
|
+
faviconUrl: z.string().url().nullable().optional(),
|
|
90
|
+
primaryColor: z
|
|
91
|
+
.string()
|
|
92
|
+
.regex(/^#[0-9A-Fa-f]{6}$/)
|
|
93
|
+
.nullable()
|
|
94
|
+
.optional(),
|
|
95
|
+
companyName: z.string().max(255).nullable().optional(),
|
|
96
|
+
customCss: z.string().max(10000).nullable().optional(),
|
|
97
|
+
});
|
|
98
|
+
const listOrganizationsSchema = z.object({
|
|
99
|
+
page: z.coerce.number().int().min(1).default(1),
|
|
100
|
+
pageSize: z.coerce.number().int().min(1).max(100).default(20),
|
|
101
|
+
status: z.enum(['active', 'suspended', 'archived']).optional(),
|
|
102
|
+
search: z.string().max(255).optional(),
|
|
103
|
+
sortBy: z.enum(['name', 'created_at']).default('created_at'),
|
|
104
|
+
sortOrder: z.enum(['asc', 'desc']).default('desc'),
|
|
105
|
+
});
|
|
106
|
+
/** Schema for cursor-based listing (keyset pagination) */
|
|
107
|
+
const listOrganizationsCursorSchema = z.object({
|
|
108
|
+
cursor: z.string().optional(),
|
|
109
|
+
limit: z.coerce.number().int().min(1).max(100).default(25),
|
|
110
|
+
status: z.enum(['active', 'suspended', 'archived']).optional(),
|
|
111
|
+
search: z.string().max(255).optional(),
|
|
112
|
+
sortBy: z.enum(['name', 'created_at']).default('created_at'),
|
|
113
|
+
sortOrder: z.enum(['asc', 'desc']).default('desc'),
|
|
114
|
+
});
|
|
115
|
+
const validateSlugSchema = z.object({
|
|
116
|
+
slug: z.string().min(1),
|
|
117
|
+
});
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
// Error handler helper
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
/**
|
|
122
|
+
* Handle domain errors and map them to HTTP responses.
|
|
123
|
+
* Unknown errors are re-thrown for the global error handler.
|
|
124
|
+
*/
|
|
125
|
+
function handleError(ctx, err) {
|
|
126
|
+
if (err instanceof OrganizationNotFoundError) {
|
|
127
|
+
ctx.throw(404, 'Organization not found');
|
|
128
|
+
}
|
|
129
|
+
if (err instanceof OrganizationValidationError) {
|
|
130
|
+
ctx.throw(400, 'Organization request is invalid');
|
|
131
|
+
}
|
|
132
|
+
if (err instanceof z.ZodError) {
|
|
133
|
+
ctx.status = 400;
|
|
134
|
+
ctx.body = { error: 'Organization request is invalid' };
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
throw err;
|
|
138
|
+
}
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
// Router factory
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
/**
|
|
143
|
+
* Create the organization management router.
|
|
144
|
+
*
|
|
145
|
+
* All routes require admin authentication (Bearer JWT via requireAdminAuth).
|
|
146
|
+
* The middleware validates the token, checks the user belongs to the
|
|
147
|
+
* super-admin org, and verifies the porta-admin role.
|
|
148
|
+
*
|
|
149
|
+
* Prefix: /api/admin/organizations
|
|
150
|
+
*
|
|
151
|
+
* @returns Configured Koa Router
|
|
152
|
+
*/
|
|
153
|
+
export function createOrganizationRouter() {
|
|
154
|
+
const router = new Router({ prefix: '/api/admin/organizations' });
|
|
155
|
+
// All routes require admin authentication
|
|
156
|
+
router.use(requireAdminAuth());
|
|
157
|
+
// -------------------------------------------------------------------------
|
|
158
|
+
// POST / — Create organization
|
|
159
|
+
// -------------------------------------------------------------------------
|
|
160
|
+
router.post('/', requirePermission(ADMIN_PERMISSIONS.ORG_CREATE), async (ctx) => {
|
|
161
|
+
try {
|
|
162
|
+
const body = createOrganizationSchema.parse(ctx.request.body);
|
|
163
|
+
const org = await organizationService.createOrganization(body);
|
|
164
|
+
ctx.status = 201;
|
|
165
|
+
ctx.body = { data: org };
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
handleError(ctx, err);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
// -------------------------------------------------------------------------
|
|
172
|
+
// GET / — List organizations (paginated)
|
|
173
|
+
// -------------------------------------------------------------------------
|
|
174
|
+
router.get('/', requirePermission(ADMIN_PERMISSIONS.ORG_READ), async (ctx) => {
|
|
175
|
+
try {
|
|
176
|
+
// Cursor-based pagination when `cursor` or `limit` param is present
|
|
177
|
+
if (ctx.query.cursor !== undefined || ctx.query.limit !== undefined) {
|
|
178
|
+
const query = listOrganizationsCursorSchema.parse(ctx.query);
|
|
179
|
+
const result = await organizationService.listOrganizationsCursor(query);
|
|
180
|
+
ctx.body = result;
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
// Default: offset-based pagination (backward compatible)
|
|
184
|
+
const query = listOrganizationsSchema.parse(ctx.query);
|
|
185
|
+
const result = await organizationService.listOrganizations(query);
|
|
186
|
+
ctx.body = result;
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
handleError(ctx, err);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
// -------------------------------------------------------------------------
|
|
193
|
+
// GET /validate-slug — Validate slug availability
|
|
194
|
+
// Must be BEFORE /:id to avoid matching "validate-slug" as an :id param
|
|
195
|
+
// -------------------------------------------------------------------------
|
|
196
|
+
router.get('/validate-slug', requirePermission(ADMIN_PERMISSIONS.ORG_READ), async (ctx) => {
|
|
197
|
+
try {
|
|
198
|
+
const { slug } = validateSlugSchema.parse(ctx.query);
|
|
199
|
+
const result = await organizationService.validateSlugAvailability(slug);
|
|
200
|
+
ctx.body = result;
|
|
201
|
+
}
|
|
202
|
+
catch (err) {
|
|
203
|
+
handleError(ctx, err);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
// -------------------------------------------------------------------------
|
|
207
|
+
// GET /:id — Get organization by ID
|
|
208
|
+
// -------------------------------------------------------------------------
|
|
209
|
+
router.get('/:id', requirePermission(ADMIN_PERMISSIONS.ORG_READ), async (ctx) => {
|
|
210
|
+
const param = ctx.params.id;
|
|
211
|
+
// Support both UUID and slug lookups — CLI and API consumers may use either
|
|
212
|
+
const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(param);
|
|
213
|
+
const org = isUuid
|
|
214
|
+
? await organizationService.getOrganizationById(param)
|
|
215
|
+
: await organizationService.getOrganizationBySlug(param);
|
|
216
|
+
if (!org) {
|
|
217
|
+
ctx.throw(404, 'Organization not found');
|
|
218
|
+
return; // unreachable — keeps TS narrowing happy
|
|
219
|
+
}
|
|
220
|
+
setETagHeader(ctx, 'organization', org.id, org.updatedAt);
|
|
221
|
+
ctx.body = { data: org };
|
|
222
|
+
});
|
|
223
|
+
// -------------------------------------------------------------------------
|
|
224
|
+
// PUT /:id — Update organization
|
|
225
|
+
// -------------------------------------------------------------------------
|
|
226
|
+
router.put('/:id', requirePermission(ADMIN_PERMISSIONS.ORG_UPDATE), async (ctx) => {
|
|
227
|
+
try {
|
|
228
|
+
const body = updateOrganizationSchema.parse(ctx.request.body);
|
|
229
|
+
// Check If-Match for optimistic concurrency (optional — backward compatible)
|
|
230
|
+
const current = await organizationService.getOrganizationById(ctx.params.id);
|
|
231
|
+
if (current && !checkIfMatch(ctx, 'organization', current.id, current.updatedAt, current))
|
|
232
|
+
return;
|
|
233
|
+
const org = await organizationService.updateOrganization(ctx.params.id, body);
|
|
234
|
+
setETagHeader(ctx, 'organization', org.id, org.updatedAt);
|
|
235
|
+
ctx.body = { data: org };
|
|
236
|
+
}
|
|
237
|
+
catch (err) {
|
|
238
|
+
handleError(ctx, err);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
// -------------------------------------------------------------------------
|
|
242
|
+
// PUT /:id/branding — Update branding
|
|
243
|
+
// -------------------------------------------------------------------------
|
|
244
|
+
router.put('/:id/branding', requirePermission(ADMIN_PERMISSIONS.ORG_UPDATE), async (ctx) => {
|
|
245
|
+
try {
|
|
246
|
+
const body = updateBrandingSchema.parse(ctx.request.body);
|
|
247
|
+
const org = await organizationService.updateOrganizationBranding(ctx.params.id, body);
|
|
248
|
+
ctx.body = { data: org };
|
|
249
|
+
}
|
|
250
|
+
catch (err) {
|
|
251
|
+
handleError(ctx, err);
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
// -------------------------------------------------------------------------
|
|
255
|
+
// POST /:id/suspend — Suspend organization
|
|
256
|
+
// -------------------------------------------------------------------------
|
|
257
|
+
router.post('/:id/suspend', requirePermission(ADMIN_PERMISSIONS.ORG_SUSPEND), async (ctx) => {
|
|
258
|
+
try {
|
|
259
|
+
const body = ctx.request.body;
|
|
260
|
+
await organizationService.suspendOrganization(ctx.params.id, body?.reason);
|
|
261
|
+
ctx.status = 204;
|
|
262
|
+
}
|
|
263
|
+
catch (err) {
|
|
264
|
+
handleError(ctx, err);
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
// -------------------------------------------------------------------------
|
|
268
|
+
// POST /:id/activate — Activate organization
|
|
269
|
+
// -------------------------------------------------------------------------
|
|
270
|
+
router.post('/:id/activate', requirePermission(ADMIN_PERMISSIONS.ORG_SUSPEND), async (ctx) => {
|
|
271
|
+
try {
|
|
272
|
+
await organizationService.activateOrganization(ctx.params.id);
|
|
273
|
+
ctx.status = 204;
|
|
274
|
+
}
|
|
275
|
+
catch (err) {
|
|
276
|
+
handleError(ctx, err);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
// -------------------------------------------------------------------------
|
|
280
|
+
// POST /:id/archive — Archive organization
|
|
281
|
+
// -------------------------------------------------------------------------
|
|
282
|
+
router.post('/:id/archive', requirePermission(ADMIN_PERMISSIONS.ORG_ARCHIVE), async (ctx) => {
|
|
283
|
+
try {
|
|
284
|
+
await organizationService.archiveOrganization(ctx.params.id);
|
|
285
|
+
ctx.status = 204;
|
|
286
|
+
}
|
|
287
|
+
catch (err) {
|
|
288
|
+
handleError(ctx, err);
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
// -------------------------------------------------------------------------
|
|
292
|
+
// GET /:id/history — Organization change history
|
|
293
|
+
// -------------------------------------------------------------------------
|
|
294
|
+
router.get('/:id/history', requirePermission(ADMIN_PERMISSIONS.ORG_READ), async (ctx) => {
|
|
295
|
+
const { limit, after, event_type } = ctx.query;
|
|
296
|
+
const result = await getEntityHistory('organization', ctx.params.id, {
|
|
297
|
+
limit: limit ? parseInt(limit, 10) : undefined,
|
|
298
|
+
after: after || undefined,
|
|
299
|
+
eventTypePrefix: event_type || undefined,
|
|
300
|
+
});
|
|
301
|
+
ctx.body = result;
|
|
302
|
+
});
|
|
303
|
+
// -------------------------------------------------------------------------
|
|
304
|
+
// POST /:id/restore — Restore organization
|
|
305
|
+
// -------------------------------------------------------------------------
|
|
306
|
+
router.post('/:id/restore', requirePermission(ADMIN_PERMISSIONS.ORG_ARCHIVE), async (ctx) => {
|
|
307
|
+
try {
|
|
308
|
+
await organizationService.restoreOrganization(ctx.params.id);
|
|
309
|
+
ctx.status = 204;
|
|
310
|
+
}
|
|
311
|
+
catch (err) {
|
|
312
|
+
handleError(ctx, err);
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
// -------------------------------------------------------------------------
|
|
316
|
+
// DELETE /:idOrSlug — Permanently destroy organization (cascade)
|
|
317
|
+
// -------------------------------------------------------------------------
|
|
318
|
+
router.delete('/:idOrSlug', requirePermission(ADMIN_PERMISSIONS.ORG_ARCHIVE), async (ctx) => {
|
|
319
|
+
try {
|
|
320
|
+
const { idOrSlug } = ctx.params;
|
|
321
|
+
const dryRun = ctx.query['dry-run'] === 'true';
|
|
322
|
+
if (dryRun) {
|
|
323
|
+
// Resolve org and return cascade counts without deleting
|
|
324
|
+
const org = (await organizationService.getOrganizationById(idOrSlug)) ??
|
|
325
|
+
(await organizationService.getOrganizationBySlug(idOrSlug));
|
|
326
|
+
if (!org) {
|
|
327
|
+
ctx.throw(404, `Organization not found: ${idOrSlug}`);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
if (org.isSuperAdmin) {
|
|
331
|
+
ctx.throw(400, 'Cannot destroy the super-admin organization');
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
const cascadeCounts = await organizationService.getCascadeCounts(org.id);
|
|
335
|
+
ctx.body = { dryRun: true, organization: org, cascadeCounts };
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
const result = await organizationService.destroyOrganization(idOrSlug, ctx.state.adminUser?.id);
|
|
339
|
+
ctx.body = result;
|
|
340
|
+
}
|
|
341
|
+
catch (err) {
|
|
342
|
+
handleError(ctx, err);
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
return router;
|
|
346
|
+
}
|
|
347
|
+
//# sourceMappingURL=organizations.js.map
|