@reventlessdev/reventless-aws 3.0.0-alpha.292 → 3.0.0-alpha.294
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 +17 -0
- package/package.json +5 -5
- package/src/Platform.res +43 -4
- package/src/Platform.res.mjs +11 -4
- package/src/Platform_Stack.res +82 -0
- package/src/Platform_Stack.res.mjs +18 -2
- package/src/adapter/Auth/Auth_ActiveRolePoolAttachment.res +517 -0
- package/src/adapter/Auth/Auth_ActiveRolePoolAttachment.res.mjs +341 -0
- package/src/adapter/Auth/Auth_ActiveRoleStore.res +280 -0
- package/src/adapter/Auth/Auth_ActiveRoleStore.res.mjs +163 -0
- package/src/adapter/Auth/Auth_ActiveRoleStore_Ops.res +148 -0
- package/src/adapter/Auth/Auth_ActiveRoleStore_Ops.res.mjs +128 -0
- package/src/adapter/Auth/Auth_ActiveRoleTrigger.res +185 -0
- package/src/adapter/Auth/Auth_ActiveRoleTrigger.res.mjs +107 -0
- package/src/adapter/Auth/Auth_ActiveRoleTrigger_Ops.res +201 -0
- package/src/adapter/Auth/Auth_ActiveRoleTrigger_Ops.res.mjs +126 -0
- package/tests/Auth_ActiveRolePoolAttachmentTest.res +285 -0
- package/tests/Auth_ActiveRolePoolAttachmentTest.res.mjs +263 -0
- package/tests/Auth_ActiveRoleStoreTest.res +42 -0
- package/tests/Auth_ActiveRoleStoreTest.res.mjs +41 -0
- package/tests/Auth_ActiveRoleTrigger_OpsTest.res +169 -0
- package/tests/Auth_ActiveRoleTrigger_OpsTest.res.mjs +189 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Aws from "@pulumi/aws";
|
|
4
|
+
import * as IAM$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/IAM.res.mjs";
|
|
5
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
6
|
+
import * as Pulumi from "@pulumi/pulumi";
|
|
7
|
+
import * as Lambda$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/Lambda/Lambda.res.mjs";
|
|
8
|
+
import * as AWS$ReventlessAws from "../AWS.res.mjs";
|
|
9
|
+
import * as AWS_Tags$ReventlessAws from "../AWS_Tags.res.mjs";
|
|
10
|
+
import * as PolicyDocument$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/PolicyDocument.res.mjs";
|
|
11
|
+
import * as Util_Bundle$ReventlessAws from "../../util/Util_Bundle.res.mjs";
|
|
12
|
+
import * as Util_Pulumi$ReventlessCore from "@reventlessdev/reventless-core/src/util/Util_Pulumi.res.mjs";
|
|
13
|
+
import * as Util_LambdaLogging$ReventlessAws from "../../util/Util_LambdaLogging.res.mjs";
|
|
14
|
+
|
|
15
|
+
function make(activeRoleTableName, activeRoleTableArn, nameOpt, opts) {
|
|
16
|
+
let name = nameOpt !== undefined ? nameOpt : "ActiveRoleTrigger";
|
|
17
|
+
let opts$1 = Util_Pulumi$ReventlessCore.ComponentResourceOptions.toCustomResourceOptions(opts);
|
|
18
|
+
let lambdaRole = IAM$PulumiAws.Role.makeWithDefaultPolicy(name, Pulumi.output(AWS$ReventlessAws.Lambda.principal), AWS_Tags$ReventlessAws.make(name, "Platform", "Identity", "Platform", undefined, undefined, undefined, undefined), opts$1);
|
|
19
|
+
activeRoleTableArn.apply(tableArn => {
|
|
20
|
+
new (Aws.iam.RolePolicy)(name + "Policy", {
|
|
21
|
+
policy: PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + "Policy", [
|
|
22
|
+
{
|
|
23
|
+
Sid: "AllowLambdaLogging",
|
|
24
|
+
Effect: "Allow",
|
|
25
|
+
Action: [
|
|
26
|
+
"logs:CreateLogGroup",
|
|
27
|
+
"logs:CreateLogStream",
|
|
28
|
+
"logs:PutLogEvents"
|
|
29
|
+
],
|
|
30
|
+
Resource: "arn:aws:logs:*:*:*"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
Sid: "AllowReadingStoredRole",
|
|
34
|
+
Effect: "Allow",
|
|
35
|
+
Action: "dynamodb:GetItem",
|
|
36
|
+
Resource: tableArn
|
|
37
|
+
}
|
|
38
|
+
])),
|
|
39
|
+
role: lambdaRole.id
|
|
40
|
+
}, opts$1);
|
|
41
|
+
});
|
|
42
|
+
let packageDirs = Object.fromEntries([[
|
|
43
|
+
"@reventlessdev/reventless-aws",
|
|
44
|
+
Util_Bundle$ReventlessAws.resolvePackageRoot(undefined, "@reventlessdev/reventless-aws")
|
|
45
|
+
]]);
|
|
46
|
+
let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Auth/Auth_ActiveRoleTrigger_Ops.res.mjs", packageDirs, undefined, false);
|
|
47
|
+
let sourceCodeHash = match.sourceCodeHash;
|
|
48
|
+
let layers = Stdlib_Option.getOr(Stdlib_Option.map(Lambda$PulumiAws.reventlessLayerArn, arn => [arn]), []);
|
|
49
|
+
let logGroup = Util_LambdaLogging$ReventlessAws.makeManagedLogGroup(name, undefined, AWS_Tags$ReventlessAws.make(name + "LogGroup", "Platform", "Logs", "Platform", undefined, undefined, undefined, undefined), opts$1, undefined);
|
|
50
|
+
let lambda = new (Aws.lambda.Function)(name, {
|
|
51
|
+
handler: "index.handler",
|
|
52
|
+
runtime: "nodejs22.x",
|
|
53
|
+
code: match.code,
|
|
54
|
+
role: lambdaRole.arn,
|
|
55
|
+
memorySize: 256,
|
|
56
|
+
timeout: 5,
|
|
57
|
+
layers: layers,
|
|
58
|
+
tags: AWS_Tags$ReventlessAws.make(name, "Platform", "Runtime", "Platform", undefined, undefined, undefined, undefined),
|
|
59
|
+
environment: {
|
|
60
|
+
variables: Object.fromEntries([
|
|
61
|
+
[
|
|
62
|
+
"Environment",
|
|
63
|
+
Pulumi.getStack()
|
|
64
|
+
],
|
|
65
|
+
[
|
|
66
|
+
"ACTIVE_ROLE_TABLE",
|
|
67
|
+
activeRoleTableName
|
|
68
|
+
],
|
|
69
|
+
[
|
|
70
|
+
"NODE_OPTIONS",
|
|
71
|
+
Util_Bundle$ReventlessAws.esmLoaderNodeOptions
|
|
72
|
+
],
|
|
73
|
+
[
|
|
74
|
+
"ESM_FALLBACK_DIRS",
|
|
75
|
+
Util_Bundle$ReventlessAws.esmFallbackDirs
|
|
76
|
+
],
|
|
77
|
+
Util_LambdaLogging$ReventlessAws.logLevelEntry()
|
|
78
|
+
])
|
|
79
|
+
},
|
|
80
|
+
sourceCodeHash: sourceCodeHash,
|
|
81
|
+
loggingConfig: Util_LambdaLogging$ReventlessAws.loggingConfigFor(logGroup)
|
|
82
|
+
}, opts$1);
|
|
83
|
+
return {
|
|
84
|
+
functionArn: lambda.arn,
|
|
85
|
+
functionName: lambda.name,
|
|
86
|
+
sourceCodeHash: sourceCodeHash
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function grantInvoke(trigger, cognitoUserPoolArn, nameOpt, opts) {
|
|
91
|
+
let name = nameOpt !== undefined ? nameOpt : "ActiveRoleTrigger";
|
|
92
|
+
let opts$1 = Util_Pulumi$ReventlessCore.ComponentResourceOptions.toCustomResourceOptions(opts);
|
|
93
|
+
trigger.functionName.apply(functionName => {
|
|
94
|
+
new (Aws.lambda.Permission)(name + "Invoke", {
|
|
95
|
+
action: "lambda:InvokeFunction",
|
|
96
|
+
function: functionName,
|
|
97
|
+
principal: "cognito-idp.amazonaws.com",
|
|
98
|
+
sourceArn: cognitoUserPoolArn
|
|
99
|
+
}, opts$1);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export {
|
|
104
|
+
make,
|
|
105
|
+
grantInvoke,
|
|
106
|
+
}
|
|
107
|
+
/* @pulumi/aws Not a pure module */
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// Runtime handler for the Cognito **pre token generation** trigger — the AWS
|
|
2
|
+
// minting point for "acting as one of the roles you hold". See
|
|
3
|
+
// [docs/plans/active-role-narrows-the-token.md] §6.
|
|
4
|
+
//
|
|
5
|
+
// Compiled, type-checked and Pulumi-free so it ships as an EntryPoint module
|
|
6
|
+
// ([Auth_ActiveRoleTrigger.res] bundles it and attaches it to the user pool).
|
|
7
|
+
//
|
|
8
|
+
// The narrowing has to happen here rather than in our own enforcement code
|
|
9
|
+
// because `@authorize(AllowGroups([...]))` compiles to `@aws_auth`, which AppSync
|
|
10
|
+
// evaluates against `cognito:groups` **before any of our code executes**. A
|
|
11
|
+
// request header we invent could scope reads correctly and still leave every
|
|
12
|
+
// group-gated mutation callable — right about the data, wrong about the writes.
|
|
13
|
+
// Overriding the group claim is the one place every enforcement point already
|
|
14
|
+
// looks.
|
|
15
|
+
//
|
|
16
|
+
// **Version 1 of the trigger, deliberately.** Group override and added ID-token
|
|
17
|
+
// claims are `V1_0` capabilities; `V2_0`/`V3_0` buy access-token customisation
|
|
18
|
+
// this does not need and are gated behind the Essentials and Plus feature plans.
|
|
19
|
+
// A deployment on the Lite tier must not be excluded from acting as a role.
|
|
20
|
+
|
|
21
|
+
open AwsSdk
|
|
22
|
+
|
|
23
|
+
// ── Environment ─────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
let getEnv = (k: string): option<string> =>
|
|
26
|
+
switch NodeProcess.env->Dict.get(k) {
|
|
27
|
+
| Some("") | None => None
|
|
28
|
+
| Some(v) => Some(v)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let tableName = (): string =>
|
|
32
|
+
switch getEnv("ACTIVE_ROLE_TABLE") {
|
|
33
|
+
| Some(t) => t
|
|
34
|
+
| None => JsError.throwWithMessage("ACTIVE_ROLE_TABLE is not configured")
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ── Cognito pre-token-generation event (V1_0) ───────────────────────────────
|
|
38
|
+
|
|
39
|
+
type userAttributes = {sub?: string}
|
|
40
|
+
|
|
41
|
+
/** The groups the pool says the user is really in, supplied by the pool itself.
|
|
42
|
+
This is why the subset check here needs no lookup and no IAM to perform one:
|
|
43
|
+
the authority arrives in the payload, and the check is a containment test
|
|
44
|
+
against it. */
|
|
45
|
+
type groupConfiguration = {
|
|
46
|
+
groupsToOverride?: array<string>,
|
|
47
|
+
iamRolesToOverride?: array<string>,
|
|
48
|
+
preferredRole?: Nullable.t<string>,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
type triggerRequest = {
|
|
52
|
+
userAttributes?: userAttributes,
|
|
53
|
+
groupConfiguration?: groupConfiguration,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
type groupOverrideDetails = {
|
|
57
|
+
groupsToOverride: array<string>,
|
|
58
|
+
iamRolesToOverride: array<string>,
|
|
59
|
+
preferredRole?: Nullable.t<string>,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
type claimsOverrideDetails = {
|
|
63
|
+
claimsToAddOrOverride?: dict<string>,
|
|
64
|
+
groupOverrideDetails?: groupOverrideDetails,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
type triggerResponse = {claimsOverrideDetails?: claimsOverrideDetails}
|
|
68
|
+
|
|
69
|
+
type event = {
|
|
70
|
+
request?: triggerRequest,
|
|
71
|
+
response?: triggerResponse,
|
|
72
|
+
userName?: string,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ── The decision ────────────────────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
type decision =
|
|
78
|
+
/** No stored preference — mint exactly the token that would have been minted
|
|
79
|
+
before any of this existed. This is the path every existing caller takes. */
|
|
80
|
+
| Unchanged
|
|
81
|
+
/** The stored role is one the pool says the caller holds. */
|
|
82
|
+
| Narrow({role: string, membership: array<string>})
|
|
83
|
+
/** The row outlived the membership that justified it.
|
|
84
|
+
|
|
85
|
+
Resolved by minting the full set rather than narrowing to a group the pool no
|
|
86
|
+
longer grants — but *decided* here rather than falling out of the code,
|
|
87
|
+
because the alternative reading is a token scoped to a role the caller has
|
|
88
|
+
lost. Unlike the write door, this path has no client to refuse: the trigger
|
|
89
|
+
meets a stale row on an ordinary refresh with nobody asking for anything. It
|
|
90
|
+
says so on the token instead, so a caller whose chosen role silently stopped
|
|
91
|
+
applying has something to read. */
|
|
92
|
+
| Stale({role: string, membership: array<string>})
|
|
93
|
+
|
|
94
|
+
let decide = (~membership: array<string>, ~storedRole: option<string>): decision =>
|
|
95
|
+
switch storedRole {
|
|
96
|
+
| None | Some("") => Unchanged
|
|
97
|
+
| Some(role) =>
|
|
98
|
+
membership->Array.includes(role) ? Narrow({role, membership}) : Stale({role, membership})
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
Turn a decision into the trigger's response.
|
|
103
|
+
|
|
104
|
+
🚨 **`groupOverrideDetails` replaces the whole group configuration.** Supplying it
|
|
105
|
+
with only `groupsToOverride` drops the caller's `iamRolesToOverride` and
|
|
106
|
+
`preferredRole` — the same reset-by-omission hazard `UpdateUserPool` has, and just
|
|
107
|
+
as quiet. Both are echoed from the incoming configuration so the override changes
|
|
108
|
+
exactly one thing.
|
|
109
|
+
|
|
110
|
+
`Unchanged` returns the event untouched rather than an empty override: an empty
|
|
111
|
+
`claimsOverrideDetails` is not the same as none, and the regression line for this
|
|
112
|
+
whole feature is that a caller with no stored role gets byte-identical output.
|
|
113
|
+
*/
|
|
114
|
+
let respond = (~event: event, ~decision: decision): event => {
|
|
115
|
+
let incoming = event.request->Option.flatMap(r => r.groupConfiguration)
|
|
116
|
+
let iamRolesToOverride = incoming->Option.flatMap(g => g.iamRolesToOverride)->Option.getOr([])
|
|
117
|
+
let preferredRole = incoming->Option.flatMap(g => g.preferredRole)
|
|
118
|
+
|
|
119
|
+
let overrideWith = (~groups: array<string>, ~claims: dict<string>) => {
|
|
120
|
+
...event,
|
|
121
|
+
response: {
|
|
122
|
+
claimsOverrideDetails: {
|
|
123
|
+
claimsToAddOrOverride: claims,
|
|
124
|
+
groupOverrideDetails: {
|
|
125
|
+
groupsToOverride: groups,
|
|
126
|
+
iamRolesToOverride,
|
|
127
|
+
preferredRole: ?preferredRole,
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
switch decision {
|
|
134
|
+
| Unchanged => event
|
|
135
|
+
| Narrow({role, membership}) =>
|
|
136
|
+
overrideWith(
|
|
137
|
+
~groups=[role],
|
|
138
|
+
~claims=Dict.fromArray([
|
|
139
|
+
(ReventlessCore.Auth_ActiveRole.activeRoleClaim, role),
|
|
140
|
+
(ReventlessCore.Auth_ActiveRole.availableRolesClaim, membership->Array.join(",")),
|
|
141
|
+
]),
|
|
142
|
+
)
|
|
143
|
+
| Stale({role, membership}) =>
|
|
144
|
+
// The full set, as the pool granted it — plus the marker that explains why the
|
|
145
|
+
// stored choice did not apply.
|
|
146
|
+
overrideWith(
|
|
147
|
+
~groups=membership,
|
|
148
|
+
~claims=Dict.fromArray([
|
|
149
|
+
(ReventlessCore.Auth_ActiveRole.staleRoleClaim, role),
|
|
150
|
+
(ReventlessCore.Auth_ActiveRole.availableRolesClaim, membership->Array.join(",")),
|
|
151
|
+
]),
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ── Stored preference ───────────────────────────────────────────────────────
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
The role this subject last chose, or `None` if they never chose one or cleared it.
|
|
160
|
+
|
|
161
|
+
A read failure resolves to `None`, not to an error. This trigger sits in the
|
|
162
|
+
critical path of every token Cognito mints for this pool: a throw here fails the
|
|
163
|
+
sign-in outright, and failing a login because a *preference* could not be read
|
|
164
|
+
trades a working session for a cosmetic one. The caller lands on full membership —
|
|
165
|
+
their existing privileges, not more — which is the safe direction to fail.
|
|
166
|
+
*/
|
|
167
|
+
let storedRoleFor = async (~sub: string, ~table: string): option<string> =>
|
|
168
|
+
try {
|
|
169
|
+
let out = await DynamoDb_DocumentClient.GetCommand.make({
|
|
170
|
+
tableName: table,
|
|
171
|
+
key: Dict.fromArray([("id", JSON.Encode.string(sub))]),
|
|
172
|
+
})->DynamoDb_DocumentClient.GetCommand.send
|
|
173
|
+
out.item
|
|
174
|
+
->Option.flatMap(JSON.Decode.object)
|
|
175
|
+
->Option.flatMap(o => o->Dict.get("activeRole"))
|
|
176
|
+
->Option.flatMap(JSON.Decode.string)
|
|
177
|
+
} catch {
|
|
178
|
+
| _ => None
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ── Handler ─────────────────────────────────────────────────────────────────
|
|
182
|
+
|
|
183
|
+
let handler = async (event: event): event => {
|
|
184
|
+
let membership =
|
|
185
|
+
event.request
|
|
186
|
+
->Option.flatMap(r => r.groupConfiguration)
|
|
187
|
+
->Option.flatMap(g => g.groupsToOverride)
|
|
188
|
+
->Option.getOr([])
|
|
189
|
+
|
|
190
|
+
let sub =
|
|
191
|
+
event.request->Option.flatMap(r => r.userAttributes)->Option.flatMap(u => u.sub)->Option.getOr("")
|
|
192
|
+
|
|
193
|
+
// No subject means no row to look up. Returning the event untouched keeps the
|
|
194
|
+
// sign-in working on exactly the membership the pool granted.
|
|
195
|
+
if sub == "" {
|
|
196
|
+
event
|
|
197
|
+
} else {
|
|
198
|
+
let storedRole = await storedRoleFor(~sub, ~table=tableName())
|
|
199
|
+
respond(~event, ~decision=decide(~membership, ~storedRole))
|
|
200
|
+
}
|
|
201
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
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_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
5
|
+
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
6
|
+
import * as LibDynamodb from "@aws-sdk/lib-dynamodb";
|
|
7
|
+
import * as Auth_ActiveRole$ReventlessCore from "@reventlessdev/reventless-core/src/adapter/Auth/Auth_ActiveRole.res.mjs";
|
|
8
|
+
import * as DynamoDb_DocumentClient$AwsSdk from "@reventlessdev/rescript-aws-sdk/src/DynamoDb_DocumentClient.res.mjs";
|
|
9
|
+
|
|
10
|
+
function getEnv(k) {
|
|
11
|
+
let v = process.env[k];
|
|
12
|
+
if (v !== undefined && v !== "") {
|
|
13
|
+
return v;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function tableName() {
|
|
18
|
+
let t = getEnv("ACTIVE_ROLE_TABLE");
|
|
19
|
+
if (t !== undefined) {
|
|
20
|
+
return t;
|
|
21
|
+
} else {
|
|
22
|
+
return Stdlib_JsError.throwWithMessage("ACTIVE_ROLE_TABLE is not configured");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function decide(membership, storedRole) {
|
|
27
|
+
if (storedRole !== undefined && storedRole !== "") {
|
|
28
|
+
if (membership.includes(storedRole)) {
|
|
29
|
+
return {
|
|
30
|
+
TAG: "Narrow",
|
|
31
|
+
role: storedRole,
|
|
32
|
+
membership: membership
|
|
33
|
+
};
|
|
34
|
+
} else {
|
|
35
|
+
return {
|
|
36
|
+
TAG: "Stale",
|
|
37
|
+
role: storedRole,
|
|
38
|
+
membership: membership
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
return "Unchanged";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function respond(event, decision) {
|
|
47
|
+
let incoming = Stdlib_Option.flatMap(event.request, r => r.groupConfiguration);
|
|
48
|
+
let iamRolesToOverride = Stdlib_Option.getOr(Stdlib_Option.flatMap(incoming, g => g.iamRolesToOverride), []);
|
|
49
|
+
let preferredRole = Stdlib_Option.flatMap(incoming, g => g.preferredRole);
|
|
50
|
+
let overrideWith = (groups, claims) => {
|
|
51
|
+
let newrecord = {...event};
|
|
52
|
+
newrecord.response = {
|
|
53
|
+
claimsOverrideDetails: {
|
|
54
|
+
claimsToAddOrOverride: claims,
|
|
55
|
+
groupOverrideDetails: {
|
|
56
|
+
groupsToOverride: groups,
|
|
57
|
+
iamRolesToOverride: iamRolesToOverride,
|
|
58
|
+
preferredRole: preferredRole
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
return newrecord;
|
|
63
|
+
};
|
|
64
|
+
if (typeof decision !== "object") {
|
|
65
|
+
return event;
|
|
66
|
+
}
|
|
67
|
+
if (decision.TAG === "Narrow") {
|
|
68
|
+
let role = decision.role;
|
|
69
|
+
return overrideWith([role], Object.fromEntries([
|
|
70
|
+
[
|
|
71
|
+
Auth_ActiveRole$ReventlessCore.activeRoleClaim,
|
|
72
|
+
role
|
|
73
|
+
],
|
|
74
|
+
[
|
|
75
|
+
Auth_ActiveRole$ReventlessCore.availableRolesClaim,
|
|
76
|
+
decision.membership.join(",")
|
|
77
|
+
]
|
|
78
|
+
]));
|
|
79
|
+
}
|
|
80
|
+
let membership = decision.membership;
|
|
81
|
+
return overrideWith(membership, Object.fromEntries([
|
|
82
|
+
[
|
|
83
|
+
Auth_ActiveRole$ReventlessCore.staleRoleClaim,
|
|
84
|
+
decision.role
|
|
85
|
+
],
|
|
86
|
+
[
|
|
87
|
+
Auth_ActiveRole$ReventlessCore.availableRolesClaim,
|
|
88
|
+
membership.join(",")
|
|
89
|
+
]
|
|
90
|
+
]));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function storedRoleFor(sub, table) {
|
|
94
|
+
try {
|
|
95
|
+
let out = await DynamoDb_DocumentClient$AwsSdk.GetCommand.send(new LibDynamodb.GetCommand({
|
|
96
|
+
TableName: table,
|
|
97
|
+
Key: Object.fromEntries([[
|
|
98
|
+
"id",
|
|
99
|
+
sub
|
|
100
|
+
]])
|
|
101
|
+
}));
|
|
102
|
+
return Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_Option.flatMap(out.Item, Stdlib_JSON.Decode.object), o => o["activeRole"]), Stdlib_JSON.Decode.string);
|
|
103
|
+
} catch (exn) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function handler(event) {
|
|
109
|
+
let membership = Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_Option.flatMap(event.request, r => r.groupConfiguration), g => g.groupsToOverride), []);
|
|
110
|
+
let sub = Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_Option.flatMap(event.request, r => r.userAttributes), u => u.sub), "");
|
|
111
|
+
if (sub === "") {
|
|
112
|
+
return event;
|
|
113
|
+
}
|
|
114
|
+
let storedRole = await storedRoleFor(sub, tableName());
|
|
115
|
+
return respond(event, decide(membership, storedRole));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export {
|
|
119
|
+
getEnv,
|
|
120
|
+
tableName,
|
|
121
|
+
decide,
|
|
122
|
+
respond,
|
|
123
|
+
storedRoleFor,
|
|
124
|
+
handler,
|
|
125
|
+
}
|
|
126
|
+
/* @aws-sdk/lib-dynamodb Not a pure module */
|