alepha 0.15.1 → 0.15.3
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/README.md +68 -80
- package/dist/api/audits/index.d.ts +10 -33
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/audits/index.js +10 -33
- package/dist/api/audits/index.js.map +1 -1
- package/dist/api/files/index.d.ts +10 -3
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/files/index.js +10 -3
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.d.ts +162 -155
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js +10 -3
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/keys/index.d.ts +413 -0
- package/dist/api/keys/index.d.ts.map +1 -0
- package/dist/api/keys/index.js +476 -0
- package/dist/api/keys/index.js.map +1 -0
- package/dist/api/notifications/index.d.ts +10 -4
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/notifications/index.js +10 -4
- package/dist/api/notifications/index.js.map +1 -1
- package/dist/api/parameters/index.d.ts +43 -50
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/parameters/index.js +30 -37
- package/dist/api/parameters/index.js.map +1 -1
- package/dist/api/users/index.d.ts +1081 -760
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +2539 -218
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.d.ts +138 -132
- package/dist/api/verifications/index.d.ts.map +1 -1
- package/dist/api/verifications/index.js +12 -4
- package/dist/api/verifications/index.js.map +1 -1
- package/dist/batch/index.d.ts +20 -40
- package/dist/batch/index.d.ts.map +1 -1
- package/dist/batch/index.js +31 -44
- package/dist/batch/index.js.map +1 -1
- package/dist/bucket/index.d.ts +440 -8
- package/dist/bucket/index.d.ts.map +1 -1
- package/dist/bucket/index.js +1861 -12
- package/dist/bucket/index.js.map +1 -1
- package/dist/cache/core/index.d.ts +179 -7
- package/dist/cache/core/index.d.ts.map +1 -1
- package/dist/cache/core/index.js +213 -7
- package/dist/cache/core/index.js.map +1 -1
- package/dist/cache/redis/index.d.ts +1 -0
- package/dist/cache/redis/index.d.ts.map +1 -1
- package/dist/cache/redis/index.js +4 -0
- package/dist/cache/redis/index.js.map +1 -1
- package/dist/cli/index.d.ts +638 -5645
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +2550 -368
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +203 -45
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js +2060 -71
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +70 -40
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +34 -13
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +90 -40
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +70 -40
- package/dist/core/index.native.js.map +1 -1
- package/dist/datetime/index.d.ts +15 -0
- package/dist/datetime/index.d.ts.map +1 -1
- package/dist/datetime/index.js +15 -0
- package/dist/datetime/index.js.map +1 -1
- package/dist/email/index.d.ts +323 -20
- package/dist/email/index.d.ts.map +1 -1
- package/dist/email/index.js +1857 -7
- package/dist/email/index.js.map +1 -1
- package/dist/fake/index.d.ts +90 -8
- package/dist/fake/index.d.ts.map +1 -1
- package/dist/fake/index.js +91 -20
- package/dist/fake/index.js.map +1 -1
- package/dist/lock/core/index.d.ts +11 -4
- package/dist/lock/core/index.d.ts.map +1 -1
- package/dist/lock/core/index.js +11 -4
- package/dist/lock/core/index.js.map +1 -1
- package/dist/logger/index.d.ts +17 -66
- package/dist/logger/index.d.ts.map +1 -1
- package/dist/logger/index.js +14 -63
- package/dist/logger/index.js.map +1 -1
- package/dist/mcp/index.d.ts +10 -30
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +12 -35
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/index.browser.js +3 -3
- package/dist/orm/index.browser.js.map +1 -1
- package/dist/orm/index.bun.js +39 -20
- package/dist/orm/index.bun.js.map +1 -1
- package/dist/orm/index.d.ts +517 -540
- package/dist/orm/index.d.ts.map +1 -1
- package/dist/orm/index.js +58 -71
- package/dist/orm/index.js.map +1 -1
- package/dist/queue/core/index.d.ts +18 -10
- package/dist/queue/core/index.d.ts.map +1 -1
- package/dist/queue/core/index.js +14 -6
- package/dist/queue/core/index.js.map +1 -1
- package/dist/react/auth/index.browser.js +108 -0
- package/dist/react/auth/index.browser.js.map +1 -0
- package/dist/react/auth/index.d.ts +100 -0
- package/dist/react/auth/index.d.ts.map +1 -0
- package/dist/react/auth/index.js +145 -0
- package/dist/react/auth/index.js.map +1 -0
- package/dist/react/core/index.d.ts +469 -0
- package/dist/react/core/index.d.ts.map +1 -0
- package/dist/react/core/index.js +464 -0
- package/dist/react/core/index.js.map +1 -0
- package/dist/react/form/index.d.ts +232 -0
- package/dist/react/form/index.d.ts.map +1 -0
- package/dist/react/form/index.js +432 -0
- package/dist/react/form/index.js.map +1 -0
- package/dist/react/head/index.browser.js +423 -0
- package/dist/react/head/index.browser.js.map +1 -0
- package/dist/react/head/index.d.ts +288 -0
- package/dist/react/head/index.d.ts.map +1 -0
- package/dist/react/head/index.js +465 -0
- package/dist/react/head/index.js.map +1 -0
- package/dist/react/i18n/index.d.ts +175 -0
- package/dist/react/i18n/index.d.ts.map +1 -0
- package/dist/react/i18n/index.js +224 -0
- package/dist/react/i18n/index.js.map +1 -0
- package/dist/react/router/index.browser.js +1974 -0
- package/dist/react/router/index.browser.js.map +1 -0
- package/dist/react/router/index.d.ts +1956 -0
- package/dist/react/router/index.d.ts.map +1 -0
- package/dist/react/router/index.js +4722 -0
- package/dist/react/router/index.js.map +1 -0
- package/dist/react/websocket/index.d.ts +117 -0
- package/dist/react/websocket/index.d.ts.map +1 -0
- package/dist/react/websocket/index.js +107 -0
- package/dist/react/websocket/index.js.map +1 -0
- package/dist/redis/index.bun.js +4 -0
- package/dist/redis/index.bun.js.map +1 -1
- package/dist/redis/index.d.ts +41 -44
- package/dist/redis/index.d.ts.map +1 -1
- package/dist/redis/index.js +16 -25
- package/dist/redis/index.js.map +1 -1
- package/dist/retry/index.d.ts +11 -2
- package/dist/retry/index.d.ts.map +1 -1
- package/dist/retry/index.js +11 -2
- package/dist/retry/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +11 -2
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/index.js +11 -2
- package/dist/scheduler/index.js.map +1 -1
- package/dist/security/index.d.ts +140 -49
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +164 -32
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +12 -7
- package/dist/server/auth/index.d.ts.map +1 -1
- package/dist/server/auth/index.js +12 -7
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cache/index.d.ts +7 -22
- package/dist/server/cache/index.d.ts.map +1 -1
- package/dist/server/cache/index.js +7 -22
- package/dist/server/cache/index.js.map +1 -1
- package/dist/server/compress/index.d.ts +10 -2
- package/dist/server/compress/index.d.ts.map +1 -1
- package/dist/server/compress/index.js +10 -2
- package/dist/server/compress/index.js.map +1 -1
- package/dist/server/cookies/index.d.ts +40 -16
- package/dist/server/cookies/index.d.ts.map +1 -1
- package/dist/server/cookies/index.js +7 -5
- package/dist/server/cookies/index.js.map +1 -1
- package/dist/server/core/index.d.ts +124 -23
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js +231 -14
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/cors/index.d.ts +13 -23
- package/dist/server/cors/index.d.ts.map +1 -1
- package/dist/server/cors/index.js +7 -21
- package/dist/server/cors/index.js.map +1 -1
- package/dist/server/health/index.d.ts +8 -2
- package/dist/server/health/index.d.ts.map +1 -1
- package/dist/server/health/index.js +8 -2
- package/dist/server/health/index.js.map +1 -1
- package/dist/server/helmet/index.d.ts +11 -3
- package/dist/server/helmet/index.d.ts.map +1 -1
- package/dist/server/helmet/index.js +11 -3
- package/dist/server/helmet/index.js.map +1 -1
- package/dist/server/links/index.d.ts +11 -6
- package/dist/server/links/index.d.ts.map +1 -1
- package/dist/server/links/index.js +11 -6
- package/dist/server/links/index.js.map +1 -1
- package/dist/server/metrics/index.d.ts +10 -3
- package/dist/server/metrics/index.d.ts.map +1 -1
- package/dist/server/metrics/index.js +10 -3
- package/dist/server/metrics/index.js.map +1 -1
- package/dist/server/multipart/index.d.ts +9 -3
- package/dist/server/multipart/index.d.ts.map +1 -1
- package/dist/server/multipart/index.js +9 -3
- package/dist/server/multipart/index.js.map +1 -1
- package/dist/server/proxy/index.d.ts +8 -2
- package/dist/server/proxy/index.d.ts.map +1 -1
- package/dist/server/proxy/index.js +8 -2
- package/dist/server/proxy/index.js.map +1 -1
- package/dist/server/rate-limit/index.d.ts +30 -35
- package/dist/server/rate-limit/index.d.ts.map +1 -1
- package/dist/server/rate-limit/index.js +18 -55
- package/dist/server/rate-limit/index.js.map +1 -1
- package/dist/server/static/index.d.ts +137 -4
- package/dist/server/static/index.d.ts.map +1 -1
- package/dist/server/static/index.js +1853 -5
- package/dist/server/static/index.js.map +1 -1
- package/dist/server/swagger/index.d.ts +309 -6
- package/dist/server/swagger/index.d.ts.map +1 -1
- package/dist/server/swagger/index.js +1854 -6
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/sms/index.d.ts +309 -7
- package/dist/sms/index.d.ts.map +1 -1
- package/dist/sms/index.js +1856 -7
- package/dist/sms/index.js.map +1 -1
- package/dist/system/index.browser.js +1218 -0
- package/dist/system/index.browser.js.map +1 -0
- package/dist/{file → system}/index.d.ts +343 -16
- package/dist/system/index.d.ts.map +1 -0
- package/dist/{file → system}/index.js +419 -22
- package/dist/system/index.js.map +1 -0
- package/dist/thread/index.d.ts +11 -2
- package/dist/thread/index.d.ts.map +1 -1
- package/dist/thread/index.js +11 -2
- package/dist/thread/index.js.map +1 -1
- package/dist/topic/core/index.d.ts +12 -5
- package/dist/topic/core/index.d.ts.map +1 -1
- package/dist/topic/core/index.js +12 -5
- package/dist/topic/core/index.js.map +1 -1
- package/dist/vite/index.d.ts +5 -6272
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +23 -10
- package/dist/vite/index.js.map +1 -1
- package/dist/websocket/index.d.ts +12 -8
- package/dist/websocket/index.d.ts.map +1 -1
- package/dist/websocket/index.js +12 -8
- package/dist/websocket/index.js.map +1 -1
- package/package.json +82 -11
- package/src/api/audits/index.ts +10 -33
- package/src/api/files/__tests__/$bucket.spec.ts +1 -1
- package/src/api/files/controllers/AdminFileStatsController.spec.ts +1 -1
- package/src/api/files/controllers/FileController.spec.ts +1 -1
- package/src/api/files/index.ts +10 -3
- package/src/api/files/jobs/FileJobs.spec.ts +1 -1
- package/src/api/files/services/FileService.spec.ts +1 -1
- package/src/api/jobs/index.ts +10 -3
- package/src/api/keys/controllers/AdminApiKeyController.ts +75 -0
- package/src/api/keys/controllers/ApiKeyController.ts +103 -0
- package/src/api/keys/entities/apiKeyEntity.ts +41 -0
- package/src/api/keys/index.ts +49 -0
- package/src/api/keys/schemas/adminApiKeyQuerySchema.ts +7 -0
- package/src/api/keys/schemas/adminApiKeyResourceSchema.ts +17 -0
- package/src/api/keys/schemas/createApiKeyBodySchema.ts +7 -0
- package/src/api/keys/schemas/createApiKeyResponseSchema.ts +11 -0
- package/src/api/keys/schemas/listApiKeyResponseSchema.ts +15 -0
- package/src/api/keys/schemas/revokeApiKeyParamsSchema.ts +5 -0
- package/src/api/keys/schemas/revokeApiKeyResponseSchema.ts +5 -0
- package/src/api/keys/services/ApiKeyService.spec.ts +553 -0
- package/src/api/keys/services/ApiKeyService.ts +306 -0
- package/src/api/logs/TODO.md +55 -0
- package/src/api/notifications/index.ts +10 -4
- package/src/api/parameters/index.ts +9 -30
- package/src/api/parameters/primitives/$config.ts +12 -4
- package/src/api/parameters/services/ConfigStore.ts +9 -3
- package/src/api/users/__tests__/ApiKeys-integration.spec.ts +1035 -0
- package/src/api/users/__tests__/ApiKeys.spec.ts +401 -0
- package/src/api/users/index.ts +14 -3
- package/src/api/users/primitives/$realm.ts +33 -5
- package/src/api/users/providers/RealmProvider.ts +1 -12
- package/src/api/users/services/SessionService.ts +1 -1
- package/src/api/verifications/controllers/VerificationController.ts +2 -0
- package/src/api/verifications/index.ts +10 -4
- package/src/batch/index.ts +9 -36
- package/src/batch/primitives/$batch.ts +0 -8
- package/src/batch/providers/BatchProvider.ts +29 -2
- package/src/bucket/__tests__/shared.ts +1 -1
- package/src/bucket/index.ts +13 -6
- package/src/bucket/primitives/$bucket.ts +1 -1
- package/src/bucket/providers/LocalFileStorageProvider.ts +1 -1
- package/src/bucket/providers/MemoryFileStorageProvider.ts +1 -1
- package/src/cache/core/__tests__/shared.ts +30 -0
- package/src/cache/core/index.ts +11 -6
- package/src/cache/core/primitives/$cache.spec.ts +5 -0
- package/src/cache/core/providers/CacheProvider.ts +17 -0
- package/src/cache/core/providers/MemoryCacheProvider.ts +300 -1
- package/src/cache/redis/__tests__/cache-redis.spec.ts +5 -0
- package/src/cache/redis/providers/RedisCacheProvider.ts +9 -0
- package/src/cli/apps/AlephaCli.ts +1 -14
- package/src/cli/apps/AlephaPackageBuilderCli.ts +10 -1
- package/src/cli/atoms/buildOptions.ts +99 -9
- package/src/cli/commands/build.ts +150 -37
- package/src/cli/commands/db.ts +22 -18
- package/src/cli/commands/deploy.ts +1 -1
- package/src/cli/commands/dev.ts +1 -20
- package/src/cli/commands/gen/env.ts +5 -2
- package/src/cli/commands/gen/openapi.ts +5 -2
- package/src/cli/commands/init.spec.ts +588 -0
- package/src/cli/commands/init.ts +115 -58
- package/src/cli/commands/lint.ts +7 -1
- package/src/cli/commands/typecheck.ts +11 -0
- package/src/cli/providers/AppEntryProvider.ts +1 -1
- package/src/cli/providers/ViteBuildProvider.ts +8 -50
- package/src/cli/providers/ViteDevServerProvider.ts +35 -16
- package/src/cli/services/AlephaCliUtils.ts +52 -121
- package/src/cli/services/PackageManagerUtils.ts +129 -11
- package/src/cli/services/ProjectScaffolder.spec.ts +97 -0
- package/src/cli/services/ProjectScaffolder.ts +148 -81
- package/src/cli/services/ViteUtils.ts +82 -0
- package/src/cli/{assets/claudeMd.ts → templates/agentMd.ts} +37 -24
- package/src/cli/templates/apiAppSecurityTs.ts +11 -0
- package/src/cli/templates/apiIndexTs.ts +30 -0
- package/src/cli/templates/gitignore.ts +39 -0
- package/src/cli/{assets → templates}/mainCss.ts +11 -2
- package/src/cli/templates/mainServerTs.ts +33 -0
- package/src/cli/templates/webAppRouterTs.ts +74 -0
- package/src/cli/templates/webHelloComponentTsx.ts +30 -0
- package/src/command/helpers/Runner.spec.ts +139 -0
- package/src/command/helpers/Runner.ts +7 -22
- package/src/command/index.ts +12 -4
- package/src/command/providers/CliProvider.spec.ts +1392 -0
- package/src/command/providers/CliProvider.ts +320 -47
- package/src/core/Alepha.ts +34 -27
- package/src/core/__tests__/Alepha-start.spec.ts +4 -4
- package/src/core/helpers/jsonSchemaToTypeBox.spec.ts +771 -0
- package/src/core/helpers/jsonSchemaToTypeBox.ts +62 -10
- package/src/core/index.shared.ts +1 -0
- package/src/core/index.ts +20 -0
- package/src/core/providers/EventManager.spec.ts +0 -71
- package/src/core/providers/EventManager.ts +3 -15
- package/src/core/providers/Json.ts +2 -14
- package/src/datetime/index.ts +15 -0
- package/src/email/index.ts +10 -5
- package/src/email/providers/LocalEmailProvider.spec.ts +1 -1
- package/src/email/providers/LocalEmailProvider.ts +1 -1
- package/src/fake/__tests__/keyName.example.ts +1 -1
- package/src/fake/__tests__/keyName.spec.ts +5 -5
- package/src/fake/index.ts +9 -6
- package/src/fake/providers/FakeProvider.spec.ts +258 -40
- package/src/fake/providers/FakeProvider.ts +133 -19
- package/src/lock/core/index.ts +11 -4
- package/src/logger/index.ts +17 -66
- package/src/mcp/index.ts +10 -27
- package/src/mcp/transports/SseMcpTransport.ts +0 -11
- package/src/orm/__tests__/PostgresProvider.spec.ts +2 -2
- package/src/orm/index.browser.ts +2 -2
- package/src/orm/index.bun.ts +5 -3
- package/src/orm/index.ts +23 -53
- package/src/orm/providers/drivers/BunSqliteProvider.ts +5 -1
- package/src/orm/providers/drivers/CloudflareD1Provider.ts +57 -30
- package/src/orm/providers/drivers/DatabaseProvider.ts +9 -1
- package/src/orm/providers/drivers/NodeSqliteProvider.ts +4 -1
- package/src/orm/services/Repository.ts +7 -3
- package/src/queue/core/index.ts +14 -6
- package/src/react/auth/__tests__/$auth.spec.ts +202 -0
- package/src/react/auth/hooks/useAuth.ts +32 -0
- package/src/react/auth/index.browser.ts +13 -0
- package/src/react/auth/index.shared.ts +2 -0
- package/src/react/auth/index.ts +48 -0
- package/src/react/auth/providers/ReactAuthProvider.ts +16 -0
- package/src/react/auth/services/ReactAuth.ts +135 -0
- package/src/react/core/__tests__/Router.spec.tsx +169 -0
- package/src/react/core/components/ClientOnly.tsx +49 -0
- package/src/react/core/components/ErrorBoundary.tsx +73 -0
- package/src/react/core/contexts/AlephaContext.ts +7 -0
- package/src/react/core/contexts/AlephaProvider.tsx +42 -0
- package/src/react/core/hooks/useAction.browser.spec.tsx +569 -0
- package/src/react/core/hooks/useAction.ts +480 -0
- package/src/react/core/hooks/useAlepha.ts +26 -0
- package/src/react/core/hooks/useClient.ts +17 -0
- package/src/react/core/hooks/useEvents.ts +51 -0
- package/src/react/core/hooks/useInject.ts +12 -0
- package/src/react/core/hooks/useStore.ts +52 -0
- package/src/react/core/index.ts +90 -0
- package/src/react/form/components/FormState.tsx +17 -0
- package/src/react/form/errors/FormValidationError.ts +18 -0
- package/src/react/form/hooks/useForm.browser.spec.tsx +366 -0
- package/src/react/form/hooks/useForm.ts +47 -0
- package/src/react/form/hooks/useFormState.ts +130 -0
- package/src/react/form/index.ts +44 -0
- package/src/react/form/services/FormModel.ts +614 -0
- package/src/react/head/helpers/SeoExpander.spec.ts +203 -0
- package/src/react/head/helpers/SeoExpander.ts +142 -0
- package/src/react/head/hooks/useHead.spec.tsx +288 -0
- package/src/react/head/hooks/useHead.ts +62 -0
- package/src/react/head/index.browser.ts +26 -0
- package/src/react/head/index.ts +44 -0
- package/src/react/head/interfaces/Head.ts +105 -0
- package/src/react/head/primitives/$head.ts +25 -0
- package/src/react/head/providers/BrowserHeadProvider.browser.spec.ts +196 -0
- package/src/react/head/providers/BrowserHeadProvider.ts +212 -0
- package/src/react/head/providers/HeadProvider.ts +168 -0
- package/src/react/head/providers/ServerHeadProvider.ts +31 -0
- package/src/react/i18n/__tests__/integration.spec.tsx +239 -0
- package/src/react/i18n/components/Localize.spec.tsx +357 -0
- package/src/react/i18n/components/Localize.tsx +35 -0
- package/src/react/i18n/hooks/useI18n.browser.spec.tsx +438 -0
- package/src/react/i18n/hooks/useI18n.ts +18 -0
- package/src/react/i18n/index.ts +41 -0
- package/src/react/i18n/primitives/$dictionary.ts +69 -0
- package/src/react/i18n/providers/I18nProvider.spec.ts +389 -0
- package/src/react/i18n/providers/I18nProvider.ts +278 -0
- package/src/react/router/__tests__/page-head-browser.browser.spec.ts +95 -0
- package/src/react/router/__tests__/page-head.spec.ts +48 -0
- package/src/react/router/__tests__/seo-head.spec.ts +125 -0
- package/src/react/router/atoms/ssrManifestAtom.ts +58 -0
- package/src/react/router/components/ErrorViewer.tsx +872 -0
- package/src/react/router/components/Link.tsx +23 -0
- package/src/react/router/components/NestedView.tsx +223 -0
- package/src/react/router/components/NotFound.tsx +30 -0
- package/src/react/router/constants/PAGE_PRELOAD_KEY.ts +6 -0
- package/src/react/router/contexts/RouterLayerContext.ts +12 -0
- package/src/react/router/errors/Redirection.ts +28 -0
- package/src/react/router/hooks/useActive.ts +52 -0
- package/src/react/router/hooks/useQueryParams.ts +63 -0
- package/src/react/router/hooks/useRouter.ts +20 -0
- package/src/react/router/hooks/useRouterState.ts +11 -0
- package/src/react/router/index.browser.ts +45 -0
- package/src/react/router/index.shared.ts +19 -0
- package/src/react/router/index.ts +146 -0
- package/src/react/router/primitives/$page.browser.spec.tsx +851 -0
- package/src/react/router/primitives/$page.spec.tsx +676 -0
- package/src/react/router/primitives/$page.ts +489 -0
- package/src/react/router/providers/ReactBrowserProvider.ts +312 -0
- package/src/react/router/providers/ReactBrowserRendererProvider.ts +25 -0
- package/src/react/router/providers/ReactBrowserRouterProvider.ts +168 -0
- package/src/react/router/providers/ReactPageProvider.ts +726 -0
- package/src/react/router/providers/ReactPreloadProvider.spec.ts +142 -0
- package/src/react/router/providers/ReactPreloadProvider.ts +85 -0
- package/src/react/router/providers/ReactServerProvider.spec.tsx +316 -0
- package/src/react/router/providers/ReactServerProvider.ts +487 -0
- package/src/react/router/providers/ReactServerTemplateProvider.spec.ts +210 -0
- package/src/react/router/providers/ReactServerTemplateProvider.ts +542 -0
- package/src/react/router/providers/SSRManifestProvider.ts +334 -0
- package/src/react/router/services/ReactPageServerService.ts +48 -0
- package/src/react/router/services/ReactPageService.ts +27 -0
- package/src/react/router/services/ReactRouter.ts +262 -0
- package/src/react/websocket/hooks/useRoom.tsx +242 -0
- package/src/react/websocket/index.ts +7 -0
- package/src/redis/__tests__/redis.spec.ts +13 -0
- package/src/redis/index.ts +9 -25
- package/src/redis/providers/BunRedisProvider.ts +9 -0
- package/src/redis/providers/NodeRedisProvider.ts +8 -0
- package/src/redis/providers/RedisProvider.ts +16 -0
- package/src/retry/index.ts +11 -2
- package/src/router/index.ts +15 -0
- package/src/scheduler/index.ts +11 -2
- package/src/security/__tests__/BasicAuth.spec.ts +2 -0
- package/src/security/__tests__/ServerSecurityProvider.spec.ts +90 -5
- package/src/security/index.ts +15 -10
- package/src/security/interfaces/IssuerResolver.ts +27 -0
- package/src/security/primitives/$issuer.ts +55 -0
- package/src/security/providers/SecurityProvider.ts +179 -0
- package/src/security/providers/ServerBasicAuthProvider.ts +6 -2
- package/src/security/providers/ServerSecurityProvider.ts +63 -41
- package/src/server/auth/index.ts +12 -7
- package/src/server/cache/index.ts +7 -22
- package/src/server/compress/index.ts +10 -2
- package/src/server/cookies/index.ts +7 -5
- package/src/server/cookies/primitives/$cookie.ts +33 -11
- package/src/server/core/index.ts +16 -6
- package/src/server/core/interfaces/ServerRequest.ts +83 -1
- package/src/server/core/primitives/$action.spec.ts +1 -1
- package/src/server/core/primitives/$action.ts +8 -3
- package/src/server/core/providers/NodeHttpServerProvider.spec.ts +9 -3
- package/src/server/core/providers/NodeHttpServerProvider.ts +9 -3
- package/src/server/core/services/ServerRequestParser.spec.ts +520 -0
- package/src/server/core/services/ServerRequestParser.ts +306 -13
- package/src/server/cors/index.ts +7 -21
- package/src/server/cors/primitives/$cors.ts +6 -2
- package/src/server/health/index.ts +8 -2
- package/src/server/helmet/index.ts +11 -3
- package/src/server/links/index.ts +11 -6
- package/src/server/metrics/index.ts +10 -3
- package/src/server/multipart/index.ts +9 -3
- package/src/server/proxy/index.ts +8 -2
- package/src/server/rate-limit/index.ts +21 -25
- package/src/server/rate-limit/primitives/$rateLimit.ts +6 -2
- package/src/server/rate-limit/providers/ServerRateLimitProvider.spec.ts +38 -14
- package/src/server/rate-limit/providers/ServerRateLimitProvider.ts +22 -56
- package/src/server/static/index.ts +8 -2
- package/src/server/static/providers/ServerStaticProvider.ts +1 -1
- package/src/server/swagger/index.ts +9 -4
- package/src/server/swagger/providers/ServerSwaggerProvider.ts +1 -1
- package/src/sms/index.ts +9 -5
- package/src/sms/providers/LocalSmsProvider.spec.ts +1 -1
- package/src/sms/providers/LocalSmsProvider.ts +1 -1
- package/src/system/index.browser.ts +36 -0
- package/src/system/index.ts +62 -0
- package/src/system/index.workerd.ts +1 -0
- package/src/{file → system}/providers/FileSystemProvider.ts +24 -0
- package/src/{file → system}/providers/MemoryFileSystemProvider.ts +116 -3
- package/src/system/providers/MemoryShellProvider.ts +164 -0
- package/src/{file → system}/providers/NodeFileSystemProvider.spec.ts +2 -2
- package/src/{file → system}/providers/NodeFileSystemProvider.ts +47 -2
- package/src/system/providers/NodeShellProvider.ts +184 -0
- package/src/system/providers/ShellProvider.ts +74 -0
- package/src/{file → system}/services/FileDetector.spec.ts +2 -2
- package/src/thread/index.ts +11 -2
- package/src/topic/core/index.ts +12 -5
- package/src/vite/tasks/buildClient.ts +2 -7
- package/src/vite/tasks/buildServer.ts +19 -13
- package/src/vite/tasks/generateCloudflare.ts +10 -7
- package/src/vite/tasks/generateDocker.ts +4 -0
- package/src/websocket/index.ts +12 -8
- package/dist/file/index.d.ts.map +0 -1
- package/dist/file/index.js.map +0 -1
- package/src/cli/assets/apiIndexTs.ts +0 -16
- package/src/cli/assets/mainServerTs.ts +0 -24
- package/src/cli/assets/webAppRouterTs.ts +0 -16
- package/src/cli/assets/webHelloComponentTsx.ts +0 -20
- package/src/cli/providers/ViteTemplateProvider.ts +0 -27
- package/src/file/index.ts +0 -43
- /package/src/cli/{assets → templates}/apiHelloControllerTs.ts +0 -0
- /package/src/cli/{assets → templates}/biomeJson.ts +0 -0
- /package/src/cli/{assets → templates}/dummySpecTs.ts +0 -0
- /package/src/cli/{assets → templates}/editorconfig.ts +0 -0
- /package/src/cli/{assets → templates}/mainBrowserTs.ts +0 -0
- /package/src/cli/{assets → templates}/tsconfigJson.ts +0 -0
- /package/src/cli/{assets → templates}/webIndexTs.ts +0 -0
- /package/src/{file → system}/errors/FileError.ts +0 -0
- /package/src/{file → system}/services/FileDetector.ts +0 -0
|
@@ -0,0 +1,1035 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { Alepha, t } from "alepha";
|
|
3
|
+
import { AdminApiKeyController, ApiKeyController } from "alepha/api/keys";
|
|
4
|
+
import { DateTimeProvider } from "alepha/datetime";
|
|
5
|
+
import { AlephaEmail } from "alepha/email";
|
|
6
|
+
import { AlephaFake, FakeProvider } from "alepha/fake";
|
|
7
|
+
import { AlephaSecurity } from "alepha/security";
|
|
8
|
+
import { $action, AlephaServer } from "alepha/server";
|
|
9
|
+
import { describe, it } from "vitest";
|
|
10
|
+
import { AdminUserController } from "../controllers/AdminUserController.ts";
|
|
11
|
+
import { $realm, AlephaApiUsers } from "../index.ts";
|
|
12
|
+
|
|
13
|
+
// Admin context for admin controller calls
|
|
14
|
+
const adminUser = { id: randomUUID(), roles: ["admin"] };
|
|
15
|
+
|
|
16
|
+
// Schema for generating fake user data
|
|
17
|
+
const userDataSchema = t.object({
|
|
18
|
+
username: t.string(),
|
|
19
|
+
email: t.email(),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const setup = async () => {
|
|
23
|
+
const alepha = Alepha.create({
|
|
24
|
+
env: { LOG_LEVEL: "error" },
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
alepha.with(AlephaServer);
|
|
28
|
+
alepha.with(AlephaSecurity);
|
|
29
|
+
alepha.with(AlephaEmail);
|
|
30
|
+
alepha.with(AlephaApiUsers);
|
|
31
|
+
alepha.with(AlephaFake);
|
|
32
|
+
|
|
33
|
+
// Create a realm with API keys enabled
|
|
34
|
+
class TestApp {
|
|
35
|
+
realm = $realm({
|
|
36
|
+
apiKeys: true,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// A protected action to test API key authentication (any authenticated user)
|
|
40
|
+
getProfile = $action({
|
|
41
|
+
path: "/profile",
|
|
42
|
+
group: "profile",
|
|
43
|
+
secure: true,
|
|
44
|
+
schema: {
|
|
45
|
+
response: t.object({
|
|
46
|
+
userId: t.string(),
|
|
47
|
+
roles: t.array(t.string()),
|
|
48
|
+
}),
|
|
49
|
+
},
|
|
50
|
+
handler: (request) => ({
|
|
51
|
+
userId: request.user.id,
|
|
52
|
+
roles: request.user.roles ?? [],
|
|
53
|
+
}),
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Admin-only action (group: "admin:*" is excluded from user role)
|
|
57
|
+
adminStats = $action({
|
|
58
|
+
path: "/admin/stats",
|
|
59
|
+
method: "GET",
|
|
60
|
+
group: "admin:stats",
|
|
61
|
+
secure: true,
|
|
62
|
+
schema: {
|
|
63
|
+
response: t.object({
|
|
64
|
+
message: t.string(),
|
|
65
|
+
adminId: t.string(),
|
|
66
|
+
roles: t.array(t.string()),
|
|
67
|
+
}),
|
|
68
|
+
},
|
|
69
|
+
handler: (request) => ({
|
|
70
|
+
message: "Admin stats retrieved",
|
|
71
|
+
adminId: request.user.id,
|
|
72
|
+
roles: request.user.roles ?? [],
|
|
73
|
+
}),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const app = alepha.inject(TestApp);
|
|
78
|
+
await alepha.start();
|
|
79
|
+
|
|
80
|
+
const adminUserController = alepha.inject(AdminUserController);
|
|
81
|
+
const apiKeyController = alepha.inject(ApiKeyController);
|
|
82
|
+
const adminApiKeyController = alepha.inject(AdminApiKeyController);
|
|
83
|
+
const dateTimeProvider = alepha.inject(DateTimeProvider);
|
|
84
|
+
const fakeProvider = alepha.inject(FakeProvider);
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
alepha,
|
|
88
|
+
app,
|
|
89
|
+
adminUserController,
|
|
90
|
+
apiKeyController,
|
|
91
|
+
adminApiKeyController,
|
|
92
|
+
dateTimeProvider,
|
|
93
|
+
fakeProvider,
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
describe("alepha/api/users - API Keys Integration (Controllers)", () => {
|
|
98
|
+
// -------------------------------------------------------------------------
|
|
99
|
+
// User API Key Management (CRUD via ApiKeyController)
|
|
100
|
+
// -------------------------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
it("should create an API key via controller", async ({ expect }) => {
|
|
103
|
+
const { adminUserController, apiKeyController, fakeProvider } =
|
|
104
|
+
await setup();
|
|
105
|
+
const fakeUser = fakeProvider.generate(userDataSchema);
|
|
106
|
+
|
|
107
|
+
// Create a user via admin controller
|
|
108
|
+
const userResponse = await adminUserController.createUser.fetch(
|
|
109
|
+
{
|
|
110
|
+
body: {
|
|
111
|
+
...fakeUser,
|
|
112
|
+
roles: ["user"],
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
{ user: adminUser },
|
|
116
|
+
);
|
|
117
|
+
const user = userResponse.data;
|
|
118
|
+
|
|
119
|
+
// Create API key via controller (authenticated as the user)
|
|
120
|
+
const response = await apiKeyController.createApiKey.fetch(
|
|
121
|
+
{
|
|
122
|
+
body: {
|
|
123
|
+
name: "My API Key",
|
|
124
|
+
description: "Test key for CI/CD",
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
{ user: { id: user.id, roles: user.roles } },
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
expect(response.status).toBe(200);
|
|
131
|
+
expect(response.data.name).toBe("My API Key");
|
|
132
|
+
expect(response.data.token).toMatch(/^ak_/);
|
|
133
|
+
expect(response.data.roles).toEqual(["user"]);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("should list user's own API keys via controller", async ({ expect }) => {
|
|
137
|
+
const { adminUserController, apiKeyController, fakeProvider } =
|
|
138
|
+
await setup();
|
|
139
|
+
const fakeUser = fakeProvider.generate(userDataSchema);
|
|
140
|
+
|
|
141
|
+
// Create a user
|
|
142
|
+
const userResponse = await adminUserController.createUser.fetch(
|
|
143
|
+
{
|
|
144
|
+
body: {
|
|
145
|
+
...fakeUser,
|
|
146
|
+
roles: ["user"],
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
{ user: adminUser },
|
|
150
|
+
);
|
|
151
|
+
const user = userResponse.data;
|
|
152
|
+
|
|
153
|
+
// Create multiple API keys
|
|
154
|
+
await apiKeyController.createApiKey.fetch(
|
|
155
|
+
{ body: { name: "Key 1" } },
|
|
156
|
+
{ user: { id: user.id, roles: user.roles } },
|
|
157
|
+
);
|
|
158
|
+
await apiKeyController.createApiKey.fetch(
|
|
159
|
+
{ body: { name: "Key 2" } },
|
|
160
|
+
{ user: { id: user.id, roles: user.roles } },
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
// List keys via controller
|
|
164
|
+
const response = await apiKeyController.listApiKeys.fetch(
|
|
165
|
+
{},
|
|
166
|
+
{ user: { id: user.id, roles: user.roles } },
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
expect(response.status).toBe(200);
|
|
170
|
+
expect(response.data).toHaveLength(2);
|
|
171
|
+
expect(response.data.map((k) => k.name).sort()).toEqual(["Key 1", "Key 2"]);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("should revoke own API key via controller", async ({ expect }) => {
|
|
175
|
+
const { adminUserController, apiKeyController, app, fakeProvider } =
|
|
176
|
+
await setup();
|
|
177
|
+
const fakeUser = fakeProvider.generate(userDataSchema);
|
|
178
|
+
|
|
179
|
+
// Create a user
|
|
180
|
+
const userResponse = await adminUserController.createUser.fetch(
|
|
181
|
+
{
|
|
182
|
+
body: {
|
|
183
|
+
...fakeUser,
|
|
184
|
+
roles: ["user"],
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
{ user: adminUser },
|
|
188
|
+
);
|
|
189
|
+
const user = userResponse.data;
|
|
190
|
+
|
|
191
|
+
// Create an API key
|
|
192
|
+
const createResponse = await apiKeyController.createApiKey.fetch(
|
|
193
|
+
{ body: { name: "My Key" } },
|
|
194
|
+
{ user: { id: user.id, roles: user.roles } },
|
|
195
|
+
);
|
|
196
|
+
const { id, token } = createResponse.data;
|
|
197
|
+
|
|
198
|
+
// Verify key works (access protected endpoint via query param)
|
|
199
|
+
const beforeRevoke = await app.getProfile.fetch({
|
|
200
|
+
query: { api_key: token },
|
|
201
|
+
});
|
|
202
|
+
expect(beforeRevoke.status).toBe(200);
|
|
203
|
+
expect(beforeRevoke.data.userId).toBe(user.id);
|
|
204
|
+
|
|
205
|
+
// Revoke the key
|
|
206
|
+
const revokeResponse = await apiKeyController.revokeApiKey.fetch(
|
|
207
|
+
{ params: { id } },
|
|
208
|
+
{ user: { id: user.id, roles: user.roles } },
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
expect(revokeResponse.status).toBe(200);
|
|
212
|
+
expect(revokeResponse.data.ok).toBe(true);
|
|
213
|
+
|
|
214
|
+
// Verify key no longer works
|
|
215
|
+
await expect(
|
|
216
|
+
app.getProfile.fetch({ query: { api_key: token } }),
|
|
217
|
+
).rejects.toThrow();
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("should not allow revoking another user's API key", async ({ expect }) => {
|
|
221
|
+
const { adminUserController, apiKeyController, fakeProvider } =
|
|
222
|
+
await setup();
|
|
223
|
+
|
|
224
|
+
// Create two users
|
|
225
|
+
const user1Response = await adminUserController.createUser.fetch(
|
|
226
|
+
{
|
|
227
|
+
body: {
|
|
228
|
+
...fakeProvider.generate(userDataSchema),
|
|
229
|
+
roles: ["user"],
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
{ user: adminUser },
|
|
233
|
+
);
|
|
234
|
+
const user1 = user1Response.data;
|
|
235
|
+
|
|
236
|
+
const user2Response = await adminUserController.createUser.fetch(
|
|
237
|
+
{
|
|
238
|
+
body: {
|
|
239
|
+
...fakeProvider.generate(userDataSchema),
|
|
240
|
+
roles: ["user"],
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
{ user: adminUser },
|
|
244
|
+
);
|
|
245
|
+
const user2 = user2Response.data;
|
|
246
|
+
|
|
247
|
+
// User1 creates an API key
|
|
248
|
+
const createResponse = await apiKeyController.createApiKey.fetch(
|
|
249
|
+
{ body: { name: "User1 Key" } },
|
|
250
|
+
{ user: { id: user1.id, roles: user1.roles } },
|
|
251
|
+
);
|
|
252
|
+
const { id } = createResponse.data;
|
|
253
|
+
|
|
254
|
+
// User2 tries to revoke User1's key - should fail with 403
|
|
255
|
+
await expect(
|
|
256
|
+
apiKeyController.revokeApiKey.fetch(
|
|
257
|
+
{ params: { id } },
|
|
258
|
+
{ user: { id: user2.id, roles: user2.roles } },
|
|
259
|
+
),
|
|
260
|
+
).rejects.toThrow("Not your API key");
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it("should create API key with expiration", async ({ expect }) => {
|
|
264
|
+
const {
|
|
265
|
+
adminUserController,
|
|
266
|
+
apiKeyController,
|
|
267
|
+
app,
|
|
268
|
+
dateTimeProvider,
|
|
269
|
+
fakeProvider,
|
|
270
|
+
} = await setup();
|
|
271
|
+
const fakeUser = fakeProvider.generate(userDataSchema);
|
|
272
|
+
|
|
273
|
+
// Create a user
|
|
274
|
+
const userResponse = await adminUserController.createUser.fetch(
|
|
275
|
+
{
|
|
276
|
+
body: {
|
|
277
|
+
...fakeUser,
|
|
278
|
+
roles: ["user"],
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
{ user: adminUser },
|
|
282
|
+
);
|
|
283
|
+
const user = userResponse.data;
|
|
284
|
+
|
|
285
|
+
// Create API key with 1 hour expiration
|
|
286
|
+
const expiresAt = dateTimeProvider.now().add(1, "hour").toISOString();
|
|
287
|
+
const createResponse = await apiKeyController.createApiKey.fetch(
|
|
288
|
+
{ body: { name: "Expiring Key", expiresAt } },
|
|
289
|
+
{ user: { id: user.id, roles: user.roles } },
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
expect(createResponse.status).toBe(200);
|
|
293
|
+
expect(createResponse.data.expiresAt).toBe(expiresAt);
|
|
294
|
+
|
|
295
|
+
const { token } = createResponse.data;
|
|
296
|
+
|
|
297
|
+
// Verify key works before expiry
|
|
298
|
+
const beforeExpiry = await app.getProfile.fetch({
|
|
299
|
+
query: { api_key: token },
|
|
300
|
+
});
|
|
301
|
+
expect(beforeExpiry.status).toBe(200);
|
|
302
|
+
|
|
303
|
+
// Travel past expiration
|
|
304
|
+
dateTimeProvider.travel(2, "hours");
|
|
305
|
+
|
|
306
|
+
// Verify key no longer works
|
|
307
|
+
await expect(
|
|
308
|
+
app.getProfile.fetch({ query: { api_key: token } }),
|
|
309
|
+
).rejects.toThrow();
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
// -------------------------------------------------------------------------
|
|
313
|
+
// Admin API Key Management (via AdminApiKeyController)
|
|
314
|
+
// -------------------------------------------------------------------------
|
|
315
|
+
|
|
316
|
+
it("should list all API keys via admin controller", async ({ expect }) => {
|
|
317
|
+
const {
|
|
318
|
+
adminUserController,
|
|
319
|
+
apiKeyController,
|
|
320
|
+
adminApiKeyController,
|
|
321
|
+
fakeProvider,
|
|
322
|
+
} = await setup();
|
|
323
|
+
|
|
324
|
+
// Create two users with API keys
|
|
325
|
+
const user1Response = await adminUserController.createUser.fetch(
|
|
326
|
+
{
|
|
327
|
+
body: {
|
|
328
|
+
...fakeProvider.generate(userDataSchema),
|
|
329
|
+
roles: ["user"],
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
{ user: adminUser },
|
|
333
|
+
);
|
|
334
|
+
const user1 = user1Response.data;
|
|
335
|
+
|
|
336
|
+
const user2Response = await adminUserController.createUser.fetch(
|
|
337
|
+
{
|
|
338
|
+
body: {
|
|
339
|
+
...fakeProvider.generate(userDataSchema),
|
|
340
|
+
roles: ["user"],
|
|
341
|
+
},
|
|
342
|
+
},
|
|
343
|
+
{ user: adminUser },
|
|
344
|
+
);
|
|
345
|
+
const user2 = user2Response.data;
|
|
346
|
+
|
|
347
|
+
// Each user creates an API key
|
|
348
|
+
await apiKeyController.createApiKey.fetch(
|
|
349
|
+
{ body: { name: "User1 Key" } },
|
|
350
|
+
{ user: { id: user1.id, roles: user1.roles } },
|
|
351
|
+
);
|
|
352
|
+
await apiKeyController.createApiKey.fetch(
|
|
353
|
+
{ body: { name: "User2 Key" } },
|
|
354
|
+
{ user: { id: user2.id, roles: user2.roles } },
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
// Admin lists all API keys
|
|
358
|
+
const response = await adminApiKeyController.findApiKeys.fetch(
|
|
359
|
+
{},
|
|
360
|
+
{ user: adminUser },
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
expect(response.status).toBe(200);
|
|
364
|
+
expect(response.data.content).toHaveLength(2);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it("should filter API keys by userId via admin controller", async ({
|
|
368
|
+
expect,
|
|
369
|
+
}) => {
|
|
370
|
+
const {
|
|
371
|
+
adminUserController,
|
|
372
|
+
apiKeyController,
|
|
373
|
+
adminApiKeyController,
|
|
374
|
+
fakeProvider,
|
|
375
|
+
} = await setup();
|
|
376
|
+
|
|
377
|
+
// Create two users
|
|
378
|
+
const user1Response = await adminUserController.createUser.fetch(
|
|
379
|
+
{
|
|
380
|
+
body: {
|
|
381
|
+
...fakeProvider.generate(userDataSchema),
|
|
382
|
+
roles: ["user"],
|
|
383
|
+
},
|
|
384
|
+
},
|
|
385
|
+
{ user: adminUser },
|
|
386
|
+
);
|
|
387
|
+
const user1 = user1Response.data;
|
|
388
|
+
|
|
389
|
+
const user2Response = await adminUserController.createUser.fetch(
|
|
390
|
+
{
|
|
391
|
+
body: {
|
|
392
|
+
...fakeProvider.generate(userDataSchema),
|
|
393
|
+
roles: ["user"],
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
{ user: adminUser },
|
|
397
|
+
);
|
|
398
|
+
const user2 = user2Response.data;
|
|
399
|
+
|
|
400
|
+
// Create keys for both users
|
|
401
|
+
await apiKeyController.createApiKey.fetch(
|
|
402
|
+
{ body: { name: "User1 Key" } },
|
|
403
|
+
{ user: { id: user1.id, roles: user1.roles } },
|
|
404
|
+
);
|
|
405
|
+
await apiKeyController.createApiKey.fetch(
|
|
406
|
+
{ body: { name: "User2 Key" } },
|
|
407
|
+
{ user: { id: user2.id, roles: user2.roles } },
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
// Admin filters by user1
|
|
411
|
+
const response = await adminApiKeyController.findApiKeys.fetch(
|
|
412
|
+
{ query: { userId: user1.id } },
|
|
413
|
+
{ user: adminUser },
|
|
414
|
+
);
|
|
415
|
+
|
|
416
|
+
expect(response.status).toBe(200);
|
|
417
|
+
expect(response.data.content).toHaveLength(1);
|
|
418
|
+
expect(response.data.content[0].name).toBe("User1 Key");
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
it("should get API key by ID via admin controller", async ({ expect }) => {
|
|
422
|
+
const {
|
|
423
|
+
adminUserController,
|
|
424
|
+
apiKeyController,
|
|
425
|
+
adminApiKeyController,
|
|
426
|
+
fakeProvider,
|
|
427
|
+
} = await setup();
|
|
428
|
+
const fakeUser = fakeProvider.generate(userDataSchema);
|
|
429
|
+
|
|
430
|
+
// Create a user with an API key
|
|
431
|
+
const userResponse = await adminUserController.createUser.fetch(
|
|
432
|
+
{
|
|
433
|
+
body: {
|
|
434
|
+
...fakeUser,
|
|
435
|
+
roles: ["user"],
|
|
436
|
+
},
|
|
437
|
+
},
|
|
438
|
+
{ user: adminUser },
|
|
439
|
+
);
|
|
440
|
+
const user = userResponse.data;
|
|
441
|
+
|
|
442
|
+
const createResponse = await apiKeyController.createApiKey.fetch(
|
|
443
|
+
{ body: { name: "My Key", description: "Test description" } },
|
|
444
|
+
{ user: { id: user.id, roles: user.roles } },
|
|
445
|
+
);
|
|
446
|
+
const { id } = createResponse.data;
|
|
447
|
+
|
|
448
|
+
// Admin gets the key by ID
|
|
449
|
+
const response = await adminApiKeyController.getApiKey.fetch(
|
|
450
|
+
{ params: { id } },
|
|
451
|
+
{ user: adminUser },
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
expect(response.status).toBe(200);
|
|
455
|
+
expect(response.data.id).toBe(id);
|
|
456
|
+
expect(response.data.name).toBe("My Key");
|
|
457
|
+
expect(response.data.description).toBe("Test description");
|
|
458
|
+
expect(response.data.userId).toBe(user.id);
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
it("should revoke any API key via admin controller", async ({ expect }) => {
|
|
462
|
+
const {
|
|
463
|
+
adminUserController,
|
|
464
|
+
apiKeyController,
|
|
465
|
+
adminApiKeyController,
|
|
466
|
+
app,
|
|
467
|
+
fakeProvider,
|
|
468
|
+
} = await setup();
|
|
469
|
+
const fakeUser = fakeProvider.generate(userDataSchema);
|
|
470
|
+
|
|
471
|
+
// Create a user with an API key
|
|
472
|
+
const userResponse = await adminUserController.createUser.fetch(
|
|
473
|
+
{
|
|
474
|
+
body: {
|
|
475
|
+
...fakeUser,
|
|
476
|
+
roles: ["user"],
|
|
477
|
+
},
|
|
478
|
+
},
|
|
479
|
+
{ user: adminUser },
|
|
480
|
+
);
|
|
481
|
+
const user = userResponse.data;
|
|
482
|
+
|
|
483
|
+
const createResponse = await apiKeyController.createApiKey.fetch(
|
|
484
|
+
{ body: { name: "My Key" } },
|
|
485
|
+
{ user: { id: user.id, roles: user.roles } },
|
|
486
|
+
);
|
|
487
|
+
const { id, token } = createResponse.data;
|
|
488
|
+
|
|
489
|
+
// Verify key works
|
|
490
|
+
const beforeRevoke = await app.getProfile.fetch({
|
|
491
|
+
query: { api_key: token },
|
|
492
|
+
});
|
|
493
|
+
expect(beforeRevoke.status).toBe(200);
|
|
494
|
+
|
|
495
|
+
// Admin revokes the key
|
|
496
|
+
const response = await adminApiKeyController.revokeApiKey.fetch(
|
|
497
|
+
{ params: { id } },
|
|
498
|
+
{ user: adminUser },
|
|
499
|
+
);
|
|
500
|
+
|
|
501
|
+
expect(response.status).toBe(200);
|
|
502
|
+
expect(response.data.ok).toBe(true);
|
|
503
|
+
|
|
504
|
+
// Verify key no longer works
|
|
505
|
+
await expect(
|
|
506
|
+
app.getProfile.fetch({ query: { api_key: token } }),
|
|
507
|
+
).rejects.toThrow();
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
it("should include revoked keys when requested via admin controller", async ({
|
|
511
|
+
expect,
|
|
512
|
+
}) => {
|
|
513
|
+
const {
|
|
514
|
+
adminUserController,
|
|
515
|
+
apiKeyController,
|
|
516
|
+
adminApiKeyController,
|
|
517
|
+
fakeProvider,
|
|
518
|
+
} = await setup();
|
|
519
|
+
const fakeUser = fakeProvider.generate(userDataSchema);
|
|
520
|
+
|
|
521
|
+
// Create a user
|
|
522
|
+
const userResponse = await adminUserController.createUser.fetch(
|
|
523
|
+
{
|
|
524
|
+
body: {
|
|
525
|
+
...fakeUser,
|
|
526
|
+
roles: ["user"],
|
|
527
|
+
},
|
|
528
|
+
},
|
|
529
|
+
{ user: adminUser },
|
|
530
|
+
);
|
|
531
|
+
const user = userResponse.data;
|
|
532
|
+
|
|
533
|
+
// Create and revoke a key
|
|
534
|
+
const createResponse = await apiKeyController.createApiKey.fetch(
|
|
535
|
+
{ body: { name: "Revoked Key" } },
|
|
536
|
+
{ user: { id: user.id, roles: user.roles } },
|
|
537
|
+
);
|
|
538
|
+
await apiKeyController.revokeApiKey.fetch(
|
|
539
|
+
{ params: { id: createResponse.data.id } },
|
|
540
|
+
{ user: { id: user.id, roles: user.roles } },
|
|
541
|
+
);
|
|
542
|
+
|
|
543
|
+
// Create an active key
|
|
544
|
+
await apiKeyController.createApiKey.fetch(
|
|
545
|
+
{ body: { name: "Active Key" } },
|
|
546
|
+
{ user: { id: user.id, roles: user.roles } },
|
|
547
|
+
);
|
|
548
|
+
|
|
549
|
+
// List without revoked (default)
|
|
550
|
+
const activeOnly = await adminApiKeyController.findApiKeys.fetch(
|
|
551
|
+
{},
|
|
552
|
+
{ user: adminUser },
|
|
553
|
+
);
|
|
554
|
+
expect(activeOnly.data.content).toHaveLength(1);
|
|
555
|
+
expect(activeOnly.data.content[0].name).toBe("Active Key");
|
|
556
|
+
|
|
557
|
+
// List with revoked
|
|
558
|
+
const withRevoked = await adminApiKeyController.findApiKeys.fetch(
|
|
559
|
+
{ query: { includeRevoked: true } },
|
|
560
|
+
{ user: adminUser },
|
|
561
|
+
);
|
|
562
|
+
expect(withRevoked.data.content).toHaveLength(2);
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
it("should handle pagination via admin controller", async ({ expect }) => {
|
|
566
|
+
const {
|
|
567
|
+
adminUserController,
|
|
568
|
+
apiKeyController,
|
|
569
|
+
adminApiKeyController,
|
|
570
|
+
fakeProvider,
|
|
571
|
+
} = await setup();
|
|
572
|
+
const fakeUser = fakeProvider.generate(userDataSchema);
|
|
573
|
+
|
|
574
|
+
// Create a user
|
|
575
|
+
const userResponse = await adminUserController.createUser.fetch(
|
|
576
|
+
{
|
|
577
|
+
body: {
|
|
578
|
+
...fakeUser,
|
|
579
|
+
roles: ["user"],
|
|
580
|
+
},
|
|
581
|
+
},
|
|
582
|
+
{ user: adminUser },
|
|
583
|
+
);
|
|
584
|
+
const user = userResponse.data;
|
|
585
|
+
|
|
586
|
+
// Create 5 API keys
|
|
587
|
+
for (let i = 1; i <= 5; i++) {
|
|
588
|
+
await apiKeyController.createApiKey.fetch(
|
|
589
|
+
{ body: { name: `Key ${i}` } },
|
|
590
|
+
{ user: { id: user.id, roles: user.roles } },
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Get first page
|
|
595
|
+
const page1 = await adminApiKeyController.findApiKeys.fetch(
|
|
596
|
+
{ query: { size: 2, page: 0 } },
|
|
597
|
+
{ user: adminUser },
|
|
598
|
+
);
|
|
599
|
+
expect(page1.data.content).toHaveLength(2);
|
|
600
|
+
expect(page1.data.page.totalElements).toBe(5);
|
|
601
|
+
expect(page1.data.page.totalPages).toBe(3);
|
|
602
|
+
|
|
603
|
+
// Get second page
|
|
604
|
+
const page2 = await adminApiKeyController.findApiKeys.fetch(
|
|
605
|
+
{ query: { size: 2, page: 1 } },
|
|
606
|
+
{ user: adminUser },
|
|
607
|
+
);
|
|
608
|
+
expect(page2.data.content).toHaveLength(2);
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
// -------------------------------------------------------------------------
|
|
612
|
+
// API Key Authentication (query param and Bearer header)
|
|
613
|
+
// -------------------------------------------------------------------------
|
|
614
|
+
|
|
615
|
+
it("should authenticate via api_key query parameter", async ({ expect }) => {
|
|
616
|
+
const { adminUserController, apiKeyController, app, fakeProvider } =
|
|
617
|
+
await setup();
|
|
618
|
+
const fakeUser = fakeProvider.generate(userDataSchema);
|
|
619
|
+
|
|
620
|
+
// Create a user with an API key
|
|
621
|
+
const userResponse = await adminUserController.createUser.fetch(
|
|
622
|
+
{
|
|
623
|
+
body: {
|
|
624
|
+
...fakeUser,
|
|
625
|
+
roles: ["user"],
|
|
626
|
+
},
|
|
627
|
+
},
|
|
628
|
+
{ user: adminUser },
|
|
629
|
+
);
|
|
630
|
+
const user = userResponse.data;
|
|
631
|
+
|
|
632
|
+
const createResponse = await apiKeyController.createApiKey.fetch(
|
|
633
|
+
{ body: { name: "My Key" } },
|
|
634
|
+
{ user: { id: user.id, roles: user.roles } },
|
|
635
|
+
);
|
|
636
|
+
const { token } = createResponse.data;
|
|
637
|
+
|
|
638
|
+
// Access protected endpoint via query param
|
|
639
|
+
const response = await app.getProfile.fetch({
|
|
640
|
+
query: { api_key: token },
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
expect(response.status).toBe(200);
|
|
644
|
+
expect(response.data.userId).toBe(user.id);
|
|
645
|
+
expect(response.data.roles).toEqual(["user"]);
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
it("should authenticate via Bearer header", async ({ expect }) => {
|
|
649
|
+
const { adminUserController, apiKeyController, app, fakeProvider } =
|
|
650
|
+
await setup();
|
|
651
|
+
const fakeUser = fakeProvider.generate(userDataSchema);
|
|
652
|
+
|
|
653
|
+
// Create a user with an API key
|
|
654
|
+
const userResponse = await adminUserController.createUser.fetch(
|
|
655
|
+
{
|
|
656
|
+
body: {
|
|
657
|
+
...fakeUser,
|
|
658
|
+
roles: ["user"],
|
|
659
|
+
},
|
|
660
|
+
},
|
|
661
|
+
{ user: adminUser },
|
|
662
|
+
);
|
|
663
|
+
const user = userResponse.data;
|
|
664
|
+
|
|
665
|
+
const createResponse = await apiKeyController.createApiKey.fetch(
|
|
666
|
+
{ body: { name: "My Key" } },
|
|
667
|
+
{ user: { id: user.id, roles: user.roles } },
|
|
668
|
+
);
|
|
669
|
+
const { token } = createResponse.data;
|
|
670
|
+
|
|
671
|
+
// Access protected endpoint via Bearer header
|
|
672
|
+
const response = await app.getProfile.fetch({
|
|
673
|
+
headers: { authorization: `Bearer ${token}` },
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
expect(response.status).toBe(200);
|
|
677
|
+
expect(response.data.userId).toBe(user.id);
|
|
678
|
+
expect(response.data.roles).toEqual(["user"]);
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
it("should reject request with invalid API key", async ({ expect }) => {
|
|
682
|
+
const { app } = await setup();
|
|
683
|
+
|
|
684
|
+
// Try to access with invalid key
|
|
685
|
+
await expect(
|
|
686
|
+
app.getProfile.fetch({ query: { api_key: "ak_invalid_token_12345" } }),
|
|
687
|
+
).rejects.toThrow();
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
it("should reject request with revoked API key", async ({ expect }) => {
|
|
691
|
+
const { adminUserController, apiKeyController, app, fakeProvider } =
|
|
692
|
+
await setup();
|
|
693
|
+
const fakeUser = fakeProvider.generate(userDataSchema);
|
|
694
|
+
|
|
695
|
+
// Create a user with an API key
|
|
696
|
+
const userResponse = await adminUserController.createUser.fetch(
|
|
697
|
+
{
|
|
698
|
+
body: {
|
|
699
|
+
...fakeUser,
|
|
700
|
+
roles: ["user"],
|
|
701
|
+
},
|
|
702
|
+
},
|
|
703
|
+
{ user: adminUser },
|
|
704
|
+
);
|
|
705
|
+
const user = userResponse.data;
|
|
706
|
+
|
|
707
|
+
const createResponse = await apiKeyController.createApiKey.fetch(
|
|
708
|
+
{ body: { name: "My Key" } },
|
|
709
|
+
{ user: { id: user.id, roles: user.roles } },
|
|
710
|
+
);
|
|
711
|
+
const { id, token } = createResponse.data;
|
|
712
|
+
|
|
713
|
+
// Revoke the key
|
|
714
|
+
await apiKeyController.revokeApiKey.fetch(
|
|
715
|
+
{ params: { id } },
|
|
716
|
+
{ user: { id: user.id, roles: user.roles } },
|
|
717
|
+
);
|
|
718
|
+
|
|
719
|
+
// Try to access with revoked key
|
|
720
|
+
await expect(
|
|
721
|
+
app.getProfile.fetch({ query: { api_key: token } }),
|
|
722
|
+
).rejects.toThrow();
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
// -------------------------------------------------------------------------
|
|
726
|
+
// Role-based Access with API Keys
|
|
727
|
+
// -------------------------------------------------------------------------
|
|
728
|
+
|
|
729
|
+
it("should allow admin API key to access admin-only endpoint", async ({
|
|
730
|
+
expect,
|
|
731
|
+
}) => {
|
|
732
|
+
const { adminUserController, apiKeyController, app, fakeProvider } =
|
|
733
|
+
await setup();
|
|
734
|
+
const fakeUser = fakeProvider.generate(userDataSchema);
|
|
735
|
+
|
|
736
|
+
// Create an admin user
|
|
737
|
+
const userResponse = await adminUserController.createUser.fetch(
|
|
738
|
+
{
|
|
739
|
+
body: {
|
|
740
|
+
...fakeUser,
|
|
741
|
+
roles: ["admin"],
|
|
742
|
+
},
|
|
743
|
+
},
|
|
744
|
+
{ user: adminUser },
|
|
745
|
+
);
|
|
746
|
+
const user = userResponse.data;
|
|
747
|
+
|
|
748
|
+
// Create API key with admin role
|
|
749
|
+
const createResponse = await apiKeyController.createApiKey.fetch(
|
|
750
|
+
{ body: { name: "Admin Key" } },
|
|
751
|
+
{ user: { id: user.id, roles: user.roles } },
|
|
752
|
+
);
|
|
753
|
+
const { token } = createResponse.data;
|
|
754
|
+
|
|
755
|
+
// Access admin endpoint
|
|
756
|
+
const response = await app.adminStats.fetch({
|
|
757
|
+
query: { api_key: token },
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
expect(response.status).toBe(200);
|
|
761
|
+
expect(response.data.message).toBe("Admin stats retrieved");
|
|
762
|
+
expect(response.data.adminId).toBe(user.id);
|
|
763
|
+
expect(response.data.roles).toContain("admin");
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
it("should reject user API key from admin-only endpoint", async ({
|
|
767
|
+
expect,
|
|
768
|
+
}) => {
|
|
769
|
+
const { adminUserController, apiKeyController, app, fakeProvider } =
|
|
770
|
+
await setup();
|
|
771
|
+
const fakeUser = fakeProvider.generate(userDataSchema);
|
|
772
|
+
|
|
773
|
+
// Create a regular user
|
|
774
|
+
const userResponse = await adminUserController.createUser.fetch(
|
|
775
|
+
{
|
|
776
|
+
body: {
|
|
777
|
+
...fakeUser,
|
|
778
|
+
roles: ["user"],
|
|
779
|
+
},
|
|
780
|
+
},
|
|
781
|
+
{ user: adminUser },
|
|
782
|
+
);
|
|
783
|
+
const user = userResponse.data;
|
|
784
|
+
|
|
785
|
+
// Create API key with user role
|
|
786
|
+
const createResponse = await apiKeyController.createApiKey.fetch(
|
|
787
|
+
{ body: { name: "User Key" } },
|
|
788
|
+
{ user: { id: user.id, roles: user.roles } },
|
|
789
|
+
);
|
|
790
|
+
const { token } = createResponse.data;
|
|
791
|
+
|
|
792
|
+
// Try to access admin endpoint - should fail
|
|
793
|
+
await expect(
|
|
794
|
+
app.adminStats.fetch({ query: { api_key: token } }),
|
|
795
|
+
).rejects.toThrow();
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
it("should allow user API key to access user endpoint", async ({
|
|
799
|
+
expect,
|
|
800
|
+
}) => {
|
|
801
|
+
const { adminUserController, apiKeyController, app, fakeProvider } =
|
|
802
|
+
await setup();
|
|
803
|
+
const fakeUser = fakeProvider.generate(userDataSchema);
|
|
804
|
+
|
|
805
|
+
// Create a regular user
|
|
806
|
+
const userResponse = await adminUserController.createUser.fetch(
|
|
807
|
+
{
|
|
808
|
+
body: {
|
|
809
|
+
...fakeUser,
|
|
810
|
+
roles: ["user"],
|
|
811
|
+
},
|
|
812
|
+
},
|
|
813
|
+
{ user: adminUser },
|
|
814
|
+
);
|
|
815
|
+
const user = userResponse.data;
|
|
816
|
+
|
|
817
|
+
// Create API key
|
|
818
|
+
const createResponse = await apiKeyController.createApiKey.fetch(
|
|
819
|
+
{ body: { name: "User Key" } },
|
|
820
|
+
{ user: { id: user.id, roles: user.roles } },
|
|
821
|
+
);
|
|
822
|
+
const { token } = createResponse.data;
|
|
823
|
+
|
|
824
|
+
// Access user endpoint via Bearer header
|
|
825
|
+
const response = await app.getProfile.fetch({
|
|
826
|
+
headers: { authorization: `Bearer ${token}` },
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
expect(response.status).toBe(200);
|
|
830
|
+
expect(response.data.userId).toBe(user.id);
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
it("should snapshot roles at API key creation time", async ({ expect }) => {
|
|
834
|
+
const { adminUserController, apiKeyController, app, fakeProvider } =
|
|
835
|
+
await setup();
|
|
836
|
+
const fakeUser = fakeProvider.generate(userDataSchema);
|
|
837
|
+
|
|
838
|
+
// Create a user with user role
|
|
839
|
+
const userResponse = await adminUserController.createUser.fetch(
|
|
840
|
+
{
|
|
841
|
+
body: {
|
|
842
|
+
...fakeUser,
|
|
843
|
+
roles: ["user"],
|
|
844
|
+
},
|
|
845
|
+
},
|
|
846
|
+
{ user: adminUser },
|
|
847
|
+
);
|
|
848
|
+
const user = userResponse.data;
|
|
849
|
+
|
|
850
|
+
// Create API key (snapshots current roles: ["user"])
|
|
851
|
+
const createResponse = await apiKeyController.createApiKey.fetch(
|
|
852
|
+
{ body: { name: "My Key" } },
|
|
853
|
+
{ user: { id: user.id, roles: user.roles } },
|
|
854
|
+
);
|
|
855
|
+
const { token } = createResponse.data;
|
|
856
|
+
|
|
857
|
+
// Update user to have admin role
|
|
858
|
+
await adminUserController.updateUser.fetch(
|
|
859
|
+
{
|
|
860
|
+
params: { id: user.id },
|
|
861
|
+
body: { roles: ["user", "admin"] },
|
|
862
|
+
},
|
|
863
|
+
{ user: adminUser },
|
|
864
|
+
);
|
|
865
|
+
|
|
866
|
+
// API key should still only have user role (snapshot)
|
|
867
|
+
// So accessing admin endpoint should fail
|
|
868
|
+
await expect(
|
|
869
|
+
app.adminStats.fetch({ query: { api_key: token } }),
|
|
870
|
+
).rejects.toThrow();
|
|
871
|
+
|
|
872
|
+
// But user endpoint should still work
|
|
873
|
+
const response = await app.getProfile.fetch({
|
|
874
|
+
query: { api_key: token },
|
|
875
|
+
});
|
|
876
|
+
expect(response.status).toBe(200);
|
|
877
|
+
expect(response.data.roles).toEqual(["user"]); // Original roles
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
it("should create API key with multiple roles", async ({ expect }) => {
|
|
881
|
+
const { adminUserController, apiKeyController, app, fakeProvider } =
|
|
882
|
+
await setup();
|
|
883
|
+
const fakeUser = fakeProvider.generate(userDataSchema);
|
|
884
|
+
|
|
885
|
+
// Create a user with multiple roles
|
|
886
|
+
const userResponse = await adminUserController.createUser.fetch(
|
|
887
|
+
{
|
|
888
|
+
body: {
|
|
889
|
+
...fakeUser,
|
|
890
|
+
roles: ["user", "admin"],
|
|
891
|
+
},
|
|
892
|
+
},
|
|
893
|
+
{ user: adminUser },
|
|
894
|
+
);
|
|
895
|
+
const user = userResponse.data;
|
|
896
|
+
|
|
897
|
+
// Create API key
|
|
898
|
+
const createResponse = await apiKeyController.createApiKey.fetch(
|
|
899
|
+
{ body: { name: "Power Key" } },
|
|
900
|
+
{ user: { id: user.id, roles: user.roles } },
|
|
901
|
+
);
|
|
902
|
+
|
|
903
|
+
expect(createResponse.data.roles).toContain("user");
|
|
904
|
+
expect(createResponse.data.roles).toContain("admin");
|
|
905
|
+
|
|
906
|
+
const { token } = createResponse.data;
|
|
907
|
+
|
|
908
|
+
// Should access both user and admin endpoints
|
|
909
|
+
const profileResponse = await app.getProfile.fetch({
|
|
910
|
+
query: { api_key: token },
|
|
911
|
+
});
|
|
912
|
+
expect(profileResponse.status).toBe(200);
|
|
913
|
+
|
|
914
|
+
const adminResponse = await app.adminStats.fetch({
|
|
915
|
+
query: { api_key: token },
|
|
916
|
+
});
|
|
917
|
+
expect(adminResponse.status).toBe(200);
|
|
918
|
+
});
|
|
919
|
+
|
|
920
|
+
// -------------------------------------------------------------------------
|
|
921
|
+
// Edge Cases and Error Handling
|
|
922
|
+
// -------------------------------------------------------------------------
|
|
923
|
+
|
|
924
|
+
it("should return 404 when revoking non-existent API key", async ({
|
|
925
|
+
expect,
|
|
926
|
+
}) => {
|
|
927
|
+
const { adminUserController, apiKeyController, fakeProvider } =
|
|
928
|
+
await setup();
|
|
929
|
+
const fakeUser = fakeProvider.generate(userDataSchema);
|
|
930
|
+
|
|
931
|
+
// Create a user
|
|
932
|
+
const userResponse = await adminUserController.createUser.fetch(
|
|
933
|
+
{
|
|
934
|
+
body: {
|
|
935
|
+
...fakeUser,
|
|
936
|
+
roles: ["user"],
|
|
937
|
+
},
|
|
938
|
+
},
|
|
939
|
+
{ user: adminUser },
|
|
940
|
+
);
|
|
941
|
+
const user = userResponse.data;
|
|
942
|
+
|
|
943
|
+
// Try to revoke non-existent key
|
|
944
|
+
await expect(
|
|
945
|
+
apiKeyController.revokeApiKey.fetch(
|
|
946
|
+
{ params: { id: "00000000-0000-0000-0000-000000000000" } },
|
|
947
|
+
{ user: { id: user.id, roles: user.roles } },
|
|
948
|
+
),
|
|
949
|
+
).rejects.toThrow("API key not found");
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
it("should return 404 when getting non-existent API key via admin", async ({
|
|
953
|
+
expect,
|
|
954
|
+
}) => {
|
|
955
|
+
const { adminApiKeyController } = await setup();
|
|
956
|
+
|
|
957
|
+
// Try to get non-existent key
|
|
958
|
+
await expect(
|
|
959
|
+
adminApiKeyController.getApiKey.fetch(
|
|
960
|
+
{ params: { id: "00000000-0000-0000-0000-000000000000" } },
|
|
961
|
+
{ user: adminUser },
|
|
962
|
+
),
|
|
963
|
+
).rejects.toThrow("API key not found");
|
|
964
|
+
});
|
|
965
|
+
|
|
966
|
+
it("should list empty when user has no API keys", async ({ expect }) => {
|
|
967
|
+
const { adminUserController, apiKeyController, fakeProvider } =
|
|
968
|
+
await setup();
|
|
969
|
+
const fakeUser = fakeProvider.generate(userDataSchema);
|
|
970
|
+
|
|
971
|
+
// Create a user without any API keys
|
|
972
|
+
const userResponse = await adminUserController.createUser.fetch(
|
|
973
|
+
{
|
|
974
|
+
body: {
|
|
975
|
+
...fakeUser,
|
|
976
|
+
roles: ["user"],
|
|
977
|
+
},
|
|
978
|
+
},
|
|
979
|
+
{ user: adminUser },
|
|
980
|
+
);
|
|
981
|
+
const user = userResponse.data;
|
|
982
|
+
|
|
983
|
+
// List should return empty array
|
|
984
|
+
const response = await apiKeyController.listApiKeys.fetch(
|
|
985
|
+
{},
|
|
986
|
+
{ user: { id: user.id, roles: user.roles } },
|
|
987
|
+
);
|
|
988
|
+
|
|
989
|
+
expect(response.status).toBe(200);
|
|
990
|
+
expect(response.data).toEqual([]);
|
|
991
|
+
});
|
|
992
|
+
|
|
993
|
+
it("should not return revoked keys in user's list", async ({ expect }) => {
|
|
994
|
+
const { adminUserController, apiKeyController, fakeProvider } =
|
|
995
|
+
await setup();
|
|
996
|
+
const fakeUser = fakeProvider.generate(userDataSchema);
|
|
997
|
+
|
|
998
|
+
// Create a user
|
|
999
|
+
const userResponse = await adminUserController.createUser.fetch(
|
|
1000
|
+
{
|
|
1001
|
+
body: {
|
|
1002
|
+
...fakeUser,
|
|
1003
|
+
roles: ["user"],
|
|
1004
|
+
},
|
|
1005
|
+
},
|
|
1006
|
+
{ user: adminUser },
|
|
1007
|
+
);
|
|
1008
|
+
const user = userResponse.data;
|
|
1009
|
+
|
|
1010
|
+
// Create and revoke a key
|
|
1011
|
+
const createResponse = await apiKeyController.createApiKey.fetch(
|
|
1012
|
+
{ body: { name: "Revoked Key" } },
|
|
1013
|
+
{ user: { id: user.id, roles: user.roles } },
|
|
1014
|
+
);
|
|
1015
|
+
await apiKeyController.revokeApiKey.fetch(
|
|
1016
|
+
{ params: { id: createResponse.data.id } },
|
|
1017
|
+
{ user: { id: user.id, roles: user.roles } },
|
|
1018
|
+
);
|
|
1019
|
+
|
|
1020
|
+
// Create an active key
|
|
1021
|
+
await apiKeyController.createApiKey.fetch(
|
|
1022
|
+
{ body: { name: "Active Key" } },
|
|
1023
|
+
{ user: { id: user.id, roles: user.roles } },
|
|
1024
|
+
);
|
|
1025
|
+
|
|
1026
|
+
// List should only show active key
|
|
1027
|
+
const response = await apiKeyController.listApiKeys.fetch(
|
|
1028
|
+
{},
|
|
1029
|
+
{ user: { id: user.id, roles: user.roles } },
|
|
1030
|
+
);
|
|
1031
|
+
|
|
1032
|
+
expect(response.data).toHaveLength(1);
|
|
1033
|
+
expect(response.data[0].name).toBe("Active Key");
|
|
1034
|
+
});
|
|
1035
|
+
});
|