@reventlessdev/reventless-aws 3.0.0-alpha.369 → 3.0.0-alpha.370
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/package.json +7 -7
- package/scripts/ProvisionAdmin.res +2 -2
- package/scripts/ProvisionAdmin.res.mjs +7 -6
- package/src/Platform.res +32 -3
- package/src/Platform.res.mjs +22 -5
- package/src/Platform_Stack.res +6 -3
- package/src/Platform_Stack.res.mjs +3 -3
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res +1 -1
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res.mjs +1 -1
- package/src/adapter/Auth/Auth_ActiveRoleTrigger_Ops.res +1 -1
- package/src/adapter/Runtime/PgQueryResolverEntryPoint_Ops.res +1 -1
- package/src/adapter/Runtime/PgQueryResolverEntryPoint_Ops.res.mjs +2 -1
- package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res +1 -0
- package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res.mjs +1 -0
- package/src/components/Api/AppSync_Adapter.res +4 -3
- package/src/components/Api/AppSync_SdlDecorate.res +7 -3
- package/src/components/Api/AppSync_SdlDecorate.res.mjs +4 -3
- package/src/util/Util_OwnerScopeEnv.res +9 -0
- package/src/util/Util_OwnerScopeEnv.res.mjs +14 -1
- package/src/util/Util_ShellConfig.res +4 -0
- package/src/util/Util_ShellConfig.res.mjs +5 -1
- package/tests/AppSync_AdapterTest.res +46 -23
- package/tests/AppSync_AdapterTest.res.mjs +35 -27
- package/tests/PgPipeline_IntegrationTest.res +2 -1
- package/tests/PgQueryDbUnion_IntegrationTest.res +2 -1
- package/tests/Util_OwnerScopeEnvTest.res +48 -2
- package/tests/Util_OwnerScopeEnvTest.res.mjs +49 -3
- package/tests/Util_ShellConfigTest.res +17 -0
- package/tests/Util_ShellConfigTest.res.mjs +18 -0
- package/tests/integration/AggTestAggregate.res +2 -1
- package/tests/integration/EpCompositeSlice.res +2 -1
- package/tests/integration/EpTestSlice.res +2 -1
- package/tests/integration/QueryDbUnion_DynamoDb_IntegrationTest.res +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,21 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# 3.0.0-alpha.370 (2026-10-01)
|
|
7
|
+
|
|
8
|
+
* feat!: roles a plugin declares, groups a platform provides ([43daace](https://github.com/ReventlessDev/reventless-core/commit/43daace6ca588afe22562d079af1e6f1da81942e))
|
|
9
|
+
|
|
10
|
+
### BREAKING CHANGES
|
|
11
|
+
|
|
12
|
+
* AllowGroups and AdminGroup are removed — write AllowRoles over
|
|
13
|
+
the plugin's Roles.t, and use Role.admin / Platform.adminGroup(). Spec module
|
|
14
|
+
types declare `type role`, and their rules are Authorization.rule<role>; a spec
|
|
15
|
+
that writes its own rule declares `type role` (Reventless.Role.name for a rule
|
|
16
|
+
over Authorization.permission). Role.name is a private string: make one from a
|
|
17
|
+
string with Role.make.
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
6
21
|
# 3.0.0-alpha.369 (2026-10-01)
|
|
7
22
|
|
|
8
23
|
### Features
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-aws",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.370",
|
|
4
4
|
"description": "AWS adapters for Reventless",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"bin": {
|
|
@@ -22,18 +22,18 @@
|
|
|
22
22
|
"uuid": "^13.0.0",
|
|
23
23
|
"yaml": "^2.8.3",
|
|
24
24
|
"@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.27",
|
|
25
|
+
"@reventlessdev/rescript-effect": "0.1.0-alpha.34",
|
|
25
26
|
"@reventlessdev/rescript-jest": "1.0.0-alpha.11",
|
|
26
27
|
"@reventlessdev/rescript-node": "2.0.0-alpha.20",
|
|
27
28
|
"@reventlessdev/rescript-pulumi-aws": "3.0.0-alpha.22",
|
|
28
|
-
"@reventlessdev/rescript-effect": "0.1.0-alpha.34",
|
|
29
|
-
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.22",
|
|
30
29
|
"@reventlessdev/rescript-uuid": "2.0.0-alpha.1",
|
|
30
|
+
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.22",
|
|
31
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.286",
|
|
31
32
|
"@reventlessdev/rescript-web": "1.0.0-alpha.7",
|
|
32
|
-
"@reventlessdev/reventless-infra": "3.0.0-alpha.183",
|
|
33
|
-
"@reventlessdev/reventless-core": "3.0.0-alpha.285",
|
|
34
|
-
"@reventlessdev/reventless-postgres": "3.0.0-alpha.149",
|
|
35
33
|
"@reventlessdev/reventless-interop": "3.0.0-alpha.38",
|
|
36
|
-
"@reventlessdev/reventless-
|
|
34
|
+
"@reventlessdev/reventless-postgres": "3.0.0-alpha.150",
|
|
35
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.156",
|
|
36
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.184"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"rescript": "12.3.1",
|
|
@@ -76,7 +76,7 @@ Make the first administrator of a Reventless deployment.
|
|
|
76
76
|
one. The run always names the stack it used.
|
|
77
77
|
--email <address> The address they sign in with.
|
|
78
78
|
|
|
79
|
-
Creates the "${Reventless.
|
|
79
|
+
Creates the "${Reventless.Role.adminGroup()}" group if it is missing, the account if
|
|
80
80
|
it is missing, a working password, and the membership between them. Provisions
|
|
81
81
|
nothing that a platform stack owns. Region and credentials come from the
|
|
82
82
|
environment, as for any AWS SDK call.
|
|
@@ -222,7 +222,7 @@ let run = async ({email, providerId, stack}: request): result<unit, string> =>
|
|
|
222
222
|
switch await checkPoolAcceptsEmail(~providerId) {
|
|
223
223
|
| Error(_) as e => e
|
|
224
224
|
| Ok() =>
|
|
225
|
-
let group = Reventless.
|
|
225
|
+
let group = Reventless.Role.adminGroup()
|
|
226
226
|
let password = Reventless.Util_Password.generate()
|
|
227
227
|
await ensureGroup(~providerId, ~group)
|
|
228
228
|
await ensureUser(~providerId, ~email)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
import * as Stdlib_Result from "@rescript/runtime/lib/es6/Stdlib_Result.js";
|
|
4
|
+
import * as Role$Reventless from "@reventlessdev/reventless-spec/src/types/Role.res.mjs";
|
|
4
5
|
import * as CliArgs$Reventless from "@reventlessdev/reventless-spec/src/CliArgs.res.mjs";
|
|
5
6
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
6
|
-
import * as AdminGroup$Reventless from "@reventlessdev/reventless-spec/src/types/AdminGroup.res.mjs";
|
|
7
7
|
import * as Util_Password$Reventless from "@reventlessdev/reventless-spec/src/util/Util_Password.res.mjs";
|
|
8
8
|
import * as Util_AwsError$ReventlessAws from "../src/util/Util_AwsError.res.mjs";
|
|
9
9
|
import * as ProvisionCognito$ReventlessAws from "./ProvisionCognito.res.mjs";
|
|
@@ -36,7 +36,7 @@ Make the first administrator of a Reventless deployment.
|
|
|
36
36
|
one. The run always names the stack it used.
|
|
37
37
|
--email <address> The address they sign in with.
|
|
38
38
|
|
|
39
|
-
Creates the "` +
|
|
39
|
+
Creates the "` + Role$Reventless.adminGroup() + `" group if it is missing, the account if
|
|
40
40
|
it is missing, a working password, and the membership between them. Provisions
|
|
41
41
|
nothing that a platform stack owns. Region and credentials come from the
|
|
42
42
|
environment, as for any AWS SDK call.
|
|
@@ -156,12 +156,13 @@ async function run(param) {
|
|
|
156
156
|
if (e$1.TAG !== "Ok") {
|
|
157
157
|
return e$1;
|
|
158
158
|
}
|
|
159
|
+
let group = Role$Reventless.adminGroup();
|
|
159
160
|
let password = Util_Password$Reventless.generate();
|
|
160
|
-
await ensureGroup(providerId,
|
|
161
|
+
await ensureGroup(providerId, group);
|
|
161
162
|
await ensureUser(providerId, email);
|
|
162
163
|
await setPassword(providerId, email, password);
|
|
163
|
-
await addToGroup(providerId, email,
|
|
164
|
-
console.log(signInDetails(providerId, email, password,
|
|
164
|
+
await addToGroup(providerId, email, group);
|
|
165
|
+
console.log(signInDetails(providerId, email, password, group));
|
|
165
166
|
return {
|
|
166
167
|
TAG: "Ok",
|
|
167
168
|
_0: undefined
|
|
@@ -212,4 +213,4 @@ export {
|
|
|
212
213
|
cli,
|
|
213
214
|
main,
|
|
214
215
|
}
|
|
215
|
-
/*
|
|
216
|
+
/* usage Not a pure module */
|
package/src/Platform.res
CHANGED
|
@@ -107,6 +107,24 @@ let messagingEmailSenderRef: ref<Pulumi.Output.t<string>> = ref(Pulumi.Output.ma
|
|
|
107
107
|
`""` means the platform named none, which the runtime reads as its default. */
|
|
108
108
|
let messagingEmailProviderRef: ref<Pulumi.Output.t<string>> = ref(Pulumi.Output.make(""))
|
|
109
109
|
|
|
110
|
+
/** Which group stands for which role, only where the two differ; every other
|
|
111
|
+
role maps to the group of its own name. Call before `Make()`: the platform
|
|
112
|
+
resolves roles while it is built, and a mapping stated later is refused. */
|
|
113
|
+
let roleGroups = Reventless.Role.setGroups
|
|
114
|
+
|
|
115
|
+
/** The groups of a user pool this deployment did not create, which no accounts
|
|
116
|
+
manifest names. They count as provided by the check that every role a plugin
|
|
117
|
+
needs has a group. */
|
|
118
|
+
let providedGroups = Reventless.Role.provideGroups
|
|
119
|
+
|
|
120
|
+
/** The group a role resolves to — the name this platform writes into its
|
|
121
|
+
directives, its shell configuration and the elevated groups. For code that
|
|
122
|
+
grants access by group, so it never spells one itself. */
|
|
123
|
+
let groupOf = Reventless.Role.groupOf
|
|
124
|
+
|
|
125
|
+
/** `groupOf(Role.admin)`. */
|
|
126
|
+
let adminGroup = Reventless.Role.adminGroup
|
|
127
|
+
|
|
110
128
|
module MakeWithConfig = (
|
|
111
129
|
Config: {
|
|
112
130
|
let splitApi: bool
|
|
@@ -144,7 +162,15 @@ module MakeWithConfig = (
|
|
|
144
162
|
// A default, never a forcing: an explicit `setElevatedGroups` in a platform
|
|
145
163
|
// root — including one naming nobody — means what it says and wins. See
|
|
146
164
|
// [Reventless.OwnerScope.defaultElevatedGroups].
|
|
147
|
-
Reventless.OwnerScope.
|
|
165
|
+
Reventless.OwnerScope.defaultElevatedRoles([Reventless.Role.admin])
|
|
166
|
+
|
|
167
|
+
// What this deployment provides for the roles its plugins need: the
|
|
168
|
+
// administrator group the stack declares, and the groups of the accounts
|
|
169
|
+
// provisioning creates. Read when each plugin is built, after the root has
|
|
170
|
+
// mapped its roles.
|
|
171
|
+
Reventless.Role.provideGroupsFrom(() =>
|
|
172
|
+
[Reventless.Role.adminGroup()]->Array.concat(Reventless.AccountsManifest.declaredGroups())
|
|
173
|
+
)
|
|
148
174
|
|
|
149
175
|
// Dispatch per-flavor commandHandlerConfig records to the four runtime
|
|
150
176
|
// builders. Every sub-record is optional; only branches the caller actually
|
|
@@ -308,13 +334,16 @@ module MakeWithConfig = (
|
|
|
308
334
|
// Admin base as a source-API document — auth-decorated (all fields Admin,
|
|
309
335
|
// Cognito-only; the deploy-time SigV4 system-caller fields died with the
|
|
310
336
|
// fragment registry) plus the canonical stamp.
|
|
311
|
-
let adminSourceSdl = (): string =>
|
|
337
|
+
let adminSourceSdl = (): string => {
|
|
338
|
+
// The directive below fixes the administrator's group for this deploy.
|
|
339
|
+
Reventless.Role.freeze()
|
|
312
340
|
assembleCanonicalSourceSdl(
|
|
313
341
|
~baseFragment=AppSync_Adapter.injectAwsAuthAll(
|
|
314
342
|
ReventlessCore.Platform_AdminApi.baseFragment(~cloner=Config.cloner),
|
|
315
|
-
~group=Reventless.
|
|
343
|
+
~group=Reventless.Role.adminGroup(),
|
|
316
344
|
),
|
|
317
345
|
)
|
|
346
|
+
}
|
|
318
347
|
|
|
319
348
|
// Split-mode Domain source document: relay base types + Platform_ping — the
|
|
320
349
|
// Domain merged API's canonical owner (plugin fields come from plugin sources).
|
package/src/Platform.res.mjs
CHANGED
|
@@ -13,10 +13,10 @@ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
|
13
13
|
import * as Pulumi from "@pulumi/pulumi";
|
|
14
14
|
import * as Belt_SetString from "@rescript/runtime/lib/es6/Belt_SetString.js";
|
|
15
15
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
16
|
+
import * as Role$Reventless from "@reventlessdev/reventless-spec/src/types/Role.res.mjs";
|
|
16
17
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
17
18
|
import * as Plugin$ReventlessAws from "./components/Plugin.res.mjs";
|
|
18
19
|
import * as Util_Sury$Reventless from "@reventlessdev/reventless-spec/src/util/Util_Sury.res.mjs";
|
|
19
|
-
import * as AdminGroup$Reventless from "@reventlessdev/reventless-spec/src/types/AdminGroup.res.mjs";
|
|
20
20
|
import * as Logger$ReventlessCore from "@reventlessdev/reventless-core/src/util/Logger.res.mjs";
|
|
21
21
|
import * as OwnerScope$Reventless from "@reventlessdev/reventless-spec/src/types/OwnerScope.res.mjs";
|
|
22
22
|
import * as Plugin$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/component/Plugin.res.mjs";
|
|
@@ -34,6 +34,7 @@ import * as Util_Bundle$ReventlessAws from "./util/Util_Bundle.res.mjs";
|
|
|
34
34
|
import * as Plugin_Stack$ReventlessAws from "./plugin/stack/Plugin_Stack.res.mjs";
|
|
35
35
|
import * as UiFragments$ReventlessCore from "@reventlessdev/reventless-core/src/admin/UiFragmentRegistry/StateView/UiFragments.res.mjs";
|
|
36
36
|
import * as Util_Pulumi$ReventlessCore from "@reventlessdev/reventless-core/src/util/Util_Pulumi.res.mjs";
|
|
37
|
+
import * as AccountsManifest$Reventless from "@reventlessdev/reventless-spec/src/types/AccountsManifest.res.mjs";
|
|
37
38
|
import * as Platform_Casts$ReventlessAws from "./Platform_Casts.res.mjs";
|
|
38
39
|
import * as Platform_Stack$ReventlessAws from "./Platform_Stack.res.mjs";
|
|
39
40
|
import * as QueryDbBackend$ReventlessAws from "./adapter/QueryDb/QueryDbBackend.res.mjs";
|
|
@@ -152,7 +153,8 @@ let messagingEmailProviderRef = {
|
|
|
152
153
|
};
|
|
153
154
|
|
|
154
155
|
function MakeWithConfig(Config) {
|
|
155
|
-
OwnerScope$Reventless.
|
|
156
|
+
OwnerScope$Reventless.defaultElevatedRoles([Role$Reventless.admin]);
|
|
157
|
+
Role$Reventless.provideGroupsFrom(() => [Role$Reventless.adminGroup()].concat(AccountsManifest$Reventless.declaredGroups()));
|
|
156
158
|
Stdlib_Option.forEach(Config.commandHandlerConfig.aggregates, param => {
|
|
157
159
|
Stdlib_Option.forEach(param.sync, AggregateRuntime_Builder_Single$ReventlessAws.setConfig);
|
|
158
160
|
Stdlib_Option.forEach(param.async, AggregateRuntime_Builder_Single_Async$ReventlessAws.setConfig);
|
|
@@ -214,7 +216,8 @@ function MakeWithConfig(Config) {
|
|
|
214
216
|
subscriptionSources: []
|
|
215
217
|
});
|
|
216
218
|
let adminSourceSdl = () => {
|
|
217
|
-
|
|
219
|
+
Role$Reventless.freeze();
|
|
220
|
+
let baseFragment = AppSync_Adapter$ReventlessAws.injectAwsAuthAll(Platform_AdminApi$ReventlessCore.baseFragment(Config.cloner), Role$Reventless.adminGroup(), undefined);
|
|
218
221
|
return AppSync_SdlDecorate$ReventlessAws.stampCanonicalTypes(AppSync_Adapter$ReventlessAws.stitchStandaloneWithAwsDirectives(baseFragment));
|
|
219
222
|
};
|
|
220
223
|
let match;
|
|
@@ -1566,7 +1569,8 @@ function MakeWithConfig(Config) {
|
|
|
1566
1569
|
|
|
1567
1570
|
function Make($star) {
|
|
1568
1571
|
let commandHandlerConfig = {};
|
|
1569
|
-
OwnerScope$Reventless.
|
|
1572
|
+
OwnerScope$Reventless.defaultElevatedRoles([Role$Reventless.admin]);
|
|
1573
|
+
Role$Reventless.provideGroupsFrom(() => [Role$Reventless.adminGroup()].concat(AccountsManifest$Reventless.declaredGroups()));
|
|
1570
1574
|
Stdlib_Option.forEach(commandHandlerConfig.aggregates, param => {
|
|
1571
1575
|
Stdlib_Option.forEach(param.sync, AggregateRuntime_Builder_Single$ReventlessAws.setConfig);
|
|
1572
1576
|
Stdlib_Option.forEach(param.async, AggregateRuntime_Builder_Single_Async$ReventlessAws.setConfig);
|
|
@@ -1628,7 +1632,8 @@ function Make($star) {
|
|
|
1628
1632
|
subscriptionSources: []
|
|
1629
1633
|
});
|
|
1630
1634
|
let adminSourceSdl = () => {
|
|
1631
|
-
|
|
1635
|
+
Role$Reventless.freeze();
|
|
1636
|
+
let baseFragment = AppSync_Adapter$ReventlessAws.injectAwsAuthAll(Platform_AdminApi$ReventlessCore.baseFragment(false), Role$Reventless.adminGroup(), undefined);
|
|
1632
1637
|
return AppSync_SdlDecorate$ReventlessAws.stampCanonicalTypes(AppSync_Adapter$ReventlessAws.stitchStandaloneWithAwsDirectives(baseFragment));
|
|
1633
1638
|
};
|
|
1634
1639
|
let match;
|
|
@@ -2958,6 +2963,14 @@ function Make($star) {
|
|
|
2958
2963
|
};
|
|
2959
2964
|
}
|
|
2960
2965
|
|
|
2966
|
+
let roleGroups = Role$Reventless.setGroups;
|
|
2967
|
+
|
|
2968
|
+
let providedGroups = Role$Reventless.provideGroups;
|
|
2969
|
+
|
|
2970
|
+
let groupOf = Role$Reventless.groupOf;
|
|
2971
|
+
|
|
2972
|
+
let adminGroup = Role$Reventless.adminGroup;
|
|
2973
|
+
|
|
2961
2974
|
export {
|
|
2962
2975
|
log,
|
|
2963
2976
|
apiConfigRef,
|
|
@@ -2969,6 +2982,10 @@ export {
|
|
|
2969
2982
|
geocoderPlaceIndexRef,
|
|
2970
2983
|
messagingEmailSenderRef,
|
|
2971
2984
|
messagingEmailProviderRef,
|
|
2985
|
+
roleGroups,
|
|
2986
|
+
providedGroups,
|
|
2987
|
+
groupOf,
|
|
2988
|
+
adminGroup,
|
|
2972
2989
|
MakeWithConfig,
|
|
2973
2990
|
Make,
|
|
2974
2991
|
}
|
package/src/Platform_Stack.res
CHANGED
|
@@ -25,7 +25,7 @@ type cognitoUserPool = {
|
|
|
25
25
|
* with SPA-friendly defaults (email sign-in unless `platform:loginIdentifier`
|
|
26
26
|
* says otherwise, 12-char password policy, no MFA, and admin-only user
|
|
27
27
|
* creation unless `platform:signUpMode` says otherwise), plus the
|
|
28
|
-
* administrator group named by [Reventless.
|
|
28
|
+
* administrator group named by [Reventless.Role.adminGroup] and an elevated-groups
|
|
29
29
|
* default naming it. What it does **not** create is the first administrator:
|
|
30
30
|
* accounts are not stack resources, and a pool full of them is not something a
|
|
31
31
|
* `pulumi destroy` should be able to empty. `provision-admin` makes that one,
|
|
@@ -302,7 +302,7 @@ let _resolveUncached = (): cognitoUserPool => {
|
|
|
302
302
|
// An ordinary child resource: the pool is ours, so this needs no out-of-band
|
|
303
303
|
// call and Pulumi's state stays the authority for whether it exists.
|
|
304
304
|
let _adminGroup = Util_CognitoGroupUser.addUserGroup(
|
|
305
|
-
~name=Reventless.
|
|
305
|
+
~name=Reventless.Role.adminGroup(),
|
|
306
306
|
~userPoolId=pool.id,
|
|
307
307
|
)
|
|
308
308
|
|
|
@@ -400,7 +400,10 @@ let _resolveUncached = (): cognitoUserPool => {
|
|
|
400
400
|
// name is what a caller needs either way, and an output that appeared in one
|
|
401
401
|
// mode and not the other would read as "there is no administrator group here"
|
|
402
402
|
// rather than "this stack did not create it".
|
|
403
|
-
Pulumi.Pulumi.export(
|
|
403
|
+
Pulumi.Pulumi.export(
|
|
404
|
+
"identityProviderAdminGroup",
|
|
405
|
+
Pulumi.Output.make(Reventless.Role.adminGroup()),
|
|
406
|
+
)
|
|
404
407
|
|
|
405
408
|
// Exported so an operator can read back the store this provider resolved to —
|
|
406
409
|
// the name is derived, not configured, so this is a convenience for checking
|
|
@@ -5,7 +5,7 @@ import * as Pulumi$Pulumi from "@reventlessdev/rescript-pulumi-pulumi/src/Pulumi
|
|
|
5
5
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
6
6
|
import * as Pulumi from "@pulumi/pulumi";
|
|
7
7
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
8
|
-
import * as
|
|
8
|
+
import * as Role$Reventless from "@reventlessdev/reventless-spec/src/types/Role.res.mjs";
|
|
9
9
|
import * as Logger$ReventlessCore from "@reventlessdev/reventless-core/src/util/Logger.res.mjs";
|
|
10
10
|
import * as AWS_Tags$ReventlessAws from "./adapter/AWS_Tags.res.mjs";
|
|
11
11
|
import * as Auth_SignUpMode$ReventlessAws from "./adapter/Auth/Auth_SignUpMode.res.mjs";
|
|
@@ -133,7 +133,7 @@ function _resolveUncached() {
|
|
|
133
133
|
mfaConfiguration: "OFF",
|
|
134
134
|
tags: AWS_Tags$ReventlessAws.make("HostUiPool", "Platform", "Auth", "Platform", undefined, undefined, undefined, undefined)
|
|
135
135
|
});
|
|
136
|
-
Util_CognitoGroupUser$ReventlessAws.addUserGroup(
|
|
136
|
+
Util_CognitoGroupUser$ReventlessAws.addUserGroup(Role$Reventless.adminGroup(), pool.id);
|
|
137
137
|
let tokenUnits2_accessToken = "minutes";
|
|
138
138
|
let tokenUnits2_idToken = "minutes";
|
|
139
139
|
let tokenUnits2_refreshToken = "days";
|
|
@@ -181,7 +181,7 @@ function _resolveUncached() {
|
|
|
181
181
|
Pulumi$Pulumi.$$export("cognitoUserPoolArn", result.poolArn);
|
|
182
182
|
Pulumi$Pulumi.$$export("cognitoRegion", regionOutput);
|
|
183
183
|
Pulumi$Pulumi.$$export("cognitoUserPoolManaged", managedStr);
|
|
184
|
-
Pulumi$Pulumi.$$export("identityProviderAdminGroup", Pulumi.output(
|
|
184
|
+
Pulumi$Pulumi.$$export("identityProviderAdminGroup", Pulumi.output(Role$Reventless.adminGroup()));
|
|
185
185
|
Pulumi$Pulumi.$$export("activeRoleStore", result.activeRoleTable.name);
|
|
186
186
|
return result;
|
|
187
187
|
}
|
|
@@ -194,7 +194,7 @@ let make = (
|
|
|
194
194
|
let adminEntryJson =
|
|
195
195
|
ReventlessCore.Platform_ComponentDefinitionsApi.encodePluginStructureEntry(
|
|
196
196
|
~pluginId=ReventlessCore.Platform_Admin_Structure.pluginId,
|
|
197
|
-
ReventlessCore.Platform_Admin_Structure.structure,
|
|
197
|
+
ReventlessCore.Platform_Admin_Structure.structure(),
|
|
198
198
|
)->JSON.stringify
|
|
199
199
|
|
|
200
200
|
// Bundle reventless-aws (the compiled `_Ops` handler lives inside it) and
|
|
@@ -107,7 +107,7 @@ function make(api, pluginReadModelTableName, offloadBucketName, schemaReady, bak
|
|
|
107
107
|
encodeSelections(j.selections)
|
|
108
108
|
]
|
|
109
109
|
]))) : "";
|
|
110
|
-
let adminEntryJson = JSON.stringify(Platform_ComponentDefinitionsApi$ReventlessCore.encodePluginStructureEntry(Platform_Admin_Structure$ReventlessCore.pluginId, undefined, Platform_Admin_Structure$ReventlessCore.structure));
|
|
110
|
+
let adminEntryJson = JSON.stringify(Platform_ComponentDefinitionsApi$ReventlessCore.encodePluginStructureEntry(Platform_Admin_Structure$ReventlessCore.pluginId, undefined, Platform_Admin_Structure$ReventlessCore.structure()));
|
|
111
111
|
let packageDirs = Object.fromEntries([[
|
|
112
112
|
"@reventlessdev/reventless-aws",
|
|
113
113
|
Util_Bundle$ReventlessAws.resolvePackageRoot(undefined, "@reventlessdev/reventless-aws")
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// ([Auth_ActiveRoleTrigger.res] bundles it and attaches it to the user pool).
|
|
7
7
|
//
|
|
8
8
|
// The narrowing has to happen here rather than in our own enforcement code
|
|
9
|
-
// because `@authorize(
|
|
9
|
+
// because `@authorize(AllowRoles([...]))` compiles to a field directive AppSync
|
|
10
10
|
// evaluates against `cognito:groups` **before any of our code executes**. A
|
|
11
11
|
// request header we invent could scope reads correctly and still leave every
|
|
12
12
|
// group-gated mutation callable — right about the data, wrong about the writes.
|
|
@@ -150,7 +150,7 @@ let registerBinding = (
|
|
|
150
150
|
),
|
|
151
151
|
labelField: entry.labelField,
|
|
152
152
|
includeIdParam: entry.includeIdParam,
|
|
153
|
-
authorization: spec.authorization,
|
|
153
|
+
authorization: spec.authorization->Reventless.Authorization.named,
|
|
154
154
|
// From the same schema `capability` is derived from, one line above, so the
|
|
155
155
|
// two cannot end up disagreeing about this read model's fields.
|
|
156
156
|
ownerField: Reventless.Owner.fieldNames(spec.stateSchema)->Array.get(0),
|
|
@@ -6,6 +6,7 @@ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
|
6
6
|
import * as Owner$Reventless from "@reventlessdev/reventless-spec/src/components/Owner.res.mjs";
|
|
7
7
|
import * as RowImage$Reventless from "@reventlessdev/reventless-spec/src/semantic/RowImage.res.mjs";
|
|
8
8
|
import * as PgRuntime$ReventlessAws from "../Postgres/PgRuntime.res.mjs";
|
|
9
|
+
import * as Authorization$Reventless from "@reventlessdev/reventless-spec/src/types/Authorization.res.mjs";
|
|
9
10
|
import * as HandlerFactoryHelpersMjs from "./HandlerFactoryHelpers.mjs";
|
|
10
11
|
import * as StateAnnotations$Reventless from "@reventlessdev/reventless-spec/src/components/StateAnnotations.res.mjs";
|
|
11
12
|
import * as PgQueryResolver_Lambda$ReventlessAws from "../QueryDb/PgQueryResolver_Lambda.res.mjs";
|
|
@@ -75,7 +76,7 @@ function registerBinding(pushdowns, pgConnection, entry, spec) {
|
|
|
75
76
|
capability: GraphQL_FragmentGenerator$ReventlessCore.deriveServerCapability(entry.readModelName, spec.stateSchema),
|
|
76
77
|
labelField: entry.labelField,
|
|
77
78
|
includeIdParam: entry.includeIdParam,
|
|
78
|
-
authorization: spec.authorization,
|
|
79
|
+
authorization: Authorization$Reventless.named(spec.authorization),
|
|
79
80
|
ownerField: Owner$Reventless.fieldNames(spec.stateSchema)[0],
|
|
80
81
|
retiredField: Stdlib_Option.map(Stdlib_Option.flatMap(StateAnnotations$Reventless.getSpec(spec.stateSchema), a => a.retired), r => r.field),
|
|
81
82
|
retiredValues: Stdlib_Option.flatMap(Stdlib_Option.flatMap(StateAnnotations$Reventless.getSpec(spec.stateSchema), a => a.retired), r => r.values),
|
|
@@ -246,6 +246,7 @@ let makeFromCodeAsset: (
|
|
|
246
246
|
// concludes nobody is elevated and stamps an operator's on-behalf write with
|
|
247
247
|
// the operator's own id.
|
|
248
248
|
Util_OwnerScopeEnv.applyElevatedGroupsDefault(variables)
|
|
249
|
+
Util_OwnerScopeEnv.applyRoleGroupsDefault(variables)
|
|
249
250
|
|
|
250
251
|
// ESM self-containment (Option C): every code archive built by
|
|
251
252
|
// Util_Bundle.buildCodeArchive ships register-hook.mjs + layer-resolver.mjs at
|
|
@@ -104,6 +104,7 @@ function makeFromCodeAsset(name, unitKind, componentKind, code, sourceCodeHash,
|
|
|
104
104
|
});
|
|
105
105
|
Util_LambdaLogging$ReventlessAws.applyLogLevelDefault(variables);
|
|
106
106
|
Util_OwnerScopeEnv$ReventlessAws.applyElevatedGroupsDefault(variables);
|
|
107
|
+
Util_OwnerScopeEnv$ReventlessAws.applyRoleGroupsDefault(variables);
|
|
107
108
|
variables["NODE_OPTIONS"] = Util_Bundle$ReventlessAws.esmLoaderNodeOptions;
|
|
108
109
|
variables["ESM_FALLBACK_DIRS"] = Util_Bundle$ReventlessAws.esmFallbackDirs;
|
|
109
110
|
if (!RuntimeExtension$ReventlessCore.isEmpty()) {
|
|
@@ -181,12 +181,13 @@ let rec waitForMergeSuccess = async (
|
|
|
181
181
|
// from `@@reventless.authorize` / `@authorize` PPX annotations.
|
|
182
182
|
//
|
|
183
183
|
// When both are present on the same field, the spec-level permission wins
|
|
184
|
-
// (it is more specific). `
|
|
185
|
-
// `@aws_cognito_user_pools(cognito_groups: ["g1", "g2", ...])
|
|
184
|
+
// (it is more specific). `AllowRoles([r1, r2, ...])` emits
|
|
185
|
+
// `@aws_cognito_user_pools(cognito_groups: ["g1", "g2", ...])`, each `g` the group
|
|
186
|
+
// the deployment maps that role to.
|
|
186
187
|
// `AllowAuthenticated` emits the group-less `@aws_cognito_user_pools` — the same
|
|
187
188
|
// reachability an undirectived field already has, but stated rather than left
|
|
188
189
|
// implicit, so `assertGateable` can tell "deliberately open" from "nobody
|
|
189
|
-
// stamped this". `
|
|
190
|
+
// stamped this". `AllowRoles([])` and `DenyAll` emit a sentinel `__deny_all__`
|
|
190
191
|
// group that no Cognito user can belong to — effectively blocking the field at
|
|
191
192
|
// the API layer.
|
|
192
193
|
//
|
|
@@ -136,8 +136,12 @@ type fieldGate =
|
|
|
136
136
|
|
|
137
137
|
let permissionToGate = (permission: Reventless.Authorization.permission): fieldGate =>
|
|
138
138
|
switch permission {
|
|
139
|
-
|
|
140
|
-
|
|
|
139
|
+
// The groups the roles map to: the deploy program is where the mapping is known.
|
|
140
|
+
| AllowRoles(_) =>
|
|
141
|
+
switch Reventless.Authorization.groupsOf(permission) {
|
|
142
|
+
| [] => Groups(["__deny_all__"])
|
|
143
|
+
| groups => Groups(groups)
|
|
144
|
+
}
|
|
141
145
|
| DenyAll => Groups(["__deny_all__"])
|
|
142
146
|
| AllowAuthenticated => AnyAuthenticated
|
|
143
147
|
| AllowAnonymous => Anonymous
|
|
@@ -165,7 +169,7 @@ let refuseAnonymousFields = (fieldNames: array<string>): 'a =>
|
|
|
165
169
|
`Anonymous fields on AWS are deferred. When they land it will be as a separate\n` ++
|
|
166
170
|
`API carrying only the unauthenticated fields. Do not add API_KEY to this one:\n` ++
|
|
167
171
|
`every field here would then have to say whether the key reaches it, and one\n` ++
|
|
168
|
-
`that forgot would be open. Until then, declare the field AllowAuthenticated\n` ++ `(or
|
|
172
|
+
`that forgot would be open. Until then, declare the field AllowAuthenticated\n` ++ `(or AllowRoles) and mean it.`,
|
|
169
173
|
)
|
|
170
174
|
|
|
171
175
|
// Injects the Cognito group gate on ALL mutation, query, and subscription fields
|
|
@@ -4,6 +4,7 @@ import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
|
4
4
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
5
5
|
import * as Stdlib_String from "@rescript/runtime/lib/es6/Stdlib_String.js";
|
|
6
6
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
7
|
+
import * as Authorization$Reventless from "@reventlessdev/reventless-spec/src/types/Authorization.res.mjs";
|
|
7
8
|
import * as GraphQL_Stitcher$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/GraphQL_Stitcher.res.mjs";
|
|
8
9
|
|
|
9
10
|
function injectAwsSubscribe(sdl, sources) {
|
|
@@ -65,7 +66,7 @@ function permissionToGate(permission) {
|
|
|
65
66
|
};
|
|
66
67
|
}
|
|
67
68
|
} else {
|
|
68
|
-
let groups = permission
|
|
69
|
+
let groups = Authorization$Reventless.groupsOf(permission);
|
|
69
70
|
if (groups.length !== 0) {
|
|
70
71
|
return {
|
|
71
72
|
TAG: "Groups",
|
|
@@ -81,7 +82,7 @@ function permissionToGate(permission) {
|
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
function refuseAnonymousFields(fieldNames) {
|
|
84
|
-
return Stdlib_JsError.throwWithMessage(`Refusing to push an AppSync schema that cannot honour AllowAnonymous.\n\n` + (` ` + fieldNames.length.toString() + ` field(s) declare AllowAnonymous:\n`) + (` ` + fieldNames.join(", ") + `\n\n`) + `AppSync has no anonymous authorization mode. This API is provisioned with\nAMAZON_COGNITO_USER_POOLS primary and AWS_IAM additional, so the only\ndirective available is @aws_cognito_user_pools — which means ANY\nAUTHENTICATED caller, the opposite of what the spec declares. Mutations have\nno runtime authorization check to correct it: on this platform the directive\nis the whole enforcement.\n\nThe local platform DOES honour AllowAnonymous (its resolvers call\nAuthorization.isAllowed), so a spec that passes locally can still not be\ndeployable here. That divergence is the reason this refuses instead of\nemitting a directive that reads as gated and is not.\n\nAnonymous fields on AWS are deferred. When they land it will be as a separate\nAPI carrying only the unauthenticated fields. Do not add API_KEY to this one:\nevery field here would then have to say whether the key reaches it, and one\nthat forgot would be open. Until then, declare the field AllowAuthenticated\n(or
|
|
85
|
+
return Stdlib_JsError.throwWithMessage(`Refusing to push an AppSync schema that cannot honour AllowAnonymous.\n\n` + (` ` + fieldNames.length.toString() + ` field(s) declare AllowAnonymous:\n`) + (` ` + fieldNames.join(", ") + `\n\n`) + `AppSync has no anonymous authorization mode. This API is provisioned with\nAMAZON_COGNITO_USER_POOLS primary and AWS_IAM additional, so the only\ndirective available is @aws_cognito_user_pools — which means ANY\nAUTHENTICATED caller, the opposite of what the spec declares. Mutations have\nno runtime authorization check to correct it: on this platform the directive\nis the whole enforcement.\n\nThe local platform DOES honour AllowAnonymous (its resolvers call\nAuthorization.isAllowed), so a spec that passes locally can still not be\ndeployable here. That divergence is the reason this refuses instead of\nemitting a directive that reads as gated and is not.\n\nAnonymous fields on AWS are deferred. When they land it will be as a separate\nAPI carrying only the unauthenticated fields. Do not add API_KEY to this one:\nevery field here would then have to say whether the key reaches it, and one\nthat forgot would be open. Until then, declare the field AllowAuthenticated\n(or AllowRoles) and mean it.`);
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
function injectAwsAuthAll(fragment, group, iamFieldNamesOpt) {
|
|
@@ -388,4 +389,4 @@ export {
|
|
|
388
389
|
canonicalTypeNames,
|
|
389
390
|
stampCanonicalTypes,
|
|
390
391
|
}
|
|
391
|
-
/*
|
|
392
|
+
/* Authorization-Reventless Not a pure module */
|
|
@@ -46,3 +46,12 @@ let applyElevatedGroupsDefault = (variables: dict<Pulumi.Input.t<string>>) =>
|
|
|
46
46
|
if variables->Dict.get(key)->Option.isNone {
|
|
47
47
|
entry()->Option.forEach(((k, v)) => variables->Dict.set(k, v))
|
|
48
48
|
}
|
|
49
|
+
|
|
50
|
+
/** The renamed roles, for the same two-process reason: a runtime that evaluates
|
|
51
|
+
a rule (the SQL-backed view reads) never runs the root that stated the
|
|
52
|
+
mapping. Only the renamed ones travel; every other role is its own group. */
|
|
53
|
+
let applyRoleGroupsDefault = (variables: dict<Pulumi.Input.t<string>>) =>
|
|
54
|
+
switch (variables->Dict.get(Reventless.Role.envKey), Reventless.Role.envValue()) {
|
|
55
|
+
| (None, Some(value)) => variables->Dict.set(Reventless.Role.envKey, value->Pulumi.Input.make)
|
|
56
|
+
| _ => ()
|
|
57
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
4
|
+
import * as Role$Reventless from "@reventlessdev/reventless-spec/src/types/Role.res.mjs";
|
|
4
5
|
import * as OwnerScope$Reventless from "@reventlessdev/reventless-spec/src/types/OwnerScope.res.mjs";
|
|
5
6
|
|
|
6
7
|
let key = "REVENTLESS_ELEVATED_GROUPS";
|
|
@@ -23,9 +24,21 @@ function applyElevatedGroupsDefault(variables) {
|
|
|
23
24
|
}
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
function applyRoleGroupsDefault(variables) {
|
|
28
|
+
let match = variables[Role$Reventless.envKey];
|
|
29
|
+
let match$1 = Role$Reventless.envValue();
|
|
30
|
+
if (match !== undefined || match$1 === undefined) {
|
|
31
|
+
return;
|
|
32
|
+
} else {
|
|
33
|
+
variables[Role$Reventless.envKey] = match$1;
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
26
38
|
export {
|
|
27
39
|
key,
|
|
28
40
|
entry,
|
|
29
41
|
applyElevatedGroupsDefault,
|
|
42
|
+
applyRoleGroupsDefault,
|
|
30
43
|
}
|
|
31
|
-
/*
|
|
44
|
+
/* Role-Reventless Not a pure module */
|
|
@@ -98,6 +98,10 @@ let fields = (
|
|
|
98
98
|
): dict<JSON.t> => {
|
|
99
99
|
let out = computed->Dict.fromArray
|
|
100
100
|
|
|
101
|
+
// Computed, like the endpoints: the group the admin API is gated on, from the
|
|
102
|
+
// deploy's own role mapping.
|
|
103
|
+
ReventlessCore.Platform_AdminGroup.configEntry()->Option.forEach(((k, v)) => out->Dict.set(k, v))
|
|
104
|
+
|
|
101
105
|
// The shell imports the module this key names, and imports nothing when the
|
|
102
106
|
// key is absent — so writing the object without naming it here would ship a
|
|
103
107
|
// file nothing fetches. Computed rather than left to `shellConfig` for the
|
|
@@ -5,6 +5,7 @@ import * as Stdlib_Dict from "@rescript/runtime/lib/es6/Stdlib_Dict.js";
|
|
|
5
5
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
6
6
|
import * as Platform$ReventlessInfra from "@reventlessdev/reventless-infra/src/types/Platform.res.mjs";
|
|
7
7
|
import * as Platform_UiSlots$ReventlessCore from "@reventlessdev/reventless-core/src/admin/Platform_UiSlots.res.mjs";
|
|
8
|
+
import * as Platform_AdminGroup$ReventlessCore from "@reventlessdev/reventless-core/src/admin/Platform_AdminGroup.res.mjs";
|
|
8
9
|
import * as Platform_BakedManifest$ReventlessCore from "@reventlessdev/reventless-core/src/admin/Platform_BakedManifest.res.mjs";
|
|
9
10
|
|
|
10
11
|
let journeyManifestsKey = "journeyManifestUrls";
|
|
@@ -59,6 +60,9 @@ function subscriptionEndpoint(httpsEndpoint) {
|
|
|
59
60
|
|
|
60
61
|
function fields(computed, viewModes, bakedManifest, uiSlotsFile, shellConfig) {
|
|
61
62
|
let out = Object.fromEntries(computed);
|
|
63
|
+
Stdlib_Option.forEach(Platform_AdminGroup$ReventlessCore.configEntry(), param => {
|
|
64
|
+
out[param[0]] = param[1];
|
|
65
|
+
});
|
|
62
66
|
Stdlib_Option.forEach(uiSlotsFile, param => {
|
|
63
67
|
out[Platform_UiSlots$ReventlessCore.configKey] = Platform_UiSlots$ReventlessCore.url;
|
|
64
68
|
});
|
|
@@ -103,4 +107,4 @@ export {
|
|
|
103
107
|
subscriptionEndpoint,
|
|
104
108
|
fields,
|
|
105
109
|
}
|
|
106
|
-
/*
|
|
110
|
+
/* Platform_AdminGroup-ReventlessCore Not a pure module */
|
|
@@ -183,20 +183,16 @@ describe("AppSync_Adapter.injectAwsAuth — Stage E2 permission lifting", () =>
|
|
|
183
183
|
?permission,
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
-
testSync("
|
|
186
|
+
testSync("AllowRoles with several roles emits comma-separated groups", () => {
|
|
187
187
|
let fp = Dict.fromArray([
|
|
188
|
-
(
|
|
188
|
+
(
|
|
189
|
+
"p_X",
|
|
190
|
+
Reventless.Authorization.AllowRoles([
|
|
191
|
+
Reventless.Role.make("Admin"),
|
|
192
|
+
Reventless.Role.make("Editor"),
|
|
193
|
+
]),
|
|
194
|
+
),
|
|
189
195
|
])
|
|
190
|
-
let entry = mutationEntry(~fieldNames=["catalog_Category_Archive"], ~fieldPermissions=fp)
|
|
191
|
-
let frag = makeFragment(["catalog_Category_Archive(id: ID!): String"], [])
|
|
192
|
-
let aug = AppSync_Adapter.injectAwsAuth(frag, ~mutationEntries=[entry], ~queryEntries=[])
|
|
193
|
-
let parts = decodeFragment(aug)
|
|
194
|
-
let m = parts.mutations->Array.getUnsafe(0)
|
|
195
|
-
expect(m)->toContain(`@aws_cognito_user_pools(cognito_groups: ["Admin"])`)
|
|
196
|
-
})
|
|
197
|
-
|
|
198
|
-
testSync("AllowGroups multi-group emits comma-separated groups", () => {
|
|
199
|
-
let fp = Dict.fromArray([("p_X", Reventless.Authorization.AllowGroups(["Admin", "Editor"]))])
|
|
200
196
|
let entry = mutationEntry(~fieldNames=["p_X"], ~fieldPermissions=fp)
|
|
201
197
|
let frag = makeFragment(["p_X(id: ID!): String"], [])
|
|
202
198
|
let aug = AppSync_Adapter.injectAwsAuth(frag, ~mutationEntries=[entry], ~queryEntries=[])
|
|
@@ -206,6 +202,29 @@ describe("AppSync_Adapter.injectAwsAuth — Stage E2 permission lifting", () =>
|
|
|
206
202
|
)->toContain(`@aws_cognito_user_pools(cognito_groups: ["Admin", "Editor"])`)
|
|
207
203
|
})
|
|
208
204
|
|
|
205
|
+
// The deploy program knows the mapping, so the directive carries the group a
|
|
206
|
+
// role maps to — never the role, which no token carries.
|
|
207
|
+
testSync("AllowRoles emits the groups its roles map to", () => {
|
|
208
|
+
Reventless.Role.clearGroups()
|
|
209
|
+
Reventless.Role.setGroups([(Reventless.Role.make("Merchandiser"), "shop-merch-team")])
|
|
210
|
+
let fp = Dict.fromArray([
|
|
211
|
+
(
|
|
212
|
+
"p_Rename",
|
|
213
|
+
Reventless.Authorization.AllowRoles([
|
|
214
|
+
Reventless.Role.make("Admin"),
|
|
215
|
+
Reventless.Role.make("Merchandiser"),
|
|
216
|
+
]),
|
|
217
|
+
),
|
|
218
|
+
])
|
|
219
|
+
let entry = mutationEntry(~fieldNames=["p_Rename"], ~fieldPermissions=fp)
|
|
220
|
+
let frag = makeFragment(["p_Rename(id: ID!): String"], [])
|
|
221
|
+
let aug = AppSync_Adapter.injectAwsAuth(frag, ~mutationEntries=[entry], ~queryEntries=[])
|
|
222
|
+
Reventless.Role.clearGroups()
|
|
223
|
+
expect(
|
|
224
|
+
decodeFragment(aug).mutations->Array.getUnsafe(0),
|
|
225
|
+
)->toContain(`@aws_cognito_user_pools(cognito_groups: ["Admin", "shop-merch-team"])`)
|
|
226
|
+
})
|
|
227
|
+
|
|
209
228
|
testSync("AllowAuthenticated emits the group-less Cognito directive", () => {
|
|
210
229
|
let fp = Dict.fromArray([("p_Add", Reventless.Authorization.AllowAuthenticated)])
|
|
211
230
|
let entry = mutationEntry(~fieldNames=["p_Add"], ~fieldPermissions=fp)
|
|
@@ -230,7 +249,7 @@ describe("AppSync_Adapter.injectAwsAuth — Stage E2 permission lifting", () =>
|
|
|
230
249
|
|
|
231
250
|
testSync("Per-field permissions: only annotated fields get directives", () => {
|
|
232
251
|
let fp = Dict.fromArray([
|
|
233
|
-
("p_Archive", Reventless.Authorization.
|
|
252
|
+
("p_Archive", Reventless.Authorization.AllowRoles([Reventless.Role.make("Admin")])),
|
|
234
253
|
("p_Add", Reventless.Authorization.AllowAuthenticated),
|
|
235
254
|
])
|
|
236
255
|
let entry = mutationEntry(~fieldNames=["p_Archive", "p_Add"], ~fieldPermissions=fp)
|
|
@@ -255,7 +274,7 @@ describe("AppSync_Adapter.injectAwsAuth — Stage E2 permission lifting", () =>
|
|
|
255
274
|
let entry = queryEntry(
|
|
256
275
|
~single="p_Item",
|
|
257
276
|
~list="p_Items",
|
|
258
|
-
~permission=Some(Reventless.Authorization.
|
|
277
|
+
~permission=Some(Reventless.Authorization.AllowRoles([Reventless.Role.make("Manager")])),
|
|
259
278
|
)
|
|
260
279
|
// Production query SDL emitted by GraphQL_FragmentGenerator always has an
|
|
261
280
|
// arg list (Relay pagination on the list field), so extractLeadingName's
|
|
@@ -362,7 +381,9 @@ describe("AppSync_Adapter.injectAwsAuth — Stage E2 permission lifting", () =>
|
|
|
362
381
|
testSync("Spec-level permission wins over legacy authorization field", () => {
|
|
363
382
|
// Legacy {tableName, group} says "Admin"; spec-level says "Manager".
|
|
364
383
|
// Spec-level must win.
|
|
365
|
-
let fp = Dict.fromArray([
|
|
384
|
+
let fp = Dict.fromArray([
|
|
385
|
+
("p_X", Reventless.Authorization.AllowRoles([Reventless.Role.make("Manager")])),
|
|
386
|
+
])
|
|
366
387
|
let entry: ReventlessInfra.Api.mutationSchemaEntry = {
|
|
367
388
|
fieldNames: ["p_X"],
|
|
368
389
|
commandSchema: S.unknown,
|
|
@@ -427,7 +448,7 @@ describe("AppSync_Adapter.injectAwsAuth — systemCallable dual-auth", () => {
|
|
|
427
448
|
fieldNames: ["p_Sync"],
|
|
428
449
|
commandSchema: S.unknown,
|
|
429
450
|
fieldPermissions: Dict.fromArray([
|
|
430
|
-
("p_Sync", Reventless.Authorization.
|
|
451
|
+
("p_Sync", Reventless.Authorization.AllowRoles([Reventless.Role.make("Admin")])),
|
|
431
452
|
]),
|
|
432
453
|
systemCallable: true,
|
|
433
454
|
}
|
|
@@ -464,8 +485,8 @@ describe("AppSync_Adapter.injectAwsAuth — systemCallable dual-auth", () => {
|
|
|
464
485
|
fieldNames: ["p_Sync", "p_Other"],
|
|
465
486
|
commandSchema: S.unknown,
|
|
466
487
|
fieldPermissions: Dict.fromArray([
|
|
467
|
-
("p_Sync", Reventless.Authorization.
|
|
468
|
-
("p_Other", Reventless.Authorization.
|
|
488
|
+
("p_Sync", Reventless.Authorization.AllowRoles([Reventless.Role.make("Admin")])),
|
|
489
|
+
("p_Other", Reventless.Authorization.AllowRoles([Reventless.Role.make("Admin")])),
|
|
469
490
|
]),
|
|
470
491
|
// systemCallable applies to every field in the entry; split the fields so only
|
|
471
492
|
// p_Sync opts in.
|
|
@@ -475,7 +496,7 @@ describe("AppSync_Adapter.injectAwsAuth — systemCallable dual-auth", () => {
|
|
|
475
496
|
fieldNames: ["p_Sync"],
|
|
476
497
|
commandSchema: S.unknown,
|
|
477
498
|
fieldPermissions: Dict.fromArray([
|
|
478
|
-
("p_Sync", Reventless.Authorization.
|
|
499
|
+
("p_Sync", Reventless.Authorization.AllowRoles([Reventless.Role.make("Admin")])),
|
|
479
500
|
]),
|
|
480
501
|
systemCallable: true,
|
|
481
502
|
}
|
|
@@ -501,7 +522,7 @@ describe("AppSync_Adapter.injectAwsAuth — systemCallable dual-auth", () => {
|
|
|
501
522
|
returnTypeName: "p_Item",
|
|
502
523
|
stateSchema: S.unknown,
|
|
503
524
|
authorization: None,
|
|
504
|
-
permission: Reventless.Authorization.
|
|
525
|
+
permission: Reventless.Authorization.AllowRoles([Reventless.Role.make("Admin")]),
|
|
505
526
|
systemCallable: true,
|
|
506
527
|
}
|
|
507
528
|
let frag = makeFragment(
|
|
@@ -532,7 +553,7 @@ describe("AppSync_Adapter.injectAwsAuth — systemCallable dual-auth", () => {
|
|
|
532
553
|
returnTypeName: "p_Item",
|
|
533
554
|
stateSchema: S.unknown,
|
|
534
555
|
authorization: None,
|
|
535
|
-
permission: Reventless.Authorization.
|
|
556
|
+
permission: Reventless.Authorization.AllowRoles([Reventless.Role.make("Admin")]),
|
|
536
557
|
systemCallable: true,
|
|
537
558
|
}
|
|
538
559
|
let frag = makeFragment(
|
|
@@ -583,7 +604,7 @@ describe("AppSync_Adapter — type-level dual-auth", () => {
|
|
|
583
604
|
returnTypeName: "p_Item",
|
|
584
605
|
stateSchema: S.unknown,
|
|
585
606
|
authorization: None,
|
|
586
|
-
permission: Reventless.Authorization.
|
|
607
|
+
permission: Reventless.Authorization.AllowRoles([Reventless.Role.make("Admin")]),
|
|
587
608
|
systemCallable: true,
|
|
588
609
|
}
|
|
589
610
|
|
|
@@ -958,7 +979,9 @@ describe("AppSync_Adapter.injectAwsAuth — subscriptions", () => {
|
|
|
958
979
|
})
|
|
959
980
|
|
|
960
981
|
testSync("a subscription named like a gated mutation inherits its groups", () => {
|
|
961
|
-
let fp = Dict.fromArray([
|
|
982
|
+
let fp = Dict.fromArray([
|
|
983
|
+
("p_Ship", Reventless.Authorization.AllowRoles([Reventless.Role.make("Fulfilment")])),
|
|
984
|
+
])
|
|
962
985
|
let frag = makeFragment(["p_Ship: CommandResult"])
|
|
963
986
|
let aug = AppSync_Adapter.injectAwsAuth(
|
|
964
987
|
frag,
|
|
@@ -4,6 +4,7 @@ import * as Sury from "sury";
|
|
|
4
4
|
import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
|
|
5
5
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
6
6
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
7
|
+
import * as Role$Reventless from "@reventlessdev/reventless-spec/src/types/Role.res.mjs";
|
|
7
8
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
8
9
|
import * as AppSync_Adapter$ReventlessAws from "../src/components/Api/AppSync_Adapter.res.mjs";
|
|
9
10
|
import * as GraphQL_Stitcher$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/GraphQL_Stitcher.res.mjs";
|
|
@@ -129,37 +130,44 @@ globalThis.describe("AppSync_Adapter.injectAwsAuth — Stage E2 permission lifti
|
|
|
129
130
|
authorization: undefined,
|
|
130
131
|
permission: permission
|
|
131
132
|
});
|
|
132
|
-
globalThis.test("
|
|
133
|
+
globalThis.test("AllowRoles with several roles emits comma-separated groups", () => {
|
|
133
134
|
let fp = Object.fromEntries([[
|
|
134
|
-
"
|
|
135
|
+
"p_X",
|
|
135
136
|
{
|
|
136
|
-
TAG: "
|
|
137
|
-
_0: [
|
|
137
|
+
TAG: "AllowRoles",
|
|
138
|
+
_0: [
|
|
139
|
+
"Admin",
|
|
140
|
+
"Editor"
|
|
141
|
+
]
|
|
138
142
|
}
|
|
139
143
|
]]);
|
|
140
|
-
let entry = mutationEntry(["
|
|
141
|
-
let frag = makeFragment(["
|
|
144
|
+
let entry = mutationEntry(["p_X"], fp);
|
|
145
|
+
let frag = makeFragment(["p_X(id: ID!): String"], []);
|
|
142
146
|
let aug = AppSync_Adapter$ReventlessAws.injectAwsAuth(frag, [entry], []);
|
|
143
147
|
let parts = GraphQL_Stitcher$ReventlessCore.decode(aug);
|
|
144
|
-
|
|
145
|
-
globalThis.expect(m).toContain(`@aws_cognito_user_pools(cognito_groups: ["Admin"])`);
|
|
148
|
+
globalThis.expect(parts.mutations[0]).toContain(`@aws_cognito_user_pools(cognito_groups: ["Admin", "Editor"])`);
|
|
146
149
|
});
|
|
147
|
-
globalThis.test("
|
|
150
|
+
globalThis.test("AllowRoles emits the groups its roles map to", () => {
|
|
151
|
+
Role$Reventless.clearGroups();
|
|
152
|
+
Role$Reventless.setGroups([[
|
|
153
|
+
"Merchandiser",
|
|
154
|
+
"shop-merch-team"
|
|
155
|
+
]]);
|
|
148
156
|
let fp = Object.fromEntries([[
|
|
149
|
-
"
|
|
157
|
+
"p_Rename",
|
|
150
158
|
{
|
|
151
|
-
TAG: "
|
|
159
|
+
TAG: "AllowRoles",
|
|
152
160
|
_0: [
|
|
153
161
|
"Admin",
|
|
154
|
-
"
|
|
162
|
+
"Merchandiser"
|
|
155
163
|
]
|
|
156
164
|
}
|
|
157
165
|
]]);
|
|
158
|
-
let entry = mutationEntry(["
|
|
159
|
-
let frag = makeFragment(["
|
|
166
|
+
let entry = mutationEntry(["p_Rename"], fp);
|
|
167
|
+
let frag = makeFragment(["p_Rename(id: ID!): String"], []);
|
|
160
168
|
let aug = AppSync_Adapter$ReventlessAws.injectAwsAuth(frag, [entry], []);
|
|
161
|
-
|
|
162
|
-
globalThis.expect(
|
|
169
|
+
Role$Reventless.clearGroups();
|
|
170
|
+
globalThis.expect(GraphQL_Stitcher$ReventlessCore.decode(aug).mutations[0]).toContain(`@aws_cognito_user_pools(cognito_groups: ["Admin", "shop-merch-team"])`);
|
|
163
171
|
});
|
|
164
172
|
globalThis.test("AllowAuthenticated emits the group-less Cognito directive", () => {
|
|
165
173
|
let fp = Object.fromEntries([[
|
|
@@ -190,7 +198,7 @@ globalThis.describe("AppSync_Adapter.injectAwsAuth — Stage E2 permission lifti
|
|
|
190
198
|
[
|
|
191
199
|
"p_Archive",
|
|
192
200
|
{
|
|
193
|
-
TAG: "
|
|
201
|
+
TAG: "AllowRoles",
|
|
194
202
|
_0: ["Admin"]
|
|
195
203
|
}
|
|
196
204
|
],
|
|
@@ -226,7 +234,7 @@ globalThis.describe("AppSync_Adapter.injectAwsAuth — Stage E2 permission lifti
|
|
|
226
234
|
});
|
|
227
235
|
globalThis.test("Query permission applies to BOTH single and list field names", () => {
|
|
228
236
|
let entry = queryEntry("p_Item", "p_Items", {
|
|
229
|
-
TAG: "
|
|
237
|
+
TAG: "AllowRoles",
|
|
230
238
|
_0: ["Manager"]
|
|
231
239
|
});
|
|
232
240
|
let frag = makeFragment([], [
|
|
@@ -318,7 +326,7 @@ globalThis.describe("AppSync_Adapter.injectAwsAuth — Stage E2 permission lifti
|
|
|
318
326
|
let fp = Object.fromEntries([[
|
|
319
327
|
"p_X",
|
|
320
328
|
{
|
|
321
|
-
TAG: "
|
|
329
|
+
TAG: "AllowRoles",
|
|
322
330
|
_0: ["Manager"]
|
|
323
331
|
}
|
|
324
332
|
]]);
|
|
@@ -380,7 +388,7 @@ globalThis.describe("AppSync_Adapter.injectAwsAuth — systemCallable dual-auth"
|
|
|
380
388
|
let entry_fieldPermissions = Object.fromEntries([[
|
|
381
389
|
"p_Sync",
|
|
382
390
|
{
|
|
383
|
-
TAG: "
|
|
391
|
+
TAG: "AllowRoles",
|
|
384
392
|
_0: ["Admin"]
|
|
385
393
|
}
|
|
386
394
|
]]);
|
|
@@ -427,14 +435,14 @@ globalThis.describe("AppSync_Adapter.injectAwsAuth — systemCallable dual-auth"
|
|
|
427
435
|
[
|
|
428
436
|
"p_Sync",
|
|
429
437
|
{
|
|
430
|
-
TAG: "
|
|
438
|
+
TAG: "AllowRoles",
|
|
431
439
|
_0: ["Admin"]
|
|
432
440
|
}
|
|
433
441
|
],
|
|
434
442
|
[
|
|
435
443
|
"p_Other",
|
|
436
444
|
{
|
|
437
|
-
TAG: "
|
|
445
|
+
TAG: "AllowRoles",
|
|
438
446
|
_0: ["Admin"]
|
|
439
447
|
}
|
|
440
448
|
]
|
|
@@ -450,7 +458,7 @@ globalThis.describe("AppSync_Adapter.injectAwsAuth — systemCallable dual-auth"
|
|
|
450
458
|
let iamEntry_fieldPermissions = Object.fromEntries([[
|
|
451
459
|
"p_Sync",
|
|
452
460
|
{
|
|
453
|
-
TAG: "
|
|
461
|
+
TAG: "AllowRoles",
|
|
454
462
|
_0: ["Admin"]
|
|
455
463
|
}
|
|
456
464
|
]]);
|
|
@@ -481,7 +489,7 @@ globalThis.describe("AppSync_Adapter.injectAwsAuth — systemCallable dual-auth"
|
|
|
481
489
|
});
|
|
482
490
|
globalThis.test("systemCallable query applies dual-auth to BOTH single and list fields", () => {
|
|
483
491
|
let entry_permission = {
|
|
484
|
-
TAG: "
|
|
492
|
+
TAG: "AllowRoles",
|
|
485
493
|
_0: ["Admin"]
|
|
486
494
|
};
|
|
487
495
|
let entry_systemCallable = true;
|
|
@@ -514,7 +522,7 @@ globalThis.describe("AppSync_Adapter.injectAwsAuth — systemCallable dual-auth"
|
|
|
514
522
|
});
|
|
515
523
|
globalThis.test("systemCallable dual-auth extends to derived query fields (Items/ByIds/By<Index>)", () => {
|
|
516
524
|
let entry_permission = {
|
|
517
|
-
TAG: "
|
|
525
|
+
TAG: "AllowRoles",
|
|
518
526
|
_0: ["Admin"]
|
|
519
527
|
};
|
|
520
528
|
let entry_systemCallable = true;
|
|
@@ -554,7 +562,7 @@ globalThis.describe("AppSync_Adapter — type-level dual-auth", () => {
|
|
|
554
562
|
subscriptionSources: []
|
|
555
563
|
});
|
|
556
564
|
let callableEntry_permission = {
|
|
557
|
-
TAG: "
|
|
565
|
+
TAG: "AllowRoles",
|
|
558
566
|
_0: ["Admin"]
|
|
559
567
|
};
|
|
560
568
|
let callableEntry_systemCallable = true;
|
|
@@ -845,7 +853,7 @@ globalThis.describe("AppSync_Adapter.injectAwsAuth — subscriptions", () => {
|
|
|
845
853
|
let fp = Object.fromEntries([[
|
|
846
854
|
"p_Ship",
|
|
847
855
|
{
|
|
848
|
-
TAG: "
|
|
856
|
+
TAG: "AllowRoles",
|
|
849
857
|
_0: ["Fulfilment"]
|
|
850
858
|
}
|
|
851
859
|
]]);
|
|
@@ -35,7 +35,8 @@ module OrdersSpec = {
|
|
|
35
35
|
|
|
36
36
|
let config = Reventless.ReadModel.config()
|
|
37
37
|
let subIdConfig = None
|
|
38
|
-
|
|
38
|
+
type role = Reventless.Role.name
|
|
39
|
+
let authorization: Reventless.Authorization.rule<role> = AllowAuthenticated
|
|
39
40
|
let visibility: Reventless.Visibility.t = Public
|
|
40
41
|
}
|
|
41
42
|
|
|
@@ -30,7 +30,8 @@ module CustomersSpec = {
|
|
|
30
30
|
|
|
31
31
|
let config = Reventless.ReadModel.config()
|
|
32
32
|
let subIdConfig = None
|
|
33
|
-
|
|
33
|
+
type role = Reventless.Role.name
|
|
34
|
+
let authorization: Reventless.Authorization.rule<role> = AllowAuthenticated
|
|
34
35
|
let visibility: Reventless.Visibility.t = Public
|
|
35
36
|
}
|
|
36
37
|
|
|
@@ -68,7 +68,7 @@ describe("Util_OwnerScopeEnv — the elevated-groups carrier", () => {
|
|
|
68
68
|
testSync("the platform's administrator default reaches a composed environment", () => {
|
|
69
69
|
Reventless.OwnerScope.clearElevatedGroups()
|
|
70
70
|
NodeProcess.env->Dict.delete(Util_OwnerScopeEnv.key)
|
|
71
|
-
Reventless.OwnerScope.
|
|
71
|
+
Reventless.OwnerScope.defaultElevatedRoles([Reventless.Role.admin])
|
|
72
72
|
|
|
73
73
|
let variables = Dict.make()
|
|
74
74
|
Util_OwnerScopeEnv.applyElevatedGroupsDefault(variables)
|
|
@@ -81,7 +81,53 @@ describe("Util_OwnerScopeEnv — the elevated-groups carrier", () => {
|
|
|
81
81
|
// Round-tripped, like the first case above: the runtime reads this back with
|
|
82
82
|
// `parseElevatedGroups`, so the encoding is asserted rather than the spelling.
|
|
83
83
|
expect(Reventless.OwnerScope.parseElevatedGroups(encoded))->toEqual([
|
|
84
|
-
Reventless.
|
|
84
|
+
Reventless.Role.adminGroup(),
|
|
85
85
|
])
|
|
86
86
|
})
|
|
87
|
+
|
|
88
|
+
// Runtimes read groups, never roles, so an elevated role travels as the group
|
|
89
|
+
// the deploy program mapped it to.
|
|
90
|
+
testSync("an elevated role travels as the group it is mapped to", () => {
|
|
91
|
+
Reventless.OwnerScope.clearElevatedGroups()
|
|
92
|
+
Reventless.Role.clearGroups()
|
|
93
|
+
NodeProcess.env->Dict.delete(Util_OwnerScopeEnv.key)
|
|
94
|
+
Reventless.Role.setGroups([(Reventless.Role.make("Fulfilment"), "shop-ops")])
|
|
95
|
+
Reventless.OwnerScope.setElevatedRoles([Reventless.Role.make("Fulfilment")])
|
|
96
|
+
Reventless.OwnerScope.setElevatedGroups(["Admin"])
|
|
97
|
+
|
|
98
|
+
let variables = Dict.make()
|
|
99
|
+
Util_OwnerScopeEnv.applyElevatedGroupsDefault(variables)
|
|
100
|
+
let encoded = switch variables->Dict.get(Util_OwnerScopeEnv.key) {
|
|
101
|
+
| Some(value) => value->Obj.magic
|
|
102
|
+
| None => ""
|
|
103
|
+
}
|
|
104
|
+
Reventless.OwnerScope.clearElevatedGroups()
|
|
105
|
+
Reventless.Role.clearGroups()
|
|
106
|
+
|
|
107
|
+
expect(Reventless.OwnerScope.parseElevatedGroups(encoded))->toEqual(["Admin", "shop-ops"])
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
describe("Util_OwnerScopeEnv.applyRoleGroupsDefault", () => {
|
|
112
|
+
testSync("carries the renamed roles, and only those", () => {
|
|
113
|
+
Reventless.Role.clearGroups()
|
|
114
|
+
Reventless.Role.setGroups([(Reventless.Role.make("Merchandiser"), "shop-merch-team")])
|
|
115
|
+
let variables = Dict.make()
|
|
116
|
+
Util_OwnerScopeEnv.applyRoleGroupsDefault(variables)
|
|
117
|
+
Reventless.Role.clearGroups()
|
|
118
|
+
let encoded = switch variables->Dict.get(Reventless.Role.envKey) {
|
|
119
|
+
| Some(value) => value->Obj.magic
|
|
120
|
+
| None => ""
|
|
121
|
+
}
|
|
122
|
+
expect(Reventless.Role.parseGroups(encoded))->toEqual([
|
|
123
|
+
(Reventless.Role.make("Merchandiser"), "shop-merch-team"),
|
|
124
|
+
])
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
testSync("adds nothing when no role is renamed", () => {
|
|
128
|
+
Reventless.Role.clearGroups()
|
|
129
|
+
let variables = Dict.make()
|
|
130
|
+
Util_OwnerScopeEnv.applyRoleGroupsDefault(variables)
|
|
131
|
+
expect(variables->Dict.get(Reventless.Role.envKey)->Option.isNone)->toBe(true)
|
|
132
|
+
})
|
|
87
133
|
})
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as Stdlib_Dict from "@rescript/runtime/lib/es6/Stdlib_Dict.js";
|
|
4
4
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
5
|
-
import * as
|
|
5
|
+
import * as Role$Reventless from "@reventlessdev/reventless-spec/src/types/Role.res.mjs";
|
|
6
6
|
import * as OwnerScope$Reventless from "@reventlessdev/reventless-spec/src/types/OwnerScope.res.mjs";
|
|
7
7
|
import * as Util_OwnerScopeEnv$ReventlessAws from "../src/util/Util_OwnerScopeEnv.res.mjs";
|
|
8
8
|
|
|
@@ -59,13 +59,59 @@ globalThis.describe("Util_OwnerScopeEnv — the elevated-groups carrier", () =>
|
|
|
59
59
|
globalThis.test("the platform's administrator default reaches a composed environment", () => {
|
|
60
60
|
OwnerScope$Reventless.clearElevatedGroups();
|
|
61
61
|
Stdlib_Dict.$$delete(process.env, Util_OwnerScopeEnv$ReventlessAws.key);
|
|
62
|
-
OwnerScope$Reventless.
|
|
62
|
+
OwnerScope$Reventless.defaultElevatedRoles([Role$Reventless.admin]);
|
|
63
63
|
let variables = {};
|
|
64
64
|
Util_OwnerScopeEnv$ReventlessAws.applyElevatedGroupsDefault(variables);
|
|
65
65
|
let value = variables[Util_OwnerScopeEnv$ReventlessAws.key];
|
|
66
66
|
let encoded = value !== undefined ? value : "";
|
|
67
67
|
OwnerScope$Reventless.clearElevatedGroups();
|
|
68
|
-
globalThis.expect(OwnerScope$Reventless.parseElevatedGroups(encoded)).toEqual([
|
|
68
|
+
globalThis.expect(OwnerScope$Reventless.parseElevatedGroups(encoded)).toEqual([Role$Reventless.adminGroup()]);
|
|
69
|
+
});
|
|
70
|
+
globalThis.test("an elevated role travels as the group it is mapped to", () => {
|
|
71
|
+
OwnerScope$Reventless.clearElevatedGroups();
|
|
72
|
+
Role$Reventless.clearGroups();
|
|
73
|
+
Stdlib_Dict.$$delete(process.env, Util_OwnerScopeEnv$ReventlessAws.key);
|
|
74
|
+
Role$Reventless.setGroups([[
|
|
75
|
+
"Fulfilment",
|
|
76
|
+
"shop-ops"
|
|
77
|
+
]]);
|
|
78
|
+
OwnerScope$Reventless.setElevatedRoles(["Fulfilment"]);
|
|
79
|
+
OwnerScope$Reventless.setElevatedGroups(["Admin"]);
|
|
80
|
+
let variables = {};
|
|
81
|
+
Util_OwnerScopeEnv$ReventlessAws.applyElevatedGroupsDefault(variables);
|
|
82
|
+
let value = variables[Util_OwnerScopeEnv$ReventlessAws.key];
|
|
83
|
+
let encoded = value !== undefined ? value : "";
|
|
84
|
+
OwnerScope$Reventless.clearElevatedGroups();
|
|
85
|
+
Role$Reventless.clearGroups();
|
|
86
|
+
globalThis.expect(OwnerScope$Reventless.parseElevatedGroups(encoded)).toEqual([
|
|
87
|
+
"Admin",
|
|
88
|
+
"shop-ops"
|
|
89
|
+
]);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
globalThis.describe("Util_OwnerScopeEnv.applyRoleGroupsDefault", () => {
|
|
94
|
+
globalThis.test("carries the renamed roles, and only those", () => {
|
|
95
|
+
Role$Reventless.clearGroups();
|
|
96
|
+
Role$Reventless.setGroups([[
|
|
97
|
+
"Merchandiser",
|
|
98
|
+
"shop-merch-team"
|
|
99
|
+
]]);
|
|
100
|
+
let variables = {};
|
|
101
|
+
Util_OwnerScopeEnv$ReventlessAws.applyRoleGroupsDefault(variables);
|
|
102
|
+
Role$Reventless.clearGroups();
|
|
103
|
+
let value = variables[Role$Reventless.envKey];
|
|
104
|
+
let encoded = value !== undefined ? value : "";
|
|
105
|
+
globalThis.expect(Role$Reventless.parseGroups(encoded)).toEqual([[
|
|
106
|
+
"Merchandiser",
|
|
107
|
+
"shop-merch-team"
|
|
108
|
+
]]);
|
|
109
|
+
});
|
|
110
|
+
globalThis.test("adds nothing when no role is renamed", () => {
|
|
111
|
+
Role$Reventless.clearGroups();
|
|
112
|
+
let variables = {};
|
|
113
|
+
Util_OwnerScopeEnv$ReventlessAws.applyRoleGroupsDefault(variables);
|
|
114
|
+
globalThis.expect(Stdlib_Option.isNone(variables[Role$Reventless.envKey])).toBe(true);
|
|
69
115
|
});
|
|
70
116
|
});
|
|
71
117
|
|
|
@@ -328,3 +328,20 @@ describe("the subscription endpoint", () => {
|
|
|
328
328
|
)
|
|
329
329
|
)
|
|
330
330
|
})
|
|
331
|
+
|
|
332
|
+
// The shell gates its admin discovery on this group; a mirror of the server's
|
|
333
|
+
// own resolution, so a renamed administrator role reaches the browser too.
|
|
334
|
+
describe("Util_ShellConfig.fields — adminGroup", () => {
|
|
335
|
+
testSync("is absent while the administrator role keeps its own name", () => {
|
|
336
|
+
Reventless.Role.clearGroups()
|
|
337
|
+
expect(Util_ShellConfig.fields(~computed)->Dict.get("adminGroup")->Option.isNone)->toBe(true)
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
testSync("carries the group a renamed administrator role maps to", () => {
|
|
341
|
+
Reventless.Role.clearGroups()
|
|
342
|
+
Reventless.Role.setGroups([(Reventless.Role.admin, "shop-admins")])
|
|
343
|
+
let out = Util_ShellConfig.fields(~computed)
|
|
344
|
+
Reventless.Role.clearGroups()
|
|
345
|
+
expect(out->get("adminGroup"))->toEqual(JSON.Encode.string("shop-admins"))
|
|
346
|
+
})
|
|
347
|
+
})
|
|
@@ -4,6 +4,7 @@ import * as JestGlobals from "@reventlessdev/rescript-jest/src/JestGlobals.res.m
|
|
|
4
4
|
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
5
5
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
6
6
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
7
|
+
import * as Role$Reventless from "@reventlessdev/reventless-spec/src/types/Role.res.mjs";
|
|
7
8
|
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
8
9
|
import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
|
|
9
10
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
@@ -341,6 +342,23 @@ globalThis.describe("the subscription endpoint", () => {
|
|
|
341
342
|
});
|
|
342
343
|
});
|
|
343
344
|
|
|
345
|
+
globalThis.describe("Util_ShellConfig.fields — adminGroup", () => {
|
|
346
|
+
globalThis.test("is absent while the administrator role keeps its own name", () => {
|
|
347
|
+
Role$Reventless.clearGroups();
|
|
348
|
+
globalThis.expect(Stdlib_Option.isNone(Util_ShellConfig$ReventlessAws.fields(computed, undefined, undefined, undefined, undefined)["adminGroup"])).toBe(true);
|
|
349
|
+
});
|
|
350
|
+
globalThis.test("carries the group a renamed administrator role maps to", () => {
|
|
351
|
+
Role$Reventless.clearGroups();
|
|
352
|
+
Role$Reventless.setGroups([[
|
|
353
|
+
Role$Reventless.admin,
|
|
354
|
+
"shop-admins"
|
|
355
|
+
]]);
|
|
356
|
+
let out = Util_ShellConfig$ReventlessAws.fields(computed, undefined, undefined, undefined, undefined);
|
|
357
|
+
Role$Reventless.clearGroups();
|
|
358
|
+
globalThis.expect(get(out, "adminGroup")).toEqual("shop-admins");
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
|
|
344
362
|
export {
|
|
345
363
|
computed,
|
|
346
364
|
get,
|
|
@@ -18,6 +18,7 @@ type error = AlreadyExists
|
|
|
18
18
|
|
|
19
19
|
let name = "AggTestAggregate"
|
|
20
20
|
let moduleUrl = "agg-test://AggTestAggregate"
|
|
21
|
-
|
|
21
|
+
type role = Reventless.Role.name
|
|
22
|
+
let commandAuthorization = (_: command): Reventless.Authorization.rule<role> => AllowAnonymous
|
|
22
23
|
type lifecycleState = unit
|
|
23
24
|
let commandTransition = (_: command): Reventless.Transition.t<lifecycleState> => Unrestricted
|
|
@@ -63,7 +63,8 @@ type event =
|
|
|
63
63
|
|
|
64
64
|
let name = "EpCompositeSlice"
|
|
65
65
|
let moduleUrl = "ep-test://EpCompositeSlice"
|
|
66
|
-
|
|
66
|
+
type role = Reventless.Role.name
|
|
67
|
+
let commandAuthorization = (_: command): Reventless.Authorization.rule<role> => AllowAnonymous
|
|
67
68
|
type lifecycleState = unit
|
|
68
69
|
let commandTransition = (_: command): Reventless.Transition.t<lifecycleState> => Unrestricted
|
|
69
70
|
let readConsistency = Reventless.ReadConsistency.EscalateOnRetry
|
|
@@ -21,7 +21,8 @@ type event = WidgetAdded({widgetId: @s.matches(Reventless.DcbTag.partition) stri
|
|
|
21
21
|
|
|
22
22
|
let name = "EpTestSlice"
|
|
23
23
|
let moduleUrl = "ep-test://EpTestSlice"
|
|
24
|
-
|
|
24
|
+
type role = Reventless.Role.name
|
|
25
|
+
let commandAuthorization = (_: command): Reventless.Authorization.rule<role> => AllowAnonymous
|
|
25
26
|
type lifecycleState = unit
|
|
26
27
|
let commandTransition = (_: command): Reventless.Transition.t<lifecycleState> => Unrestricted
|
|
27
28
|
let readConsistency = Reventless.ReadConsistency.EscalateOnRetry
|
|
@@ -32,7 +32,8 @@ module CustomersSpec = {
|
|
|
32
32
|
|
|
33
33
|
let config = Reventless.ReadModel.config()
|
|
34
34
|
let subIdConfig = None
|
|
35
|
-
|
|
35
|
+
type role = Reventless.Role.name
|
|
36
|
+
let authorization: Reventless.Authorization.rule<role> = AllowAuthenticated
|
|
36
37
|
let visibility: Reventless.Visibility.t = Public
|
|
37
38
|
}
|
|
38
39
|
|