alepha 0.19.2 → 0.19.4
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/assets/swagger-ui/swagger-ui-bundle.js +1 -1
- package/dist/api/audits/index.d.ts +8 -8
- package/dist/api/invitations/index.d.ts +790 -0
- package/dist/api/invitations/index.d.ts.map +1 -0
- package/dist/api/invitations/index.js +665 -0
- package/dist/api/invitations/index.js.map +1 -0
- package/dist/api/jobs/index.browser.js +8 -9
- package/dist/api/jobs/index.browser.js.map +1 -1
- package/dist/api/jobs/index.d.ts +90 -34
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js +267 -44
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/notifications/index.browser.js +0 -1
- package/dist/api/notifications/index.browser.js.map +1 -1
- package/dist/api/notifications/index.d.ts +3 -3
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/notifications/index.js +0 -1
- package/dist/api/notifications/index.js.map +1 -1
- package/dist/api/parameters/index.browser.js +112 -1
- package/dist/api/parameters/index.browser.js.map +1 -1
- package/dist/api/parameters/index.d.ts +90 -3
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/parameters/index.js +79 -12
- package/dist/api/parameters/index.js.map +1 -1
- package/dist/{billing → api/payments}/index.d.ts +67 -49
- package/dist/api/payments/index.d.ts.map +1 -0
- package/dist/{billing → api/payments}/index.js +108 -74
- package/dist/api/payments/index.js.map +1 -0
- package/dist/api/subscriptions/index.d.ts +1692 -0
- package/dist/api/subscriptions/index.d.ts.map +1 -0
- package/dist/api/subscriptions/index.js +1870 -0
- package/dist/api/subscriptions/index.js.map +1 -0
- package/dist/api/users/index.d.ts +27 -21
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +167 -34
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/workflows/index.browser.js +246 -0
- package/dist/api/workflows/index.browser.js.map +1 -0
- package/dist/api/workflows/index.d.ts +1618 -0
- package/dist/api/workflows/index.d.ts.map +1 -0
- package/dist/api/workflows/index.js +1504 -0
- package/dist/api/workflows/index.js.map +1 -0
- package/dist/cli/config/index.d.ts +6 -28
- package/dist/cli/config/index.d.ts.map +1 -1
- package/dist/cli/config/index.js +5 -10
- package/dist/cli/config/index.js.map +1 -1
- package/dist/cli/core/index.d.ts +11669 -208
- package/dist/cli/core/index.d.ts.map +1 -1
- package/dist/cli/core/index.js +60 -69
- package/dist/cli/core/index.js.map +1 -1
- package/dist/cli/devtools/index.d.ts +5 -0
- package/dist/cli/devtools/index.d.ts.map +1 -1
- package/dist/cli/devtools/index.js +4 -0
- package/dist/cli/devtools/index.js.map +1 -1
- package/dist/cli/platform/index.d.ts +69 -64
- package/dist/cli/platform/index.d.ts.map +1 -1
- package/dist/cli/platform/index.js +6 -2
- package/dist/cli/platform/index.js.map +1 -1
- package/dist/cli/vendor/index.d.ts +38 -10
- package/dist/cli/vendor/index.d.ts.map +1 -1
- package/dist/cli/vendor/index.js +85 -26
- package/dist/cli/vendor/index.js.map +1 -1
- package/dist/core/index.browser.js +21 -2
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +33 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +25 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +25 -2
- package/dist/core/index.native.js.map +1 -1
- package/dist/core/index.workerd.js +25 -2
- package/dist/core/index.workerd.js.map +1 -1
- package/dist/email/smtp/index.js +24 -8
- package/dist/email/smtp/index.js.map +1 -1
- package/dist/logger/index.d.ts.map +1 -1
- package/dist/logger/index.js +1 -1
- package/dist/logger/index.js.map +1 -1
- package/dist/orm/core/index.browser.js +0 -18
- package/dist/orm/core/index.browser.js.map +1 -1
- package/dist/orm/core/index.bun.js +25 -73
- package/dist/orm/core/index.bun.js.map +1 -1
- package/dist/orm/core/index.d.ts +10 -32
- package/dist/orm/core/index.d.ts.map +1 -1
- package/dist/orm/core/index.js +25 -73
- package/dist/orm/core/index.js.map +1 -1
- package/dist/orm/postgres/index.bun.js +3 -3
- package/dist/orm/postgres/index.bun.js.map +1 -1
- package/dist/orm/postgres/index.d.ts +2 -1
- package/dist/orm/postgres/index.d.ts.map +1 -1
- package/dist/orm/postgres/index.js +3 -3
- package/dist/orm/postgres/index.js.map +1 -1
- package/dist/react/router/index.browser.js +25 -3
- package/dist/react/router/index.browser.js.map +1 -1
- package/dist/react/router/index.d.ts +16 -1
- package/dist/react/router/index.d.ts.map +1 -1
- package/dist/react/router/index.js +25 -3
- package/dist/react/router/index.js.map +1 -1
- package/dist/security/index.d.ts +28 -0
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +28 -0
- package/dist/security/index.js.map +1 -1
- package/package.json +37 -20
- package/src/api/invitations/__tests__/InvitationService.spec.ts +439 -0
- package/src/api/invitations/controllers/AdminInvitationController.ts +86 -0
- package/src/api/invitations/controllers/InvitationController.ts +84 -0
- package/src/api/invitations/entities/invitations.ts +33 -0
- package/src/api/invitations/index.ts +65 -0
- package/src/api/invitations/jobs/InvitationJobs.ts +37 -0
- package/src/api/invitations/providers/InvitationProvider.ts +45 -0
- package/src/api/invitations/schemas/createInvitationSchema.ts +12 -0
- package/src/api/invitations/schemas/invitationConfigAtom.ts +20 -0
- package/src/api/invitations/schemas/invitationQuerySchema.ts +15 -0
- package/src/api/invitations/schemas/invitationResourceSchema.ts +6 -0
- package/src/api/invitations/schemas/invitationWithResourceInfoSchema.ts +22 -0
- package/src/api/invitations/schemas/myInvitationsQuerySchema.ts +10 -0
- package/src/api/invitations/services/InvitationService.ts +556 -0
- package/src/api/jobs/__tests__/$job.spec.ts +876 -0
- package/src/api/jobs/controllers/AdminJobController.ts +44 -0
- package/src/api/jobs/entities/jobExecutionEntity.ts +0 -2
- package/src/api/jobs/index.ts +0 -3
- package/src/api/jobs/primitives/$job.ts +22 -11
- package/src/api/jobs/providers/JobProvider.ts +239 -25
- package/src/api/jobs/schemas/jobConfigAtom.ts +4 -0
- package/src/api/jobs/schemas/jobCronInfoSchema.ts +1 -0
- package/src/api/jobs/schemas/jobExecutionQuerySchema.ts +0 -1
- package/src/api/jobs/schemas/jobQueueDepthSchema.ts +1 -0
- package/src/api/jobs/schemas/jobRegistrationSchema.ts +1 -6
- package/src/api/jobs/services/JobService.ts +51 -12
- package/src/api/notifications/schemas/notificationQuerySchema.ts +0 -1
- package/src/api/parameters/__tests__/$parameter.spec.ts +327 -0
- package/src/api/parameters/controllers/AdminParameterController.ts +29 -3
- package/src/api/parameters/index.browser.ts +12 -0
- package/src/api/parameters/primitives/$parameter.ts +20 -3
- package/src/api/parameters/services/ParameterProvider.ts +48 -7
- package/src/{billing → api/payments}/__tests__/PaymentMethodService.spec.ts +32 -6
- package/src/api/payments/__tests__/PaymentService.spec.ts +279 -0
- package/src/{billing/controllers/AdminBillingController.ts → api/payments/controllers/AdminPaymentController.ts} +26 -21
- package/src/{billing/controllers/BillingController.ts → api/payments/controllers/PaymentController.ts} +23 -11
- package/src/{billing → api/payments}/entities/paymentIntents.ts +1 -0
- package/src/{billing/errors/BillingError.ts → api/payments/errors/PaymentError.ts} +1 -1
- package/src/{billing → api/payments}/index.ts +31 -25
- package/src/{billing/providers/MemoryBillingProvider.ts → api/payments/providers/MemoryPaymentProvider.ts} +4 -4
- package/src/{billing/providers/BillingProvider.ts → api/payments/providers/PaymentProvider.ts} +9 -2
- package/src/{billing → api/payments}/services/PaymentMethodService.ts +5 -5
- package/src/{billing/services/BillingService.ts → api/payments/services/PaymentService.ts} +94 -18
- package/src/api/subscriptions/__tests__/BillingService.spec.ts +218 -0
- package/src/api/subscriptions/__tests__/SubscriptionService.spec.ts +278 -0
- package/src/api/subscriptions/controllers/AdminSubscriptionController.ts +212 -0
- package/src/api/subscriptions/controllers/SubscriptionController.ts +189 -0
- package/src/api/subscriptions/entities/subscriptionEvents.ts +54 -0
- package/src/api/subscriptions/entities/subscriptions.ts +68 -0
- package/src/api/subscriptions/index.ts +144 -0
- package/src/api/subscriptions/jobs/SubscriptionJobs.ts +382 -0
- package/src/api/subscriptions/middleware/$requireLimit.ts +50 -0
- package/src/api/subscriptions/middleware/$requirePlan.ts +49 -0
- package/src/api/subscriptions/notifications/SubscriptionNotifications.ts +110 -0
- package/src/api/subscriptions/schemas/cancelSubscriptionSchema.ts +8 -0
- package/src/api/subscriptions/schemas/changePlanSchema.ts +9 -0
- package/src/api/subscriptions/schemas/createSubscriptionSchema.ts +11 -0
- package/src/api/subscriptions/schemas/entitlementsSchema.ts +21 -0
- package/src/api/subscriptions/schemas/mrrSchema.ts +13 -0
- package/src/api/subscriptions/schemas/planDefinitionSchema.ts +71 -0
- package/src/api/subscriptions/schemas/planResourceSchema.ts +25 -0
- package/src/api/subscriptions/schemas/subscriptionEventResourceSchema.ts +8 -0
- package/src/api/subscriptions/schemas/subscriptionQuerySchema.ts +19 -0
- package/src/api/subscriptions/schemas/subscriptionResourceSchema.ts +6 -0
- package/src/api/subscriptions/schemas/subscriptionSettingsSchema.ts +32 -0
- package/src/api/subscriptions/schemas/subscriptionStatsSchema.ts +23 -0
- package/src/api/subscriptions/services/BillingService.ts +437 -0
- package/src/api/subscriptions/services/SubscriptionConfig.ts +56 -0
- package/src/api/subscriptions/services/SubscriptionService.ts +867 -0
- package/src/api/subscriptions/services/UsageService.ts +118 -0
- package/src/api/users/__tests__/AdminUserController.spec.ts +80 -1
- package/src/api/users/__tests__/CredentialService.spec.ts +177 -0
- package/src/api/users/__tests__/EmailVerification.spec.ts +29 -18
- package/src/api/users/__tests__/PasswordReset.spec.ts +3 -0
- package/src/api/users/__tests__/RegistrationService.spec.ts +148 -1
- package/src/api/users/__tests__/SessionService.spec.ts +142 -1
- package/src/api/users/atoms/realmAuthSettingsAtom.ts +10 -1
- package/src/api/users/controllers/UserController.ts +3 -8
- package/src/api/users/notifications/UserNotifications.ts +23 -0
- package/src/api/users/schemas/loginSchema.ts +1 -1
- package/src/api/users/services/CredentialService.ts +51 -4
- package/src/api/users/services/RegistrationService.ts +38 -9
- package/src/api/users/services/SessionService.ts +62 -9
- package/src/api/users/services/UserService.ts +21 -12
- package/src/api/workflows/__tests__/$workflow.spec.ts +616 -0
- package/src/api/workflows/controllers/AdminWorkflowController.ts +191 -0
- package/src/api/workflows/entities/workflowExecutions.ts +74 -0
- package/src/api/workflows/entities/workflowStepExecutions.ts +74 -0
- package/src/api/workflows/entities/workflowStepLogs.ts +13 -0
- package/src/api/workflows/index.browser.ts +22 -0
- package/src/api/workflows/index.ts +124 -0
- package/src/api/workflows/jobs/WorkflowJobs.ts +77 -0
- package/src/api/workflows/primitives/$workflow.ts +202 -0
- package/src/api/workflows/providers/WorkflowProvider.ts +1284 -0
- package/src/api/workflows/schemas/workflowActivitySchema.ts +15 -0
- package/src/api/workflows/schemas/workflowConfigAtom.ts +51 -0
- package/src/api/workflows/schemas/workflowExecutionDetailSchema.ts +18 -0
- package/src/api/workflows/schemas/workflowExecutionQuerySchema.ts +26 -0
- package/src/api/workflows/schemas/workflowExecutionResourceSchema.ts +30 -0
- package/src/api/workflows/schemas/workflowRegistrationSchema.ts +26 -0
- package/src/api/workflows/schemas/workflowStatsSchema.ts +16 -0
- package/src/api/workflows/schemas/workflowStepExecutionResourceSchema.ts +15 -0
- package/src/api/workflows/services/WorkflowService.ts +382 -0
- package/src/cli/config/defineConfig.ts +17 -46
- package/src/cli/core/providers/ViteDevServerProvider.ts +45 -3
- package/src/cli/core/services/PackageManagerUtils.ts +3 -1
- package/src/cli/core/services/ProjectScaffolder.ts +5 -5
- package/src/cli/core/templates/agentMd.ts +14 -5
- package/src/cli/core/templates/webAppRouterTs.ts +5 -58
- package/src/cli/devtools/index.ts +21 -1
- package/src/cli/platform/index.ts +23 -2
- package/src/cli/vendor/__tests__/VendorService.spec.ts +283 -178
- package/src/cli/vendor/index.ts +20 -3
- package/src/cli/vendor/services/VendorService.ts +126 -27
- package/src/core/Alepha.ts +10 -0
- package/src/core/__tests__/TypeProvider.spec.ts +4 -2
- package/src/core/providers/SchemaValidator.ts +1 -1
- package/src/core/providers/TypeProvider.ts +46 -3
- package/src/logger/index.ts +6 -1
- package/src/orm/__tests__/enums.spec.ts +22 -29
- package/src/orm/__tests__/orm-showcase-tests.ts +430 -0
- package/src/orm/__tests__/orm-showcase.spec.ts +167 -0
- package/src/orm/core/providers/DatabaseTypeProvider.ts +0 -29
- package/src/orm/core/providers/DrizzleKitProvider.ts +56 -105
- package/src/orm/postgres/services/PostgresModelBuilder.ts +3 -6
- package/src/react/router/__tests__/$page.browser.spec.tsx +157 -0
- package/src/react/router/providers/ReactBrowserProvider.ts +39 -0
- package/src/react/router/providers/ReactBrowserRouterProvider.ts +22 -0
- package/src/security/__tests__/$secure-combinations.spec.ts +945 -0
- package/src/security/primitives/$secure.ts +28 -0
- package/tsconfig.base.json +0 -1
- package/dist/billing/index.d.ts.map +0 -1
- package/dist/billing/index.js.map +0 -1
- package/src/billing/__tests__/BillingService.spec.ts +0 -136
- /package/src/{billing → api/payments}/entities/paymentMethods.ts +0 -0
- /package/src/{billing → api/payments}/entities/refunds.ts +0 -0
- /package/src/{billing → api/payments}/schemas/intentSchemas.ts +0 -0
- /package/src/{billing → api/payments}/schemas/paymentMethodSchemas.ts +0 -0
- /package/src/{billing → api/payments}/schemas/refundSchemas.ts +0 -0
|
@@ -0,0 +1,945 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { Alepha } from "alepha";
|
|
3
|
+
import {
|
|
4
|
+
$action,
|
|
5
|
+
AlephaServer,
|
|
6
|
+
ForbiddenError,
|
|
7
|
+
UnauthorizedError,
|
|
8
|
+
} from "alepha/server";
|
|
9
|
+
import { describe, expect, it } from "vitest";
|
|
10
|
+
import { $issuer, $secure, AlephaSecurity } from "../index.ts";
|
|
11
|
+
|
|
12
|
+
// -----------------------------------------------------------------------------------------------------------------
|
|
13
|
+
// Shared setup
|
|
14
|
+
// -----------------------------------------------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
function setup() {
|
|
17
|
+
class TestApp {
|
|
18
|
+
main = $issuer({
|
|
19
|
+
secret: "test-main",
|
|
20
|
+
roles: [
|
|
21
|
+
{
|
|
22
|
+
name: "admin",
|
|
23
|
+
permissions: [{ name: "*" }],
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: "editor",
|
|
27
|
+
permissions: [
|
|
28
|
+
{ name: "posts:read" },
|
|
29
|
+
{ name: "posts:create" },
|
|
30
|
+
{ name: "posts:update" },
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: "viewer",
|
|
35
|
+
permissions: [{ name: "posts:read" }],
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: "moderator",
|
|
39
|
+
permissions: [
|
|
40
|
+
{ name: "posts:read" },
|
|
41
|
+
{ name: "posts:delete" },
|
|
42
|
+
{ name: "comments:delete" },
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
external = $issuer({
|
|
49
|
+
secret: "test-external",
|
|
50
|
+
roles: [
|
|
51
|
+
{
|
|
52
|
+
name: "partner",
|
|
53
|
+
permissions: [{ name: "posts:read" }],
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// ---------------------------------------------------------------------------------------------------------------
|
|
59
|
+
// Actions with single options
|
|
60
|
+
// ---------------------------------------------------------------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
authOnly = $action({
|
|
63
|
+
use: [$secure()],
|
|
64
|
+
handler: () => "auth-only",
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
requireViewer = $action({
|
|
68
|
+
use: [$secure({ roles: ["viewer"] })],
|
|
69
|
+
handler: () => "viewer",
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
requireRead = $action({
|
|
73
|
+
use: [$secure({ permissions: ["posts:read"] })],
|
|
74
|
+
handler: () => "read",
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
requireMainIssuer = $action({
|
|
78
|
+
use: [$secure({ issuers: ["main"] })],
|
|
79
|
+
handler: () => "main-issuer",
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// ---------------------------------------------------------------------------------------------------------------
|
|
83
|
+
// Actions with two-option combinations
|
|
84
|
+
// ---------------------------------------------------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
issuerAndRole = $action({
|
|
87
|
+
use: [$secure({ issuers: ["main"], roles: ["editor"] })],
|
|
88
|
+
handler: () => "issuer+role",
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
issuerAndPermission = $action({
|
|
92
|
+
use: [$secure({ issuers: ["main"], permissions: ["posts:create"] })],
|
|
93
|
+
handler: () => "issuer+permission",
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
issuerAndGuard = $action({
|
|
97
|
+
use: [
|
|
98
|
+
$secure({
|
|
99
|
+
issuers: ["main"],
|
|
100
|
+
guard: (user) => user.email === "allowed@test.com",
|
|
101
|
+
}),
|
|
102
|
+
],
|
|
103
|
+
handler: () => "issuer+guard",
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
roleAndPermission = $action({
|
|
107
|
+
use: [$secure({ roles: ["editor"], permissions: ["posts:create"] })],
|
|
108
|
+
handler: () => "role+permission",
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
roleAndGuard = $action({
|
|
112
|
+
use: [
|
|
113
|
+
$secure({
|
|
114
|
+
roles: ["editor"],
|
|
115
|
+
guard: (user) => user.email === "allowed@test.com",
|
|
116
|
+
}),
|
|
117
|
+
],
|
|
118
|
+
handler: () => "role+guard",
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
permissionAndGuard = $action({
|
|
122
|
+
use: [
|
|
123
|
+
$secure({
|
|
124
|
+
permissions: ["posts:read"],
|
|
125
|
+
guard: (user) => user.email === "allowed@test.com",
|
|
126
|
+
}),
|
|
127
|
+
],
|
|
128
|
+
handler: () => "permission+guard",
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// ---------------------------------------------------------------------------------------------------------------
|
|
132
|
+
// Actions with three-option combinations
|
|
133
|
+
// ---------------------------------------------------------------------------------------------------------------
|
|
134
|
+
|
|
135
|
+
issuerRolePermission = $action({
|
|
136
|
+
use: [
|
|
137
|
+
$secure({
|
|
138
|
+
issuers: ["main"],
|
|
139
|
+
roles: ["editor"],
|
|
140
|
+
permissions: ["posts:create"],
|
|
141
|
+
}),
|
|
142
|
+
],
|
|
143
|
+
handler: () => "issuer+role+permission",
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
issuerRoleGuard = $action({
|
|
147
|
+
use: [
|
|
148
|
+
$secure({
|
|
149
|
+
issuers: ["main"],
|
|
150
|
+
roles: ["editor"],
|
|
151
|
+
guard: (user) => user.email === "allowed@test.com",
|
|
152
|
+
}),
|
|
153
|
+
],
|
|
154
|
+
handler: () => "issuer+role+guard",
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
issuerPermissionGuard = $action({
|
|
158
|
+
use: [
|
|
159
|
+
$secure({
|
|
160
|
+
issuers: ["main"],
|
|
161
|
+
permissions: ["posts:create"],
|
|
162
|
+
guard: (user) => user.email === "allowed@test.com",
|
|
163
|
+
}),
|
|
164
|
+
],
|
|
165
|
+
handler: () => "issuer+permission+guard",
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
rolePermissionGuard = $action({
|
|
169
|
+
use: [
|
|
170
|
+
$secure({
|
|
171
|
+
roles: ["editor"],
|
|
172
|
+
permissions: ["posts:create"],
|
|
173
|
+
guard: (user) => user.email === "allowed@test.com",
|
|
174
|
+
}),
|
|
175
|
+
],
|
|
176
|
+
handler: () => "role+permission+guard",
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// ---------------------------------------------------------------------------------------------------------------
|
|
180
|
+
// All four options
|
|
181
|
+
// ---------------------------------------------------------------------------------------------------------------
|
|
182
|
+
|
|
183
|
+
allOptions = $action({
|
|
184
|
+
use: [
|
|
185
|
+
$secure({
|
|
186
|
+
issuers: ["main"],
|
|
187
|
+
roles: ["editor"],
|
|
188
|
+
permissions: ["posts:create"],
|
|
189
|
+
guard: (user) => user.email === "allowed@test.com",
|
|
190
|
+
}),
|
|
191
|
+
],
|
|
192
|
+
handler: () => "all",
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// ---------------------------------------------------------------------------------------------------------------
|
|
196
|
+
// Multiple permissions (AND logic)
|
|
197
|
+
// ---------------------------------------------------------------------------------------------------------------
|
|
198
|
+
|
|
199
|
+
multiplePermissions = $action({
|
|
200
|
+
use: [$secure({ permissions: ["posts:read", "posts:delete"] })],
|
|
201
|
+
handler: () => "multi-perm",
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// ---------------------------------------------------------------------------------------------------------------
|
|
205
|
+
// Multiple issuers (OR logic)
|
|
206
|
+
// ---------------------------------------------------------------------------------------------------------------
|
|
207
|
+
|
|
208
|
+
multipleIssuers = $action({
|
|
209
|
+
use: [$secure({ issuers: ["main", "external"] })],
|
|
210
|
+
handler: () => "multi-issuer",
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// ---------------------------------------------------------------------------------------------------------------
|
|
214
|
+
// Multiple roles (OR logic)
|
|
215
|
+
// ---------------------------------------------------------------------------------------------------------------
|
|
216
|
+
|
|
217
|
+
multipleRoles = $action({
|
|
218
|
+
use: [$secure({ roles: ["editor", "moderator"] })],
|
|
219
|
+
handler: () => "multi-role",
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const alepha = Alepha.create().with(AlephaServer).with(AlephaSecurity);
|
|
224
|
+
const app = alepha.inject(TestApp);
|
|
225
|
+
|
|
226
|
+
const users = {
|
|
227
|
+
admin: {
|
|
228
|
+
id: randomUUID(),
|
|
229
|
+
roles: ["admin"],
|
|
230
|
+
realm: "main",
|
|
231
|
+
email: "admin@test.com",
|
|
232
|
+
name: "Admin",
|
|
233
|
+
},
|
|
234
|
+
editor: {
|
|
235
|
+
id: randomUUID(),
|
|
236
|
+
roles: ["editor"],
|
|
237
|
+
realm: "main",
|
|
238
|
+
email: "allowed@test.com",
|
|
239
|
+
name: "Editor",
|
|
240
|
+
},
|
|
241
|
+
editorWrongEmail: {
|
|
242
|
+
id: randomUUID(),
|
|
243
|
+
roles: ["editor"],
|
|
244
|
+
realm: "main",
|
|
245
|
+
email: "other@test.com",
|
|
246
|
+
name: "Editor2",
|
|
247
|
+
},
|
|
248
|
+
viewer: {
|
|
249
|
+
id: randomUUID(),
|
|
250
|
+
roles: ["viewer"],
|
|
251
|
+
realm: "main",
|
|
252
|
+
email: "viewer@test.com",
|
|
253
|
+
name: "Viewer",
|
|
254
|
+
},
|
|
255
|
+
moderator: {
|
|
256
|
+
id: randomUUID(),
|
|
257
|
+
roles: ["moderator"],
|
|
258
|
+
realm: "main",
|
|
259
|
+
email: "mod@test.com",
|
|
260
|
+
name: "Moderator",
|
|
261
|
+
},
|
|
262
|
+
partner: {
|
|
263
|
+
id: randomUUID(),
|
|
264
|
+
roles: ["partner"],
|
|
265
|
+
realm: "external",
|
|
266
|
+
email: "partner@test.com",
|
|
267
|
+
name: "Partner",
|
|
268
|
+
},
|
|
269
|
+
noRoles: {
|
|
270
|
+
id: randomUUID(),
|
|
271
|
+
roles: [] as string[],
|
|
272
|
+
realm: "main",
|
|
273
|
+
email: "noroles@test.com",
|
|
274
|
+
name: "NoRoles",
|
|
275
|
+
},
|
|
276
|
+
noRealm: {
|
|
277
|
+
id: randomUUID(),
|
|
278
|
+
roles: ["editor"],
|
|
279
|
+
email: "norealm@test.com",
|
|
280
|
+
name: "NoRealm",
|
|
281
|
+
},
|
|
282
|
+
editorAndModerator: {
|
|
283
|
+
id: randomUUID(),
|
|
284
|
+
roles: ["editor", "moderator"],
|
|
285
|
+
realm: "main",
|
|
286
|
+
email: "allowed@test.com",
|
|
287
|
+
name: "EditorMod",
|
|
288
|
+
},
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
return { alepha, app, users };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// -----------------------------------------------------------------------------------------------------------------
|
|
295
|
+
// Two-option combinations
|
|
296
|
+
// -----------------------------------------------------------------------------------------------------------------
|
|
297
|
+
|
|
298
|
+
describe("$secure combinations", () => {
|
|
299
|
+
describe("issuers + roles", () => {
|
|
300
|
+
it("should allow when both issuer and role match", async () => {
|
|
301
|
+
const { alepha, app, users } = setup();
|
|
302
|
+
await alepha.start();
|
|
303
|
+
|
|
304
|
+
expect(await app.issuerAndRole.run({}, { user: users.editor })).toBe(
|
|
305
|
+
"issuer+role",
|
|
306
|
+
);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it("should deny when issuer matches but role does not", async () => {
|
|
310
|
+
const { alepha, app, users } = setup();
|
|
311
|
+
await alepha.start();
|
|
312
|
+
|
|
313
|
+
await expect(
|
|
314
|
+
app.issuerAndRole.run({}, { user: users.viewer }),
|
|
315
|
+
).rejects.toThrowError(ForbiddenError);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it("should deny when role matches but issuer does not", async () => {
|
|
319
|
+
const { alepha, app, users } = setup();
|
|
320
|
+
await alepha.start();
|
|
321
|
+
|
|
322
|
+
const externalEditor = {
|
|
323
|
+
...users.editor,
|
|
324
|
+
id: randomUUID(),
|
|
325
|
+
realm: "external",
|
|
326
|
+
};
|
|
327
|
+
await expect(
|
|
328
|
+
app.issuerAndRole.run({}, { user: externalEditor }),
|
|
329
|
+
).rejects.toThrowError(ForbiddenError);
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
describe("issuers + permissions", () => {
|
|
334
|
+
it("should allow when both issuer and permission match", async () => {
|
|
335
|
+
const { alepha, app, users } = setup();
|
|
336
|
+
await alepha.start();
|
|
337
|
+
|
|
338
|
+
expect(
|
|
339
|
+
await app.issuerAndPermission.run({}, { user: users.editor }),
|
|
340
|
+
).toBe("issuer+permission");
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it("should deny when issuer matches but permission does not", async () => {
|
|
344
|
+
const { alepha, app, users } = setup();
|
|
345
|
+
await alepha.start();
|
|
346
|
+
|
|
347
|
+
await expect(
|
|
348
|
+
app.issuerAndPermission.run({}, { user: users.viewer }),
|
|
349
|
+
).rejects.toThrowError(ForbiddenError);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it("should deny when permission would match but issuer does not", async () => {
|
|
353
|
+
const { alepha, app, users } = setup();
|
|
354
|
+
await alepha.start();
|
|
355
|
+
|
|
356
|
+
const externalEditor = {
|
|
357
|
+
...users.editor,
|
|
358
|
+
id: randomUUID(),
|
|
359
|
+
realm: "external",
|
|
360
|
+
};
|
|
361
|
+
await expect(
|
|
362
|
+
app.issuerAndPermission.run({}, { user: externalEditor }),
|
|
363
|
+
).rejects.toThrowError(ForbiddenError);
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
describe("issuers + guard", () => {
|
|
368
|
+
it("should allow when both issuer and guard pass", async () => {
|
|
369
|
+
const { alepha, app, users } = setup();
|
|
370
|
+
await alepha.start();
|
|
371
|
+
|
|
372
|
+
expect(await app.issuerAndGuard.run({}, { user: users.editor })).toBe(
|
|
373
|
+
"issuer+guard",
|
|
374
|
+
);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it("should deny when issuer matches but guard fails", async () => {
|
|
378
|
+
const { alepha, app, users } = setup();
|
|
379
|
+
await alepha.start();
|
|
380
|
+
|
|
381
|
+
await expect(
|
|
382
|
+
app.issuerAndGuard.run({}, { user: users.editorWrongEmail }),
|
|
383
|
+
).rejects.toThrowError(ForbiddenError);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it("should deny when guard would pass but issuer does not", async () => {
|
|
387
|
+
const { alepha, app, users } = setup();
|
|
388
|
+
await alepha.start();
|
|
389
|
+
|
|
390
|
+
const externalAllowed = {
|
|
391
|
+
...users.editor,
|
|
392
|
+
id: randomUUID(),
|
|
393
|
+
realm: "external",
|
|
394
|
+
};
|
|
395
|
+
await expect(
|
|
396
|
+
app.issuerAndGuard.run({}, { user: externalAllowed }),
|
|
397
|
+
).rejects.toThrowError(ForbiddenError);
|
|
398
|
+
});
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
describe("roles + permissions", () => {
|
|
402
|
+
it("should allow when both role and permission match", async () => {
|
|
403
|
+
const { alepha, app, users } = setup();
|
|
404
|
+
await alepha.start();
|
|
405
|
+
|
|
406
|
+
expect(await app.roleAndPermission.run({}, { user: users.editor })).toBe(
|
|
407
|
+
"role+permission",
|
|
408
|
+
);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it("should deny when role matches but permission does not", async () => {
|
|
412
|
+
const { alepha, app, users } = setup();
|
|
413
|
+
await alepha.start();
|
|
414
|
+
|
|
415
|
+
// viewer role doesn't have posts:create, but we need a user WITH editor role but WITHOUT the permission
|
|
416
|
+
// Actually: editor role has posts:create. So use a viewer who somehow has editor role name... no.
|
|
417
|
+
// The check is: roles check is OR on role NAME, permissions check is on role PERMISSIONS.
|
|
418
|
+
// A user with roles: ["editor"] has posts:create. A user with roles: ["viewer"] does NOT have posts:create.
|
|
419
|
+
// But viewer also doesn't have the "editor" role name. So both checks fail.
|
|
420
|
+
// To test "role matches but permission doesn't": we'd need a role that exists by name but lacks the permission.
|
|
421
|
+
// viewer role exists, has posts:read, but NOT posts:create.
|
|
422
|
+
// So: $secure({ roles: ["viewer"], permissions: ["posts:create"] }) with a viewer user would fail on permission.
|
|
423
|
+
|
|
424
|
+
// Let's just verify viewer fails on roleAndPermission (requires editor role)
|
|
425
|
+
await expect(
|
|
426
|
+
app.roleAndPermission.run({}, { user: users.viewer }),
|
|
427
|
+
).rejects.toThrowError(ForbiddenError);
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
it("should deny when user has no roles", async () => {
|
|
431
|
+
const { alepha, app, users } = setup();
|
|
432
|
+
await alepha.start();
|
|
433
|
+
|
|
434
|
+
await expect(
|
|
435
|
+
app.roleAndPermission.run({}, { user: users.noRoles }),
|
|
436
|
+
).rejects.toThrowError(ForbiddenError);
|
|
437
|
+
});
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
describe("roles + guard", () => {
|
|
441
|
+
it("should allow when both role and guard pass", async () => {
|
|
442
|
+
const { alepha, app, users } = setup();
|
|
443
|
+
await alepha.start();
|
|
444
|
+
|
|
445
|
+
expect(await app.roleAndGuard.run({}, { user: users.editor })).toBe(
|
|
446
|
+
"role+guard",
|
|
447
|
+
);
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
it("should deny when role matches but guard fails", async () => {
|
|
451
|
+
const { alepha, app, users } = setup();
|
|
452
|
+
await alepha.start();
|
|
453
|
+
|
|
454
|
+
await expect(
|
|
455
|
+
app.roleAndGuard.run({}, { user: users.editorWrongEmail }),
|
|
456
|
+
).rejects.toThrowError(ForbiddenError);
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
it("should deny when guard would pass but role does not", async () => {
|
|
460
|
+
const { alepha, app, users } = setup();
|
|
461
|
+
await alepha.start();
|
|
462
|
+
|
|
463
|
+
// viewer with the "allowed" email — guard would pass, but role check fails
|
|
464
|
+
const viewerAllowed = { ...users.viewer, email: "allowed@test.com" };
|
|
465
|
+
await expect(
|
|
466
|
+
app.roleAndGuard.run({}, { user: viewerAllowed }),
|
|
467
|
+
).rejects.toThrowError(ForbiddenError);
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
describe("permissions + guard", () => {
|
|
472
|
+
it("should allow when both permission and guard pass", async () => {
|
|
473
|
+
const { alepha, app, users } = setup();
|
|
474
|
+
await alepha.start();
|
|
475
|
+
|
|
476
|
+
expect(await app.permissionAndGuard.run({}, { user: users.editor })).toBe(
|
|
477
|
+
"permission+guard",
|
|
478
|
+
);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
it("should deny when permission matches but guard fails", async () => {
|
|
482
|
+
const { alepha, app, users } = setup();
|
|
483
|
+
await alepha.start();
|
|
484
|
+
|
|
485
|
+
// viewer has posts:read but wrong email
|
|
486
|
+
await expect(
|
|
487
|
+
app.permissionAndGuard.run({}, { user: users.viewer }),
|
|
488
|
+
).rejects.toThrowError(ForbiddenError);
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
it("should deny when guard would pass but permission does not", async () => {
|
|
492
|
+
const { alepha, app, users } = setup();
|
|
493
|
+
await alepha.start();
|
|
494
|
+
|
|
495
|
+
// user with correct email but no posts:read permission
|
|
496
|
+
const noPermsAllowed = { ...users.noRoles, email: "allowed@test.com" };
|
|
497
|
+
await expect(
|
|
498
|
+
app.permissionAndGuard.run({}, { user: noPermsAllowed }),
|
|
499
|
+
).rejects.toThrowError(ForbiddenError);
|
|
500
|
+
});
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
// -----------------------------------------------------------------------------------------------------------------
|
|
505
|
+
// Three-option combinations
|
|
506
|
+
// -----------------------------------------------------------------------------------------------------------------
|
|
507
|
+
|
|
508
|
+
describe("$secure three-option combinations", () => {
|
|
509
|
+
describe("issuers + roles + permissions", () => {
|
|
510
|
+
it("should allow when all three match", async () => {
|
|
511
|
+
const { alepha, app, users } = setup();
|
|
512
|
+
await alepha.start();
|
|
513
|
+
|
|
514
|
+
expect(
|
|
515
|
+
await app.issuerRolePermission.run({}, { user: users.editor }),
|
|
516
|
+
).toBe("issuer+role+permission");
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
it("should deny when issuer fails", async () => {
|
|
520
|
+
const { alepha, app, users } = setup();
|
|
521
|
+
await alepha.start();
|
|
522
|
+
|
|
523
|
+
const externalEditor = {
|
|
524
|
+
...users.editor,
|
|
525
|
+
id: randomUUID(),
|
|
526
|
+
realm: "external",
|
|
527
|
+
};
|
|
528
|
+
await expect(
|
|
529
|
+
app.issuerRolePermission.run({}, { user: externalEditor }),
|
|
530
|
+
).rejects.toThrowError(ForbiddenError);
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
it("should deny when role fails", async () => {
|
|
534
|
+
const { alepha, app, users } = setup();
|
|
535
|
+
await alepha.start();
|
|
536
|
+
|
|
537
|
+
await expect(
|
|
538
|
+
app.issuerRolePermission.run({}, { user: users.viewer }),
|
|
539
|
+
).rejects.toThrowError(ForbiddenError);
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
it("should deny when permission fails", async () => {
|
|
543
|
+
const { alepha, app, users } = setup();
|
|
544
|
+
await alepha.start();
|
|
545
|
+
|
|
546
|
+
// moderator role doesn't have posts:create
|
|
547
|
+
await expect(
|
|
548
|
+
app.issuerRolePermission.run({}, { user: users.moderator }),
|
|
549
|
+
).rejects.toThrowError(ForbiddenError);
|
|
550
|
+
});
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
describe("issuers + roles + guard", () => {
|
|
554
|
+
it("should allow when all three match", async () => {
|
|
555
|
+
const { alepha, app, users } = setup();
|
|
556
|
+
await alepha.start();
|
|
557
|
+
|
|
558
|
+
expect(await app.issuerRoleGuard.run({}, { user: users.editor })).toBe(
|
|
559
|
+
"issuer+role+guard",
|
|
560
|
+
);
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
it("should deny when guard fails (issuer and role pass)", async () => {
|
|
564
|
+
const { alepha, app, users } = setup();
|
|
565
|
+
await alepha.start();
|
|
566
|
+
|
|
567
|
+
await expect(
|
|
568
|
+
app.issuerRoleGuard.run({}, { user: users.editorWrongEmail }),
|
|
569
|
+
).rejects.toThrowError(ForbiddenError);
|
|
570
|
+
});
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
describe("issuers + permissions + guard", () => {
|
|
574
|
+
it("should allow when all three match", async () => {
|
|
575
|
+
const { alepha, app, users } = setup();
|
|
576
|
+
await alepha.start();
|
|
577
|
+
|
|
578
|
+
expect(
|
|
579
|
+
await app.issuerPermissionGuard.run({}, { user: users.editor }),
|
|
580
|
+
).toBe("issuer+permission+guard");
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
it("should deny when permission fails (issuer and guard would pass)", async () => {
|
|
584
|
+
const { alepha, app, users } = setup();
|
|
585
|
+
await alepha.start();
|
|
586
|
+
|
|
587
|
+
// viewer has posts:read but not posts:create
|
|
588
|
+
const viewerAllowed = { ...users.viewer, email: "allowed@test.com" };
|
|
589
|
+
await expect(
|
|
590
|
+
app.issuerPermissionGuard.run({}, { user: viewerAllowed }),
|
|
591
|
+
).rejects.toThrowError(ForbiddenError);
|
|
592
|
+
});
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
describe("roles + permissions + guard", () => {
|
|
596
|
+
it("should allow when all three match", async () => {
|
|
597
|
+
const { alepha, app, users } = setup();
|
|
598
|
+
await alepha.start();
|
|
599
|
+
|
|
600
|
+
expect(
|
|
601
|
+
await app.rolePermissionGuard.run({}, { user: users.editor }),
|
|
602
|
+
).toBe("role+permission+guard");
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
it("should deny when only guard fails", async () => {
|
|
606
|
+
const { alepha, app, users } = setup();
|
|
607
|
+
await alepha.start();
|
|
608
|
+
|
|
609
|
+
await expect(
|
|
610
|
+
app.rolePermissionGuard.run({}, { user: users.editorWrongEmail }),
|
|
611
|
+
).rejects.toThrowError(ForbiddenError);
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
it("should deny when only role fails", async () => {
|
|
615
|
+
const { alepha, app, users } = setup();
|
|
616
|
+
await alepha.start();
|
|
617
|
+
|
|
618
|
+
// admin has all permissions and correct email but not "editor" role
|
|
619
|
+
const adminAllowed = { ...users.admin, email: "allowed@test.com" };
|
|
620
|
+
await expect(
|
|
621
|
+
app.rolePermissionGuard.run({}, { user: adminAllowed }),
|
|
622
|
+
).rejects.toThrowError(ForbiddenError);
|
|
623
|
+
});
|
|
624
|
+
});
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
// -----------------------------------------------------------------------------------------------------------------
|
|
628
|
+
// All four options
|
|
629
|
+
// -----------------------------------------------------------------------------------------------------------------
|
|
630
|
+
|
|
631
|
+
describe("$secure all options combined", () => {
|
|
632
|
+
it("should allow when issuer + role + permission + guard all pass", async () => {
|
|
633
|
+
const { alepha, app, users } = setup();
|
|
634
|
+
await alepha.start();
|
|
635
|
+
|
|
636
|
+
expect(await app.allOptions.run({}, { user: users.editor })).toBe("all");
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
it("should deny when only issuer fails", async () => {
|
|
640
|
+
const { alepha, app, users } = setup();
|
|
641
|
+
await alepha.start();
|
|
642
|
+
|
|
643
|
+
const externalEditor = {
|
|
644
|
+
...users.editor,
|
|
645
|
+
id: randomUUID(),
|
|
646
|
+
realm: "external",
|
|
647
|
+
};
|
|
648
|
+
await expect(
|
|
649
|
+
app.allOptions.run({}, { user: externalEditor }),
|
|
650
|
+
).rejects.toThrowError(ForbiddenError);
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
it("should deny when only role fails", async () => {
|
|
654
|
+
const { alepha, app, users } = setup();
|
|
655
|
+
await alepha.start();
|
|
656
|
+
|
|
657
|
+
const viewerAllowed = { ...users.viewer, email: "allowed@test.com" };
|
|
658
|
+
await expect(
|
|
659
|
+
app.allOptions.run({}, { user: viewerAllowed }),
|
|
660
|
+
).rejects.toThrowError(ForbiddenError);
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
it("should deny when only permission fails", async () => {
|
|
664
|
+
const { alepha, app, users } = setup();
|
|
665
|
+
await alepha.start();
|
|
666
|
+
|
|
667
|
+
// moderator role has posts:delete and comments:delete, but not posts:create
|
|
668
|
+
const modAllowed = {
|
|
669
|
+
...users.moderator,
|
|
670
|
+
roles: ["moderator"],
|
|
671
|
+
email: "allowed@test.com",
|
|
672
|
+
};
|
|
673
|
+
await expect(
|
|
674
|
+
app.allOptions.run({}, { user: modAllowed }),
|
|
675
|
+
).rejects.toThrowError(ForbiddenError);
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
it("should deny when only guard fails", async () => {
|
|
679
|
+
const { alepha, app, users } = setup();
|
|
680
|
+
await alepha.start();
|
|
681
|
+
|
|
682
|
+
await expect(
|
|
683
|
+
app.allOptions.run({}, { user: users.editorWrongEmail }),
|
|
684
|
+
).rejects.toThrowError(ForbiddenError);
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
it("should deny unauthenticated user", async () => {
|
|
688
|
+
const { alepha, app } = setup();
|
|
689
|
+
await alepha.start();
|
|
690
|
+
|
|
691
|
+
await expect(app.allOptions.run({})).rejects.toThrowError(
|
|
692
|
+
UnauthorizedError,
|
|
693
|
+
);
|
|
694
|
+
});
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
// -----------------------------------------------------------------------------------------------------------------
|
|
698
|
+
// Multiple permissions (AND logic)
|
|
699
|
+
// -----------------------------------------------------------------------------------------------------------------
|
|
700
|
+
|
|
701
|
+
describe("$secure multiple permissions (AND)", () => {
|
|
702
|
+
it("should allow when user has all required permissions", async () => {
|
|
703
|
+
const { alepha, app, users } = setup();
|
|
704
|
+
await alepha.start();
|
|
705
|
+
|
|
706
|
+
// moderator has posts:read AND posts:delete
|
|
707
|
+
expect(
|
|
708
|
+
await app.multiplePermissions.run({}, { user: users.moderator }),
|
|
709
|
+
).toBe("multi-perm");
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
it("should deny when user has only some of the required permissions", async () => {
|
|
713
|
+
const { alepha, app, users } = setup();
|
|
714
|
+
await alepha.start();
|
|
715
|
+
|
|
716
|
+
// viewer has posts:read but NOT posts:delete
|
|
717
|
+
await expect(
|
|
718
|
+
app.multiplePermissions.run({}, { user: users.viewer }),
|
|
719
|
+
).rejects.toThrowError(ForbiddenError);
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
it("should deny when user has none of the required permissions", async () => {
|
|
723
|
+
const { alepha, app, users } = setup();
|
|
724
|
+
await alepha.start();
|
|
725
|
+
|
|
726
|
+
await expect(
|
|
727
|
+
app.multiplePermissions.run({}, { user: users.noRoles }),
|
|
728
|
+
).rejects.toThrowError(ForbiddenError);
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
it("should allow admin with wildcard permission", async () => {
|
|
732
|
+
const { alepha, app, users } = setup();
|
|
733
|
+
await alepha.start();
|
|
734
|
+
|
|
735
|
+
expect(await app.multiplePermissions.run({}, { user: users.admin })).toBe(
|
|
736
|
+
"multi-perm",
|
|
737
|
+
);
|
|
738
|
+
});
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
// -----------------------------------------------------------------------------------------------------------------
|
|
742
|
+
// Multiple issuers (OR logic)
|
|
743
|
+
// -----------------------------------------------------------------------------------------------------------------
|
|
744
|
+
|
|
745
|
+
describe("$secure multiple issuers (OR)", () => {
|
|
746
|
+
it("should allow user from first issuer", async () => {
|
|
747
|
+
const { alepha, app, users } = setup();
|
|
748
|
+
await alepha.start();
|
|
749
|
+
|
|
750
|
+
expect(await app.multipleIssuers.run({}, { user: users.editor })).toBe(
|
|
751
|
+
"multi-issuer",
|
|
752
|
+
);
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
it("should allow user from second issuer", async () => {
|
|
756
|
+
const { alepha, app, users } = setup();
|
|
757
|
+
await alepha.start();
|
|
758
|
+
|
|
759
|
+
expect(await app.multipleIssuers.run({}, { user: users.partner })).toBe(
|
|
760
|
+
"multi-issuer",
|
|
761
|
+
);
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
it("should deny user from unknown issuer", async () => {
|
|
765
|
+
const { alepha, app } = setup();
|
|
766
|
+
await alepha.start();
|
|
767
|
+
|
|
768
|
+
const unknownRealmUser = {
|
|
769
|
+
id: randomUUID(),
|
|
770
|
+
roles: ["admin"],
|
|
771
|
+
realm: "unknown",
|
|
772
|
+
name: "Unknown",
|
|
773
|
+
};
|
|
774
|
+
await expect(
|
|
775
|
+
app.multipleIssuers.run({}, { user: unknownRealmUser }),
|
|
776
|
+
).rejects.toThrowError(ForbiddenError);
|
|
777
|
+
});
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
// -----------------------------------------------------------------------------------------------------------------
|
|
781
|
+
// Multiple roles (OR logic)
|
|
782
|
+
// -----------------------------------------------------------------------------------------------------------------
|
|
783
|
+
|
|
784
|
+
describe("$secure multiple roles (OR)", () => {
|
|
785
|
+
it("should allow user with first matching role", async () => {
|
|
786
|
+
const { alepha, app, users } = setup();
|
|
787
|
+
await alepha.start();
|
|
788
|
+
|
|
789
|
+
expect(await app.multipleRoles.run({}, { user: users.editor })).toBe(
|
|
790
|
+
"multi-role",
|
|
791
|
+
);
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
it("should allow user with second matching role", async () => {
|
|
795
|
+
const { alepha, app, users } = setup();
|
|
796
|
+
await alepha.start();
|
|
797
|
+
|
|
798
|
+
expect(await app.multipleRoles.run({}, { user: users.moderator })).toBe(
|
|
799
|
+
"multi-role",
|
|
800
|
+
);
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
it("should allow user with both roles", async () => {
|
|
804
|
+
const { alepha, app, users } = setup();
|
|
805
|
+
await alepha.start();
|
|
806
|
+
|
|
807
|
+
expect(
|
|
808
|
+
await app.multipleRoles.run({}, { user: users.editorAndModerator }),
|
|
809
|
+
).toBe("multi-role");
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
it("should deny user with none of the required roles", async () => {
|
|
813
|
+
const { alepha, app, users } = setup();
|
|
814
|
+
await alepha.start();
|
|
815
|
+
|
|
816
|
+
await expect(
|
|
817
|
+
app.multipleRoles.run({}, { user: users.viewer }),
|
|
818
|
+
).rejects.toThrowError(ForbiddenError);
|
|
819
|
+
});
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
// -----------------------------------------------------------------------------------------------------------------
|
|
823
|
+
// Check order: issuer → role → permission → guard
|
|
824
|
+
// -----------------------------------------------------------------------------------------------------------------
|
|
825
|
+
|
|
826
|
+
describe("$secure check order", () => {
|
|
827
|
+
it("should check issuer before role", async () => {
|
|
828
|
+
const { alepha, app, users } = setup();
|
|
829
|
+
await alepha.start();
|
|
830
|
+
|
|
831
|
+
// User from wrong issuer with wrong role — error message should be about issuer, not role
|
|
832
|
+
const wrongBoth = {
|
|
833
|
+
id: randomUUID(),
|
|
834
|
+
roles: ["viewer"],
|
|
835
|
+
realm: "external",
|
|
836
|
+
name: "Wrong",
|
|
837
|
+
};
|
|
838
|
+
await expect(
|
|
839
|
+
app.issuerAndRole.run({}, { user: wrongBoth }),
|
|
840
|
+
).rejects.toThrowError(/issuer/i);
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
it("should check role before permission", async () => {
|
|
844
|
+
const { alepha, app, users } = setup();
|
|
845
|
+
await alepha.start();
|
|
846
|
+
|
|
847
|
+
// User with wrong role and wrong permission — error should be about role
|
|
848
|
+
await expect(
|
|
849
|
+
app.roleAndPermission.run({}, { user: users.viewer }),
|
|
850
|
+
).rejects.toThrowError(/role.*required/i);
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
it("should check permission before guard", async () => {
|
|
854
|
+
const { alepha, app, users } = setup();
|
|
855
|
+
await alepha.start();
|
|
856
|
+
|
|
857
|
+
// User with correct email (guard would pass) but no permission
|
|
858
|
+
const noPermsAllowed = { ...users.noRoles, email: "allowed@test.com" };
|
|
859
|
+
await expect(
|
|
860
|
+
app.permissionAndGuard.run({}, { user: noPermsAllowed }),
|
|
861
|
+
).rejects.toThrowError(/permission.*required/i);
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
it("should reach guard only after all other checks pass", async () => {
|
|
865
|
+
const { alepha, app, users } = setup();
|
|
866
|
+
await alepha.start();
|
|
867
|
+
|
|
868
|
+
// Editor with wrong email — issuer/role/permission all pass, only guard fails
|
|
869
|
+
await expect(
|
|
870
|
+
app.allOptions.run({}, { user: users.editorWrongEmail }),
|
|
871
|
+
).rejects.toThrowError(/access denied/i);
|
|
872
|
+
});
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
// -----------------------------------------------------------------------------------------------------------------
|
|
876
|
+
// Edge cases
|
|
877
|
+
// -----------------------------------------------------------------------------------------------------------------
|
|
878
|
+
|
|
879
|
+
describe("$secure edge cases", () => {
|
|
880
|
+
it("should deny user with empty roles array against role check", async () => {
|
|
881
|
+
const { alepha, app, users } = setup();
|
|
882
|
+
await alepha.start();
|
|
883
|
+
|
|
884
|
+
await expect(
|
|
885
|
+
app.requireViewer.run({}, { user: users.noRoles }),
|
|
886
|
+
).rejects.toThrowError(ForbiddenError);
|
|
887
|
+
});
|
|
888
|
+
|
|
889
|
+
it("should deny user with undefined roles against role check", async () => {
|
|
890
|
+
const { alepha, app } = setup();
|
|
891
|
+
await alepha.start();
|
|
892
|
+
|
|
893
|
+
const noRolesUndefined = {
|
|
894
|
+
id: randomUUID(),
|
|
895
|
+
realm: "main",
|
|
896
|
+
name: "NoRoles",
|
|
897
|
+
};
|
|
898
|
+
await expect(
|
|
899
|
+
app.requireViewer.run({}, { user: noRolesUndefined }),
|
|
900
|
+
).rejects.toThrowError(ForbiddenError);
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
it("should deny user with no realm against issuer check", async () => {
|
|
904
|
+
const { alepha, app, users } = setup();
|
|
905
|
+
await alepha.start();
|
|
906
|
+
|
|
907
|
+
await expect(
|
|
908
|
+
app.requireMainIssuer.run({}, { user: users.noRealm }),
|
|
909
|
+
).rejects.toThrowError(ForbiddenError);
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
it("should deny user with empty roles array against permission check", async () => {
|
|
913
|
+
const { alepha, app, users } = setup();
|
|
914
|
+
await alepha.start();
|
|
915
|
+
|
|
916
|
+
await expect(
|
|
917
|
+
app.requireRead.run({}, { user: users.noRoles }),
|
|
918
|
+
).rejects.toThrowError(ForbiddenError);
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
it("should allow auth-only action for user with no roles", async () => {
|
|
922
|
+
const { alepha, app, users } = setup();
|
|
923
|
+
await alepha.start();
|
|
924
|
+
|
|
925
|
+
expect(await app.authOnly.run({}, { user: users.noRoles })).toBe(
|
|
926
|
+
"auth-only",
|
|
927
|
+
);
|
|
928
|
+
});
|
|
929
|
+
|
|
930
|
+
it("should allow auth-only action for user with no realm", async () => {
|
|
931
|
+
const { alepha, app, users } = setup();
|
|
932
|
+
await alepha.start();
|
|
933
|
+
|
|
934
|
+
expect(await app.authOnly.run({}, { user: users.noRealm })).toBe(
|
|
935
|
+
"auth-only",
|
|
936
|
+
);
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
it("should allow admin wildcard through any permission check", async () => {
|
|
940
|
+
const { alepha, app, users } = setup();
|
|
941
|
+
await alepha.start();
|
|
942
|
+
|
|
943
|
+
expect(await app.requireRead.run({}, { user: users.admin })).toBe("read");
|
|
944
|
+
});
|
|
945
|
+
});
|