@sixb/core 0.1.13 → 0.1.14
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/dist/actions/index.js +9 -9
- package/dist/agents/index.js +16 -16
- package/dist/auth/errors.d.ts +12 -0
- package/dist/auth/errors.d.ts.map +1 -1
- package/dist/auth/index.d.ts +1 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +5 -3
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/runtime.d.ts.map +1 -1
- package/dist/auth/strategy.d.ts +2 -0
- package/dist/auth/strategy.d.ts.map +1 -1
- package/dist/auth/strategy.js +4 -2
- package/dist/auth/strategy.js.map +1 -1
- package/dist/auth/types.d.ts +8 -0
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/bootstrap/index.js +20 -20
- package/dist/{chunk-rqtdtm43.js → chunk-00pfcqh4.js} +1 -1
- package/dist/{chunk-cvrbhvtr.js → chunk-16jenmzw.js} +10 -10
- package/dist/{chunk-0321g649.js → chunk-2jntasmh.js} +13 -2
- package/dist/{chunk-0321g649.js.map → chunk-2jntasmh.js.map} +3 -3
- package/dist/{chunk-n33e8dz8.js → chunk-3a338wrp.js} +1 -1
- package/dist/{chunk-gc226ten.js → chunk-3fpgxpsq.js} +3 -3
- package/dist/{chunk-gmt22nxx.js → chunk-4ypbe95r.js} +1 -1
- package/dist/{chunk-9pfw47qx.js → chunk-6rzb1g6h.js} +52 -47
- package/dist/{chunk-9pfw47qx.js.map → chunk-6rzb1g6h.js.map} +7 -7
- package/dist/{chunk-898y24dd.js → chunk-728fp090.js} +1 -1
- package/dist/{chunk-693f3zn8.js → chunk-dv4ct8mp.js} +2 -2
- package/dist/{chunk-jhyc777j.js → chunk-e2c5yxfe.js} +1 -1
- package/dist/{chunk-darm7eeg.js → chunk-gct3m1da.js} +1 -1
- package/dist/{chunk-mf6hwpzh.js → chunk-hj0n3c2p.js} +12 -12
- package/dist/{chunk-91wthfwe.js → chunk-hxgj4kzd.js} +4 -4
- package/dist/{chunk-w9bp61e7.js → chunk-jfdggg1p.js} +1 -1
- package/dist/{chunk-04h4t2b5.js → chunk-kxea54yg.js} +1 -1
- package/dist/{chunk-jmwy25mf.js → chunk-ma1mhh7w.js} +5 -5
- package/dist/{chunk-63fp9x3q.js → chunk-pvgpckm1.js} +2 -2
- package/dist/{chunk-a69fkrtr.js → chunk-q2xghyft.js} +1 -1
- package/dist/{chunk-s24qbp32.js → chunk-wn7mznxc.js} +2 -2
- package/dist/{chunk-gbcrz0gr.js → chunk-wnxabtn4.js} +2 -2
- package/dist/{chunk-bep5rrdq.js → chunk-ww185kt6.js} +1 -1
- package/dist/{chunk-2hbhn1ff.js → chunk-yvf2jqj8.js} +1 -1
- package/dist/execution/agent.js +16 -16
- package/dist/execution/primitive.js +16 -16
- package/dist/execution/request.js +16 -16
- package/dist/index.js +22 -22
- package/dist/materializer/index.js +6 -6
- package/dist/models/execution/index.js +6 -6
- package/dist/objects/index.js +9 -9
- package/dist/objects/query/index.js +6 -6
- package/dist/pipelines/index.js +7 -7
- package/dist/projections/internal.js +5 -5
- package/dist/runtime/internal.js +8 -8
- package/dist/shares/internal.js +6 -6
- package/dist/storage/auth/errors.d.ts +1 -1
- package/dist/storage/auth/errors.d.ts.map +1 -1
- package/dist/storage/auth/in-memory/auth-storage.d.ts +2 -1
- package/dist/storage/auth/in-memory/auth-storage.d.ts.map +1 -1
- package/dist/storage/auth/types.d.ts +15 -7
- package/dist/storage/auth/types.d.ts.map +1 -1
- package/dist/storage/file-upload-sessions/provider.js +3 -3
- package/dist/storage/index.js +5 -5
- package/dist/storage/workflow-runs/provider.js +3 -3
- package/dist/syncs/index.js +7 -7
- package/dist/testing/auth-storage-contract.d.ts.map +1 -1
- package/dist/testing/index.js +107 -139
- package/dist/testing/index.js.map +3 -3
- package/dist/webhooks/internal.js +5 -5
- package/dist/workflows/index.js +17 -17
- package/package.json +1 -1
- package/src/auth/errors.ts +23 -0
- package/src/auth/index.ts +1 -1
- package/src/auth/runtime.ts +56 -22
- package/src/auth/strategy.ts +2 -0
- package/src/auth/types.ts +8 -0
- package/src/storage/auth/errors.ts +0 -1
- package/src/storage/auth/in-memory/auth-storage.ts +44 -65
- package/src/storage/auth/types.ts +15 -7
- package/src/testing/auth-storage-contract.ts +104 -130
- /package/dist/{chunk-rqtdtm43.js.map → chunk-00pfcqh4.js.map} +0 -0
- /package/dist/{chunk-cvrbhvtr.js.map → chunk-16jenmzw.js.map} +0 -0
- /package/dist/{chunk-n33e8dz8.js.map → chunk-3a338wrp.js.map} +0 -0
- /package/dist/{chunk-gc226ten.js.map → chunk-3fpgxpsq.js.map} +0 -0
- /package/dist/{chunk-gmt22nxx.js.map → chunk-4ypbe95r.js.map} +0 -0
- /package/dist/{chunk-898y24dd.js.map → chunk-728fp090.js.map} +0 -0
- /package/dist/{chunk-693f3zn8.js.map → chunk-dv4ct8mp.js.map} +0 -0
- /package/dist/{chunk-jhyc777j.js.map → chunk-e2c5yxfe.js.map} +0 -0
- /package/dist/{chunk-darm7eeg.js.map → chunk-gct3m1da.js.map} +0 -0
- /package/dist/{chunk-mf6hwpzh.js.map → chunk-hj0n3c2p.js.map} +0 -0
- /package/dist/{chunk-91wthfwe.js.map → chunk-hxgj4kzd.js.map} +0 -0
- /package/dist/{chunk-w9bp61e7.js.map → chunk-jfdggg1p.js.map} +0 -0
- /package/dist/{chunk-04h4t2b5.js.map → chunk-kxea54yg.js.map} +0 -0
- /package/dist/{chunk-jmwy25mf.js.map → chunk-ma1mhh7w.js.map} +0 -0
- /package/dist/{chunk-63fp9x3q.js.map → chunk-pvgpckm1.js.map} +0 -0
- /package/dist/{chunk-a69fkrtr.js.map → chunk-q2xghyft.js.map} +0 -0
- /package/dist/{chunk-s24qbp32.js.map → chunk-wn7mznxc.js.map} +0 -0
- /package/dist/{chunk-gbcrz0gr.js.map → chunk-wnxabtn4.js.map} +0 -0
- /package/dist/{chunk-bep5rrdq.js.map → chunk-ww185kt6.js.map} +0 -0
- /package/dist/{chunk-2hbhn1ff.js.map → chunk-yvf2jqj8.js.map} +0 -0
|
@@ -10,9 +10,9 @@ import {
|
|
|
10
10
|
} from "../chunk-cbn96bve.js";
|
|
11
11
|
import"../chunk-xgptywp0.js";
|
|
12
12
|
import"../chunk-fc5zfxy1.js";
|
|
13
|
-
import"../chunk-
|
|
13
|
+
import"../chunk-6rzb1g6h.js";
|
|
14
14
|
import"../chunk-drz59ve0.js";
|
|
15
|
-
import"../chunk-
|
|
15
|
+
import"../chunk-2jntasmh.js";
|
|
16
16
|
import"../chunk-pqajwytd.js";
|
|
17
17
|
import"../chunk-ky7zpbtt.js";
|
|
18
18
|
import"../chunk-gfdh9kqa.js";
|
|
@@ -21,7 +21,7 @@ import"../chunk-69266efg.js";
|
|
|
21
21
|
import"../chunk-xnx96gfy.js";
|
|
22
22
|
import"../chunk-jgwb25gc.js";
|
|
23
23
|
import"../chunk-h89wz928.js";
|
|
24
|
-
import"../chunk-
|
|
24
|
+
import"../chunk-3a338wrp.js";
|
|
25
25
|
import {
|
|
26
26
|
ExecutionStorageError2
|
|
27
27
|
} from "../chunk-tw03bsmb.js";
|
|
@@ -29,7 +29,7 @@ import"../chunk-3nx8vj5a.js";
|
|
|
29
29
|
import"../chunk-ht4a7mjg.js";
|
|
30
30
|
import {
|
|
31
31
|
canRetryWebhookRun2
|
|
32
|
-
} from "../chunk-
|
|
32
|
+
} from "../chunk-3fpgxpsq.js";
|
|
33
33
|
import"../chunk-7ytf06d3.js";
|
|
34
34
|
import"../chunk-mr60an99.js";
|
|
35
35
|
import"../chunk-mmagke4x.js";
|
|
@@ -39,7 +39,7 @@ import"../chunk-jmfet9r4.js";
|
|
|
39
39
|
import {
|
|
40
40
|
WebhookRunError2
|
|
41
41
|
} from "../chunk-qf954wje.js";
|
|
42
|
-
import"../chunk-
|
|
42
|
+
import"../chunk-gct3m1da.js";
|
|
43
43
|
import {
|
|
44
44
|
isOAuthConnectorDefinition2,
|
|
45
45
|
isStaticConnectorDefinition2
|
package/dist/workflows/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
defineWorkflow2,
|
|
9
9
|
createWorkflowNodeFailure2,
|
|
10
10
|
unwrapWorkflowNodeFailure2
|
|
11
|
-
} from "../chunk-
|
|
11
|
+
} from "../chunk-wn7mznxc.js";
|
|
12
12
|
import"../chunk-e1c18xjx.js";
|
|
13
13
|
import"../chunk-fb3feg1e.js";
|
|
14
14
|
import"../chunk-dfm066qg.js";
|
|
@@ -45,31 +45,31 @@ import {
|
|
|
45
45
|
snapshotWorkflowActionInput2,
|
|
46
46
|
WorkflowRunDispatcher2,
|
|
47
47
|
requestWorkflowRun2
|
|
48
|
-
} from "../chunk-
|
|
49
|
-
import"../chunk-
|
|
48
|
+
} from "../chunk-16jenmzw.js";
|
|
49
|
+
import"../chunk-q2xghyft.js";
|
|
50
50
|
import"../chunk-cbn96bve.js";
|
|
51
|
-
import"../chunk-
|
|
52
|
-
import"../chunk-
|
|
53
|
-
import"../chunk-
|
|
51
|
+
import"../chunk-ww185kt6.js";
|
|
52
|
+
import"../chunk-4ypbe95r.js";
|
|
53
|
+
import"../chunk-hxgj4kzd.js";
|
|
54
54
|
import"../chunk-85cd7e8c.js";
|
|
55
|
-
import"../chunk-
|
|
55
|
+
import"../chunk-jfdggg1p.js";
|
|
56
56
|
import"../chunk-bxx3ms5t.js";
|
|
57
|
-
import"../chunk-
|
|
57
|
+
import"../chunk-kxea54yg.js";
|
|
58
58
|
import"../chunk-f85hpqnm.js";
|
|
59
59
|
import"../chunk-4tnrzqqh.js";
|
|
60
60
|
import"../chunk-5a4a52tq.js";
|
|
61
61
|
import"../chunk-91kcnbf0.js";
|
|
62
62
|
import"../chunk-rb143ggb.js";
|
|
63
63
|
import"../chunk-cd234ba4.js";
|
|
64
|
-
import"../chunk-
|
|
65
|
-
import"../chunk-
|
|
66
|
-
import"../chunk-
|
|
67
|
-
import"../chunk-
|
|
64
|
+
import"../chunk-dv4ct8mp.js";
|
|
65
|
+
import"../chunk-pvgpckm1.js";
|
|
66
|
+
import"../chunk-728fp090.js";
|
|
67
|
+
import"../chunk-00pfcqh4.js";
|
|
68
68
|
import"../chunk-m8zkvep1.js";
|
|
69
|
-
import"../chunk-
|
|
69
|
+
import"../chunk-3fpgxpsq.js";
|
|
70
70
|
import"../chunk-jmfet9r4.js";
|
|
71
71
|
import"../chunk-qf954wje.js";
|
|
72
|
-
import"../chunk-
|
|
72
|
+
import"../chunk-gct3m1da.js";
|
|
73
73
|
import"../chunk-zdkcdshk.js";
|
|
74
74
|
import"../chunk-ht4a7mjg.js";
|
|
75
75
|
import"../chunk-mr60an99.js";
|
|
@@ -83,7 +83,7 @@ import"../chunk-xnx96gfy.js";
|
|
|
83
83
|
import"../chunk-s3csryxe.js";
|
|
84
84
|
import"../chunk-jgwb25gc.js";
|
|
85
85
|
import"../chunk-tw03bsmb.js";
|
|
86
|
-
import"../chunk-
|
|
86
|
+
import"../chunk-3a338wrp.js";
|
|
87
87
|
import"../chunk-h89wz928.js";
|
|
88
88
|
import"../chunk-xgptywp0.js";
|
|
89
89
|
import"../chunk-ky7zpbtt.js";
|
|
@@ -97,7 +97,7 @@ import"../chunk-gfdh9kqa.js";
|
|
|
97
97
|
import"../chunk-wr8qp979.js";
|
|
98
98
|
import"../chunk-dmn70ztd.js";
|
|
99
99
|
import"../chunk-vn3yh259.js";
|
|
100
|
-
import"../chunk-
|
|
100
|
+
import"../chunk-6rzb1g6h.js";
|
|
101
101
|
import"../chunk-2fv8sajb.js";
|
|
102
102
|
import"../chunk-drz59ve0.js";
|
|
103
103
|
import"../chunk-rzcjp5m2.js";
|
|
@@ -105,7 +105,7 @@ import"../chunk-v4jbb3rs.js";
|
|
|
105
105
|
import"../chunk-d8yes9yx.js";
|
|
106
106
|
import"../chunk-rzkr70gr.js";
|
|
107
107
|
import"../chunk-z3y5kg1x.js";
|
|
108
|
-
import"../chunk-
|
|
108
|
+
import"../chunk-2jntasmh.js";
|
|
109
109
|
export {
|
|
110
110
|
WorkflowDefinitionError2 as WorkflowDefinitionError,
|
|
111
111
|
WorkflowRunDispatcher2 as WorkflowRunDispatcher,
|
package/package.json
CHANGED
package/src/auth/errors.ts
CHANGED
|
@@ -17,3 +17,26 @@ export class AuthRuntimeError extends Error {
|
|
|
17
17
|
super(message)
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
+
|
|
21
|
+
/** Why a strategy refused someone who authenticated. Each is something a person can act on. */
|
|
22
|
+
export type SignInRefusalReason =
|
|
23
|
+
| "not_invited"
|
|
24
|
+
| "suspended"
|
|
25
|
+
| "domain_not_allowed"
|
|
26
|
+
| "no_trusted_address"
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Thrown by a strategy that refuses a sign-in for a known reason. The sign-in page tells the
|
|
30
|
+
* person which reason, naming `email` when there is one; `message` is for the server log.
|
|
31
|
+
*/
|
|
32
|
+
export class SignInRefusedError extends Error {
|
|
33
|
+
readonly name = "SignInRefusedError"
|
|
34
|
+
|
|
35
|
+
constructor(
|
|
36
|
+
readonly reason: SignInRefusalReason,
|
|
37
|
+
message: string,
|
|
38
|
+
readonly email?: string
|
|
39
|
+
) {
|
|
40
|
+
super(message)
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/auth/index.ts
CHANGED
|
@@ -37,7 +37,7 @@ export {
|
|
|
37
37
|
verifyDoubleSubmitCsrf,
|
|
38
38
|
} from "./csrf"
|
|
39
39
|
export type { AuthRuntimeErrorCode } from "./errors"
|
|
40
|
-
export { AuthRuntimeError } from "./errors"
|
|
40
|
+
export { AuthRuntimeError, SignInRefusedError } from "./errors"
|
|
41
41
|
export type { AuthRuntimeOptions } from "./runtime"
|
|
42
42
|
export {
|
|
43
43
|
AuthRuntime,
|
package/src/auth/runtime.ts
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
type AuthStorage,
|
|
20
20
|
AuthStorageError,
|
|
21
21
|
type CompleteDeviceAuthorizationInput,
|
|
22
|
+
type GroupMembershipRecord,
|
|
22
23
|
type InvitationRecord,
|
|
23
24
|
type ServiceAccountGroupMembershipRecord,
|
|
24
25
|
type ServiceAccountRecord,
|
|
@@ -991,9 +992,9 @@ export class AuthRuntime {
|
|
|
991
992
|
|
|
992
993
|
const members: MemberSummary[] = []
|
|
993
994
|
for (const user of result.users) {
|
|
994
|
-
const groupIds = (
|
|
995
|
+
const { groupIds, managedGroupIds } = memberGroups(
|
|
995
996
|
await storage.groupMemberships.listForUser({ projectId: this.projectId, userId: user.id })
|
|
996
|
-
)
|
|
997
|
+
)
|
|
997
998
|
|
|
998
999
|
// Visibility is scope-based: a member is listed when the caller can assign
|
|
999
1000
|
// groups or suspend over the member's current groups. This keeps out-of-scope
|
|
@@ -1008,6 +1009,7 @@ export class AuthRuntime {
|
|
|
1008
1009
|
members.push({
|
|
1009
1010
|
user,
|
|
1010
1011
|
groupIds,
|
|
1012
|
+
managedGroupIds,
|
|
1011
1013
|
capabilities: {
|
|
1012
1014
|
assignGroups: canAssignGroups,
|
|
1013
1015
|
// A caller cannot suspend themselves; reactivation only applies to a
|
|
@@ -1033,12 +1035,11 @@ export class AuthRuntime {
|
|
|
1033
1035
|
// The target must exist and every group it currently holds must be assignable
|
|
1034
1036
|
// by the caller. Missing and out-of-scope targets raise the same error so a
|
|
1035
1037
|
// caller cannot probe which users exist.
|
|
1036
|
-
const {
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
)
|
|
1038
|
+
const {
|
|
1039
|
+
user,
|
|
1040
|
+
groupIds: currentGroupIds,
|
|
1041
|
+
managedGroupIds: currentManagedGroupIds,
|
|
1042
|
+
} = await this.requireManageableMember(storage, scope, "assignGroups", userId)
|
|
1042
1043
|
|
|
1043
1044
|
const requestedGroupIds = this.resolveMemberGroupIds(input.groupIds)
|
|
1044
1045
|
// Every requested group must fall within the caller's assign scope.
|
|
@@ -1055,6 +1056,15 @@ export class AuthRuntime {
|
|
|
1055
1056
|
const additions = requestedGroupIds.filter((groupId) => !current.has(groupId))
|
|
1056
1057
|
const removals = currentGroupIds.filter((groupId) => !requested.has(groupId))
|
|
1057
1058
|
|
|
1059
|
+
// The identity provider would restore these at the user's next sign-in.
|
|
1060
|
+
const managedRemovals = removals.filter((groupId) => currentManagedGroupIds.includes(groupId))
|
|
1061
|
+
if (managedRemovals.length > 0) {
|
|
1062
|
+
throw new AuthRuntimeError(
|
|
1063
|
+
"invalid_auth_input",
|
|
1064
|
+
`[Sixb] Group(s) ${managedRemovals.join(", ")} are managed by the identity provider. Change them there; they sync at the user's next sign-in.`
|
|
1065
|
+
)
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1058
1068
|
// Self-protection: a caller may add in-scope groups to themselves but may not
|
|
1059
1069
|
// remove any of their own current groups, so they cannot lock themselves out.
|
|
1060
1070
|
if (user.id === caller.user.id && removals.length > 0) {
|
|
@@ -1086,11 +1096,11 @@ export class AuthRuntime {
|
|
|
1086
1096
|
// request resolves the updated membership.
|
|
1087
1097
|
this.sessionCache?.invalidateUser(user.id)
|
|
1088
1098
|
|
|
1089
|
-
const groupIds = (
|
|
1099
|
+
const { groupIds, managedGroupIds } = memberGroups(
|
|
1090
1100
|
await storage.groupMemberships.listForUser({ projectId: this.projectId, userId: user.id })
|
|
1091
|
-
)
|
|
1101
|
+
)
|
|
1092
1102
|
|
|
1093
|
-
return { user, groupIds }
|
|
1103
|
+
return { user, groupIds, managedGroupIds }
|
|
1094
1104
|
}
|
|
1095
1105
|
|
|
1096
1106
|
async suspendMember(
|
|
@@ -1100,7 +1110,12 @@ export class AuthRuntime {
|
|
|
1100
1110
|
const storage = this.requireAuthStorage()
|
|
1101
1111
|
const scope = this.resolveMembershipPolicyScopeForUser(caller.groupIds)
|
|
1102
1112
|
const userId = assertNonEmpty(input.userId, "User id")
|
|
1103
|
-
const { user, groupIds } = await this.requireManageableMember(
|
|
1113
|
+
const { user, groupIds, managedGroupIds } = await this.requireManageableMember(
|
|
1114
|
+
storage,
|
|
1115
|
+
scope,
|
|
1116
|
+
"suspend",
|
|
1117
|
+
userId
|
|
1118
|
+
)
|
|
1104
1119
|
|
|
1105
1120
|
if (user.id === caller.user.id) {
|
|
1106
1121
|
throw new AuthRuntimeError(
|
|
@@ -1118,7 +1133,7 @@ export class AuthRuntime {
|
|
|
1118
1133
|
// user stops authenticating immediately.
|
|
1119
1134
|
this.sessionCache?.invalidateUser(user.id)
|
|
1120
1135
|
|
|
1121
|
-
return { user: suspended, groupIds }
|
|
1136
|
+
return { user: suspended, groupIds, managedGroupIds }
|
|
1122
1137
|
}
|
|
1123
1138
|
|
|
1124
1139
|
async reactivateMember(
|
|
@@ -1128,7 +1143,12 @@ export class AuthRuntime {
|
|
|
1128
1143
|
const storage = this.requireAuthStorage()
|
|
1129
1144
|
const scope = this.resolveMembershipPolicyScopeForUser(caller.groupIds)
|
|
1130
1145
|
const userId = assertNonEmpty(input.userId, "User id")
|
|
1131
|
-
const { user, groupIds } = await this.requireManageableMember(
|
|
1146
|
+
const { user, groupIds, managedGroupIds } = await this.requireManageableMember(
|
|
1147
|
+
storage,
|
|
1148
|
+
scope,
|
|
1149
|
+
"suspend",
|
|
1150
|
+
userId
|
|
1151
|
+
)
|
|
1132
1152
|
|
|
1133
1153
|
// Reactivation restores access but not sessions; the user signs in again.
|
|
1134
1154
|
const reactivated = await storage.users.updateStatus({
|
|
@@ -1138,7 +1158,7 @@ export class AuthRuntime {
|
|
|
1138
1158
|
updatedAt: new Date(),
|
|
1139
1159
|
})
|
|
1140
1160
|
|
|
1141
|
-
return { user: reactivated, groupIds }
|
|
1161
|
+
return { user: reactivated, groupIds, managedGroupIds }
|
|
1142
1162
|
}
|
|
1143
1163
|
|
|
1144
1164
|
/** The caller's active sessions across every audience, most recently active first. */
|
|
@@ -1210,13 +1230,15 @@ export class AuthRuntime {
|
|
|
1210
1230
|
scope: MembershipPolicyScope,
|
|
1211
1231
|
operation: MembershipOperation,
|
|
1212
1232
|
userId: string
|
|
1213
|
-
): Promise<{
|
|
1233
|
+
): Promise<{
|
|
1234
|
+
readonly user: UserRecord
|
|
1235
|
+
readonly groupIds: readonly string[]
|
|
1236
|
+
readonly managedGroupIds: readonly string[]
|
|
1237
|
+
}> {
|
|
1214
1238
|
const user = await storage.users.getById({ projectId: this.projectId, id: userId })
|
|
1215
|
-
const groupIds =
|
|
1216
|
-
?
|
|
1217
|
-
|
|
1218
|
-
)
|
|
1219
|
-
: []
|
|
1239
|
+
const { groupIds, managedGroupIds } = memberGroups(
|
|
1240
|
+
user ? await storage.groupMemberships.listForUser({ projectId: this.projectId, userId }) : []
|
|
1241
|
+
)
|
|
1220
1242
|
|
|
1221
1243
|
if (!user || !canPerformMembershipOperation(scope, operation, groupIds)) {
|
|
1222
1244
|
throw new AuthStorageError(
|
|
@@ -1225,7 +1247,7 @@ export class AuthRuntime {
|
|
|
1225
1247
|
)
|
|
1226
1248
|
}
|
|
1227
1249
|
|
|
1228
|
-
return { user, groupIds }
|
|
1250
|
+
return { user, groupIds, managedGroupIds }
|
|
1229
1251
|
}
|
|
1230
1252
|
|
|
1231
1253
|
private resolveMemberGroupIds(input: readonly string[]): readonly string[] {
|
|
@@ -1525,6 +1547,18 @@ export class AuthRuntime {
|
|
|
1525
1547
|
}
|
|
1526
1548
|
}
|
|
1527
1549
|
|
|
1550
|
+
function memberGroups(memberships: readonly GroupMembershipRecord[]): {
|
|
1551
|
+
readonly groupIds: readonly string[]
|
|
1552
|
+
readonly managedGroupIds: readonly string[]
|
|
1553
|
+
} {
|
|
1554
|
+
return {
|
|
1555
|
+
groupIds: memberships.map((membership) => membership.groupId),
|
|
1556
|
+
managedGroupIds: memberships
|
|
1557
|
+
.filter((membership) => membership.source === "directory")
|
|
1558
|
+
.map((membership) => membership.groupId),
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1528
1562
|
/** The interactive session a caller authenticated with, when it used one. */
|
|
1529
1563
|
function callerSessionId(caller: AuthenticatedRequestAuthSession): string | undefined {
|
|
1530
1564
|
return caller.credentialSource === "session" ? caller.session.id : undefined
|
package/src/auth/strategy.ts
CHANGED
package/src/auth/types.ts
CHANGED
|
@@ -322,6 +322,11 @@ export interface MemberCapabilities {
|
|
|
322
322
|
export interface MemberSummary {
|
|
323
323
|
readonly user: UserRecord
|
|
324
324
|
readonly groupIds: readonly string[]
|
|
325
|
+
/**
|
|
326
|
+
* The groups among `groupIds` that the identity provider manages. They sync at the user's next
|
|
327
|
+
* sign-in, so they cannot be removed here.
|
|
328
|
+
*/
|
|
329
|
+
readonly managedGroupIds: readonly string[]
|
|
325
330
|
readonly capabilities: MemberCapabilities
|
|
326
331
|
}
|
|
327
332
|
|
|
@@ -340,6 +345,7 @@ export interface UpdateMemberGroupsInput {
|
|
|
340
345
|
export interface UpdateMemberGroupsResult {
|
|
341
346
|
readonly user: UserRecord
|
|
342
347
|
readonly groupIds: readonly string[]
|
|
348
|
+
readonly managedGroupIds: readonly string[]
|
|
343
349
|
}
|
|
344
350
|
|
|
345
351
|
export interface SuspendMemberInput {
|
|
@@ -349,6 +355,7 @@ export interface SuspendMemberInput {
|
|
|
349
355
|
export interface SuspendMemberResult {
|
|
350
356
|
readonly user: UserRecord
|
|
351
357
|
readonly groupIds: readonly string[]
|
|
358
|
+
readonly managedGroupIds: readonly string[]
|
|
352
359
|
}
|
|
353
360
|
|
|
354
361
|
export interface ReactivateMemberInput {
|
|
@@ -358,6 +365,7 @@ export interface ReactivateMemberInput {
|
|
|
358
365
|
export interface ReactivateMemberResult {
|
|
359
366
|
readonly user: UserRecord
|
|
360
367
|
readonly groupIds: readonly string[]
|
|
368
|
+
readonly managedGroupIds: readonly string[]
|
|
361
369
|
}
|
|
362
370
|
|
|
363
371
|
/** `"session"` reads only the cookie; `"any"` reads a bearer access token, else the cookie. */
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
CompleteOidcSignInInput,
|
|
8
8
|
CompleteSignInResult,
|
|
9
9
|
GroupMembershipRecord,
|
|
10
|
+
GroupMembershipSource,
|
|
10
11
|
InvitationRecord,
|
|
11
12
|
SessionRecord,
|
|
12
13
|
SuspendUserAndRevokeSessionsInput,
|
|
@@ -42,6 +43,7 @@ import {
|
|
|
42
43
|
normalizeEmail,
|
|
43
44
|
normalizeGroupIds,
|
|
44
45
|
oidcAttemptKey,
|
|
46
|
+
removeGroupMembershipRecord,
|
|
45
47
|
revokeActiveSessionsForUser,
|
|
46
48
|
upsertGroupMembershipRecord,
|
|
47
49
|
userKey,
|
|
@@ -219,25 +221,6 @@ export class InMemoryAuthStorage implements AuthStorage {
|
|
|
219
221
|
)
|
|
220
222
|
}
|
|
221
223
|
|
|
222
|
-
if (
|
|
223
|
-
shouldCreateUser &&
|
|
224
|
-
!activeInvitation &&
|
|
225
|
-
input.allowUserCreationWithoutInvitation &&
|
|
226
|
-
input.requireNoActiveUsersForUserCreation &&
|
|
227
|
-
hasActiveUsers(this.state, projectId)
|
|
228
|
-
) {
|
|
229
|
-
consumeMagicLinkRecord(this.state, {
|
|
230
|
-
projectId,
|
|
231
|
-
id: input.magicLinkId,
|
|
232
|
-
tokenHash: input.tokenHash,
|
|
233
|
-
consumedAt: completedAt,
|
|
234
|
-
})
|
|
235
|
-
throw new AuthStorageError(
|
|
236
|
-
"user_creation_not_allowed",
|
|
237
|
-
`[Sixb] Magic link '${input.magicLinkId}' cannot create a user for project '${projectId}'.`
|
|
238
|
-
)
|
|
239
|
-
}
|
|
240
|
-
|
|
241
224
|
validateCompleteSessionInput(this.state, projectId, input.session)
|
|
242
225
|
assertSignInSessionAudience(projectId, input.session.audience, magicLink.audience)
|
|
243
226
|
|
|
@@ -280,8 +263,9 @@ export class InMemoryAuthStorage implements AuthStorage {
|
|
|
280
263
|
projectId,
|
|
281
264
|
user,
|
|
282
265
|
})
|
|
283
|
-
const memberships = this.
|
|
266
|
+
const memberships = this.applyGroups({
|
|
284
267
|
completedAt,
|
|
268
|
+
source: "manual",
|
|
285
269
|
groupIds: manualGroupIds,
|
|
286
270
|
projectId,
|
|
287
271
|
userId: user.id,
|
|
@@ -352,14 +336,6 @@ export class InMemoryAuthStorage implements AuthStorage {
|
|
|
352
336
|
)
|
|
353
337
|
}
|
|
354
338
|
|
|
355
|
-
if (!identity && user && (!input.autoLinkByVerifiedEmail || !input.emailVerified)) {
|
|
356
|
-
this.consumeOidcAttempt(input, completedAt, projectId)
|
|
357
|
-
throw new AuthStorageError(
|
|
358
|
-
"email_link_not_allowed",
|
|
359
|
-
`[Sixb] OIDC identity cannot auto-link to user '${user.id}' for project '${projectId}'.`
|
|
360
|
-
)
|
|
361
|
-
}
|
|
362
|
-
|
|
363
339
|
if (user?.status === "suspended") {
|
|
364
340
|
this.consumeOidcAttempt(input, completedAt, projectId)
|
|
365
341
|
throw new AuthStorageError(
|
|
@@ -368,14 +344,6 @@ export class InMemoryAuthStorage implements AuthStorage {
|
|
|
368
344
|
)
|
|
369
345
|
}
|
|
370
346
|
|
|
371
|
-
if (shouldCreateUser && !input.emailVerified) {
|
|
372
|
-
this.consumeOidcAttempt(input, completedAt, projectId)
|
|
373
|
-
throw new AuthStorageError(
|
|
374
|
-
"user_creation_not_allowed",
|
|
375
|
-
`[Sixb] OIDC authorization attempt '${input.oidcAuthorizationAttemptId}' cannot create a user for project '${projectId}'.`
|
|
376
|
-
)
|
|
377
|
-
}
|
|
378
|
-
|
|
379
347
|
if (shouldCreateUser && !activeInvitation && !input.allowUserCreationWithoutInvitation) {
|
|
380
348
|
this.consumeOidcAttempt(input, completedAt, projectId)
|
|
381
349
|
throw new AuthStorageError(
|
|
@@ -384,20 +352,6 @@ export class InMemoryAuthStorage implements AuthStorage {
|
|
|
384
352
|
)
|
|
385
353
|
}
|
|
386
354
|
|
|
387
|
-
if (
|
|
388
|
-
shouldCreateUser &&
|
|
389
|
-
!activeInvitation &&
|
|
390
|
-
input.allowUserCreationWithoutInvitation &&
|
|
391
|
-
input.requireNoActiveUsersForUserCreation &&
|
|
392
|
-
hasActiveUsers(this.state, projectId)
|
|
393
|
-
) {
|
|
394
|
-
this.consumeOidcAttempt(input, completedAt, projectId)
|
|
395
|
-
throw new AuthStorageError(
|
|
396
|
-
"user_creation_not_allowed",
|
|
397
|
-
`[Sixb] OIDC authorization attempt '${input.oidcAuthorizationAttemptId}' cannot create a user for project '${projectId}'.`
|
|
398
|
-
)
|
|
399
|
-
}
|
|
400
|
-
|
|
401
355
|
validateCompleteSessionInput(this.state, projectId, input.session)
|
|
402
356
|
assertSignInSessionAudience(projectId, input.session.audience, attempt.audience)
|
|
403
357
|
|
|
@@ -434,12 +388,19 @@ export class InMemoryAuthStorage implements AuthStorage {
|
|
|
434
388
|
projectId,
|
|
435
389
|
user,
|
|
436
390
|
})
|
|
437
|
-
const memberships = this.
|
|
391
|
+
const memberships = this.syncDirectoryGroups({
|
|
438
392
|
completedAt,
|
|
439
|
-
groupIds:
|
|
393
|
+
groupIds: input.directoryGroupIds,
|
|
440
394
|
projectId,
|
|
441
395
|
userId: user.id,
|
|
442
|
-
existing:
|
|
396
|
+
existing: this.applyGroups({
|
|
397
|
+
completedAt,
|
|
398
|
+
source: "manual",
|
|
399
|
+
groupIds: manualGroupIds,
|
|
400
|
+
projectId,
|
|
401
|
+
userId: user.id,
|
|
402
|
+
existing: invitation.memberships,
|
|
403
|
+
}),
|
|
443
404
|
})
|
|
444
405
|
|
|
445
406
|
const nextIdentity: UserIdentityRecord = {
|
|
@@ -533,10 +494,11 @@ export class InMemoryAuthStorage implements AuthStorage {
|
|
|
533
494
|
return { invitation, memberships }
|
|
534
495
|
}
|
|
535
496
|
|
|
536
|
-
private
|
|
497
|
+
private applyGroups(input: {
|
|
537
498
|
readonly completedAt: Date
|
|
538
499
|
readonly groupIds: readonly string[]
|
|
539
500
|
readonly projectId: string
|
|
501
|
+
readonly source: GroupMembershipSource
|
|
540
502
|
readonly userId: string
|
|
541
503
|
readonly existing: readonly GroupMembershipRecord[]
|
|
542
504
|
}): readonly GroupMembershipRecord[] {
|
|
@@ -547,13 +509,40 @@ export class InMemoryAuthStorage implements AuthStorage {
|
|
|
547
509
|
projectId: input.projectId,
|
|
548
510
|
userId: input.userId,
|
|
549
511
|
groupId,
|
|
550
|
-
source:
|
|
512
|
+
source: input.source,
|
|
551
513
|
createdAt: input.completedAt,
|
|
552
514
|
})
|
|
553
515
|
)
|
|
554
516
|
}
|
|
555
517
|
return memberships
|
|
556
518
|
}
|
|
519
|
+
// Directory memberships become exactly `groupIds`. A group the user already holds from another
|
|
520
|
+
// source keeps that source, and memberships from other sources are never removed.
|
|
521
|
+
private syncDirectoryGroups(input: {
|
|
522
|
+
readonly completedAt: Date
|
|
523
|
+
readonly groupIds?: readonly string[]
|
|
524
|
+
readonly projectId: string
|
|
525
|
+
readonly userId: string
|
|
526
|
+
readonly existing: readonly GroupMembershipRecord[]
|
|
527
|
+
}): readonly GroupMembershipRecord[] {
|
|
528
|
+
if (!input.groupIds) {
|
|
529
|
+
return input.existing
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const groupIds = normalizeGroupIds(input.groupIds)
|
|
533
|
+
const stale = [...this.state.groupMemberships.values()].filter(
|
|
534
|
+
(membership) =>
|
|
535
|
+
membership.projectId === input.projectId &&
|
|
536
|
+
membership.userId === input.userId &&
|
|
537
|
+
membership.source === "directory" &&
|
|
538
|
+
!groupIds.includes(membership.groupId)
|
|
539
|
+
)
|
|
540
|
+
for (const membership of stale) {
|
|
541
|
+
removeGroupMembershipRecord(this.state, membership)
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
return this.applyGroups({ ...input, groupIds, source: "directory" })
|
|
545
|
+
}
|
|
557
546
|
|
|
558
547
|
private createSignInSession(input: {
|
|
559
548
|
readonly projectId: string
|
|
@@ -599,16 +588,6 @@ function restoreMap<TKey, TValue>(target: Map<TKey, TValue>, snapshot: Map<TKey,
|
|
|
599
588
|
}
|
|
600
589
|
}
|
|
601
590
|
|
|
602
|
-
function hasActiveUsers(state: AuthStorageState, projectId: string): boolean {
|
|
603
|
-
for (const user of state.users.values()) {
|
|
604
|
-
if (user.projectId === projectId && user.status === "active") {
|
|
605
|
-
return true
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
return false
|
|
610
|
-
}
|
|
611
|
-
|
|
612
591
|
function assertSignInSessionAudience(
|
|
613
592
|
projectId: string,
|
|
614
593
|
sessionAudience: string,
|
|
@@ -4,7 +4,8 @@ import type { AuthorizablePrincipal } from "../../execution/types"
|
|
|
4
4
|
export type UserStatus = "active" | "suspended"
|
|
5
5
|
export type ServiceAccountStatus = "active" | "suspended"
|
|
6
6
|
export type InvitationStatus = "pending" | "accepted" | "revoked"
|
|
7
|
-
|
|
7
|
+
/** `directory`: owned by the identity provider, and synced to its groups on every sign-in. */
|
|
8
|
+
export type GroupMembershipSource = "invitation" | "manual" | "agent" | "directory"
|
|
8
9
|
export type DeviceAuthorizationStatus = "pending" | "approved" | "denied" | "consumed"
|
|
9
10
|
export const MAX_PENDING_DEVICE_AUTHORIZATIONS = 100
|
|
10
11
|
|
|
@@ -45,7 +46,8 @@ export interface ServiceAccountGroupMembershipRecord {
|
|
|
45
46
|
readonly projectId: string
|
|
46
47
|
readonly serviceAccountId: string
|
|
47
48
|
readonly groupId: string
|
|
48
|
-
|
|
49
|
+
/** Service accounts never sign in, so no identity provider owns their memberships. */
|
|
50
|
+
readonly source: Exclude<GroupMembershipSource, "directory">
|
|
49
51
|
readonly createdAt: Date
|
|
50
52
|
}
|
|
51
53
|
|
|
@@ -269,7 +271,7 @@ export interface UpsertAuthServiceAccountGroupMembershipInput {
|
|
|
269
271
|
readonly projectId: string
|
|
270
272
|
readonly serviceAccountId: string
|
|
271
273
|
readonly groupId: string
|
|
272
|
-
readonly source:
|
|
274
|
+
readonly source: ServiceAccountGroupMembershipRecord["source"]
|
|
273
275
|
readonly createdAt?: Date
|
|
274
276
|
}
|
|
275
277
|
|
|
@@ -431,7 +433,6 @@ export interface CompleteMagicLinkSignInInput {
|
|
|
431
433
|
readonly newUserDisplayName?: string
|
|
432
434
|
readonly newUserAvatarUrl?: string
|
|
433
435
|
readonly allowUserCreationWithoutInvitation?: boolean
|
|
434
|
-
readonly requireNoActiveUsersForUserCreation?: boolean
|
|
435
436
|
readonly manualGroupIds?: readonly string[]
|
|
436
437
|
readonly session: CompleteAuthSessionInput
|
|
437
438
|
}
|
|
@@ -442,15 +443,22 @@ export interface CompleteOidcSignInInput {
|
|
|
442
443
|
readonly stateHash: string
|
|
443
444
|
readonly completedAt: Date
|
|
444
445
|
readonly subject: string
|
|
446
|
+
/**
|
|
447
|
+
* An address the strategy has already decided to trust. Without an identity for `subject`, it
|
|
448
|
+
* links the user who has this email, or claims this email's invitation for a new user.
|
|
449
|
+
*/
|
|
445
450
|
readonly email: string
|
|
446
|
-
readonly emailVerified?: boolean
|
|
447
451
|
readonly displayName?: string
|
|
448
452
|
readonly avatarUrl?: string
|
|
449
453
|
readonly claims?: Readonly<Record<string, unknown>>
|
|
450
|
-
readonly autoLinkByVerifiedEmail?: boolean
|
|
451
454
|
readonly allowUserCreationWithoutInvitation?: boolean
|
|
452
|
-
readonly requireNoActiveUsersForUserCreation?: boolean
|
|
453
455
|
readonly manualGroupIds?: readonly string[]
|
|
456
|
+
/**
|
|
457
|
+
* The groups the identity provider grants this user. When present, the user's `directory`
|
|
458
|
+
* memberships become exactly these; memberships from any other source are never touched, and a
|
|
459
|
+
* group the user already has from another source keeps it. Omitted, nothing is synced.
|
|
460
|
+
*/
|
|
461
|
+
readonly directoryGroupIds?: readonly string[]
|
|
454
462
|
readonly newUserId: string
|
|
455
463
|
readonly session: CompleteAuthSessionInput
|
|
456
464
|
}
|