@reventlessdev/reventless-gwt 1.0.0-alpha.232 → 1.0.0-alpha.233
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 +5 -5
- package/src/Behavior_GWT.res +128 -30
- package/src/Behavior_GWT.res.mjs +144 -14
- package/src/StateChange/ExternalAddCategorySlice.res +5 -0
- package/src/StateChange/ExternalAddCategorySlice.res.mjs +5 -0
- package/src/StateChange/WithFixtures.res +5 -0
- package/src/StateChange/WithFixtures.res.mjs +5 -0
- package/src/StateChange/WithManualOpen.res +5 -0
- package/src/StateChange/WithManualOpen.res.mjs +5 -0
- package/tests/CallerTest.res +33 -3
- package/tests/CallerTest.res.mjs +35 -3
- package/tests/DelegateGwtTest.res +10 -5
- package/tests/FlowCrossPluginGwtTest.res +8 -4
- package/tests/FlowCrossPluginGwtTest.res.mjs +4 -2
- package/tests/StateChangeSliceAuthorizeGwtTest.res +174 -0
- package/tests/StateChangeSliceAuthorizeGwtTest.res.mjs +326 -0
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
|
+
# 1.0.0-alpha.233 (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
|
# 1.0.0-alpha.232 (2026-10-01)
|
|
7
22
|
|
|
8
23
|
### Features
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-gwt",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.233",
|
|
4
4
|
"description": "Given-When-Then DSLs and test harness for Reventless slice testing",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"jest": {
|
|
@@ -16,15 +16,15 @@
|
|
|
16
16
|
"sury": "11.0.0",
|
|
17
17
|
"@reventlessdev/rescript-effect": "0.1.0-alpha.34",
|
|
18
18
|
"@reventlessdev/rescript-jest": "1.0.0-alpha.11",
|
|
19
|
-
"@reventlessdev/reventless-core": "3.0.0-alpha.285",
|
|
20
|
-
"@reventlessdev/reventless-infra": "3.0.0-alpha.183",
|
|
21
19
|
"@reventlessdev/rescript-node": "2.0.0-alpha.20",
|
|
22
|
-
"@reventlessdev/reventless-
|
|
20
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.184",
|
|
21
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.286",
|
|
22
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.156"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"rescript": "12.3.1",
|
|
26
26
|
"sury-ppx": "11.0.0",
|
|
27
|
-
"@reventlessdev/reventless-ppx": "1.0.0-alpha.
|
|
27
|
+
"@reventlessdev/reventless-ppx": "1.0.0-alpha.96"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"rescript": "^12.3.1"
|
package/src/Behavior_GWT.res
CHANGED
|
@@ -23,18 +23,40 @@ open ReventlessCore
|
|
|
23
23
|
// differs). Pick the matching entry point.
|
|
24
24
|
|
|
25
25
|
// ---------------------------------------------------------------------------
|
|
26
|
-
// Who issues the command
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
// carries no caller claim, which the handler reads as the
|
|
31
|
-
// itself — so every scenario written before this existed is
|
|
26
|
+
// Who issues the command. `asCaller` names the caller before `whenCmd`; the
|
|
27
|
+
// command is then checked as production checks it: its `@authorize` rule against
|
|
28
|
+
// the caller's roles, then — for slices and aggregates that mark `@owner` on the
|
|
29
|
+
// events they decide on — against the owners the given events record. Without
|
|
30
|
+
// `asCaller` the command carries no caller claim, which the handler reads as the
|
|
31
|
+
// platform acting for itself — so every scenario written before this existed is
|
|
32
|
+
// unchanged.
|
|
32
33
|
// ---------------------------------------------------------------------------
|
|
33
34
|
module Caller = {
|
|
34
|
-
external unsafeAsString: '
|
|
35
|
+
external unsafeAsString: 'a => string = "%identity"
|
|
36
|
+
|
|
37
|
+
type t = {
|
|
38
|
+
claim: Message.CallerClaim.t,
|
|
39
|
+
/** The roles held, by name. A scenario names them by its plugin's own
|
|
40
|
+
`Roles.t`, whose cases are their names at run time. */
|
|
41
|
+
roles: array<Reventless.Role.name>,
|
|
42
|
+
signedIn: bool,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// A plugin's roles are payload-less variants, strings at run time; anything
|
|
46
|
+
// else would be compared with a rule's role names and never match.
|
|
47
|
+
let roleNames = (roles: array<'role>): array<Reventless.Role.name> =>
|
|
48
|
+
roles->Array.map(role =>
|
|
49
|
+
if Reventless.OwnerScope.isJsString(role) {
|
|
50
|
+
Reventless.Role.make(role->unsafeAsString)
|
|
51
|
+
} else {
|
|
52
|
+
JsError.throwWithMessage(
|
|
53
|
+
"Caller: expected roles of the plugin's Roles.t (payload-less, so strings at runtime)",
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
)
|
|
35
57
|
|
|
36
58
|
/**
|
|
37
|
-
A caller who owns only what records their id.
|
|
59
|
+
A caller who owns only what records their id, holding `roles` (none by default).
|
|
38
60
|
|
|
39
61
|
Takes the id as the scenario has it — `Caller.owner(c1)` with `c1: CustomerId.t`,
|
|
40
62
|
or a plain string — because every `Id.Make` identity is a string on the wire.
|
|
@@ -42,18 +64,52 @@ module Caller = {
|
|
|
42
64
|
recorded owner, so it can only be refused, which fails the scenario that
|
|
43
65
|
expected the owner to act. A value that is not a string at all is refused here.
|
|
44
66
|
*/
|
|
45
|
-
let owner = (id: 'id):
|
|
67
|
+
let owner = (id: 'id, ~roles: array<'role>=[]): t =>
|
|
46
68
|
if Reventless.OwnerScope.isJsString(id) {
|
|
47
|
-
Owned({userId: id->unsafeAsString})
|
|
69
|
+
{claim: Owned({userId: id->unsafeAsString}), roles: roleNames(roles), signedIn: true}
|
|
48
70
|
} else {
|
|
49
71
|
JsError.throwWithMessage(
|
|
50
72
|
"Caller.owner: expected an id (a string at runtime, such as an Id.Make identity)",
|
|
51
73
|
)
|
|
52
74
|
}
|
|
53
|
-
|
|
54
|
-
|
|
75
|
+
|
|
76
|
+
/** A signed-in caller holding `roles`, who owns nothing the given events record. */
|
|
77
|
+
let inRoles = (roles: array<'role>): t => {
|
|
78
|
+
claim: Owned({userId: "caller-in-roles"}),
|
|
79
|
+
roles: roleNames(roles),
|
|
80
|
+
signedIn: true,
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** A caller exempt from ownership rules, acting on anyone's behalf. Holds no
|
|
84
|
+
role: elevation does not grant a command whose rule the caller fails. */
|
|
85
|
+
let operator: t = {claim: Exempt, roles: [], signedIn: true}
|
|
55
86
|
/** A caller with no identity. */
|
|
56
|
-
let anonymous:
|
|
87
|
+
let anonymous: t = {claim: Unidentified, roles: [], signedIn: false}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** `Caller` as a scenario sees it: roles typed by the spec's own `role`, so they
|
|
91
|
+
are written bare (`Caller.inRoles([Merchandiser])`) and another plugin's role
|
|
92
|
+
does not compile. */
|
|
93
|
+
module type Callers = {
|
|
94
|
+
type role
|
|
95
|
+
type t = Caller.t
|
|
96
|
+
let owner: ('id, ~roles: array<role>=?) => t
|
|
97
|
+
let inRoles: array<role> => t
|
|
98
|
+
let operator: t
|
|
99
|
+
let anonymous: t
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
module Callers = (
|
|
103
|
+
R: {
|
|
104
|
+
type role
|
|
105
|
+
},
|
|
106
|
+
): (Callers with type role = R.role) => {
|
|
107
|
+
type role = R.role
|
|
108
|
+
type t = Caller.t
|
|
109
|
+
let owner = (id, ~roles: array<role>=[]) => Caller.owner(id, ~roles)
|
|
110
|
+
let inRoles = (roles: array<role>) => Caller.inRoles(roles)
|
|
111
|
+
let operator = Caller.operator
|
|
112
|
+
let anonymous = Caller.anonymous
|
|
57
113
|
}
|
|
58
114
|
|
|
59
115
|
module type ActingSpec = {
|
|
@@ -62,28 +118,58 @@ module type ActingSpec = {
|
|
|
62
118
|
type history
|
|
63
119
|
@schema
|
|
64
120
|
type command
|
|
121
|
+
type role
|
|
122
|
+
let commandAuthorization: command => Reventless.Authorization.rule<role>
|
|
65
123
|
}
|
|
66
124
|
|
|
67
125
|
module Acting = (Spec: ActingSpec) => {
|
|
68
|
-
let caller: ref<option<
|
|
126
|
+
let caller: ref<option<Caller.t>> = ref(None)
|
|
69
127
|
let refusal: ref<option<CommandTopic_Helpers.rejectedResult>> = ref(None)
|
|
70
128
|
|
|
71
129
|
let ownerFieldsByEventType = Reventless.Owner.fieldNamesByVariant(
|
|
72
130
|
Spec.historySchema->S.castToUnknown,
|
|
73
131
|
)
|
|
74
132
|
|
|
75
|
-
let asCaller = (history,
|
|
76
|
-
caller := Some(
|
|
133
|
+
let asCaller = (history, who: Caller.t) => {
|
|
134
|
+
caller := Some(who)
|
|
77
135
|
history
|
|
78
136
|
}
|
|
79
137
|
|
|
138
|
+
let commandName = command =>
|
|
139
|
+
command->Message.encode(Spec.commandSchema)->Message.variantNameOfJson
|
|
140
|
+
|
|
141
|
+
// The rule first, as production checks it, against the roles alone: which group
|
|
142
|
+
// a role maps to is a deployment's fact, not the scenario's.
|
|
143
|
+
let refusedByRule = (who: Caller.t, command: Spec.command): bool =>
|
|
144
|
+
if (
|
|
145
|
+
Spec.commandAuthorization(command)
|
|
146
|
+
->Reventless.Authorization.named
|
|
147
|
+
->Reventless.Authorization.admits(~signedIn=who.signedIn, ~holds=role =>
|
|
148
|
+
who.roles->Array.includes(role)
|
|
149
|
+
)
|
|
150
|
+
) {
|
|
151
|
+
false
|
|
152
|
+
} else {
|
|
153
|
+
refusal :=
|
|
154
|
+
Some({
|
|
155
|
+
errorCode: "Forbidden",
|
|
156
|
+
errorDetail: `${Spec.name}.${commandName(
|
|
157
|
+
command,
|
|
158
|
+
)}: the caller holds no role this command's rule admits`,
|
|
159
|
+
})
|
|
160
|
+
true
|
|
161
|
+
}
|
|
162
|
+
|
|
80
163
|
// Every given event is the command's own history here, so all of them count.
|
|
81
164
|
// The caller is consumed: the next scenario starts with no claim.
|
|
82
165
|
let refuses = (history: array<Spec.history>, command: Spec.command): bool => {
|
|
83
|
-
let
|
|
166
|
+
let who = caller.contents
|
|
84
167
|
caller := None
|
|
85
168
|
refusal := None
|
|
86
|
-
|
|
169
|
+
let claim = who->Option.map(w => w.claim)
|
|
170
|
+
if who->Option.mapOr(false, refusedByRule(_, command)) {
|
|
171
|
+
true
|
|
172
|
+
} else if ownerFieldsByEventType->Dict.keysToArray->Array.length == 0 {
|
|
87
173
|
false
|
|
88
174
|
} else {
|
|
89
175
|
let owners = history->Array.reduce([], (owners, event) => {
|
|
@@ -95,10 +181,7 @@ module Acting = (Spec: ActingSpec) => {
|
|
|
95
181
|
})
|
|
96
182
|
refusal :=
|
|
97
183
|
Reventless.OwnerScope.decideActing(~claim, ~recordedOwners=Some(owners))
|
|
98
|
-
->CommandTopic_Helpers.ownershipRefusal(
|
|
99
|
-
~component=Spec.name,
|
|
100
|
-
~command=command->Message.encode(Spec.commandSchema)->Message.variantNameOfJson,
|
|
101
|
-
)
|
|
184
|
+
->CommandTopic_Helpers.ownershipRefusal(~component=Spec.name, ~command=commandName(command))
|
|
102
185
|
->Option.map(r => r.rejected)
|
|
103
186
|
refusal.contents->Option.isSome
|
|
104
187
|
}
|
|
@@ -155,6 +238,9 @@ module type BehaviorSpec = {
|
|
|
155
238
|
|
|
156
239
|
@schema
|
|
157
240
|
type event
|
|
241
|
+
|
|
242
|
+
type role
|
|
243
|
+
let commandAuthorization: command => Reventless.Authorization.rule<role>
|
|
158
244
|
}
|
|
159
245
|
|
|
160
246
|
module type Behavior = {
|
|
@@ -176,8 +262,8 @@ module type T = {
|
|
|
176
262
|
let givenEvents: array<Spec.consumedEvent> => array<Spec.consumedEvent>
|
|
177
263
|
|
|
178
264
|
// Who issues the next `whenCmd`; see `Caller`.
|
|
179
|
-
module Caller:
|
|
180
|
-
let asCaller: (array<Spec.consumedEvent>,
|
|
265
|
+
module Caller: Callers with type role = Spec.role
|
|
266
|
+
let asCaller: (array<Spec.consumedEvent>, Caller.t) => array<Spec.consumedEvent>
|
|
181
267
|
|
|
182
268
|
let whenCmd: (array<Spec.consumedEvent>, Spec.command) => array<Spec.event>
|
|
183
269
|
|
|
@@ -187,7 +273,8 @@ module type T = {
|
|
|
187
273
|
let thenEventWithError: (array<Spec.event>, Spec.event, Spec.error) => Outcome.outcome
|
|
188
274
|
let thenEventsWithError: (array<Spec.event>, array<Spec.event>, Spec.error) => Outcome.outcome
|
|
189
275
|
let thenError: (array<Spec.event>, Spec.error) => Outcome.outcome
|
|
190
|
-
// Refused before `decide
|
|
276
|
+
// Refused before `decide`: by the command's rule, or because the caller does not
|
|
277
|
+
// own what it acts on.
|
|
191
278
|
let thenRefused: array<Spec.event> => Outcome.outcome
|
|
192
279
|
|
|
193
280
|
// DCB optimistic-concurrency assertions.
|
|
@@ -363,13 +450,17 @@ module Make = (Spec: BehaviorSpec, Behavior: Behavior with module Spec := Spec):
|
|
|
363
450
|
module Core = AssertionCore(Spec)
|
|
364
451
|
let errors = Core.errors
|
|
365
452
|
|
|
366
|
-
module Caller =
|
|
453
|
+
module Caller = Callers({
|
|
454
|
+
type role = Spec.role
|
|
455
|
+
})
|
|
367
456
|
module Guard = Acting({
|
|
368
457
|
let name = Spec.name
|
|
369
458
|
type history = Spec.consumedEvent
|
|
370
459
|
let historySchema = Spec.consumedEventSchema
|
|
371
460
|
type command = Spec.command
|
|
372
461
|
let commandSchema = Spec.commandSchema
|
|
462
|
+
type role = Spec.role
|
|
463
|
+
let commandAuthorization = Spec.commandAuthorization
|
|
373
464
|
})
|
|
374
465
|
let asCaller = Guard.asCaller
|
|
375
466
|
|
|
@@ -637,6 +728,8 @@ module type AggregateSpec = {
|
|
|
637
728
|
type event
|
|
638
729
|
@schema
|
|
639
730
|
type error
|
|
731
|
+
type role
|
|
732
|
+
let commandAuthorization: command => Reventless.Authorization.rule<role>
|
|
640
733
|
}
|
|
641
734
|
|
|
642
735
|
module type AggregateT = {
|
|
@@ -648,8 +741,8 @@ module type AggregateT = {
|
|
|
648
741
|
let givenEvents: array<Spec.event> => array<Spec.event>
|
|
649
742
|
|
|
650
743
|
// Who issues the next `whenCmd`; see `Caller`.
|
|
651
|
-
module Caller:
|
|
652
|
-
let asCaller: (array<Spec.event>,
|
|
744
|
+
module Caller: Callers with type role = Spec.role
|
|
745
|
+
let asCaller: (array<Spec.event>, Caller.t) => array<Spec.event>
|
|
653
746
|
|
|
654
747
|
let whenCmd: (array<Spec.event>, Spec.command) => array<Spec.event>
|
|
655
748
|
|
|
@@ -669,7 +762,8 @@ module type AggregateT = {
|
|
|
669
762
|
) => Outcome.outcome
|
|
670
763
|
let thenEventsWithError: (array<Spec.event>, array<Spec.event>, Spec.error) => Outcome.outcome
|
|
671
764
|
let thenError: (array<Spec.event>, Spec.error) => Outcome.outcome
|
|
672
|
-
// Refused before `decide
|
|
765
|
+
// Refused before `decide`: by the command's rule, or because the caller does not
|
|
766
|
+
// own what it acts on.
|
|
673
767
|
let thenRefused: array<Spec.event> => Outcome.outcome
|
|
674
768
|
}
|
|
675
769
|
|
|
@@ -686,13 +780,17 @@ module MakeFromAggregate = (Spec: AggregateSpec, Behavior: Behavior.T with modul
|
|
|
686
780
|
module Core = AssertionCore(Spec)
|
|
687
781
|
let errors = Core.errors
|
|
688
782
|
|
|
689
|
-
module Caller =
|
|
783
|
+
module Caller = Callers({
|
|
784
|
+
type role = Spec.role
|
|
785
|
+
})
|
|
690
786
|
module Guard = Acting({
|
|
691
787
|
let name = Spec.name
|
|
692
788
|
type history = Spec.event
|
|
693
789
|
let historySchema = Spec.eventSchema
|
|
694
790
|
type command = Spec.command
|
|
695
791
|
let commandSchema = Spec.commandSchema
|
|
792
|
+
type role = Spec.role
|
|
793
|
+
let commandAuthorization = Spec.commandAuthorization
|
|
696
794
|
})
|
|
697
795
|
let asCaller = Guard.asCaller
|
|
698
796
|
|
package/src/Behavior_GWT.res.mjs
CHANGED
|
@@ -13,26 +13,85 @@ import * as Outcome$ReventlessGwt from "./Outcome.res.mjs";
|
|
|
13
13
|
import * as OwnerScope$Reventless from "@reventlessdev/reventless-spec/src/types/OwnerScope.res.mjs";
|
|
14
14
|
import * as JestBind$ReventlessGwt from "./JestBind.res.mjs";
|
|
15
15
|
import * as Message$ReventlessCore from "@reventlessdev/reventless-core/src/Message.res.mjs";
|
|
16
|
+
import * as Authorization$Reventless from "@reventlessdev/reventless-spec/src/types/Authorization.res.mjs";
|
|
16
17
|
import * as DcbScopeInference$Reventless from "@reventlessdev/reventless-spec/src/components/DcbScopeInference.res.mjs";
|
|
17
18
|
import * as CommandTopic_Helpers$ReventlessCore from "@reventlessdev/reventless-core/src/components/CommandTopic/CommandTopic_Helpers.res.mjs";
|
|
18
19
|
|
|
19
|
-
function
|
|
20
|
+
function roleNames(roles) {
|
|
21
|
+
return roles.map(role => {
|
|
22
|
+
if (OwnerScope$Reventless.isJsString(role)) {
|
|
23
|
+
return role;
|
|
24
|
+
} else {
|
|
25
|
+
return Stdlib_JsError.throwWithMessage("Caller: expected roles of the plugin's Roles.t (payload-less, so strings at runtime)");
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function owner(id, rolesOpt) {
|
|
31
|
+
let roles = rolesOpt !== undefined ? rolesOpt : [];
|
|
20
32
|
if (OwnerScope$Reventless.isJsString(id)) {
|
|
21
33
|
return {
|
|
22
|
-
|
|
23
|
-
|
|
34
|
+
claim: {
|
|
35
|
+
TAG: "Owned",
|
|
36
|
+
userId: id
|
|
37
|
+
},
|
|
38
|
+
roles: roleNames(roles),
|
|
39
|
+
signedIn: true
|
|
24
40
|
};
|
|
25
41
|
} else {
|
|
26
42
|
return Stdlib_JsError.throwWithMessage("Caller.owner: expected an id (a string at runtime, such as an Id.Make identity)");
|
|
27
43
|
}
|
|
28
44
|
}
|
|
29
45
|
|
|
46
|
+
function inRoles(roles) {
|
|
47
|
+
return {
|
|
48
|
+
claim: {
|
|
49
|
+
TAG: "Owned",
|
|
50
|
+
userId: "caller-in-roles"
|
|
51
|
+
},
|
|
52
|
+
roles: roleNames(roles),
|
|
53
|
+
signedIn: true
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let operator_roles = [];
|
|
58
|
+
|
|
59
|
+
let operator = {
|
|
60
|
+
claim: "Exempt",
|
|
61
|
+
roles: operator_roles,
|
|
62
|
+
signedIn: true
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
let anonymous_roles = [];
|
|
66
|
+
|
|
67
|
+
let anonymous = {
|
|
68
|
+
claim: "Unidentified",
|
|
69
|
+
roles: anonymous_roles,
|
|
70
|
+
signedIn: false
|
|
71
|
+
};
|
|
72
|
+
|
|
30
73
|
let Caller = {
|
|
74
|
+
roleNames: roleNames,
|
|
31
75
|
owner: owner,
|
|
32
|
-
|
|
33
|
-
|
|
76
|
+
inRoles: inRoles,
|
|
77
|
+
operator: operator,
|
|
78
|
+
anonymous: anonymous
|
|
34
79
|
};
|
|
35
80
|
|
|
81
|
+
function Callers(R) {
|
|
82
|
+
let owner$1 = (id, rolesOpt) => {
|
|
83
|
+
let roles = rolesOpt !== undefined ? rolesOpt : [];
|
|
84
|
+
return owner(id, roles);
|
|
85
|
+
};
|
|
86
|
+
let inRoles$1 = inRoles;
|
|
87
|
+
return {
|
|
88
|
+
owner: owner$1,
|
|
89
|
+
inRoles: inRoles$1,
|
|
90
|
+
operator: operator,
|
|
91
|
+
anonymous: anonymous
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
36
95
|
function Acting(Spec) {
|
|
37
96
|
let caller = {
|
|
38
97
|
contents: undefined
|
|
@@ -41,14 +100,30 @@ function Acting(Spec) {
|
|
|
41
100
|
contents: undefined
|
|
42
101
|
};
|
|
43
102
|
let ownerFieldsByEventType = Owner$Reventless.fieldNamesByVariant(Spec.historySchema);
|
|
44
|
-
let asCaller = (history,
|
|
45
|
-
caller.contents =
|
|
103
|
+
let asCaller = (history, who) => {
|
|
104
|
+
caller.contents = who;
|
|
46
105
|
return history;
|
|
47
106
|
};
|
|
107
|
+
let commandName = command => Message$ReventlessCore.variantNameOfJson(Message$ReventlessCore.encode(command, Spec.commandSchema));
|
|
108
|
+
let refusedByRule = (who, command) => {
|
|
109
|
+
if (Authorization$Reventless.admits(Authorization$Reventless.named(Spec.commandAuthorization(command)), who.signedIn, role => who.roles.includes(role))) {
|
|
110
|
+
return false;
|
|
111
|
+
} else {
|
|
112
|
+
refusal.contents = {
|
|
113
|
+
errorCode: "Forbidden",
|
|
114
|
+
errorDetail: Spec.name + `.` + Message$ReventlessCore.variantNameOfJson(Message$ReventlessCore.encode(command, Spec.commandSchema)) + `: the caller holds no role this command's rule admits`
|
|
115
|
+
};
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
};
|
|
48
119
|
let refuses = (history, command) => {
|
|
49
|
-
let
|
|
120
|
+
let who = caller.contents;
|
|
50
121
|
caller.contents = undefined;
|
|
51
122
|
refusal.contents = undefined;
|
|
123
|
+
let claim = Stdlib_Option.map(who, w => w.claim);
|
|
124
|
+
if (Stdlib_Option.mapOr(who, false, __x => refusedByRule(__x, command))) {
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
52
127
|
if (Object.keys(ownerFieldsByEventType).length === 0) {
|
|
53
128
|
return false;
|
|
54
129
|
}
|
|
@@ -101,6 +176,8 @@ function Acting(Spec) {
|
|
|
101
176
|
refusal: refusal,
|
|
102
177
|
ownerFieldsByEventType: ownerFieldsByEventType,
|
|
103
178
|
asCaller: asCaller,
|
|
179
|
+
commandName: commandName,
|
|
180
|
+
refusedByRule: refusedByRule,
|
|
104
181
|
refuses: refuses,
|
|
105
182
|
encRefusal: encRefusal,
|
|
106
183
|
unexpectedRefusal: unexpectedRefusal,
|
|
@@ -285,9 +362,21 @@ function Make(Spec) {
|
|
|
285
362
|
});
|
|
286
363
|
}
|
|
287
364
|
};
|
|
365
|
+
let owner$1 = (id, rolesOpt) => {
|
|
366
|
+
let roles = rolesOpt !== undefined ? rolesOpt : [];
|
|
367
|
+
return owner(id, roles);
|
|
368
|
+
};
|
|
369
|
+
let inRoles$1 = inRoles;
|
|
370
|
+
let Caller = {
|
|
371
|
+
owner: owner$1,
|
|
372
|
+
inRoles: inRoles$1,
|
|
373
|
+
operator: operator,
|
|
374
|
+
anonymous: anonymous
|
|
375
|
+
};
|
|
288
376
|
let name = Spec.name;
|
|
289
377
|
let historySchema = Spec.consumedEventSchema;
|
|
290
378
|
let commandSchema = Spec.commandSchema;
|
|
379
|
+
let commandAuthorization = Spec.commandAuthorization;
|
|
291
380
|
let caller = {
|
|
292
381
|
contents: undefined
|
|
293
382
|
};
|
|
@@ -295,14 +384,28 @@ function Make(Spec) {
|
|
|
295
384
|
contents: undefined
|
|
296
385
|
};
|
|
297
386
|
let ownerFieldsByEventType = Owner$Reventless.fieldNamesByVariant(historySchema);
|
|
298
|
-
let asCaller = (history,
|
|
299
|
-
caller.contents =
|
|
387
|
+
let asCaller = (history, who) => {
|
|
388
|
+
caller.contents = who;
|
|
300
389
|
return history;
|
|
301
390
|
};
|
|
302
391
|
let refuses = (history, command) => {
|
|
303
|
-
let
|
|
392
|
+
let who = caller.contents;
|
|
304
393
|
caller.contents = undefined;
|
|
305
394
|
refusal.contents = undefined;
|
|
395
|
+
let claim = Stdlib_Option.map(who, w => w.claim);
|
|
396
|
+
if (Stdlib_Option.mapOr(who, false, __x => {
|
|
397
|
+
if (Authorization$Reventless.admits(Authorization$Reventless.named(commandAuthorization(command)), __x.signedIn, role => __x.roles.includes(role))) {
|
|
398
|
+
return false;
|
|
399
|
+
} else {
|
|
400
|
+
refusal.contents = {
|
|
401
|
+
errorCode: "Forbidden",
|
|
402
|
+
errorDetail: name + `.` + Message$ReventlessCore.variantNameOfJson(Message$ReventlessCore.encode(command, commandSchema)) + `: the caller holds no role this command's rule admits`
|
|
403
|
+
};
|
|
404
|
+
return true;
|
|
405
|
+
}
|
|
406
|
+
})) {
|
|
407
|
+
return true;
|
|
408
|
+
}
|
|
306
409
|
if (Object.keys(ownerFieldsByEventType).length === 0) {
|
|
307
410
|
return false;
|
|
308
411
|
}
|
|
@@ -630,9 +733,21 @@ function MakeFromAggregate(Spec) {
|
|
|
630
733
|
});
|
|
631
734
|
}
|
|
632
735
|
};
|
|
736
|
+
let owner$1 = (id, rolesOpt) => {
|
|
737
|
+
let roles = rolesOpt !== undefined ? rolesOpt : [];
|
|
738
|
+
return owner(id, roles);
|
|
739
|
+
};
|
|
740
|
+
let inRoles$1 = inRoles;
|
|
741
|
+
let Caller = {
|
|
742
|
+
owner: owner$1,
|
|
743
|
+
inRoles: inRoles$1,
|
|
744
|
+
operator: operator,
|
|
745
|
+
anonymous: anonymous
|
|
746
|
+
};
|
|
633
747
|
let name = Spec.name;
|
|
634
748
|
let historySchema = Spec.eventSchema;
|
|
635
749
|
let commandSchema = Spec.commandSchema;
|
|
750
|
+
let commandAuthorization = Spec.commandAuthorization;
|
|
636
751
|
let caller = {
|
|
637
752
|
contents: undefined
|
|
638
753
|
};
|
|
@@ -640,14 +755,28 @@ function MakeFromAggregate(Spec) {
|
|
|
640
755
|
contents: undefined
|
|
641
756
|
};
|
|
642
757
|
let ownerFieldsByEventType = Owner$Reventless.fieldNamesByVariant(historySchema);
|
|
643
|
-
let asCaller = (history,
|
|
644
|
-
caller.contents =
|
|
758
|
+
let asCaller = (history, who) => {
|
|
759
|
+
caller.contents = who;
|
|
645
760
|
return history;
|
|
646
761
|
};
|
|
647
762
|
let refuses = (history, command) => {
|
|
648
|
-
let
|
|
763
|
+
let who = caller.contents;
|
|
649
764
|
caller.contents = undefined;
|
|
650
765
|
refusal.contents = undefined;
|
|
766
|
+
let claim = Stdlib_Option.map(who, w => w.claim);
|
|
767
|
+
if (Stdlib_Option.mapOr(who, false, __x => {
|
|
768
|
+
if (Authorization$Reventless.admits(Authorization$Reventless.named(commandAuthorization(command)), __x.signedIn, role => __x.roles.includes(role))) {
|
|
769
|
+
return false;
|
|
770
|
+
} else {
|
|
771
|
+
refusal.contents = {
|
|
772
|
+
errorCode: "Forbidden",
|
|
773
|
+
errorDetail: name + `.` + Message$ReventlessCore.variantNameOfJson(Message$ReventlessCore.encode(command, commandSchema)) + `: the caller holds no role this command's rule admits`
|
|
774
|
+
};
|
|
775
|
+
return true;
|
|
776
|
+
}
|
|
777
|
+
})) {
|
|
778
|
+
return true;
|
|
779
|
+
}
|
|
651
780
|
if (Object.keys(ownerFieldsByEventType).length === 0) {
|
|
652
781
|
return false;
|
|
653
782
|
}
|
|
@@ -772,6 +901,7 @@ function MakeFromAggregate(Spec) {
|
|
|
772
901
|
|
|
773
902
|
export {
|
|
774
903
|
Caller,
|
|
904
|
+
Callers,
|
|
775
905
|
Acting,
|
|
776
906
|
encodeTag,
|
|
777
907
|
encodeQueryItem,
|
|
@@ -21,3 +21,8 @@ type error = CategoryAlreadyExists
|
|
|
21
21
|
|
|
22
22
|
@schema
|
|
23
23
|
type event = CategoryAdded({categoryId: @s.matches(Reventless.DcbTag.string) string, name: string})
|
|
24
|
+
|
|
25
|
+
// What `@@reventless.spec` injects into a real spec: the default rule.
|
|
26
|
+
type role = Reventless.Role.name
|
|
27
|
+
let commandAuthorization = (_: command): Reventless.Authorization.rule<role> =>
|
|
28
|
+
Reventless.Authorization.AllowAuthenticated
|
|
@@ -22,6 +22,10 @@ let eventSchema = Sury.$schema(s => ({
|
|
|
22
22
|
name: s.m(Sury.string)
|
|
23
23
|
}));
|
|
24
24
|
|
|
25
|
+
function commandAuthorization(param) {
|
|
26
|
+
return "AllowAuthenticated";
|
|
27
|
+
}
|
|
28
|
+
|
|
25
29
|
let name = "ExternalAddCategory";
|
|
26
30
|
|
|
27
31
|
export {
|
|
@@ -30,5 +34,6 @@ export {
|
|
|
30
34
|
commandSchema,
|
|
31
35
|
errorSchema,
|
|
32
36
|
eventSchema,
|
|
37
|
+
commandAuthorization,
|
|
33
38
|
}
|
|
34
39
|
/* consumedEventSchema Not a pure module */
|
|
@@ -17,3 +17,8 @@ type error = CategoryAlreadyExists
|
|
|
17
17
|
|
|
18
18
|
@schema
|
|
19
19
|
type event = CategoryAdded({categoryId: @s.matches(Reventless.DcbTag.string) string, name: string})
|
|
20
|
+
|
|
21
|
+
// What `@@reventless.spec` injects into a real spec: the default rule.
|
|
22
|
+
type role = Reventless.Role.name
|
|
23
|
+
let commandAuthorization = (_: command): Reventless.Authorization.rule<role> =>
|
|
24
|
+
Reventless.Authorization.AllowAuthenticated
|
|
@@ -19,6 +19,10 @@ let eventSchema = Sury.$schema(s => ({
|
|
|
19
19
|
name: s.m(Sury.string)
|
|
20
20
|
}));
|
|
21
21
|
|
|
22
|
+
function commandAuthorization(param) {
|
|
23
|
+
return "AllowAuthenticated";
|
|
24
|
+
}
|
|
25
|
+
|
|
22
26
|
let name = "WithFixtures";
|
|
23
27
|
|
|
24
28
|
export {
|
|
@@ -27,5 +31,6 @@ export {
|
|
|
27
31
|
commandSchema,
|
|
28
32
|
errorSchema,
|
|
29
33
|
eventSchema,
|
|
34
|
+
commandAuthorization,
|
|
30
35
|
}
|
|
31
36
|
/* consumedEventSchema Not a pure module */
|
|
@@ -18,3 +18,8 @@ type error = CategoryAlreadyExists
|
|
|
18
18
|
|
|
19
19
|
@schema
|
|
20
20
|
type event = CategoryAdded({categoryId: @s.matches(Reventless.DcbTag.string) string, name: string})
|
|
21
|
+
|
|
22
|
+
// What `@@reventless.spec` injects into a real spec: the default rule.
|
|
23
|
+
type role = Reventless.Role.name
|
|
24
|
+
let commandAuthorization = (_: command): Reventless.Authorization.rule<role> =>
|
|
25
|
+
Reventless.Authorization.AllowAuthenticated
|
|
@@ -19,6 +19,10 @@ let eventSchema = Sury.$schema(s => ({
|
|
|
19
19
|
name: s.m(Sury.string)
|
|
20
20
|
}));
|
|
21
21
|
|
|
22
|
+
function commandAuthorization(param) {
|
|
23
|
+
return "AllowAuthenticated";
|
|
24
|
+
}
|
|
25
|
+
|
|
22
26
|
let name = "WithManualOpen";
|
|
23
27
|
|
|
24
28
|
export {
|
|
@@ -27,5 +31,6 @@ export {
|
|
|
27
31
|
commandSchema,
|
|
28
32
|
errorSchema,
|
|
29
33
|
eventSchema,
|
|
34
|
+
commandAuthorization,
|
|
30
35
|
}
|
|
31
36
|
/* consumedEventSchema Not a pure module */
|