@reventlessdev/reventless-aws 3.0.0-alpha.370 → 3.0.0-alpha.371
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 +7 -0
- package/package.json +10 -10
- package/src/Platform.res +31 -11
- package/src/Platform.res.mjs +24 -2
- package/src/Platform_ProvidedGroups.res +97 -0
- package/src/Platform_ProvidedGroups.res.mjs +103 -0
- package/tests/Platform_ProvidedGroupsTest.res +52 -0
- package/tests/Platform_ProvidedGroupsTest.res.mjs +98 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,13 @@
|
|
|
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.371 (2026-10-01)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* check a plugin's roles against the user pool's groups ([ddb3c42](https://github.com/ReventlessDev/reventless-core/commit/ddb3c422b7226a19800e781abd5f03af18867007))
|
|
11
|
+
|
|
12
|
+
|
|
6
13
|
# 3.0.0-alpha.370 (2026-10-01)
|
|
7
14
|
|
|
8
15
|
* feat!: roles a plugin declares, groups a platform provides ([43daace](https://github.com/ReventlessDev/reventless-core/commit/43daace6ca588afe22562d079af1e6f1da81942e))
|
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.371",
|
|
4
4
|
"description": "AWS adapters for Reventless",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"bin": {
|
|
@@ -21,19 +21,19 @@
|
|
|
21
21
|
"sury": "11.0.0",
|
|
22
22
|
"uuid": "^13.0.0",
|
|
23
23
|
"yaml": "^2.8.3",
|
|
24
|
-
"@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.
|
|
24
|
+
"@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.28",
|
|
25
25
|
"@reventlessdev/rescript-effect": "0.1.0-alpha.34",
|
|
26
|
-
"@reventlessdev/rescript-jest": "1.0.0-alpha.11",
|
|
27
26
|
"@reventlessdev/rescript-node": "2.0.0-alpha.20",
|
|
28
|
-
"@reventlessdev/rescript-
|
|
27
|
+
"@reventlessdev/rescript-jest": "1.0.0-alpha.11",
|
|
28
|
+
"@reventlessdev/rescript-pulumi-aws": "3.0.0-alpha.23",
|
|
29
|
+
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.23",
|
|
29
30
|
"@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",
|
|
32
31
|
"@reventlessdev/rescript-web": "1.0.0-alpha.7",
|
|
33
|
-
"@reventlessdev/reventless-
|
|
34
|
-
"@reventlessdev/reventless-
|
|
35
|
-
"@reventlessdev/reventless-
|
|
36
|
-
"@reventlessdev/reventless-
|
|
32
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.185",
|
|
33
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.287",
|
|
34
|
+
"@reventlessdev/reventless-postgres": "3.0.0-alpha.151",
|
|
35
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.157",
|
|
36
|
+
"@reventlessdev/reventless-interop": "3.0.0-alpha.39"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"rescript": "12.3.1",
|
package/src/Platform.res
CHANGED
|
@@ -112,11 +112,34 @@ let messagingEmailProviderRef: ref<Pulumi.Output.t<string>> = ref(Pulumi.Output.
|
|
|
112
112
|
resolves roles while it is built, and a mapping stated later is refused. */
|
|
113
113
|
let roleGroups = Reventless.Role.setGroups
|
|
114
114
|
|
|
115
|
-
/**
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
/** Groups a root says its identity provider has, for a provider the platform
|
|
116
|
+
cannot list. Where it can (`loadProvidedGroups`), the listing decides, and a
|
|
117
|
+
declared group the pool lacks fails the deploy. */
|
|
118
118
|
let providedGroups = Reventless.Role.provideGroups
|
|
119
119
|
|
|
120
|
+
/** The platform stack a plugin stack deploys against (`platform:stack`), or
|
|
121
|
+
`None` in a program that is its own platform. One reference per program:
|
|
122
|
+
a second with the same name is a duplicate resource. */
|
|
123
|
+
let platformStackRefCache: ref<option<option<Pulumi.StackReference.t>>> = ref(None)
|
|
124
|
+
|
|
125
|
+
let platformStackReference = (): option<Pulumi.StackReference.t> =>
|
|
126
|
+
switch platformStackRefCache.contents {
|
|
127
|
+
| Some(stackRef) => stackRef
|
|
128
|
+
| None =>
|
|
129
|
+
let stackRef =
|
|
130
|
+
Pulumi.Config.make(Some("platform"))
|
|
131
|
+
->Pulumi.Config.get("stack")
|
|
132
|
+
->Option.map(stack => Pulumi.StackReference.make(stack))
|
|
133
|
+
platformStackRefCache := Some(stackRef)
|
|
134
|
+
stackRef
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Ask the user pool which groups it has, so the role check compares each
|
|
138
|
+
plugin's roles with the pool. A plugin's deploy program calls `deployPlugin`
|
|
139
|
+
once this resolves; see [Platform_ProvidedGroups]. */
|
|
140
|
+
let loadProvidedGroups = (): promise<unit> =>
|
|
141
|
+
Platform_ProvidedGroups.load(~stackRef=platformStackReference())
|
|
142
|
+
|
|
120
143
|
/** The group a role resolves to — the name this platform writes into its
|
|
121
144
|
directives, its shell configuration and the elevated groups. For code that
|
|
122
145
|
grants access by group, so it never spells one itself. */
|
|
@@ -164,10 +187,10 @@ module MakeWithConfig = (
|
|
|
164
187
|
// [Reventless.OwnerScope.defaultElevatedGroups].
|
|
165
188
|
Reventless.OwnerScope.defaultElevatedRoles([Reventless.Role.admin])
|
|
166
189
|
|
|
167
|
-
// What this deployment provides for the roles its plugins need
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
// mapped its roles.
|
|
190
|
+
// What this deployment provides for the roles its plugins need, where the pool
|
|
191
|
+
// was not listed (`loadProvidedGroups`): the administrator group the stack
|
|
192
|
+
// declares, and the groups of the accounts provisioning creates. Read when each
|
|
193
|
+
// plugin is built, after the root has mapped its roles.
|
|
171
194
|
Reventless.Role.provideGroupsFrom(() =>
|
|
172
195
|
[Reventless.Role.adminGroup()]->Array.concat(Reventless.AccountsManifest.declaredGroups())
|
|
173
196
|
)
|
|
@@ -228,10 +251,7 @@ module MakeWithConfig = (
|
|
|
228
251
|
// Determine API source based on platform:stack config.
|
|
229
252
|
// - Platform/monolithic mode (no config): create a real AppSync API resource.
|
|
230
253
|
// - Plugin mode (config set): reference the platform's shared API via StackReference.
|
|
231
|
-
let platformStackRef =
|
|
232
|
-
Pulumi.Config.make(Some("platform"))
|
|
233
|
-
->Pulumi.Config.get("stack")
|
|
234
|
-
->Option.map(stack => Pulumi.StackReference.make(stack))
|
|
254
|
+
let platformStackRef = platformStackReference()
|
|
235
255
|
|
|
236
256
|
// The declared object stores, as the upload claimer needs them: qualified
|
|
237
257
|
// name, physical bucket, served prefix.
|
package/src/Platform.res.mjs
CHANGED
|
@@ -80,6 +80,7 @@ import * as EventTopicPublisher_SNS$ReventlessAws from "./adapter/EventTopic/Eve
|
|
|
80
80
|
import * as ExtensionPointMapping$ReventlessInfra from "@reventlessdev/reventless-infra/src/types/ExtensionPointMapping.res.mjs";
|
|
81
81
|
import * as PgQueryResolver_Builder$ReventlessAws from "./adapter/QueryDb/PgQueryResolver_Builder.res.mjs";
|
|
82
82
|
import * as Platform_BakedManifest$ReventlessCore from "@reventlessdev/reventless-core/src/admin/Platform_BakedManifest.res.mjs";
|
|
83
|
+
import * as Platform_ProvidedGroups$ReventlessAws from "./Platform_ProvidedGroups.res.mjs";
|
|
83
84
|
import * as UiFragments_Projection$ReventlessCore from "@reventlessdev/reventless-core/src/admin/UiFragmentRegistry/StateView/UiFragments_Projection.res.mjs";
|
|
84
85
|
import * as Aggregate_Builder_Single$ReventlessAws from "./components/Aggregate_Builder_Single.res.mjs";
|
|
85
86
|
import * as ReadModel_Builder_Single$ReventlessAws from "./components/ReadModel_Builder_Single.res.mjs";
|
|
@@ -152,6 +153,24 @@ let messagingEmailProviderRef = {
|
|
|
152
153
|
contents: Pulumi.output("")
|
|
153
154
|
};
|
|
154
155
|
|
|
156
|
+
let platformStackRefCache = {
|
|
157
|
+
contents: undefined
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
function platformStackReference() {
|
|
161
|
+
let stackRef = platformStackRefCache.contents;
|
|
162
|
+
if (stackRef !== undefined) {
|
|
163
|
+
return Primitive_option.valFromOption(stackRef);
|
|
164
|
+
}
|
|
165
|
+
let stackRef$1 = Stdlib_Option.map(new Pulumi.Config("platform").get("stack"), stack => new Pulumi.StackReference(stack));
|
|
166
|
+
platformStackRefCache.contents = Primitive_option.some(stackRef$1);
|
|
167
|
+
return stackRef$1;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function loadProvidedGroups() {
|
|
171
|
+
return Platform_ProvidedGroups$ReventlessAws.load(platformStackReference());
|
|
172
|
+
}
|
|
173
|
+
|
|
155
174
|
function MakeWithConfig(Config) {
|
|
156
175
|
OwnerScope$Reventless.defaultElevatedRoles([Role$Reventless.admin]);
|
|
157
176
|
Role$Reventless.provideGroupsFrom(() => [Role$Reventless.adminGroup()].concat(AccountsManifest$Reventless.declaredGroups()));
|
|
@@ -183,7 +202,7 @@ function MakeWithConfig(Config) {
|
|
|
183
202
|
let currentDeployTarget = {
|
|
184
203
|
contents: "Domain"
|
|
185
204
|
};
|
|
186
|
-
let platformStackRef =
|
|
205
|
+
let platformStackRef = platformStackReference();
|
|
187
206
|
let claimStores = () => {
|
|
188
207
|
if (platformStackRef !== undefined) {
|
|
189
208
|
return Primitive_option.valFromOption(platformStackRef).getOutput("objectStores").apply(objectStores => Stdlib_Array.filterMap(Object.entries(Stdlib_Option.getOr(Stdlib_Option.flatMap(objectStores, Stdlib_JSON.Decode.object), {})), param => {
|
|
@@ -1599,7 +1618,7 @@ function Make($star) {
|
|
|
1599
1618
|
let currentDeployTarget = {
|
|
1600
1619
|
contents: "Domain"
|
|
1601
1620
|
};
|
|
1602
|
-
let platformStackRef =
|
|
1621
|
+
let platformStackRef = platformStackReference();
|
|
1603
1622
|
let claimStores = () => {
|
|
1604
1623
|
if (platformStackRef !== undefined) {
|
|
1605
1624
|
return Primitive_option.valFromOption(platformStackRef).getOutput("objectStores").apply(objectStores => Stdlib_Array.filterMap(Object.entries(Stdlib_Option.getOr(Stdlib_Option.flatMap(objectStores, Stdlib_JSON.Decode.object), {})), param => {
|
|
@@ -2984,6 +3003,9 @@ export {
|
|
|
2984
3003
|
messagingEmailProviderRef,
|
|
2985
3004
|
roleGroups,
|
|
2986
3005
|
providedGroups,
|
|
3006
|
+
platformStackRefCache,
|
|
3007
|
+
platformStackReference,
|
|
3008
|
+
loadProvidedGroups,
|
|
2987
3009
|
groupOf,
|
|
2988
3010
|
adminGroup,
|
|
2989
3011
|
MakeWithConfig,
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/***
|
|
2
|
+
The user pool's groups, read before a plugin is built so the role check compares
|
|
3
|
+
its roles with the pool, not a manifest (`Role.provideListedGroups`). A group the
|
|
4
|
+
pool lacks refuses every caller, which nothing at runtime can tell from a caller
|
|
5
|
+
without the role. Works in a preview: `ListGroups` is a read, and the pool id is
|
|
6
|
+
read outside any `Output.apply`.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
module Cognito = AwsSdk.CognitoIdentityServiceProvider
|
|
10
|
+
|
|
11
|
+
type pool = {id: string, region: option<string>}
|
|
12
|
+
|
|
13
|
+
type send = Cognito.ListGroupsCommand.input => promise<Cognito.ListGroupsCommand.output>
|
|
14
|
+
|
|
15
|
+
/** Every group of the pool, following `NextToken` across pages. */
|
|
16
|
+
let listAll = async (~send: send, ~userPoolId: string): array<string> => {
|
|
17
|
+
let rec page = async (nextToken, acc) => {
|
|
18
|
+
let out = await send({userPoolId, limit: 60, ?nextToken})
|
|
19
|
+
let names = out.groups->Option.getOr([])->Array.filterMap(g => g.groupName)
|
|
20
|
+
let acc = acc->Array.concat(names)
|
|
21
|
+
switch out.nextToken {
|
|
22
|
+
| Some(_) as next => await page(next, acc)
|
|
23
|
+
| None => acc
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
await page(None, [])
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** A plugin stack's pool, from the platform stack's exports. The new key before
|
|
30
|
+
the old one, and each directly before under `default`, where ESM programs
|
|
31
|
+
nest their exports. */
|
|
32
|
+
let poolFromExports = (~get: string => option<JSON.t>): option<pool> => {
|
|
33
|
+
let nested = get("default")->Option.flatMap(JSON.Decode.object)
|
|
34
|
+
let read = key =>
|
|
35
|
+
get(key)
|
|
36
|
+
->Option.orElse(nested->Option.flatMap(d => d->Dict.get(key)))
|
|
37
|
+
->Option.flatMap(JSON.Decode.string)
|
|
38
|
+
read("identityProviderId")
|
|
39
|
+
->Option.orElse(read("cognitoUserPoolId"))
|
|
40
|
+
->Option.map(id => {
|
|
41
|
+
id,
|
|
42
|
+
region: read("identityProviderRegion")->Option.orElse(read("cognitoRegion")),
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
The pool this program deploys against, or `None` where it cannot be listed yet:
|
|
48
|
+
a pool this program creates does not exist before its first deploy.
|
|
49
|
+
|
|
50
|
+
A plugin stack reads its platform stack's exports, the same ones its resolvers
|
|
51
|
+
use. A program that is its own platform reads a supplied pool's id from config.
|
|
52
|
+
*/
|
|
53
|
+
let resolvePool = async (~stackRef: option<Pulumi.StackReference.t>): option<pool> =>
|
|
54
|
+
switch stackRef {
|
|
55
|
+
| Some(stackRef) =>
|
|
56
|
+
let keys = [
|
|
57
|
+
"identityProviderId",
|
|
58
|
+
"cognitoUserPoolId",
|
|
59
|
+
"identityProviderRegion",
|
|
60
|
+
"cognitoRegion",
|
|
61
|
+
"default",
|
|
62
|
+
]
|
|
63
|
+
let values = await Promise.all(
|
|
64
|
+
keys->Array.map(key => stackRef->Pulumi.StackReference.getOutputValue(key)),
|
|
65
|
+
)
|
|
66
|
+
let exports = Dict.fromArray(
|
|
67
|
+
keys->Array.mapWithIndex((key, i) => (key, values->Array.getUnsafe(i))),
|
|
68
|
+
)
|
|
69
|
+
poolFromExports(~get=key => exports->Dict.get(key)->Option.flatMap(v => v))
|
|
70
|
+
| None =>
|
|
71
|
+
Platform_Stack._identityProviderId(
|
|
72
|
+
~cfg=Pulumi.Config.make(Some("platform")),
|
|
73
|
+
)->Option.map(id => {
|
|
74
|
+
id,
|
|
75
|
+
region: Pulumi.Config.make(Some("aws"))->Pulumi.Config.get("region"),
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let sendTo = (~region: option<string>): send => {
|
|
80
|
+
let client = Cognito.Raw.client(~options={region: ?region})
|
|
81
|
+
input => Cognito.ListGroupsCommand.make(input)->Cognito.ListGroupsCommand.Raw.send(client, _)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** List the pool's groups and record them for the role check. Leaves the check
|
|
85
|
+
on its other sources where there is no pool to ask yet. */
|
|
86
|
+
let load = async (~stackRef: option<Pulumi.StackReference.t>): unit =>
|
|
87
|
+
switch await resolvePool(~stackRef) {
|
|
88
|
+
| None => ()
|
|
89
|
+
| Some({id, region}) =>
|
|
90
|
+
let groups = try await listAll(~send=sendTo(~region), ~userPoolId=id) catch {
|
|
91
|
+
| exn if exn->Util_AwsError.hasCode(~code="AccessDeniedException") =>
|
|
92
|
+
JsError.throwWithMessage(
|
|
93
|
+
`Cannot list the groups of the user pool ${id}: the deploying principal needs ` ++ `cognito-idp:ListGroups on it, so the deploy can check that every role a plugin needs has a group.`,
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
Reventless.Role.provideListedGroups(~source=`the user pool ${id}`, groups)
|
|
97
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
4
|
+
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
5
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
6
|
+
import * as Pulumi from "@pulumi/pulumi";
|
|
7
|
+
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
8
|
+
import * as Role$Reventless from "@reventlessdev/reventless-spec/src/types/Role.res.mjs";
|
|
9
|
+
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
10
|
+
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
11
|
+
import * as Util_AwsError$ReventlessAws from "./util/Util_AwsError.res.mjs";
|
|
12
|
+
import * as Platform_Stack$ReventlessAws from "./Platform_Stack.res.mjs";
|
|
13
|
+
import * as ClientCognitoIdentityProvider from "@aws-sdk/client-cognito-identity-provider";
|
|
14
|
+
|
|
15
|
+
async function listAll(send, userPoolId) {
|
|
16
|
+
let page = async (nextToken, acc) => {
|
|
17
|
+
let out = await send({
|
|
18
|
+
UserPoolId: userPoolId,
|
|
19
|
+
Limit: 60,
|
|
20
|
+
NextToken: nextToken
|
|
21
|
+
});
|
|
22
|
+
let names = Stdlib_Array.filterMap(Stdlib_Option.getOr(out.Groups, []), g => g.GroupName);
|
|
23
|
+
let acc$1 = acc.concat(names);
|
|
24
|
+
let next = out.NextToken;
|
|
25
|
+
if (next !== undefined) {
|
|
26
|
+
return await page(next, acc$1);
|
|
27
|
+
} else {
|
|
28
|
+
return acc$1;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
return await page(undefined, []);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function poolFromExports(get) {
|
|
35
|
+
let nested = Stdlib_Option.flatMap(get("default"), Stdlib_JSON.Decode.object);
|
|
36
|
+
let read = key => Stdlib_Option.flatMap(Stdlib_Option.orElse(get(key), Stdlib_Option.flatMap(nested, d => d[key])), Stdlib_JSON.Decode.string);
|
|
37
|
+
return Stdlib_Option.map(Stdlib_Option.orElse(read("identityProviderId"), read("cognitoUserPoolId")), id => ({
|
|
38
|
+
id: id,
|
|
39
|
+
region: Stdlib_Option.orElse(read("identityProviderRegion"), read("cognitoRegion"))
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function resolvePool(stackRef) {
|
|
44
|
+
if (stackRef === undefined) {
|
|
45
|
+
return Stdlib_Option.map(Platform_Stack$ReventlessAws._identityProviderId(new Pulumi.Config("platform")), id => ({
|
|
46
|
+
id: id,
|
|
47
|
+
region: new Pulumi.Config("aws").get("region")
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
let stackRef$1 = Primitive_option.valFromOption(stackRef);
|
|
51
|
+
let keys = [
|
|
52
|
+
"identityProviderId",
|
|
53
|
+
"cognitoUserPoolId",
|
|
54
|
+
"identityProviderRegion",
|
|
55
|
+
"cognitoRegion",
|
|
56
|
+
"default"
|
|
57
|
+
];
|
|
58
|
+
let values = await Promise.all(keys.map(key => stackRef$1.getOutputValue(key)));
|
|
59
|
+
let $$exports = Object.fromEntries(keys.map((key, i) => [
|
|
60
|
+
key,
|
|
61
|
+
values[i]
|
|
62
|
+
]));
|
|
63
|
+
return poolFromExports(key => Stdlib_Option.flatMap($$exports[key], v => v));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function sendTo(region) {
|
|
67
|
+
let client = new ClientCognitoIdentityProvider.CognitoIdentityProviderClient({
|
|
68
|
+
region: region
|
|
69
|
+
});
|
|
70
|
+
return input => client.send(new ClientCognitoIdentityProvider.ListGroupsCommand(input));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function load(stackRef) {
|
|
74
|
+
let match = await resolvePool(stackRef);
|
|
75
|
+
if (match === undefined) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
let id = match.id;
|
|
79
|
+
let groups;
|
|
80
|
+
try {
|
|
81
|
+
groups = await listAll(sendTo(match.region), id);
|
|
82
|
+
} catch (raw_exn) {
|
|
83
|
+
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
84
|
+
if (Util_AwsError$ReventlessAws.hasCode(exn, "AccessDeniedException")) {
|
|
85
|
+
groups = Stdlib_JsError.throwWithMessage(`Cannot list the groups of the user pool ` + id + `: the deploying principal needs cognito-idp:ListGroups on it, so the deploy can check that every role a plugin needs has a group.`);
|
|
86
|
+
} else {
|
|
87
|
+
throw exn;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return Role$Reventless.provideListedGroups(`the user pool ` + id, groups);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let Cognito;
|
|
94
|
+
|
|
95
|
+
export {
|
|
96
|
+
Cognito,
|
|
97
|
+
listAll,
|
|
98
|
+
poolFromExports,
|
|
99
|
+
resolvePool,
|
|
100
|
+
sendTo,
|
|
101
|
+
load,
|
|
102
|
+
}
|
|
103
|
+
/* @pulumi/pulumi Not a pure module */
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
open JestGlobals
|
|
2
|
+
|
|
3
|
+
// What the role check compares a plugin's roles with on AWS: the groups the user
|
|
4
|
+
// pool reports, found through the platform stack's exports.
|
|
5
|
+
|
|
6
|
+
module Cognito = AwsSdk.CognitoIdentityServiceProvider
|
|
7
|
+
|
|
8
|
+
let group = (name): Cognito.ListGroupsCommand.group => {groupName: name}
|
|
9
|
+
|
|
10
|
+
describe("Platform_ProvidedGroups.listAll", () => {
|
|
11
|
+
test("follows NextToken until the last page", async () => {
|
|
12
|
+
let seen = []
|
|
13
|
+
let send = async (input: Cognito.ListGroupsCommand.input): Cognito.ListGroupsCommand.output => {
|
|
14
|
+
seen->Array.push(input.nextToken)
|
|
15
|
+
switch input.nextToken {
|
|
16
|
+
| None => {groups: [group("Admin"), group("Shopper")], nextToken: "p2"}
|
|
17
|
+
| Some(_) => {groups: [group("Merchandiser")]}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
let groups = await Platform_ProvidedGroups.listAll(~send, ~userPoolId="p1")
|
|
21
|
+
expect((groups, seen))->toEqual((["Admin", "Shopper", "Merchandiser"], [None, Some("p2")]))
|
|
22
|
+
})
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
describe("Platform_ProvidedGroups.poolFromExports", () => {
|
|
26
|
+
let from = (pairs: array<(string, JSON.t)>) => {
|
|
27
|
+
let exports = Dict.fromArray(pairs)
|
|
28
|
+
Platform_ProvidedGroups.poolFromExports(~get=key => exports->Dict.get(key))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
testSync("reads the pool id and region", () =>
|
|
32
|
+
expect(
|
|
33
|
+
from([
|
|
34
|
+
("identityProviderId", JSON.Encode.string("p1")),
|
|
35
|
+
("identityProviderRegion", JSON.Encode.string("eu-west-1")),
|
|
36
|
+
]),
|
|
37
|
+
)->toEqual(Some({Platform_ProvidedGroups.id: "p1", region: Some("eu-west-1")}))
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
testSync("reads them nested under default, as an ESM platform exports them", () =>
|
|
41
|
+
expect(
|
|
42
|
+
from([
|
|
43
|
+
(
|
|
44
|
+
"default",
|
|
45
|
+
JSON.Encode.object(Dict.fromArray([("cognitoUserPoolId", JSON.Encode.string("p-old"))])),
|
|
46
|
+
),
|
|
47
|
+
]),
|
|
48
|
+
)->toEqual(Some({Platform_ProvidedGroups.id: "p-old", region: None}))
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
testSync("no pool id is no pool to list", () => expect(from([]))->toEqual(None))
|
|
52
|
+
})
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Platform_ProvidedGroups$ReventlessAws from "../src/Platform_ProvidedGroups.res.mjs";
|
|
4
|
+
|
|
5
|
+
function group(name) {
|
|
6
|
+
return {
|
|
7
|
+
GroupName: name
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
globalThis.describe("Platform_ProvidedGroups.listAll", () => {
|
|
12
|
+
globalThis.test("follows NextToken until the last page", async () => {
|
|
13
|
+
let seen = [];
|
|
14
|
+
let send = async input => {
|
|
15
|
+
seen.push(input.NextToken);
|
|
16
|
+
let match = input.NextToken;
|
|
17
|
+
if (match !== undefined) {
|
|
18
|
+
return {
|
|
19
|
+
Groups: [{
|
|
20
|
+
GroupName: "Merchandiser"
|
|
21
|
+
}]
|
|
22
|
+
};
|
|
23
|
+
} else {
|
|
24
|
+
return {
|
|
25
|
+
Groups: [
|
|
26
|
+
{
|
|
27
|
+
GroupName: "Admin"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
GroupName: "Shopper"
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
NextToken: "p2"
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
let groups = await Platform_ProvidedGroups$ReventlessAws.listAll(send, "p1");
|
|
38
|
+
globalThis.expect([
|
|
39
|
+
groups,
|
|
40
|
+
seen
|
|
41
|
+
]).toEqual([
|
|
42
|
+
[
|
|
43
|
+
"Admin",
|
|
44
|
+
"Shopper",
|
|
45
|
+
"Merchandiser"
|
|
46
|
+
],
|
|
47
|
+
[
|
|
48
|
+
undefined,
|
|
49
|
+
"p2"
|
|
50
|
+
]
|
|
51
|
+
]);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
globalThis.describe("Platform_ProvidedGroups.poolFromExports", () => {
|
|
56
|
+
let from = pairs => {
|
|
57
|
+
let $$exports = Object.fromEntries(pairs);
|
|
58
|
+
return Platform_ProvidedGroups$ReventlessAws.poolFromExports(key => $$exports[key]);
|
|
59
|
+
};
|
|
60
|
+
globalThis.test("reads the pool id and region", () => {
|
|
61
|
+
globalThis.expect(from([
|
|
62
|
+
[
|
|
63
|
+
"identityProviderId",
|
|
64
|
+
"p1"
|
|
65
|
+
],
|
|
66
|
+
[
|
|
67
|
+
"identityProviderRegion",
|
|
68
|
+
"eu-west-1"
|
|
69
|
+
]
|
|
70
|
+
])).toEqual({
|
|
71
|
+
id: "p1",
|
|
72
|
+
region: "eu-west-1"
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
globalThis.test("reads them nested under default, as an ESM platform exports them", () => {
|
|
76
|
+
globalThis.expect(from([[
|
|
77
|
+
"default",
|
|
78
|
+
Object.fromEntries([[
|
|
79
|
+
"cognitoUserPoolId",
|
|
80
|
+
"p-old"
|
|
81
|
+
]])
|
|
82
|
+
]])).toEqual({
|
|
83
|
+
id: "p-old",
|
|
84
|
+
region: undefined
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
globalThis.test("no pool id is no pool to list", () => {
|
|
88
|
+
globalThis.expect(from([])).toEqual(undefined);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
let Cognito;
|
|
93
|
+
|
|
94
|
+
export {
|
|
95
|
+
Cognito,
|
|
96
|
+
group,
|
|
97
|
+
}
|
|
98
|
+
/* Not a pure module */
|