@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,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signing key management for the OIDC provider.
|
|
3
|
+
*
|
|
4
|
+
* Handles loading ES256 key pairs from the database, converting between
|
|
5
|
+
* PEM and JWK formats, and generating new key pairs for bootstrapping
|
|
6
|
+
* and rotation.
|
|
7
|
+
*
|
|
8
|
+
* Key format:
|
|
9
|
+
* - Database stores PEM-encoded keys (public_key, private_key columns)
|
|
10
|
+
* - node-oidc-provider requires JWK format (jwks.keys configuration)
|
|
11
|
+
* - Node.js crypto module handles PEM ↔ JWK conversion
|
|
12
|
+
*
|
|
13
|
+
* Key lifecycle:
|
|
14
|
+
* active → Used for signing new tokens, published in JWKS
|
|
15
|
+
* retired → Not used for signing, still published in JWKS for verification
|
|
16
|
+
* revoked → Not loaded, not published
|
|
17
|
+
*/
|
|
18
|
+
import { createPrivateKey, generateKeyPairSync, createHash } from 'node:crypto';
|
|
19
|
+
import { config } from '../config/index.js';
|
|
20
|
+
import { getPool } from './database.js';
|
|
21
|
+
import { logger } from './logger.js';
|
|
22
|
+
import { encryptPrivateKey, decryptPrivateKey } from './signing-key-crypto.js';
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Cached JWKS for JWT verification (admin auth middleware, etc.)
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
/** In-memory cache for the active JWK set — avoids a DB round-trip per request */
|
|
27
|
+
let cachedJwks = null;
|
|
28
|
+
let jwksCacheTimestamp = 0;
|
|
29
|
+
/** Cache TTL for the active JWK set — 60 seconds matches system-config cache */
|
|
30
|
+
const JWKS_CACHE_TTL_MS = 60_000;
|
|
31
|
+
/**
|
|
32
|
+
* Get the active JWK set (public + private keys) for JWT verification.
|
|
33
|
+
*
|
|
34
|
+
* Loads signing keys from the database on first call, then serves from
|
|
35
|
+
* an in-memory cache for 60 seconds. Used by the admin auth middleware
|
|
36
|
+
* to verify Bearer tokens without a DB query on every request.
|
|
37
|
+
*
|
|
38
|
+
* @returns JWK key set containing all active and retired keys
|
|
39
|
+
*/
|
|
40
|
+
export async function getActiveJwks() {
|
|
41
|
+
const now = Date.now();
|
|
42
|
+
if (cachedJwks && now - jwksCacheTimestamp < JWKS_CACHE_TTL_MS) {
|
|
43
|
+
return cachedJwks;
|
|
44
|
+
}
|
|
45
|
+
const records = await loadSigningKeysFromDb();
|
|
46
|
+
cachedJwks = signingKeysToJwks(records);
|
|
47
|
+
jwksCacheTimestamp = now;
|
|
48
|
+
return cachedJwks;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Clear the cached JWK set — useful for testing and after key rotation.
|
|
52
|
+
*/
|
|
53
|
+
export function clearJwksCache() {
|
|
54
|
+
cachedJwks = null;
|
|
55
|
+
jwksCacheTimestamp = 0;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Generate a new ES256 (ECDSA P-256) key pair.
|
|
59
|
+
*
|
|
60
|
+
* Creates a cryptographically secure key pair suitable for JWT signing.
|
|
61
|
+
* Returns PEM-encoded keys for database storage and a deterministic kid
|
|
62
|
+
* derived from the public key hash.
|
|
63
|
+
*
|
|
64
|
+
* @returns Object containing PEM keys, kid, and algorithm identifier
|
|
65
|
+
*/
|
|
66
|
+
export function generateES256KeyPair() {
|
|
67
|
+
// Generate ECDSA P-256 key pair using Node.js crypto
|
|
68
|
+
const { publicKey, privateKey } = generateKeyPairSync('ec', {
|
|
69
|
+
namedCurve: 'P-256',
|
|
70
|
+
});
|
|
71
|
+
// Export as PEM format for database storage
|
|
72
|
+
const publicKeyPem = publicKey.export({ type: 'spki', format: 'pem' });
|
|
73
|
+
const privateKeyPem = privateKey.export({ type: 'pkcs8', format: 'pem' });
|
|
74
|
+
// Generate deterministic kid from public key hash (first 16 hex chars of SHA-256).
|
|
75
|
+
// This ensures uniqueness while keeping the kid short enough for JWKS headers.
|
|
76
|
+
const kid = createHash('sha256').update(publicKeyPem).digest('hex').substring(0, 16);
|
|
77
|
+
return {
|
|
78
|
+
kid,
|
|
79
|
+
algorithm: 'ES256',
|
|
80
|
+
publicKeyPem,
|
|
81
|
+
privateKeyPem,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Convert a PEM-encoded private key to JWK format for node-oidc-provider.
|
|
86
|
+
*
|
|
87
|
+
* Uses Node.js crypto module to parse PEM and export as JWK. The resulting
|
|
88
|
+
* JWK includes the private key (d parameter) which is needed by the provider
|
|
89
|
+
* for token signing.
|
|
90
|
+
*
|
|
91
|
+
* @param privatePem - PEM-encoded private key (PKCS#8 format)
|
|
92
|
+
* @param kid - Key ID to include in the JWK
|
|
93
|
+
* @returns JWK object with both public and private key parameters
|
|
94
|
+
*/
|
|
95
|
+
export function pemToJwk(privatePem, kid) {
|
|
96
|
+
// Parse PEM into a Node.js KeyObject
|
|
97
|
+
const keyObject = createPrivateKey(privatePem);
|
|
98
|
+
// Export as JWK — includes both public (x, y) and private (d) parameters
|
|
99
|
+
const jwk = keyObject.export({ format: 'jwk' });
|
|
100
|
+
return {
|
|
101
|
+
...jwk,
|
|
102
|
+
kid,
|
|
103
|
+
use: 'sig',
|
|
104
|
+
alg: 'ES256',
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Convert signing key records from the database into JWK format
|
|
109
|
+
* suitable for node-oidc-provider's jwks.keys configuration.
|
|
110
|
+
*
|
|
111
|
+
* All active and retired keys are included — the provider uses the first key
|
|
112
|
+
* for signing and all keys for verification (supporting rotation).
|
|
113
|
+
*
|
|
114
|
+
* @param records - Array of signing key records from the database
|
|
115
|
+
* @returns JWK key set with keys array
|
|
116
|
+
*/
|
|
117
|
+
export function signingKeysToJwks(records) {
|
|
118
|
+
const keys = [];
|
|
119
|
+
for (const record of records) {
|
|
120
|
+
try {
|
|
121
|
+
keys.push(pemToJwk(record.privateKey, record.kid));
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
// Skip invalid PEM keys rather than crashing — log error and continue
|
|
125
|
+
// with remaining keys so the provider can still start
|
|
126
|
+
logger.error({ kid: record.kid, error }, 'Failed to convert signing key PEM to JWK, skipping');
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return { keys };
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Load signing keys from the database.
|
|
133
|
+
*
|
|
134
|
+
* Queries the signing_keys table for active and retired keys that
|
|
135
|
+
* haven't expired. Returns them sorted by activated_at DESC (newest first)
|
|
136
|
+
* so the provider uses the newest active key for signing.
|
|
137
|
+
*
|
|
138
|
+
* @returns Array of signing key records, newest first
|
|
139
|
+
*/
|
|
140
|
+
export async function loadSigningKeysFromDb() {
|
|
141
|
+
const pool = getPool();
|
|
142
|
+
const result = await pool.query(`SELECT id, kid, algorithm, public_key, private_key,
|
|
143
|
+
private_key_iv, private_key_tag, encrypted,
|
|
144
|
+
status, activated_at, retired_at, expires_at
|
|
145
|
+
FROM signing_keys
|
|
146
|
+
WHERE status IN ('active', 'retired')
|
|
147
|
+
AND (expires_at IS NULL OR expires_at > NOW())
|
|
148
|
+
ORDER BY activated_at DESC`);
|
|
149
|
+
// Map snake_case DB columns to camelCase TypeScript interface.
|
|
150
|
+
// Decrypt private keys that are stored encrypted; pass through plaintext legacy keys.
|
|
151
|
+
return result.rows.map((row) => {
|
|
152
|
+
let privateKey;
|
|
153
|
+
if (row.encrypted && row.private_key_iv && row.private_key_tag) {
|
|
154
|
+
// Encrypted key — decrypt with AES-256-GCM
|
|
155
|
+
privateKey = decryptPrivateKey(row.private_key, row.private_key_iv, row.private_key_tag, config.signingKeyEncryptionKey);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
// Legacy plaintext key — use as-is
|
|
159
|
+
privateKey = row.private_key;
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
id: row.id,
|
|
163
|
+
kid: row.kid,
|
|
164
|
+
algorithm: row.algorithm,
|
|
165
|
+
publicKey: row.public_key,
|
|
166
|
+
privateKey,
|
|
167
|
+
status: row.status,
|
|
168
|
+
activatedAt: row.activated_at,
|
|
169
|
+
retiredAt: row.retired_at,
|
|
170
|
+
expiresAt: row.expires_at,
|
|
171
|
+
};
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Ensure at least one active signing key exists in the database.
|
|
176
|
+
*
|
|
177
|
+
* Called at application startup. If no active keys are found:
|
|
178
|
+
* 1. Generate a new ES256 key pair
|
|
179
|
+
* 2. Insert it into the signing_keys table with status='active'
|
|
180
|
+
* 3. Log a warning that a key was auto-generated
|
|
181
|
+
*
|
|
182
|
+
* This guarantees the OIDC provider can always start, even on a fresh database
|
|
183
|
+
* that has no signing keys yet (e.g., first run after migrations).
|
|
184
|
+
*
|
|
185
|
+
* @returns The JWK key set containing all active and retired keys
|
|
186
|
+
*/
|
|
187
|
+
export async function ensureSigningKeys() {
|
|
188
|
+
let records = await loadSigningKeysFromDb();
|
|
189
|
+
// Check if there are any active keys
|
|
190
|
+
const hasActiveKey = records.some((r) => r.status === 'active');
|
|
191
|
+
if (!hasActiveKey) {
|
|
192
|
+
logger.warn('No active signing keys found — auto-generating a new ES256 key pair');
|
|
193
|
+
const keyPair = generateES256KeyPair();
|
|
194
|
+
const pool = getPool();
|
|
195
|
+
// Encrypt the private key before storage (AES-256-GCM)
|
|
196
|
+
const { encrypted, iv, tag } = encryptPrivateKey(keyPair.privateKeyPem, config.signingKeyEncryptionKey);
|
|
197
|
+
// Insert the new key with encrypted private key
|
|
198
|
+
await pool.query(`INSERT INTO signing_keys (kid, algorithm, public_key, private_key, private_key_iv, private_key_tag, encrypted, status, activated_at)
|
|
199
|
+
VALUES ($1, $2, $3, $4, $5, $6, true, 'active', NOW())`, [keyPair.kid, keyPair.algorithm, keyPair.publicKeyPem, encrypted, iv, tag]);
|
|
200
|
+
logger.info({ kid: keyPair.kid }, 'Auto-generated signing key inserted into database');
|
|
201
|
+
// Reload keys from DB to get the full record including generated UUID
|
|
202
|
+
records = await loadSigningKeysFromDb();
|
|
203
|
+
}
|
|
204
|
+
return signingKeysToJwks(records);
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=signing-keys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signing-keys.js","sourceRoot":"","sources":["../../src/lib/signing-keys.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dashboard statistics service.
|
|
3
|
+
*
|
|
4
|
+
* Provides system-wide and per-organization statistics for the admin
|
|
5
|
+
* dashboard. All functions are standalone (no class), use parameterized
|
|
6
|
+
* SQL queries, and return typed result objects.
|
|
7
|
+
*
|
|
8
|
+
* Login activity metrics are derived from the `audit_log` table using
|
|
9
|
+
* event type filters. Entity counts use `GROUP BY status` for efficient
|
|
10
|
+
* aggregation.
|
|
11
|
+
*
|
|
12
|
+
* @module stats
|
|
13
|
+
* @see 05-dashboard-sessions-history.md
|
|
14
|
+
*/
|
|
15
|
+
/** Count breakdown by entity status */
|
|
16
|
+
export interface StatusCounts {
|
|
17
|
+
total: number;
|
|
18
|
+
[status: string]: number;
|
|
19
|
+
}
|
|
20
|
+
/** Login activity for a time window */
|
|
21
|
+
export interface LoginActivity {
|
|
22
|
+
successful: number;
|
|
23
|
+
failed: number;
|
|
24
|
+
}
|
|
25
|
+
/** System health check results */
|
|
26
|
+
export interface SystemHealth {
|
|
27
|
+
database: boolean;
|
|
28
|
+
redis: boolean;
|
|
29
|
+
}
|
|
30
|
+
/** Full stats overview response */
|
|
31
|
+
export interface StatsOverview {
|
|
32
|
+
organizations: StatusCounts;
|
|
33
|
+
users: StatusCounts & {
|
|
34
|
+
newLast7d: number;
|
|
35
|
+
newLast30d: number;
|
|
36
|
+
activeLast30d: number;
|
|
37
|
+
};
|
|
38
|
+
applications: StatusCounts;
|
|
39
|
+
clients: StatusCounts;
|
|
40
|
+
loginActivity: {
|
|
41
|
+
last24h: LoginActivity;
|
|
42
|
+
last7d: LoginActivity;
|
|
43
|
+
last30d: LoginActivity;
|
|
44
|
+
};
|
|
45
|
+
systemHealth: SystemHealth;
|
|
46
|
+
generatedAt: string;
|
|
47
|
+
}
|
|
48
|
+
/** Per-org stats response (scoped to a single organization) */
|
|
49
|
+
export interface OrgStats {
|
|
50
|
+
organizationId: string;
|
|
51
|
+
users: StatusCounts & {
|
|
52
|
+
newLast7d: number;
|
|
53
|
+
newLast30d: number;
|
|
54
|
+
activeLast30d: number;
|
|
55
|
+
};
|
|
56
|
+
clients: StatusCounts;
|
|
57
|
+
loginActivity: {
|
|
58
|
+
last24h: LoginActivity;
|
|
59
|
+
last7d: LoginActivity;
|
|
60
|
+
last30d: LoginActivity;
|
|
61
|
+
};
|
|
62
|
+
generatedAt: string;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Get system-wide dashboard statistics.
|
|
66
|
+
*
|
|
67
|
+
* Aggregates counts across all organizations, user activity metrics,
|
|
68
|
+
* login event history from the audit log, and system health checks.
|
|
69
|
+
*
|
|
70
|
+
* @returns Full stats overview
|
|
71
|
+
*/
|
|
72
|
+
export declare function getStatsOverview(): Promise<StatsOverview>;
|
|
73
|
+
/**
|
|
74
|
+
* Get per-organization dashboard statistics.
|
|
75
|
+
*
|
|
76
|
+
* All counts are scoped to the specified organization. Login activity
|
|
77
|
+
* is filtered by `organization_id` in the audit log.
|
|
78
|
+
*
|
|
79
|
+
* @param orgId - Organization UUID to scope statistics to
|
|
80
|
+
* @returns Per-org stats
|
|
81
|
+
*/
|
|
82
|
+
export declare function getOrgStats(orgId: string): Promise<OrgStats>;
|
|
83
|
+
//# sourceMappingURL=stats.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../../src/lib/stats.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAQH,uCAAuC;AACvC,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;CAC1B;AAED,uCAAuC;AACvC,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,kCAAkC;AAClC,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,mCAAmC;AACnC,MAAM,WAAW,aAAa;IAC5B,aAAa,EAAE,YAAY,CAAC;IAC5B,KAAK,EAAE,YAAY,GAAG;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC;IACvF,YAAY,EAAE,YAAY,CAAC;IAC3B,OAAO,EAAE,YAAY,CAAC;IACtB,aAAa,EAAE;QACb,OAAO,EAAE,aAAa,CAAC;QACvB,MAAM,EAAE,aAAa,CAAC;QACtB,OAAO,EAAE,aAAa,CAAC;KACxB,CAAC;IACF,YAAY,EAAE,YAAY,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,+DAA+D;AAC/D,MAAM,WAAW,QAAQ;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,YAAY,GAAG;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC;IACvF,OAAO,EAAE,YAAY,CAAC;IACtB,aAAa,EAAE;QACb,OAAO,EAAE,aAAa,CAAC;QACvB,MAAM,EAAE,aAAa,CAAC;QACtB,OAAO,EAAE,aAAa,CAAC;KACxB,CAAC;IACF,WAAW,EAAE,MAAM,CAAC;CACrB;AAgHD;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,aAAa,CAAC,CAkD/D;AAED;;;;;;;;GAQG;AACH,wBAAsB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAqClE"}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dashboard statistics service.
|
|
3
|
+
*
|
|
4
|
+
* Provides system-wide and per-organization statistics for the admin
|
|
5
|
+
* dashboard. All functions are standalone (no class), use parameterized
|
|
6
|
+
* SQL queries, and return typed result objects.
|
|
7
|
+
*
|
|
8
|
+
* Login activity metrics are derived from the `audit_log` table using
|
|
9
|
+
* event type filters. Entity counts use `GROUP BY status` for efficient
|
|
10
|
+
* aggregation.
|
|
11
|
+
*
|
|
12
|
+
* @module stats
|
|
13
|
+
* @see 05-dashboard-sessions-history.md
|
|
14
|
+
*/
|
|
15
|
+
import { getPool } from './database.js';
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Internal helpers
|
|
18
|
+
// ============================================================================
|
|
19
|
+
/**
|
|
20
|
+
* Count rows grouped by status for an entity table.
|
|
21
|
+
* Returns { total, active, suspended, ... } with all statuses found.
|
|
22
|
+
*/
|
|
23
|
+
async function countByStatus(table, orgFilter) {
|
|
24
|
+
const pool = getPool();
|
|
25
|
+
const conditions = orgFilter ? `WHERE ${orgFilter.column} = $1` : '';
|
|
26
|
+
const params = orgFilter ? [orgFilter.value] : [];
|
|
27
|
+
const { rows } = await pool.query(`SELECT status, COUNT(*)::text AS count FROM ${table} ${conditions} GROUP BY status`, params);
|
|
28
|
+
const result = { total: 0 };
|
|
29
|
+
for (const row of rows) {
|
|
30
|
+
const count = parseInt(row.count, 10);
|
|
31
|
+
result[row.status] = count;
|
|
32
|
+
result.total += count;
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Count new users created within a given interval.
|
|
38
|
+
*/
|
|
39
|
+
async function countNewUsers(interval, orgId) {
|
|
40
|
+
const pool = getPool();
|
|
41
|
+
const orgFilter = orgId ? ' AND organization_id = $1' : '';
|
|
42
|
+
const params = orgId ? [orgId] : [];
|
|
43
|
+
const { rows } = await pool.query(`SELECT COUNT(*)::text AS count FROM users WHERE created_at > NOW() - $${orgId ? '2' : '1'}::interval${orgFilter}`, [...params, interval]);
|
|
44
|
+
return parseInt(rows[0].count, 10);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Count users active (logged in) within a given interval.
|
|
48
|
+
*/
|
|
49
|
+
async function countActiveUsers(interval, orgId) {
|
|
50
|
+
const pool = getPool();
|
|
51
|
+
const orgFilter = orgId ? ' AND organization_id = $1' : '';
|
|
52
|
+
const params = orgId ? [orgId] : [];
|
|
53
|
+
const { rows } = await pool.query(`SELECT COUNT(*)::text AS count FROM users WHERE last_login_at > NOW() - $${orgId ? '2' : '1'}::interval${orgFilter}`, [...params, interval]);
|
|
54
|
+
return parseInt(rows[0].count, 10);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Count login events from audit_log for a time window.
|
|
58
|
+
* Successful: event_type LIKE 'user.login.%' AND NOT LIKE '%.failed'
|
|
59
|
+
* Failed: event_type LIKE 'user.login.%.failed'
|
|
60
|
+
*/
|
|
61
|
+
async function countLoginActivity(interval, orgId) {
|
|
62
|
+
const pool = getPool();
|
|
63
|
+
const orgFilter = orgId ? ' AND organization_id = $1' : '';
|
|
64
|
+
const baseParams = orgId ? [orgId] : [];
|
|
65
|
+
const intervalParam = `$${orgId ? '2' : '1'}`;
|
|
66
|
+
const { rows } = await pool.query(`SELECT
|
|
67
|
+
CASE
|
|
68
|
+
WHEN event_type LIKE '%.failed' THEN 'failed'
|
|
69
|
+
ELSE 'successful'
|
|
70
|
+
END AS category,
|
|
71
|
+
COUNT(*)::text AS count
|
|
72
|
+
FROM audit_log
|
|
73
|
+
WHERE event_type LIKE 'user.login.%'
|
|
74
|
+
AND created_at > NOW() - ${intervalParam}::interval
|
|
75
|
+
${orgFilter}
|
|
76
|
+
GROUP BY category`, [...baseParams, interval]);
|
|
77
|
+
const result = { successful: 0, failed: 0 };
|
|
78
|
+
for (const row of rows) {
|
|
79
|
+
if (row.category === 'successful')
|
|
80
|
+
result.successful = parseInt(row.count, 10);
|
|
81
|
+
if (row.category === 'failed')
|
|
82
|
+
result.failed = parseInt(row.count, 10);
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Check system health (database connectivity).
|
|
88
|
+
* Redis health is checked separately since it may not be available
|
|
89
|
+
* from a pure database context.
|
|
90
|
+
*/
|
|
91
|
+
async function checkDatabaseHealth() {
|
|
92
|
+
try {
|
|
93
|
+
const pool = getPool();
|
|
94
|
+
await pool.query('SELECT 1');
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// ============================================================================
|
|
102
|
+
// Public API
|
|
103
|
+
// ============================================================================
|
|
104
|
+
/**
|
|
105
|
+
* Get system-wide dashboard statistics.
|
|
106
|
+
*
|
|
107
|
+
* Aggregates counts across all organizations, user activity metrics,
|
|
108
|
+
* login event history from the audit log, and system health checks.
|
|
109
|
+
*
|
|
110
|
+
* @returns Full stats overview
|
|
111
|
+
*/
|
|
112
|
+
export async function getStatsOverview() {
|
|
113
|
+
const [organizations, usersBase, applications, clients, newUsers7d, newUsers30d, activeUsers30d, loginLast24h, loginLast7d, loginLast30d, dbHealth,] = await Promise.all([
|
|
114
|
+
countByStatus('organizations'),
|
|
115
|
+
countByStatus('users'),
|
|
116
|
+
countByStatus('applications'),
|
|
117
|
+
countByStatus('clients'),
|
|
118
|
+
countNewUsers('7 days'),
|
|
119
|
+
countNewUsers('30 days'),
|
|
120
|
+
countActiveUsers('30 days'),
|
|
121
|
+
countLoginActivity('24 hours'),
|
|
122
|
+
countLoginActivity('7 days'),
|
|
123
|
+
countLoginActivity('30 days'),
|
|
124
|
+
checkDatabaseHealth(),
|
|
125
|
+
]);
|
|
126
|
+
return {
|
|
127
|
+
organizations,
|
|
128
|
+
users: {
|
|
129
|
+
...usersBase,
|
|
130
|
+
newLast7d: newUsers7d,
|
|
131
|
+
newLast30d: newUsers30d,
|
|
132
|
+
activeLast30d: activeUsers30d,
|
|
133
|
+
},
|
|
134
|
+
applications,
|
|
135
|
+
clients,
|
|
136
|
+
loginActivity: {
|
|
137
|
+
last24h: loginLast24h,
|
|
138
|
+
last7d: loginLast7d,
|
|
139
|
+
last30d: loginLast30d,
|
|
140
|
+
},
|
|
141
|
+
systemHealth: {
|
|
142
|
+
database: dbHealth,
|
|
143
|
+
// Redis health is checked via the health endpoint; we report true here
|
|
144
|
+
// since if we got this far the DB is reachable. Full health check is at GET /health.
|
|
145
|
+
redis: true,
|
|
146
|
+
},
|
|
147
|
+
generatedAt: new Date().toISOString(),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Get per-organization dashboard statistics.
|
|
152
|
+
*
|
|
153
|
+
* All counts are scoped to the specified organization. Login activity
|
|
154
|
+
* is filtered by `organization_id` in the audit log.
|
|
155
|
+
*
|
|
156
|
+
* @param orgId - Organization UUID to scope statistics to
|
|
157
|
+
* @returns Per-org stats
|
|
158
|
+
*/
|
|
159
|
+
export async function getOrgStats(orgId) {
|
|
160
|
+
const [usersBase, clientsCounts, newUsers7d, newUsers30d, activeUsers30d, loginLast24h, loginLast7d, loginLast30d,] = await Promise.all([
|
|
161
|
+
countByStatus('users', { column: 'organization_id', value: orgId }),
|
|
162
|
+
countByStatus('clients', { column: 'organization_id', value: orgId }),
|
|
163
|
+
countNewUsers('7 days', orgId),
|
|
164
|
+
countNewUsers('30 days', orgId),
|
|
165
|
+
countActiveUsers('30 days', orgId),
|
|
166
|
+
countLoginActivity('24 hours', orgId),
|
|
167
|
+
countLoginActivity('7 days', orgId),
|
|
168
|
+
countLoginActivity('30 days', orgId),
|
|
169
|
+
]);
|
|
170
|
+
return {
|
|
171
|
+
organizationId: orgId,
|
|
172
|
+
users: {
|
|
173
|
+
...usersBase,
|
|
174
|
+
newLast7d: newUsers7d,
|
|
175
|
+
newLast30d: newUsers30d,
|
|
176
|
+
activeLast30d: activeUsers30d,
|
|
177
|
+
},
|
|
178
|
+
clients: clientsCounts,
|
|
179
|
+
loginActivity: {
|
|
180
|
+
last24h: loginLast24h,
|
|
181
|
+
last7d: loginLast7d,
|
|
182
|
+
last30d: loginLast30d,
|
|
183
|
+
},
|
|
184
|
+
generatedAt: new Date().toISOString(),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=stats.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stats.js","sourceRoot":"","sources":["../../src/lib/stats.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Super-admin user protection module.
|
|
3
|
+
*
|
|
4
|
+
* The super-admin user (created during `porta init`) is the system bootstrap
|
|
5
|
+
* user and cannot be subjected to destructive operations. This module provides
|
|
6
|
+
* detection and guard functions to enforce this protection.
|
|
7
|
+
*
|
|
8
|
+
* The super-admin user ID is stored in `system_config` under the key
|
|
9
|
+
* `super_admin_user_id` during `porta init`. This is the single source
|
|
10
|
+
* of truth for super-admin detection.
|
|
11
|
+
*
|
|
12
|
+
* Protected operations (all throw 403 Forbidden):
|
|
13
|
+
* - Delete
|
|
14
|
+
* - Suspend
|
|
15
|
+
* - Lock
|
|
16
|
+
* - Deactivate
|
|
17
|
+
* - Remove porta-super-admin role
|
|
18
|
+
* - Manage 2FA (disable, reset, regenerate recovery codes)
|
|
19
|
+
*
|
|
20
|
+
* @module lib/super-admin-protection
|
|
21
|
+
*/
|
|
22
|
+
/** System config key where the super-admin user ID is stored */
|
|
23
|
+
export declare const SUPER_ADMIN_USER_ID_KEY = "super_admin_user_id";
|
|
24
|
+
/**
|
|
25
|
+
* Operations that are forbidden on the super-admin user.
|
|
26
|
+
* Used in error messages to clearly communicate what was attempted.
|
|
27
|
+
*/
|
|
28
|
+
export declare const PROTECTED_OPERATIONS: readonly ['delete', 'suspend', 'lock', 'deactivate', 'remove-super-admin-role', 'manage-2fa'];
|
|
29
|
+
export type ProtectedOperation = (typeof PROTECTED_OPERATIONS)[number];
|
|
30
|
+
/**
|
|
31
|
+
* Check if a user is the protected super-admin user.
|
|
32
|
+
*
|
|
33
|
+
* Queries `system_config` for the `super_admin_user_id` key (with 60s cache)
|
|
34
|
+
* and compares it to the given user ID. Returns false if no super-admin user
|
|
35
|
+
* has been configured (i.e., before `porta init` has been run).
|
|
36
|
+
*
|
|
37
|
+
* @param userId - The user ID to check
|
|
38
|
+
* @returns true if the user is the super-admin
|
|
39
|
+
*/
|
|
40
|
+
export declare function isSuperAdminUser(userId: string): Promise<boolean>;
|
|
41
|
+
/**
|
|
42
|
+
* Error thrown when a protected operation is attempted on the super-admin user.
|
|
43
|
+
* Carries a 403 status code for HTTP responses.
|
|
44
|
+
*/
|
|
45
|
+
export declare class SuperAdminProtectionError extends Error {
|
|
46
|
+
/** HTTP status code (always 403) */
|
|
47
|
+
readonly status = 403;
|
|
48
|
+
/** The operation that was attempted */
|
|
49
|
+
readonly operation: ProtectedOperation;
|
|
50
|
+
constructor(operation: ProtectedOperation);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Guard function — throws 403 if attempting a forbidden action on the super-admin user.
|
|
54
|
+
*
|
|
55
|
+
* Call this before executing any destructive operation on a user:
|
|
56
|
+
* ```typescript
|
|
57
|
+
* await guardSuperAdmin(userId, 'suspend');
|
|
58
|
+
* // ... proceed with suspend logic
|
|
59
|
+
* ```
|
|
60
|
+
*
|
|
61
|
+
* If the user is not the super-admin, this function is a no-op.
|
|
62
|
+
* If the user IS the super-admin, it throws a `SuperAdminProtectionError`.
|
|
63
|
+
*
|
|
64
|
+
* @param userId - The user ID being acted upon
|
|
65
|
+
* @param operation - The operation being attempted
|
|
66
|
+
* @throws SuperAdminProtectionError if the user is the super-admin
|
|
67
|
+
*/
|
|
68
|
+
export declare function guardSuperAdmin(userId: string, operation: ProtectedOperation): Promise<void>;
|
|
69
|
+
//# sourceMappingURL=super-admin-protection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"super-admin-protection.d.ts","sourceRoot":"","sources":["../../src/lib/super-admin-protection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAQH,gEAAgE;AAChE,eAAO,MAAM,uBAAuB,wBAAwB,CAAC;AAE7D;;;GAGG;AACH,eAAO,MAAM,oBAAoB,YAC/B,QAAQ,EACR,SAAS,EACT,MAAM,EACN,YAAY,EACZ,yBAAyB,EACzB,YAAY,CACJ,CAAC;AAEX,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAMvE;;;;;;;;;GASG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQvE;AAMD;;;GAGG;AACH,qBAAa,yBAA0B,SAAQ,KAAK;IAClD,oCAAoC;IACpC,QAAQ,CAAC,MAAM,OAAO;IACtB,uCAAuC;IACvC,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAC;IAEvC,YAAY,SAAS,EAAE,kBAAkB,EAIxC;CACF;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,kBAAkB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAKf"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Super-admin user protection module.
|
|
3
|
+
*
|
|
4
|
+
* The super-admin user (created during `porta init`) is the system bootstrap
|
|
5
|
+
* user and cannot be subjected to destructive operations. This module provides
|
|
6
|
+
* detection and guard functions to enforce this protection.
|
|
7
|
+
*
|
|
8
|
+
* The super-admin user ID is stored in `system_config` under the key
|
|
9
|
+
* `super_admin_user_id` during `porta init`. This is the single source
|
|
10
|
+
* of truth for super-admin detection.
|
|
11
|
+
*
|
|
12
|
+
* Protected operations (all throw 403 Forbidden):
|
|
13
|
+
* - Delete
|
|
14
|
+
* - Suspend
|
|
15
|
+
* - Lock
|
|
16
|
+
* - Deactivate
|
|
17
|
+
* - Remove porta-super-admin role
|
|
18
|
+
* - Manage 2FA (disable, reset, regenerate recovery codes)
|
|
19
|
+
*
|
|
20
|
+
* @module lib/super-admin-protection
|
|
21
|
+
*/
|
|
22
|
+
import { getSystemConfigString } from './system-config.js';
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// Constants
|
|
25
|
+
// ============================================================================
|
|
26
|
+
/** System config key where the super-admin user ID is stored */
|
|
27
|
+
export const SUPER_ADMIN_USER_ID_KEY = 'super_admin_user_id';
|
|
28
|
+
/**
|
|
29
|
+
* Operations that are forbidden on the super-admin user.
|
|
30
|
+
* Used in error messages to clearly communicate what was attempted.
|
|
31
|
+
*/
|
|
32
|
+
export const PROTECTED_OPERATIONS = [
|
|
33
|
+
'delete',
|
|
34
|
+
'suspend',
|
|
35
|
+
'lock',
|
|
36
|
+
'deactivate',
|
|
37
|
+
'remove-super-admin-role',
|
|
38
|
+
'manage-2fa',
|
|
39
|
+
];
|
|
40
|
+
// ============================================================================
|
|
41
|
+
// Detection
|
|
42
|
+
// ============================================================================
|
|
43
|
+
/**
|
|
44
|
+
* Check if a user is the protected super-admin user.
|
|
45
|
+
*
|
|
46
|
+
* Queries `system_config` for the `super_admin_user_id` key (with 60s cache)
|
|
47
|
+
* and compares it to the given user ID. Returns false if no super-admin user
|
|
48
|
+
* has been configured (i.e., before `porta init` has been run).
|
|
49
|
+
*
|
|
50
|
+
* @param userId - The user ID to check
|
|
51
|
+
* @returns true if the user is the super-admin
|
|
52
|
+
*/
|
|
53
|
+
export async function isSuperAdminUser(userId) {
|
|
54
|
+
// getSystemConfigString returns the fallback when the key doesn't exist.
|
|
55
|
+
// Empty string fallback means: if no super-admin is configured, nobody matches.
|
|
56
|
+
const superAdminUserId = await getSystemConfigString(SUPER_ADMIN_USER_ID_KEY, '');
|
|
57
|
+
if (!superAdminUserId) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
return superAdminUserId === userId;
|
|
61
|
+
}
|
|
62
|
+
// ============================================================================
|
|
63
|
+
// Guard
|
|
64
|
+
// ============================================================================
|
|
65
|
+
/**
|
|
66
|
+
* Error thrown when a protected operation is attempted on the super-admin user.
|
|
67
|
+
* Carries a 403 status code for HTTP responses.
|
|
68
|
+
*/
|
|
69
|
+
export class SuperAdminProtectionError extends Error {
|
|
70
|
+
/** HTTP status code (always 403) */
|
|
71
|
+
status = 403;
|
|
72
|
+
/** The operation that was attempted */
|
|
73
|
+
operation;
|
|
74
|
+
constructor(operation) {
|
|
75
|
+
super(`Cannot ${operation} the super-admin user`);
|
|
76
|
+
this.name = 'SuperAdminProtectionError';
|
|
77
|
+
this.operation = operation;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Guard function — throws 403 if attempting a forbidden action on the super-admin user.
|
|
82
|
+
*
|
|
83
|
+
* Call this before executing any destructive operation on a user:
|
|
84
|
+
* ```typescript
|
|
85
|
+
* await guardSuperAdmin(userId, 'suspend');
|
|
86
|
+
* // ... proceed with suspend logic
|
|
87
|
+
* ```
|
|
88
|
+
*
|
|
89
|
+
* If the user is not the super-admin, this function is a no-op.
|
|
90
|
+
* If the user IS the super-admin, it throws a `SuperAdminProtectionError`.
|
|
91
|
+
*
|
|
92
|
+
* @param userId - The user ID being acted upon
|
|
93
|
+
* @param operation - The operation being attempted
|
|
94
|
+
* @throws SuperAdminProtectionError if the user is the super-admin
|
|
95
|
+
*/
|
|
96
|
+
export async function guardSuperAdmin(userId, operation) {
|
|
97
|
+
const isProtected = await isSuperAdminUser(userId);
|
|
98
|
+
if (isProtected) {
|
|
99
|
+
throw new SuperAdminProtectionError(operation);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=super-admin-protection.js.map
|