@xnetjs/data 0.0.2 → 0.1.0
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/README.md +112 -0
- package/dist/account-MWNCH4GJ.js +12 -0
- package/dist/account-ledger-R2GAFJL6.js +38 -0
- package/dist/auth/index.d.ts +420 -0
- package/dist/auth/index.js +102 -0
- package/dist/budget-NN7P35XP.js +12 -0
- package/dist/canvas-6WYBNH53.js +10 -0
- package/dist/channel-AT3LQF4A.js +12 -0
- package/dist/chat-message-DSVO72B3.js +10 -0
- package/dist/chunk-2ZNJWIQG.js +84 -0
- package/dist/chunk-3J3HILXO.js +46 -0
- package/dist/chunk-3RUDVTMH.js +5543 -0
- package/dist/chunk-4EYKGTSQ.js +49 -0
- package/dist/chunk-53F4PRNC.js +32 -0
- package/dist/chunk-5EXHNXPL.js +48 -0
- package/dist/{chunk-BQBPA5HS.js → chunk-5HC5NO57.js} +16 -2
- package/dist/chunk-6KLGA6IR.js +47 -0
- package/dist/chunk-6OMG4I7M.js +41 -0
- package/dist/chunk-7PDRDSFH.js +69 -0
- package/dist/chunk-BQLQSWKZ.js +232 -0
- package/dist/chunk-BY5O6LNC.js +380 -0
- package/dist/chunk-DCTRX6II.js +40 -0
- package/dist/{chunk-VYR5GPJP.js → chunk-DWEXKD6E.js} +9 -3
- package/dist/chunk-EBNMV2VO.js +896 -0
- package/dist/chunk-FVEACSKE.js +102 -0
- package/dist/chunk-GU6THOAB.js +45 -0
- package/dist/chunk-H4GA4BBK.js +75 -0
- package/dist/chunk-IJ44LR7N.js +76 -0
- package/dist/chunk-J4ANIXIV.js +402 -0
- package/dist/chunk-JDFNMFOZ.js +45 -0
- package/dist/chunk-K7DOZWWT.js +1811 -0
- package/dist/chunk-KQAT4XBL.js +49 -0
- package/dist/chunk-KQUALW4O.js +183 -0
- package/dist/chunk-LYSWLCOI.js +47 -0
- package/dist/chunk-MBTUO3ZL.js +65 -0
- package/dist/chunk-NI4FHG2K.js +52 -0
- package/dist/chunk-OCMSAKWV.js +78 -0
- package/dist/chunk-OGJCRNGE.js +57 -0
- package/dist/chunk-OPPHF3TF.js +143 -0
- package/dist/{chunk-IDMBCRUC.js → chunk-OSAWNZVM.js} +1 -1
- package/dist/chunk-PMUQACPY.js +33 -0
- package/dist/chunk-PNESGUH5.js +63 -0
- package/dist/chunk-Q3IEGH4B.js +51 -0
- package/dist/chunk-QJW5LDP4.js +52 -0
- package/dist/chunk-QWFTRZQT.js +142 -0
- package/dist/{chunk-SZC345Z2.js → chunk-RL64OJJ5.js} +1025 -1110
- package/dist/chunk-S5RP5RKY.js +416 -0
- package/dist/chunk-S6U6TCMN.js +722 -0
- package/dist/chunk-SVNGSZZA.js +69 -0
- package/dist/chunk-T5AZAOG5.js +54 -0
- package/dist/chunk-TCTZW4A6.js +42 -0
- package/dist/chunk-U64CW73O.js +109 -0
- package/dist/chunk-UDWKWTAX.js +6526 -0
- package/dist/chunk-UQM3G5A2.js +148 -0
- package/dist/chunk-VQ7JHB67.js +57 -0
- package/dist/chunk-W7EBL7ZU.js +73 -0
- package/dist/chunk-WEPK7SZF.js +65 -0
- package/dist/chunk-XHEA5UER.js +75 -0
- package/dist/chunk-XMSKJ5PV.js +66 -0
- package/dist/chunk-XROI44ZP.js +64 -0
- package/dist/chunk-Y3S5SRVM.js +45 -0
- package/dist/chunk-YEFKQYKA.js +43 -0
- package/dist/chunk-YSUIXD2Y.js +105 -0
- package/dist/chunk-ZCOFZY5M.js +70 -0
- package/dist/chunk-ZLU4Y3O2.js +50 -0
- package/dist/chunk-ZPZ7XDA6.js +54 -0
- package/dist/chunk-ZZ6TWKGS.js +40 -0
- package/dist/clone-C0Jhk2UN.d.ts +2205 -0
- package/dist/comment-QSWYAQVS.js +10 -0
- package/dist/crm-L5RYAAF3.js +68 -0
- package/dist/dashboard-JMKTTRA7.js +10 -0
- package/dist/database/index.d.ts +882 -0
- package/dist/database/index.js +523 -0
- package/dist/database-GIXSPZJW.js +10 -0
- package/dist/database-field-3EPAMZUR.js +10 -0
- package/dist/database-row-6Q6TRVE4.js +10 -0
- package/dist/database-select-option-BBBJFLLH.js +10 -0
- package/dist/database-view-5PLSOBI2.js +10 -0
- package/dist/experiment-DOVOZ34Y.js +10 -0
- package/dist/external-item-P2E5X7BZ.js +14 -0
- package/dist/external-reference-7BSFF6SA.js +8 -0
- package/dist/feed-2IXHHP6O.js +12 -0
- package/dist/feed-item-DPE6LN7I.js +12 -0
- package/dist/folder-6KM3KYD5.js +21 -0
- package/dist/game-5AK5XWBR.js +50 -0
- package/dist/grant-WTQSNT5A.js +8 -0
- package/dist/grant-expiration-cleaner-CY6BcmlR.d.ts +249 -0
- package/dist/import-batch-IXBJLQPJ.js +12 -0
- package/dist/inbox-state-E5NSVOGP.js +12 -0
- package/dist/index.d.ts +224 -4548
- package/dist/index.js +2252 -4754
- package/dist/map-RG5Y3CPC.js +10 -0
- package/dist/media-asset-QTCGLIZA.js +8 -0
- package/dist/memory-PNRTMBZO.js +12 -0
- package/dist/metric-65SVOX5C.js +10 -0
- package/dist/milestone-XEO6ZGDG.js +12 -0
- package/dist/moderation-FFHETMRB.js +30 -0
- package/dist/observation-2Y3QKUXM.js +10 -0
- package/dist/page-2V27JGKJ.js +10 -0
- package/dist/posting-GBOSZCMZ.js +12 -0
- package/dist/profile-BLLDWIDK.js +8 -0
- package/dist/project-CQL5LN26.js +10 -0
- package/dist/query-ast-C3m6kZYv.d.ts +226 -0
- package/dist/reaction-T2DTJBBV.js +10 -0
- package/dist/registry-DunPv__d.d.ts +99 -0
- package/dist/saved-view-4R2DBCL3.js +10 -0
- package/dist/schema/index.d.ts +6489 -0
- package/dist/schema/index.js +677 -0
- package/dist/schema-extension-LNRF2YRD.js +18 -0
- package/dist/space-IJU24PBH.js +42 -0
- package/dist/space-membership-DEUMM7NV.js +14 -0
- package/dist/store/index.d.ts +518 -0
- package/dist/store/index.js +135 -0
- package/dist/store-DtIDjk7c.d.ts +355 -0
- package/dist/sync/awareness.d.ts +69 -0
- package/dist/sync/awareness.js +20 -0
- package/dist/system-ILHCZOVO.js +38 -0
- package/dist/tag-SKCHM3U5.js +16 -0
- package/dist/task-7AOBP7WC.js +16 -0
- package/dist/task-view-NUGKELJP.js +10 -0
- package/dist/transaction-ZDSEGDIR.js +12 -0
- package/dist/transcription-PSCLDAGX.js +12 -0
- package/dist/types-S-BJAKyK.d.ts +1576 -0
- package/dist/updates.d.ts +44 -0
- package/dist/updates.js +16 -0
- package/dist/user-widget-RLY2N46A.js +8 -0
- package/dist/view-types-DSde-uT_.d.ts +316 -0
- package/package.json +32 -9
- package/dist/canvas-HSIKIQFK.js +0 -7
- package/dist/chunk-2L5ZUGG5.js +0 -53
- package/dist/chunk-4MTS5KAQ.js +0 -22
- package/dist/chunk-GZHARFKC.js +0 -25
- package/dist/chunk-WKIKJTI2.js +0 -45
- package/dist/comment-277JD7DV.js +0 -7
- package/dist/database-W3KEHLI3.js +0 -7
- package/dist/database-row-3O2QSNZN.js +0 -7
- package/dist/grant-QVZ454YC.js +0 -7
- package/dist/page-O4WTOIEO.js +0 -7
- package/dist/task-SEKAYJH7.js +0 -7
|
@@ -1,255 +1,236 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
return {
|
|
8
|
-
iri,
|
|
9
|
-
baseIRI: iri,
|
|
10
|
-
namespace: "",
|
|
11
|
-
name: iri,
|
|
12
|
-
version: DEFAULT_SCHEMA_VERSION,
|
|
13
|
-
hasExplicitVersion: false
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
const [, namespace, name, version] = match;
|
|
17
|
-
const hasExplicitVersion = version !== void 0;
|
|
18
|
-
const resolvedVersion = version ?? DEFAULT_SCHEMA_VERSION;
|
|
19
|
-
const baseIRI = `${namespace}${name}`;
|
|
20
|
-
return {
|
|
21
|
-
iri: hasExplicitVersion ? iri : `${baseIRI}@${resolvedVersion}`,
|
|
22
|
-
baseIRI,
|
|
23
|
-
namespace,
|
|
24
|
-
name,
|
|
25
|
-
version: resolvedVersion,
|
|
26
|
-
hasExplicitVersion
|
|
27
|
-
};
|
|
1
|
+
// src/auth/index.ts
|
|
2
|
+
import { AUTH_ACTIONS as AUTH_ACTIONS2 } from "@xnetjs/core";
|
|
3
|
+
|
|
4
|
+
// src/auth/builders.ts
|
|
5
|
+
function allow(...roles) {
|
|
6
|
+
return { _tag: "allow", roles };
|
|
28
7
|
}
|
|
29
|
-
function
|
|
30
|
-
|
|
31
|
-
return parsed.iri;
|
|
8
|
+
function deny(...roles) {
|
|
9
|
+
return { _tag: "deny", roles };
|
|
32
10
|
}
|
|
33
|
-
function
|
|
34
|
-
return
|
|
11
|
+
function and(...exprs) {
|
|
12
|
+
return { _tag: "and", exprs };
|
|
35
13
|
}
|
|
36
|
-
function
|
|
37
|
-
|
|
38
|
-
const obj = value;
|
|
39
|
-
return typeof obj.id === "string" && typeof obj.schemaId === "string" && obj.schemaId.startsWith("xnet://") && typeof obj.createdAt === "number" && typeof obj.createdBy === "string" && obj.createdBy.startsWith("did:key:");
|
|
14
|
+
function or(...exprs) {
|
|
15
|
+
return { _tag: "or", exprs };
|
|
40
16
|
}
|
|
41
|
-
function
|
|
42
|
-
return
|
|
17
|
+
function not(expr) {
|
|
18
|
+
return { _tag: "not", expr };
|
|
43
19
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
var
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
20
|
+
var PUBLIC = { _tag: "public" };
|
|
21
|
+
var AUTHENTICATED = { _tag: "authenticated" };
|
|
22
|
+
var role = {
|
|
23
|
+
/**
|
|
24
|
+
* Role held by the node's creator.
|
|
25
|
+
* The DID in `createdBy` holds this role.
|
|
26
|
+
*/
|
|
27
|
+
creator() {
|
|
28
|
+
return { _tag: "creator" };
|
|
29
|
+
},
|
|
30
|
+
/**
|
|
31
|
+
* Role determined by a person property on the node.
|
|
32
|
+
* The DID(s) in that property hold this role.
|
|
33
|
+
*
|
|
34
|
+
* @param propertyName - Name of the person/person[] property
|
|
35
|
+
*/
|
|
36
|
+
property(propertyName) {
|
|
37
|
+
return { _tag: "property", propertyName };
|
|
38
|
+
},
|
|
39
|
+
/**
|
|
40
|
+
* Role inherited from a related node.
|
|
41
|
+
* Users who hold `targetRole` on the related node hold this role.
|
|
42
|
+
*
|
|
43
|
+
* @param relationName - Name of the relation property
|
|
44
|
+
* @param targetRole - Role to check on the related node
|
|
45
|
+
*/
|
|
46
|
+
relation(relationName, targetRole) {
|
|
47
|
+
return { _tag: "relation", relationName, targetRole };
|
|
48
|
+
},
|
|
49
|
+
/**
|
|
50
|
+
* Role determined by membership edges that reference THIS node.
|
|
51
|
+
*
|
|
52
|
+
* Unlike `relation` (which follows a forward relation) this is a reverse-edge
|
|
53
|
+
* lookup: the subject holds the role when a `config.edgeSchema` edge points at
|
|
54
|
+
* this node (or, when `parentProp` is set, any ancestor) with the subject as
|
|
55
|
+
* `memberProp` and a `roleProp` rank `>= minRole`. This is how Space
|
|
56
|
+
* membership cascades down a nested container tree.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* spaceMember: role.members({
|
|
61
|
+
* edgeSchema: SPACE_MEMBERSHIP_SCHEMA_IRI, containerProp: 'space',
|
|
62
|
+
* memberProp: 'member', roleProp: 'role', minRole: 'member',
|
|
63
|
+
* roleOrder: SPACE_ROLES, parentProp: 'parent'
|
|
64
|
+
* })
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
members(config) {
|
|
68
|
+
return {
|
|
69
|
+
_tag: "membership",
|
|
70
|
+
edgeSchema: config.edgeSchema,
|
|
71
|
+
containerProp: config.containerProp,
|
|
72
|
+
memberProp: config.memberProp,
|
|
73
|
+
roleProp: config.roleProp,
|
|
74
|
+
minRole: config.minRole,
|
|
75
|
+
roleOrder: config.roleOrder,
|
|
76
|
+
parentProp: config.parentProp
|
|
77
|
+
};
|
|
51
78
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
79
|
+
};
|
|
80
|
+
var relation = role.relation;
|
|
81
|
+
|
|
82
|
+
// src/auth/validate.ts
|
|
83
|
+
var BUILTIN_ROLES = ["owner"];
|
|
84
|
+
function extractRoleRefs(expr) {
|
|
85
|
+
const roles = [];
|
|
86
|
+
function visit(e) {
|
|
87
|
+
switch (e._tag) {
|
|
88
|
+
case "allow":
|
|
89
|
+
case "deny":
|
|
90
|
+
roles.push(...e.roles);
|
|
91
|
+
break;
|
|
92
|
+
case "and":
|
|
93
|
+
case "or":
|
|
94
|
+
for (const sub of e.exprs) visit(sub);
|
|
95
|
+
break;
|
|
96
|
+
case "not":
|
|
97
|
+
visit(e.expr);
|
|
98
|
+
break;
|
|
99
|
+
case "roleRef":
|
|
100
|
+
roles.push(e.role);
|
|
101
|
+
break;
|
|
102
|
+
case "public":
|
|
103
|
+
case "authenticated":
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
55
106
|
}
|
|
56
|
-
|
|
107
|
+
visit(expr);
|
|
108
|
+
return roles;
|
|
57
109
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
byResource = /* @__PURE__ */ new Map();
|
|
66
|
-
grantsById = /* @__PURE__ */ new Map();
|
|
67
|
-
unsubscribe = null;
|
|
68
|
-
schemaId;
|
|
69
|
-
clock;
|
|
70
|
-
async initialize() {
|
|
71
|
-
const grants = await this.loadGrantNodes();
|
|
72
|
-
for (const grant of grants) {
|
|
73
|
-
this.indexGrant(grant);
|
|
74
|
-
}
|
|
75
|
-
this.unsubscribe = this.store.subscribe((event) => {
|
|
76
|
-
const node = event.node;
|
|
77
|
-
if (!node || !this.isGrantSchema(node.schemaId)) {
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
this.removeGrant(node.id);
|
|
81
|
-
if (!node.deleted) {
|
|
82
|
-
this.indexGrant(node);
|
|
110
|
+
function countExpressionNodes(expr) {
|
|
111
|
+
let count = 1;
|
|
112
|
+
switch (expr._tag) {
|
|
113
|
+
case "and":
|
|
114
|
+
case "or":
|
|
115
|
+
for (const sub of expr.exprs) {
|
|
116
|
+
count += countExpressionNodes(sub);
|
|
83
117
|
}
|
|
84
|
-
|
|
118
|
+
break;
|
|
119
|
+
case "not":
|
|
120
|
+
count += countExpressionNodes(expr.expr);
|
|
121
|
+
break;
|
|
85
122
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
123
|
+
return count;
|
|
124
|
+
}
|
|
125
|
+
function hasPublicAccess(expr) {
|
|
126
|
+
if (expr._tag === "public") return true;
|
|
127
|
+
if (expr._tag === "or") return expr.exprs.some(hasPublicAccess);
|
|
128
|
+
if (expr._tag === "and") return expr.exprs.every(hasPublicAccess);
|
|
129
|
+
if (expr._tag === "not") return false;
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
var MAX_EXPRESSION_NODES = 50;
|
|
133
|
+
function validateAuthorization(auth, properties) {
|
|
134
|
+
const errors = [];
|
|
135
|
+
for (const [actionName, expr] of Object.entries(auth.actions)) {
|
|
136
|
+
const referencedRoles = extractRoleRefs(expr);
|
|
137
|
+
for (const ref of referencedRoles) {
|
|
138
|
+
if (!(ref in auth.roles) && !BUILTIN_ROLES.includes(ref)) {
|
|
139
|
+
errors.push({
|
|
140
|
+
code: "AUTH_SCHEMA_INVALID_ROLE_REF",
|
|
141
|
+
message: `Action '${actionName}' references unknown role '${ref}'`,
|
|
142
|
+
path: `authorization.actions.${actionName}`
|
|
143
|
+
});
|
|
97
144
|
}
|
|
98
145
|
}
|
|
99
|
-
return grants;
|
|
100
146
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
147
|
+
for (const [roleName, resolver] of Object.entries(auth.roles)) {
|
|
148
|
+
if (resolver._tag === "property") {
|
|
149
|
+
const propDef = properties[resolver.propertyName];
|
|
150
|
+
if (!propDef) {
|
|
151
|
+
errors.push({
|
|
152
|
+
code: "AUTH_SCHEMA_INVALID_RELATION_PATH",
|
|
153
|
+
message: `Role '${roleName}' references non-existent property '${resolver.propertyName}'`,
|
|
154
|
+
path: `authorization.roles.${roleName}`
|
|
155
|
+
});
|
|
156
|
+
} else if (propDef.type !== "person") {
|
|
157
|
+
errors.push({
|
|
158
|
+
code: "AUTH_SCHEMA_INVALID_RELATION_PATH",
|
|
159
|
+
message: `Role '${roleName}' references property '${resolver.propertyName}' which is not a person type (got '${propDef.type}')`,
|
|
160
|
+
path: `authorization.roles.${roleName}`
|
|
161
|
+
});
|
|
110
162
|
}
|
|
111
163
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (
|
|
121
|
-
|
|
164
|
+
if (resolver._tag === "relation") {
|
|
165
|
+
const propDef = properties[resolver.relationName];
|
|
166
|
+
if (!propDef) {
|
|
167
|
+
errors.push({
|
|
168
|
+
code: "AUTH_SCHEMA_INVALID_RELATION_PATH",
|
|
169
|
+
message: `Role '${roleName}' references non-existent relation '${resolver.relationName}'`,
|
|
170
|
+
path: `authorization.roles.${roleName}`
|
|
171
|
+
});
|
|
172
|
+
} else if (propDef.type !== "relation") {
|
|
173
|
+
errors.push({
|
|
174
|
+
code: "AUTH_SCHEMA_INVALID_RELATION_PATH",
|
|
175
|
+
message: `Role '${roleName}' references property '${resolver.relationName}' which is not a relation type (got '${propDef.type}')`,
|
|
176
|
+
path: `authorization.roles.${roleName}`
|
|
177
|
+
});
|
|
122
178
|
}
|
|
123
179
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if (!grantIds) continue;
|
|
132
|
-
for (const grantId of grantIds) {
|
|
133
|
-
const grant = this.grantsById.get(grantId);
|
|
134
|
-
if (grant && isGrantActive(grant, now)) {
|
|
135
|
-
grants.push(grant);
|
|
136
|
-
}
|
|
180
|
+
if (resolver._tag === "membership") {
|
|
181
|
+
if (!resolver.roleOrder.includes(resolver.minRole)) {
|
|
182
|
+
errors.push({
|
|
183
|
+
code: "AUTH_SCHEMA_INVALID_ROLE_REF",
|
|
184
|
+
message: `Role '${roleName}' membership minRole '${resolver.minRole}' is not in its roleOrder`,
|
|
185
|
+
path: `authorization.roles.${roleName}`
|
|
186
|
+
});
|
|
137
187
|
}
|
|
138
188
|
}
|
|
139
|
-
return grants;
|
|
140
|
-
}
|
|
141
|
-
dispose() {
|
|
142
|
-
this.unsubscribe?.();
|
|
143
|
-
this.unsubscribe = null;
|
|
144
|
-
this.byResourceAndGrantee.clear();
|
|
145
|
-
this.byResource.clear();
|
|
146
|
-
this.grantsById.clear();
|
|
147
189
|
}
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
190
|
+
if (auth.publicProps) {
|
|
191
|
+
for (const prop of auth.publicProps) {
|
|
192
|
+
if (!(prop in properties)) {
|
|
193
|
+
errors.push({
|
|
194
|
+
code: "AUTH_SCHEMA_INVALID_PUBLIC_PROP",
|
|
195
|
+
message: `publicProps references non-existent property '${prop}'`,
|
|
196
|
+
path: `authorization.publicProps`
|
|
197
|
+
});
|
|
198
|
+
}
|
|
152
199
|
}
|
|
153
|
-
const allNodes = await this.store.list({ includeDeleted: false });
|
|
154
|
-
return allNodes.filter((node) => this.isGrantSchema(node.schemaId));
|
|
155
|
-
}
|
|
156
|
-
isGrantSchema(schemaId) {
|
|
157
|
-
return schemaId === this.schemaId || schemaId.startsWith(`${this.schemaId}@`);
|
|
158
200
|
}
|
|
159
|
-
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
properties: node.properties,
|
|
168
|
-
deleted: node.deleted
|
|
169
|
-
};
|
|
170
|
-
this.grantsById.set(node.id, grant);
|
|
171
|
-
let byGrantee = this.byResourceAndGrantee.get(resource);
|
|
172
|
-
if (!byGrantee) {
|
|
173
|
-
byGrantee = /* @__PURE__ */ new Map();
|
|
174
|
-
this.byResourceAndGrantee.set(resource, byGrantee);
|
|
175
|
-
}
|
|
176
|
-
let grantIdsForGrantee = byGrantee.get(grantee);
|
|
177
|
-
if (!grantIdsForGrantee) {
|
|
178
|
-
grantIdsForGrantee = /* @__PURE__ */ new Set();
|
|
179
|
-
byGrantee.set(grantee, grantIdsForGrantee);
|
|
180
|
-
}
|
|
181
|
-
grantIdsForGrantee.add(node.id);
|
|
182
|
-
let grantIdsForResource = this.byResource.get(resource);
|
|
183
|
-
if (!grantIdsForResource) {
|
|
184
|
-
grantIdsForResource = /* @__PURE__ */ new Set();
|
|
185
|
-
this.byResource.set(resource, grantIdsForResource);
|
|
201
|
+
for (const [actionName, expr] of Object.entries(auth.actions)) {
|
|
202
|
+
const nodeCount = countExpressionNodes(expr);
|
|
203
|
+
if (nodeCount > MAX_EXPRESSION_NODES) {
|
|
204
|
+
errors.push({
|
|
205
|
+
code: "AUTH_SCHEMA_EXPR_LIMIT_EXCEEDED",
|
|
206
|
+
message: `Action '${actionName}' expression has ${nodeCount} nodes (max ${MAX_EXPRESSION_NODES})`,
|
|
207
|
+
path: `authorization.actions.${actionName}`
|
|
208
|
+
});
|
|
186
209
|
}
|
|
187
|
-
grantIdsForResource.add(node.id);
|
|
188
210
|
}
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
if (!resource || !grantee) {
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
const byGrantee = this.byResourceAndGrantee.get(resource);
|
|
201
|
-
if (byGrantee) {
|
|
202
|
-
const grantIds = byGrantee.get(grantee);
|
|
203
|
-
if (grantIds) {
|
|
204
|
-
grantIds.delete(grantId);
|
|
205
|
-
if (grantIds.size === 0) {
|
|
206
|
-
byGrantee.delete(grantee);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
if (byGrantee.size === 0) {
|
|
210
|
-
this.byResourceAndGrantee.delete(resource);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
const byResource = this.byResource.get(resource);
|
|
214
|
-
if (byResource) {
|
|
215
|
-
byResource.delete(grantId);
|
|
216
|
-
if (byResource.size === 0) {
|
|
217
|
-
this.byResource.delete(resource);
|
|
211
|
+
if (auth.fieldRules) {
|
|
212
|
+
for (const fieldName of Object.keys(auth.fieldRules)) {
|
|
213
|
+
if (!(fieldName in properties)) {
|
|
214
|
+
errors.push({
|
|
215
|
+
code: "AUTH_SCHEMA_INVALID_FIELD_REF",
|
|
216
|
+
message: `fieldRules references non-existent property '${fieldName}'`,
|
|
217
|
+
path: `authorization.fieldRules.${fieldName}`
|
|
218
|
+
});
|
|
218
219
|
}
|
|
219
220
|
}
|
|
220
221
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
222
|
+
const mutationActions = ["write", "delete", "share"];
|
|
223
|
+
for (const actionName of mutationActions) {
|
|
224
|
+
const expr = auth.actions[actionName];
|
|
225
|
+
if (expr && hasPublicAccess(expr)) {
|
|
226
|
+
errors.push({
|
|
227
|
+
code: "AUTH_SCHEMA_UNSAFE_PUBLIC_MUTATION",
|
|
228
|
+
message: `Action '${actionName}' allows PUBLIC access, which is unsafe for mutation operations`,
|
|
229
|
+
path: `authorization.actions.${actionName}`
|
|
230
|
+
});
|
|
231
|
+
}
|
|
225
232
|
}
|
|
226
|
-
return
|
|
227
|
-
}
|
|
228
|
-
function asString(value) {
|
|
229
|
-
return typeof value === "string" ? value : null;
|
|
230
|
-
}
|
|
231
|
-
function asNumber(value) {
|
|
232
|
-
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// src/auth/offline-policy.ts
|
|
236
|
-
var DEFAULT_OFFLINE_POLICY = {
|
|
237
|
-
decisionCacheTTL: 5 * 60 * 1e3,
|
|
238
|
-
maxStaleness: 60 * 60 * 1e3,
|
|
239
|
-
revalidation: "hybrid",
|
|
240
|
-
allowOfflineGrants: true
|
|
241
|
-
};
|
|
242
|
-
function mergeOfflinePolicy(current, patch) {
|
|
243
|
-
return {
|
|
244
|
-
...current,
|
|
245
|
-
...patch
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// src/auth/mode.ts
|
|
250
|
-
function getAuthMode(schema) {
|
|
251
|
-
if (!schema.authorization) return "legacy";
|
|
252
|
-
return "enforce";
|
|
233
|
+
return { valid: errors.length === 0, errors };
|
|
253
234
|
}
|
|
254
235
|
|
|
255
236
|
// src/auth/serialize.ts
|
|
@@ -305,6 +286,17 @@ function serializeRoleResolver(resolver) {
|
|
|
305
286
|
relationName: resolver.relationName,
|
|
306
287
|
targetRole: resolver.targetRole
|
|
307
288
|
};
|
|
289
|
+
case "membership":
|
|
290
|
+
return {
|
|
291
|
+
_tag: "membership",
|
|
292
|
+
edgeSchema: resolver.edgeSchema,
|
|
293
|
+
containerProp: resolver.containerProp,
|
|
294
|
+
memberProp: resolver.memberProp,
|
|
295
|
+
roleProp: resolver.roleProp,
|
|
296
|
+
minRole: resolver.minRole,
|
|
297
|
+
roleOrder: [...resolver.roleOrder],
|
|
298
|
+
parentProp: resolver.parentProp
|
|
299
|
+
};
|
|
308
300
|
}
|
|
309
301
|
}
|
|
310
302
|
function deserializeRoleResolver(serialized) {
|
|
@@ -319,6 +311,17 @@ function deserializeRoleResolver(serialized) {
|
|
|
319
311
|
relationName: serialized.relationName,
|
|
320
312
|
targetRole: serialized.targetRole
|
|
321
313
|
};
|
|
314
|
+
case "membership":
|
|
315
|
+
return {
|
|
316
|
+
_tag: "membership",
|
|
317
|
+
edgeSchema: serialized.edgeSchema,
|
|
318
|
+
containerProp: serialized.containerProp,
|
|
319
|
+
memberProp: serialized.memberProp,
|
|
320
|
+
roleProp: serialized.roleProp,
|
|
321
|
+
minRole: serialized.minRole,
|
|
322
|
+
roleOrder: [...serialized.roleOrder],
|
|
323
|
+
parentProp: serialized.parentProp
|
|
324
|
+
};
|
|
322
325
|
}
|
|
323
326
|
}
|
|
324
327
|
function serializeAuthorization(auth) {
|
|
@@ -384,152 +387,632 @@ function deserializeAuthorization(serialized) {
|
|
|
384
387
|
return auth;
|
|
385
388
|
}
|
|
386
389
|
|
|
387
|
-
// src/auth/
|
|
388
|
-
var
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
break;
|
|
404
|
-
case "roleRef":
|
|
405
|
-
roles.push(e.role);
|
|
406
|
-
break;
|
|
407
|
-
case "public":
|
|
408
|
-
case "authenticated":
|
|
409
|
-
break;
|
|
390
|
+
// src/auth/presets.ts
|
|
391
|
+
var presets = {
|
|
392
|
+
/**
|
|
393
|
+
* Private - only the creator can access.
|
|
394
|
+
*
|
|
395
|
+
* Use for personal notes, private documents, etc.
|
|
396
|
+
*/
|
|
397
|
+
private: () => ({
|
|
398
|
+
roles: {
|
|
399
|
+
owner: role.creator()
|
|
400
|
+
},
|
|
401
|
+
actions: {
|
|
402
|
+
read: allow("owner"),
|
|
403
|
+
write: allow("owner"),
|
|
404
|
+
delete: allow("owner"),
|
|
405
|
+
share: allow("owner")
|
|
410
406
|
}
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
407
|
+
}),
|
|
408
|
+
/**
|
|
409
|
+
* Public read - anyone can read, only creator can write.
|
|
410
|
+
*
|
|
411
|
+
* Use for published content, blog posts, etc.
|
|
412
|
+
*/
|
|
413
|
+
publicRead: () => ({
|
|
414
|
+
roles: {
|
|
415
|
+
owner: role.creator()
|
|
416
|
+
},
|
|
417
|
+
actions: {
|
|
418
|
+
read: PUBLIC,
|
|
419
|
+
write: allow("owner"),
|
|
420
|
+
delete: allow("owner"),
|
|
421
|
+
share: allow("owner")
|
|
422
|
+
}
|
|
423
|
+
}),
|
|
424
|
+
/**
|
|
425
|
+
* Collaborative - roles inherited from a parent relation.
|
|
426
|
+
*
|
|
427
|
+
* Use for items within a workspace/project that inherits permissions.
|
|
428
|
+
*
|
|
429
|
+
* @param parentRelation - Name of the relation property to the parent node
|
|
430
|
+
*/
|
|
431
|
+
collaborative: (parentRelation) => ({
|
|
432
|
+
roles: {
|
|
433
|
+
owner: role.creator(),
|
|
434
|
+
admin: role.relation(parentRelation, "admin"),
|
|
435
|
+
editor: role.relation(parentRelation, "editor"),
|
|
436
|
+
viewer: role.relation(parentRelation, "viewer")
|
|
437
|
+
},
|
|
438
|
+
actions: {
|
|
439
|
+
read: allow("viewer", "editor", "admin", "owner"),
|
|
440
|
+
write: allow("editor", "admin", "owner"),
|
|
441
|
+
delete: allow("admin", "owner"),
|
|
442
|
+
share: allow("admin", "owner")
|
|
443
|
+
}
|
|
444
|
+
}),
|
|
445
|
+
/**
|
|
446
|
+
* Open - any authenticated user can read/write, only creator can delete.
|
|
447
|
+
*
|
|
448
|
+
* Use for wiki-style collaborative content.
|
|
449
|
+
*/
|
|
450
|
+
open: () => ({
|
|
451
|
+
roles: {
|
|
452
|
+
owner: role.creator()
|
|
453
|
+
},
|
|
454
|
+
actions: {
|
|
455
|
+
read: AUTHENTICATED,
|
|
456
|
+
write: AUTHENTICATED,
|
|
457
|
+
delete: allow("owner"),
|
|
458
|
+
share: allow("owner")
|
|
459
|
+
}
|
|
460
|
+
}),
|
|
461
|
+
/**
|
|
462
|
+
* Team-based - permissions based on a team/editors property.
|
|
463
|
+
*
|
|
464
|
+
* @param editorsProperty - Name of the person[] property containing editors
|
|
465
|
+
*/
|
|
466
|
+
team: (editorsProperty) => ({
|
|
467
|
+
roles: {
|
|
468
|
+
owner: role.creator(),
|
|
469
|
+
editor: role.property(editorsProperty)
|
|
470
|
+
},
|
|
471
|
+
actions: {
|
|
472
|
+
read: allow("editor", "owner"),
|
|
473
|
+
write: allow("editor", "owner"),
|
|
474
|
+
delete: allow("owner"),
|
|
475
|
+
share: allow("owner")
|
|
476
|
+
}
|
|
477
|
+
})
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
// src/auth/permission-matrix.ts
|
|
481
|
+
import { AUTH_ACTIONS } from "@xnetjs/core";
|
|
482
|
+
function collect(expr, polarity, acc) {
|
|
417
483
|
switch (expr._tag) {
|
|
484
|
+
case "allow":
|
|
485
|
+
for (const role2 of expr.roles) (polarity ? acc.allow : acc.deny).add(role2);
|
|
486
|
+
return;
|
|
487
|
+
case "roleRef": {
|
|
488
|
+
const bucket = polarity ? acc.allow : acc.deny;
|
|
489
|
+
bucket.add(expr.role);
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
case "deny":
|
|
493
|
+
for (const role2 of expr.roles) (polarity ? acc.deny : acc.allow).add(role2);
|
|
494
|
+
return;
|
|
495
|
+
case "public":
|
|
496
|
+
if (polarity) acc.public = true;
|
|
497
|
+
return;
|
|
498
|
+
case "authenticated":
|
|
499
|
+
if (polarity) acc.authenticated = true;
|
|
500
|
+
return;
|
|
418
501
|
case "and":
|
|
419
502
|
case "or":
|
|
420
|
-
for (const sub of expr.exprs)
|
|
421
|
-
|
|
503
|
+
for (const sub of expr.exprs) collect(sub, polarity, acc);
|
|
504
|
+
return;
|
|
505
|
+
case "not":
|
|
506
|
+
collect(expr.expr, !polarity, acc);
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
function summarizeAction(action, expr) {
|
|
511
|
+
const acc = { allow: /* @__PURE__ */ new Set(), deny: /* @__PURE__ */ new Set(), public: false, authenticated: false };
|
|
512
|
+
if (expr) collect(expr, true, acc);
|
|
513
|
+
return {
|
|
514
|
+
action,
|
|
515
|
+
roles: [...acc.allow],
|
|
516
|
+
denied: [...acc.deny],
|
|
517
|
+
public: acc.public,
|
|
518
|
+
authenticated: acc.authenticated
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
function describeRoleResolver(resolver) {
|
|
522
|
+
switch (resolver._tag) {
|
|
523
|
+
case "creator":
|
|
524
|
+
return "Node creator";
|
|
525
|
+
case "property":
|
|
526
|
+
return `Listed in "${resolver.propertyName}"`;
|
|
527
|
+
case "relation":
|
|
528
|
+
return `Inherited from "${resolver.relationName}" (as ${resolver.targetRole})`;
|
|
529
|
+
case "membership":
|
|
530
|
+
return `Member of "${resolver.containerProp}" (\u2265 ${resolver.minRole})`;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
function buildPermissionMatrix(authorization) {
|
|
534
|
+
if (!authorization) {
|
|
535
|
+
return {
|
|
536
|
+
roles: [],
|
|
537
|
+
actions: AUTH_ACTIONS.map((action) => ({
|
|
538
|
+
action,
|
|
539
|
+
roles: [],
|
|
540
|
+
denied: [],
|
|
541
|
+
public: true,
|
|
542
|
+
authenticated: false
|
|
543
|
+
}))
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
const roles = Object.entries(authorization.roles).map(([role2, resolver]) => ({
|
|
547
|
+
role: role2,
|
|
548
|
+
kind: resolver._tag,
|
|
549
|
+
provenance: describeRoleResolver(resolver)
|
|
550
|
+
}));
|
|
551
|
+
const actionKeys = [
|
|
552
|
+
...AUTH_ACTIONS.filter((action) => action in authorization.actions),
|
|
553
|
+
...Object.keys(authorization.actions).filter(
|
|
554
|
+
(action) => !AUTH_ACTIONS.includes(action)
|
|
555
|
+
)
|
|
556
|
+
];
|
|
557
|
+
return {
|
|
558
|
+
roles,
|
|
559
|
+
actions: actionKeys.map((action) => summarizeAction(action, authorization.actions[action]))
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// src/auth/mode.ts
|
|
564
|
+
function getAuthMode(schema) {
|
|
565
|
+
if (!schema.authorization) return "legacy";
|
|
566
|
+
return "enforce";
|
|
567
|
+
}
|
|
568
|
+
function warnLegacySchema(schema) {
|
|
569
|
+
if (!schema.authorization) {
|
|
570
|
+
console.warn(
|
|
571
|
+
`[xnet:auth] Schema '${schema.name}' has no authorization block - data will be owner-only in legacy mode. Add an 'authorization' block to enable explicit access control.`
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// src/auth/hub-policy.ts
|
|
577
|
+
var SCHEMA_ACTION_TO_HUB = {
|
|
578
|
+
read: "read",
|
|
579
|
+
write: "write",
|
|
580
|
+
share: "share",
|
|
581
|
+
delete: "admin"
|
|
582
|
+
};
|
|
583
|
+
function schemaToHubPolicy(schema) {
|
|
584
|
+
if (!schema.authorization) {
|
|
585
|
+
return { roleActions: {}, public: false };
|
|
586
|
+
}
|
|
587
|
+
const auth = deserializeAuthorization(schema.authorization);
|
|
588
|
+
const roleActions = {};
|
|
589
|
+
let isPublic = false;
|
|
590
|
+
for (const [action, expr] of Object.entries(auth.actions)) {
|
|
591
|
+
if (!expr) continue;
|
|
592
|
+
if (action === "read" && hasPublicAccess(expr)) {
|
|
593
|
+
isPublic = true;
|
|
594
|
+
}
|
|
595
|
+
const hubAction = SCHEMA_ACTION_TO_HUB[action];
|
|
596
|
+
if (!hubAction) continue;
|
|
597
|
+
for (const roleName of extractRoleRefs(expr)) {
|
|
598
|
+
const actions = roleActions[roleName] ??= /* @__PURE__ */ new Set();
|
|
599
|
+
actions.add(hubAction);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
return {
|
|
603
|
+
roleActions: Object.fromEntries(
|
|
604
|
+
Object.entries(roleActions).map(([role2, actions]) => [role2, [...actions].sort()])
|
|
605
|
+
),
|
|
606
|
+
public: isPublic
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
function capitalize(value) {
|
|
610
|
+
return value.length === 0 ? value : `${value[0].toUpperCase()}${value.slice(1)}`;
|
|
611
|
+
}
|
|
612
|
+
function hubActionsForSpaceRole(schema, spaceRole) {
|
|
613
|
+
const cascadeRole = `space${capitalize(spaceRole)}`;
|
|
614
|
+
return schemaToHubPolicy(schema).roleActions[cascadeRole] ?? [];
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// src/auth/recipients.ts
|
|
618
|
+
var PUBLIC_CONTENT_KEY = new Uint8Array(32);
|
|
619
|
+
var PUBLIC_RECIPIENT = "PUBLIC";
|
|
620
|
+
var MAX_CONTAINER_DEPTH = 32;
|
|
621
|
+
function finalizeRecipients(recipients, dependencies) {
|
|
622
|
+
const expand = dependencies.expandDeviceRecipients;
|
|
623
|
+
if (!expand) return [...recipients];
|
|
624
|
+
const out = new Set(recipients);
|
|
625
|
+
for (const recipient of recipients) {
|
|
626
|
+
if (recipient === PUBLIC_RECIPIENT) continue;
|
|
627
|
+
for (const did of expand(recipient)) out.add(did);
|
|
628
|
+
}
|
|
629
|
+
return [...out];
|
|
630
|
+
}
|
|
631
|
+
async function computeRecipients(schema, node, dependencies) {
|
|
632
|
+
const recipients = /* @__PURE__ */ new Set();
|
|
633
|
+
recipients.add(node.createdBy);
|
|
634
|
+
const addGrantRecipients = () => {
|
|
635
|
+
const grants = dependencies.grantIndex?.findGrantsForResource(node.id) ?? [];
|
|
636
|
+
for (const grant of grants) {
|
|
637
|
+
const actions = parseGrantActions(grant.properties.actions);
|
|
638
|
+
if (actions.includes("read") || actions.includes("write")) {
|
|
639
|
+
const grantee = grant.properties.grantee;
|
|
640
|
+
if (typeof grantee === "string" && grantee.startsWith("did:key:")) {
|
|
641
|
+
recipients.add(grantee);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
if (!schema.authorization) {
|
|
647
|
+
addGrantRecipients();
|
|
648
|
+
return finalizeRecipients(recipients, dependencies);
|
|
649
|
+
}
|
|
650
|
+
const auth = deserializeAuthorization(schema.authorization);
|
|
651
|
+
const readExpr = auth.actions.read;
|
|
652
|
+
if (!readExpr) {
|
|
653
|
+
addGrantRecipients();
|
|
654
|
+
return finalizeRecipients(recipients, dependencies);
|
|
655
|
+
}
|
|
656
|
+
if (hasPublicAccess(readExpr)) {
|
|
657
|
+
return [PUBLIC_RECIPIENT];
|
|
658
|
+
}
|
|
659
|
+
const readRoles = extractRoleRefs(readExpr);
|
|
660
|
+
for (const roleName of readRoles) {
|
|
661
|
+
const resolver = auth.roles[roleName];
|
|
662
|
+
if (!resolver) continue;
|
|
663
|
+
const members = await resolveRoleMembers(resolver, node, schema, dependencies, 0);
|
|
664
|
+
for (const did of members) {
|
|
665
|
+
recipients.add(did);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
addGrantRecipients();
|
|
669
|
+
return finalizeRecipients(recipients, dependencies);
|
|
670
|
+
}
|
|
671
|
+
async function resolveRoleMembers(resolver, node, _schema, dependencies, depth) {
|
|
672
|
+
if (depth > (dependencies.maxDepth ?? 3)) {
|
|
673
|
+
return [];
|
|
674
|
+
}
|
|
675
|
+
switch (resolver._tag) {
|
|
676
|
+
case "creator":
|
|
677
|
+
return [node.createdBy];
|
|
678
|
+
case "property":
|
|
679
|
+
return readDidProperty(node.properties[resolver.propertyName]);
|
|
680
|
+
case "relation": {
|
|
681
|
+
const targetId = node.properties[resolver.relationName];
|
|
682
|
+
if (typeof targetId !== "string") {
|
|
683
|
+
return [];
|
|
684
|
+
}
|
|
685
|
+
const targetNode = await dependencies.getNode(targetId);
|
|
686
|
+
if (!targetNode || !dependencies.getSchema) {
|
|
687
|
+
return [];
|
|
688
|
+
}
|
|
689
|
+
const targetSchema = await dependencies.getSchema(targetNode.schemaId);
|
|
690
|
+
if (!targetSchema?.authorization) {
|
|
691
|
+
return [];
|
|
692
|
+
}
|
|
693
|
+
const targetAuth = deserializeAuthorization(targetSchema.authorization);
|
|
694
|
+
const targetResolver = targetAuth.roles[resolver.targetRole];
|
|
695
|
+
if (!targetResolver) {
|
|
696
|
+
return [];
|
|
697
|
+
}
|
|
698
|
+
return resolveRoleMembers(targetResolver, targetNode, targetSchema, dependencies, depth + 1);
|
|
699
|
+
}
|
|
700
|
+
case "membership": {
|
|
701
|
+
if (!dependencies.listNodes) {
|
|
702
|
+
return [];
|
|
703
|
+
}
|
|
704
|
+
const containerIds = await collectContainerIds(node, resolver.parentProp, dependencies);
|
|
705
|
+
const edges = await dependencies.listNodes(resolver.edgeSchema);
|
|
706
|
+
const members = /* @__PURE__ */ new Set();
|
|
707
|
+
for (const edge of edges) {
|
|
708
|
+
if (edge.deleted) continue;
|
|
709
|
+
const container = edge.properties[resolver.containerProp];
|
|
710
|
+
if (typeof container !== "string" || !containerIds.has(container)) continue;
|
|
711
|
+
const edgeRole = edge.properties[resolver.roleProp];
|
|
712
|
+
if (typeof edgeRole !== "string") continue;
|
|
713
|
+
const have = resolver.roleOrder.indexOf(edgeRole);
|
|
714
|
+
const need = resolver.roleOrder.indexOf(resolver.minRole);
|
|
715
|
+
if (have < 0 || need < 0 || have < need) continue;
|
|
716
|
+
for (const did of readDidProperty(edge.properties[resolver.memberProp])) {
|
|
717
|
+
members.add(did);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
return [...members];
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
async function collectContainerIds(node, parentProp, dependencies) {
|
|
725
|
+
const ids = /* @__PURE__ */ new Set([node.id]);
|
|
726
|
+
if (!parentProp) {
|
|
727
|
+
return ids;
|
|
728
|
+
}
|
|
729
|
+
let current = node;
|
|
730
|
+
let depth = 0;
|
|
731
|
+
while (current && depth < MAX_CONTAINER_DEPTH) {
|
|
732
|
+
const parentId = current.properties[parentProp];
|
|
733
|
+
if (typeof parentId !== "string" || ids.has(parentId)) {
|
|
734
|
+
break;
|
|
735
|
+
}
|
|
736
|
+
ids.add(parentId);
|
|
737
|
+
current = await dependencies.getNode(parentId);
|
|
738
|
+
depth += 1;
|
|
739
|
+
}
|
|
740
|
+
return ids;
|
|
741
|
+
}
|
|
742
|
+
function readDidProperty(value) {
|
|
743
|
+
if (Array.isArray(value)) {
|
|
744
|
+
return value.filter(isDid);
|
|
745
|
+
}
|
|
746
|
+
if (isDid(value)) {
|
|
747
|
+
return [value];
|
|
748
|
+
}
|
|
749
|
+
return [];
|
|
750
|
+
}
|
|
751
|
+
function isDid(value) {
|
|
752
|
+
return typeof value === "string" && value.startsWith("did:key:");
|
|
753
|
+
}
|
|
754
|
+
function parseGrantActions(value) {
|
|
755
|
+
if (Array.isArray(value)) {
|
|
756
|
+
return value.filter((entry) => typeof entry === "string");
|
|
757
|
+
}
|
|
758
|
+
if (typeof value !== "string") {
|
|
759
|
+
return [];
|
|
760
|
+
}
|
|
761
|
+
try {
|
|
762
|
+
const parsed = JSON.parse(value);
|
|
763
|
+
if (!Array.isArray(parsed)) {
|
|
764
|
+
return [];
|
|
765
|
+
}
|
|
766
|
+
return parsed.filter((entry) => typeof entry === "string");
|
|
767
|
+
} catch {
|
|
768
|
+
return [];
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
function hasRecipientsChanged(previous, next) {
|
|
772
|
+
if (previous.length !== next.length) {
|
|
773
|
+
return true;
|
|
774
|
+
}
|
|
775
|
+
const nextSet = new Set(next);
|
|
776
|
+
return previous.some((recipient) => !nextSet.has(recipient));
|
|
777
|
+
}
|
|
778
|
+
function canTransitionToPublic(readExpr) {
|
|
779
|
+
if (!readExpr) {
|
|
780
|
+
return false;
|
|
781
|
+
}
|
|
782
|
+
return hasPublicAccess(readExpr);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// src/auth/migration.ts
|
|
786
|
+
async function handleAuthMigration(oldSchema, newSchema, node, dependencies) {
|
|
787
|
+
const oldAuth = oldSchema.authorization;
|
|
788
|
+
const newAuth = newSchema.authorization;
|
|
789
|
+
if (!oldAuth && newAuth) {
|
|
790
|
+
await dependencies.encryptExistingNode(node, newSchema);
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
if (oldAuth && !newAuth) {
|
|
794
|
+
console.warn(
|
|
795
|
+
`[xnet:auth] Schema '${newSchema.name}' removed authorization block. Existing nodes remain encrypted until manually migrated.`
|
|
796
|
+
);
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
if (!oldAuth || !newAuth) {
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
const oldRecipients = await computeRecipients(oldSchema, node, dependencies);
|
|
803
|
+
const newRecipients = await computeRecipients(newSchema, node, dependencies);
|
|
804
|
+
if (!hasRecipientsChanged(oldRecipients, newRecipients)) {
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
await dependencies.rotateContentKeyForNode(node.id, newRecipients);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// src/auth/grants.ts
|
|
811
|
+
var GRANT_SCHEMA_IRI = "xnet://xnet.fyi/Grant";
|
|
812
|
+
function isGrantActive(grant, now = Date.now()) {
|
|
813
|
+
const revokedAt = asNumber(grant.properties.revokedAt);
|
|
814
|
+
if (revokedAt > 0) {
|
|
815
|
+
return false;
|
|
816
|
+
}
|
|
817
|
+
const expiresAt = asNumber(grant.properties.expiresAt);
|
|
818
|
+
if (expiresAt > 0 && expiresAt <= now) {
|
|
819
|
+
return false;
|
|
820
|
+
}
|
|
821
|
+
return true;
|
|
822
|
+
}
|
|
823
|
+
var GrantIndex = class {
|
|
824
|
+
constructor(store, options = {}) {
|
|
825
|
+
this.store = store;
|
|
826
|
+
this.schemaId = options.schemaId ?? GRANT_SCHEMA_IRI;
|
|
827
|
+
this.clock = options.clock ?? Date.now;
|
|
828
|
+
}
|
|
829
|
+
byResourceAndGrantee = /* @__PURE__ */ new Map();
|
|
830
|
+
byResource = /* @__PURE__ */ new Map();
|
|
831
|
+
grantsById = /* @__PURE__ */ new Map();
|
|
832
|
+
unsubscribe = null;
|
|
833
|
+
schemaId;
|
|
834
|
+
clock;
|
|
835
|
+
async initialize() {
|
|
836
|
+
const grants = await this.loadGrantNodes();
|
|
837
|
+
for (const grant of grants) {
|
|
838
|
+
this.indexGrant(grant);
|
|
839
|
+
}
|
|
840
|
+
this.unsubscribe = this.store.subscribe((event) => {
|
|
841
|
+
const node = event.node;
|
|
842
|
+
if (!node || !this.isGrantSchema(node.schemaId)) {
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
this.removeGrant(node.id);
|
|
846
|
+
if (!node.deleted) {
|
|
847
|
+
this.indexGrant(node);
|
|
848
|
+
}
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
findGrants(resource, grantee) {
|
|
852
|
+
const grantsByGrantee = this.byResourceAndGrantee.get(resource);
|
|
853
|
+
if (!grantsByGrantee) return [];
|
|
854
|
+
const grantIds = grantsByGrantee.get(grantee);
|
|
855
|
+
if (!grantIds) return [];
|
|
856
|
+
const now = this.clock();
|
|
857
|
+
const grants = [];
|
|
858
|
+
for (const grantId of grantIds) {
|
|
859
|
+
const grant = this.grantsById.get(grantId);
|
|
860
|
+
if (grant && isGrantActive(grant, now)) {
|
|
861
|
+
grants.push(grant);
|
|
422
862
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
count += countExpressionNodes(expr.expr);
|
|
426
|
-
break;
|
|
863
|
+
}
|
|
864
|
+
return grants;
|
|
427
865
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
var MAX_EXPRESSION_NODES = 50;
|
|
438
|
-
function validateAuthorization(auth, properties) {
|
|
439
|
-
const errors = [];
|
|
440
|
-
for (const [actionName, expr] of Object.entries(auth.actions)) {
|
|
441
|
-
const referencedRoles = extractRoleRefs(expr);
|
|
442
|
-
for (const ref of referencedRoles) {
|
|
443
|
-
if (!(ref in auth.roles) && !BUILTIN_ROLES.includes(ref)) {
|
|
444
|
-
errors.push({
|
|
445
|
-
code: "AUTH_SCHEMA_INVALID_ROLE_REF",
|
|
446
|
-
message: `Action '${actionName}' references unknown role '${ref}'`,
|
|
447
|
-
path: `authorization.actions.${actionName}`
|
|
448
|
-
});
|
|
866
|
+
findGrantsForResource(resource) {
|
|
867
|
+
const grantIds = this.byResource.get(resource);
|
|
868
|
+
if (!grantIds) return [];
|
|
869
|
+
const now = this.clock();
|
|
870
|
+
const grants = [];
|
|
871
|
+
for (const grantId of grantIds) {
|
|
872
|
+
const grant = this.grantsById.get(grantId);
|
|
873
|
+
if (grant && isGrantActive(grant, now)) {
|
|
874
|
+
grants.push(grant);
|
|
449
875
|
}
|
|
450
876
|
}
|
|
877
|
+
return grants;
|
|
451
878
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
});
|
|
461
|
-
} else if (propDef.type !== "person") {
|
|
462
|
-
errors.push({
|
|
463
|
-
code: "AUTH_SCHEMA_INVALID_RELATION_PATH",
|
|
464
|
-
message: `Role '${roleName}' references property '${resolver.propertyName}' which is not a person type (got '${propDef.type}')`,
|
|
465
|
-
path: `authorization.roles.${roleName}`
|
|
466
|
-
});
|
|
879
|
+
findAllGrantsForResource(resource) {
|
|
880
|
+
const grantIds = this.byResource.get(resource);
|
|
881
|
+
if (!grantIds) return [];
|
|
882
|
+
const grants = [];
|
|
883
|
+
for (const grantId of grantIds) {
|
|
884
|
+
const grant = this.grantsById.get(grantId);
|
|
885
|
+
if (grant) {
|
|
886
|
+
grants.push(grant);
|
|
467
887
|
}
|
|
468
888
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
});
|
|
889
|
+
return grants;
|
|
890
|
+
}
|
|
891
|
+
findGrantsForGrantee(grantee) {
|
|
892
|
+
const grants = [];
|
|
893
|
+
const now = this.clock();
|
|
894
|
+
for (const grantsByGrantee of this.byResourceAndGrantee.values()) {
|
|
895
|
+
const grantIds = grantsByGrantee.get(grantee);
|
|
896
|
+
if (!grantIds) continue;
|
|
897
|
+
for (const grantId of grantIds) {
|
|
898
|
+
const grant = this.grantsById.get(grantId);
|
|
899
|
+
if (grant && isGrantActive(grant, now)) {
|
|
900
|
+
grants.push(grant);
|
|
901
|
+
}
|
|
483
902
|
}
|
|
484
903
|
}
|
|
904
|
+
return grants;
|
|
485
905
|
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
906
|
+
dispose() {
|
|
907
|
+
this.unsubscribe?.();
|
|
908
|
+
this.unsubscribe = null;
|
|
909
|
+
this.byResourceAndGrantee.clear();
|
|
910
|
+
this.byResource.clear();
|
|
911
|
+
this.grantsById.clear();
|
|
912
|
+
}
|
|
913
|
+
async loadGrantNodes() {
|
|
914
|
+
const directMatches = await this.store.list({ schemaId: this.schemaId, includeDeleted: false });
|
|
915
|
+
if (directMatches.length > 0 || this.schemaId.includes("@")) {
|
|
916
|
+
return directMatches;
|
|
495
917
|
}
|
|
918
|
+
const allNodes = await this.store.list({ includeDeleted: false });
|
|
919
|
+
return allNodes.filter((node) => this.isGrantSchema(node.schemaId));
|
|
496
920
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
921
|
+
isGrantSchema(schemaId) {
|
|
922
|
+
return schemaId === this.schemaId || schemaId.startsWith(`${this.schemaId}@`);
|
|
923
|
+
}
|
|
924
|
+
indexGrant(node) {
|
|
925
|
+
const resource = asString(node.properties.resource);
|
|
926
|
+
const grantee = asDid(node.properties.grantee);
|
|
927
|
+
if (!resource || !grantee) {
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
const grant = {
|
|
931
|
+
id: node.id,
|
|
932
|
+
properties: node.properties,
|
|
933
|
+
deleted: node.deleted
|
|
934
|
+
};
|
|
935
|
+
this.grantsById.set(node.id, grant);
|
|
936
|
+
let byGrantee = this.byResourceAndGrantee.get(resource);
|
|
937
|
+
if (!byGrantee) {
|
|
938
|
+
byGrantee = /* @__PURE__ */ new Map();
|
|
939
|
+
this.byResourceAndGrantee.set(resource, byGrantee);
|
|
940
|
+
}
|
|
941
|
+
let grantIdsForGrantee = byGrantee.get(grantee);
|
|
942
|
+
if (!grantIdsForGrantee) {
|
|
943
|
+
grantIdsForGrantee = /* @__PURE__ */ new Set();
|
|
944
|
+
byGrantee.set(grantee, grantIdsForGrantee);
|
|
945
|
+
}
|
|
946
|
+
grantIdsForGrantee.add(node.id);
|
|
947
|
+
let grantIdsForResource = this.byResource.get(resource);
|
|
948
|
+
if (!grantIdsForResource) {
|
|
949
|
+
grantIdsForResource = /* @__PURE__ */ new Set();
|
|
950
|
+
this.byResource.set(resource, grantIdsForResource);
|
|
505
951
|
}
|
|
952
|
+
grantIdsForResource.add(node.id);
|
|
506
953
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
954
|
+
removeGrant(grantId) {
|
|
955
|
+
const existing = this.grantsById.get(grantId);
|
|
956
|
+
if (!existing) {
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
const resource = asString(existing.properties.resource);
|
|
960
|
+
const grantee = asDid(existing.properties.grantee);
|
|
961
|
+
this.grantsById.delete(grantId);
|
|
962
|
+
if (!resource || !grantee) {
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
const byGrantee = this.byResourceAndGrantee.get(resource);
|
|
966
|
+
if (byGrantee) {
|
|
967
|
+
const grantIds = byGrantee.get(grantee);
|
|
968
|
+
if (grantIds) {
|
|
969
|
+
grantIds.delete(grantId);
|
|
970
|
+
if (grantIds.size === 0) {
|
|
971
|
+
byGrantee.delete(grantee);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
if (byGrantee.size === 0) {
|
|
975
|
+
this.byResourceAndGrantee.delete(resource);
|
|
515
976
|
}
|
|
516
977
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
code: "AUTH_SCHEMA_UNSAFE_PUBLIC_MUTATION",
|
|
524
|
-
message: `Action '${actionName}' allows PUBLIC access, which is unsafe for mutation operations`,
|
|
525
|
-
path: `authorization.actions.${actionName}`
|
|
526
|
-
});
|
|
978
|
+
const byResource = this.byResource.get(resource);
|
|
979
|
+
if (byResource) {
|
|
980
|
+
byResource.delete(grantId);
|
|
981
|
+
if (byResource.size === 0) {
|
|
982
|
+
this.byResource.delete(resource);
|
|
983
|
+
}
|
|
527
984
|
}
|
|
528
985
|
}
|
|
529
|
-
|
|
986
|
+
};
|
|
987
|
+
function asDid(value) {
|
|
988
|
+
if (typeof value === "string" && value.startsWith("did:key:")) {
|
|
989
|
+
return value;
|
|
990
|
+
}
|
|
991
|
+
return null;
|
|
992
|
+
}
|
|
993
|
+
function asString(value) {
|
|
994
|
+
return typeof value === "string" ? value : null;
|
|
995
|
+
}
|
|
996
|
+
function asNumber(value) {
|
|
997
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
// src/auth/offline-policy.ts
|
|
1001
|
+
var DEFAULT_OFFLINE_POLICY = {
|
|
1002
|
+
decisionCacheTTL: 5 * 60 * 1e3,
|
|
1003
|
+
maxStaleness: 60 * 60 * 1e3,
|
|
1004
|
+
revalidation: "hybrid",
|
|
1005
|
+
allowOfflineGrants: true
|
|
1006
|
+
};
|
|
1007
|
+
function mergeOfflinePolicy(current, patch) {
|
|
1008
|
+
return {
|
|
1009
|
+
...current,
|
|
1010
|
+
...patch
|
|
1011
|
+
};
|
|
530
1012
|
}
|
|
531
1013
|
|
|
532
1014
|
// src/auth/evaluator.ts
|
|
1015
|
+
var MAX_CONTAINER_DEPTH2 = 32;
|
|
533
1016
|
var DecisionCache = class {
|
|
534
1017
|
cache = /* @__PURE__ */ new Map();
|
|
535
1018
|
ttlMs;
|
|
@@ -632,7 +1115,7 @@ var DefaultRoleResolver = class {
|
|
|
632
1115
|
case "creator":
|
|
633
1116
|
return [node.createdBy];
|
|
634
1117
|
case "property":
|
|
635
|
-
return
|
|
1118
|
+
return readDidProperty2(node.properties[resolver.propertyName]);
|
|
636
1119
|
case "relation": {
|
|
637
1120
|
const relatedNodes = await this.loadRelatedNodes(node, resolver.relationName);
|
|
638
1121
|
if (relatedNodes.length === 0) {
|
|
@@ -661,6 +1144,20 @@ var DefaultRoleResolver = class {
|
|
|
661
1144
|
}
|
|
662
1145
|
return [...members];
|
|
663
1146
|
}
|
|
1147
|
+
case "membership": {
|
|
1148
|
+
const containerIds = await this.collectContainerIds(node, resolver.parentProp);
|
|
1149
|
+
const edges = await this.store.list({ schemaId: resolver.edgeSchema });
|
|
1150
|
+
const members = /* @__PURE__ */ new Set();
|
|
1151
|
+
for (const edge of edges) {
|
|
1152
|
+
if (edge.deleted) continue;
|
|
1153
|
+
if (!this.membershipMatchesContainer(edge, resolver, containerIds)) continue;
|
|
1154
|
+
if (!membershipRoleAtLeast(edge.properties[resolver.roleProp], resolver)) continue;
|
|
1155
|
+
for (const did of readDidProperty2(edge.properties[resolver.memberProp])) {
|
|
1156
|
+
members.add(did);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
return [...members];
|
|
1160
|
+
}
|
|
664
1161
|
}
|
|
665
1162
|
}
|
|
666
1163
|
async checkRole(did, resolver, node, depth, visited) {
|
|
@@ -676,7 +1173,7 @@ var DefaultRoleResolver = class {
|
|
|
676
1173
|
case "creator":
|
|
677
1174
|
return did === node.createdBy;
|
|
678
1175
|
case "property":
|
|
679
|
-
return
|
|
1176
|
+
return readDidProperty2(node.properties[resolver.propertyName]).includes(did);
|
|
680
1177
|
case "relation": {
|
|
681
1178
|
const relatedNodes = await this.loadRelatedNodes(node, resolver.relationName);
|
|
682
1179
|
for (const relatedNode of relatedNodes) {
|
|
@@ -696,7 +1193,47 @@ var DefaultRoleResolver = class {
|
|
|
696
1193
|
}
|
|
697
1194
|
return false;
|
|
698
1195
|
}
|
|
1196
|
+
case "membership": {
|
|
1197
|
+
const containerIds = await this.collectContainerIds(node, resolver.parentProp);
|
|
1198
|
+
const edges = await this.store.list({ schemaId: resolver.edgeSchema });
|
|
1199
|
+
for (const edge of edges) {
|
|
1200
|
+
if (edge.deleted) continue;
|
|
1201
|
+
if (!this.membershipMatchesContainer(edge, resolver, containerIds)) continue;
|
|
1202
|
+
if (!readDidProperty2(edge.properties[resolver.memberProp]).includes(did)) continue;
|
|
1203
|
+
if (membershipRoleAtLeast(edge.properties[resolver.roleProp], resolver)) {
|
|
1204
|
+
return true;
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
return false;
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
membershipMatchesContainer(edge, resolver, containerIds) {
|
|
1212
|
+
const value = edge.properties[resolver.containerProp];
|
|
1213
|
+
return typeof value === "string" && containerIds.has(value);
|
|
1214
|
+
}
|
|
1215
|
+
/**
|
|
1216
|
+
* The node's own id plus the ids of every ancestor reachable by walking
|
|
1217
|
+
* `parentProp`. Cycle- and depth-bounded. When `parentProp` is undefined the
|
|
1218
|
+
* set is just the node itself (no inheritance).
|
|
1219
|
+
*/
|
|
1220
|
+
async collectContainerIds(node, parentProp) {
|
|
1221
|
+
const ids = /* @__PURE__ */ new Set([node.id]);
|
|
1222
|
+
if (!parentProp) {
|
|
1223
|
+
return ids;
|
|
1224
|
+
}
|
|
1225
|
+
let current = node;
|
|
1226
|
+
let depth = 0;
|
|
1227
|
+
while (current && depth < MAX_CONTAINER_DEPTH2) {
|
|
1228
|
+
const parentId = current.properties[parentProp];
|
|
1229
|
+
if (typeof parentId !== "string" || ids.has(parentId)) {
|
|
1230
|
+
break;
|
|
1231
|
+
}
|
|
1232
|
+
ids.add(parentId);
|
|
1233
|
+
current = await this.store.get(parentId);
|
|
1234
|
+
depth += 1;
|
|
699
1235
|
}
|
|
1236
|
+
return ids;
|
|
700
1237
|
}
|
|
701
1238
|
async loadRelatedNodes(node, relationName) {
|
|
702
1239
|
const value = node.properties[relationName];
|
|
@@ -766,7 +1303,8 @@ var DefaultPolicyEvaluator = class {
|
|
|
766
1303
|
this.emitDecision(decision2);
|
|
767
1304
|
return decision2;
|
|
768
1305
|
}
|
|
769
|
-
const
|
|
1306
|
+
const useCache = !input.patch;
|
|
1307
|
+
const cached = useCache ? this.cache.get(input.subject, input.action, input.nodeId) : void 0;
|
|
770
1308
|
if (cached) {
|
|
771
1309
|
const decision2 = {
|
|
772
1310
|
...cached,
|
|
@@ -789,15 +1327,24 @@ var DefaultPolicyEvaluator = class {
|
|
|
789
1327
|
return decision2;
|
|
790
1328
|
}
|
|
791
1329
|
const mode = getAuthMode(schema.schema);
|
|
792
|
-
if (mode === "legacy") {
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
1330
|
+
if (mode === "legacy" || !schema.schema.authorization) {
|
|
1331
|
+
if (node.createdBy === input.subject) {
|
|
1332
|
+
const decision3 = this.decision(input, true, ["owner"], start);
|
|
1333
|
+
if (useCache) this.cache.set(input.subject, input.action, input.nodeId, decision3);
|
|
1334
|
+
this.emitDecision(decision3);
|
|
1335
|
+
return decision3;
|
|
1336
|
+
}
|
|
1337
|
+
const grant2 = this.findMatchingGrant(
|
|
1338
|
+
input,
|
|
1339
|
+
this.grantIndex?.findGrants(input.nodeId, input.subject) ?? []
|
|
1340
|
+
);
|
|
1341
|
+
if (grant2) {
|
|
1342
|
+
const decision3 = this.decision(input, true, [], start, [grant2.id]);
|
|
1343
|
+
if (useCache) this.cache.set(input.subject, input.action, input.nodeId, decision3);
|
|
1344
|
+
this.emitDecision(decision3);
|
|
1345
|
+
return decision3;
|
|
1346
|
+
}
|
|
1347
|
+
const decision2 = this.deny(input, ["DENY_NO_ROLE_MATCH", "DENY_NO_GRANT"], start);
|
|
801
1348
|
this.emitDecision(decision2);
|
|
802
1349
|
return decision2;
|
|
803
1350
|
}
|
|
@@ -823,7 +1370,7 @@ var DefaultPolicyEvaluator = class {
|
|
|
823
1370
|
const allowedByRole = evaluateExpression(actionExpr, roles, true);
|
|
824
1371
|
if (allowedByRole) {
|
|
825
1372
|
const decision2 = this.decision(input, true, [...roles], start);
|
|
826
|
-
this.cache.set(input.subject, input.action, input.nodeId, decision2);
|
|
1373
|
+
if (useCache) this.cache.set(input.subject, input.action, input.nodeId, decision2);
|
|
827
1374
|
this.emitDecision(decision2);
|
|
828
1375
|
return decision2;
|
|
829
1376
|
}
|
|
@@ -833,7 +1380,7 @@ var DefaultPolicyEvaluator = class {
|
|
|
833
1380
|
);
|
|
834
1381
|
if (grant) {
|
|
835
1382
|
const decision2 = this.decision(input, true, [...roles], start, [grant.id]);
|
|
836
|
-
this.cache.set(input.subject, input.action, input.nodeId, decision2);
|
|
1383
|
+
if (useCache) this.cache.set(input.subject, input.action, input.nodeId, decision2);
|
|
837
1384
|
this.emitDecision(decision2);
|
|
838
1385
|
return decision2;
|
|
839
1386
|
}
|
|
@@ -1050,16 +1597,24 @@ function matchesDenyExpression(expr, roles, isAuthenticated) {
|
|
|
1050
1597
|
return false;
|
|
1051
1598
|
}
|
|
1052
1599
|
}
|
|
1053
|
-
function
|
|
1600
|
+
function readDidProperty2(value) {
|
|
1054
1601
|
if (Array.isArray(value)) {
|
|
1055
|
-
return value.filter(
|
|
1602
|
+
return value.filter(isDid2);
|
|
1056
1603
|
}
|
|
1057
|
-
if (
|
|
1604
|
+
if (isDid2(value)) {
|
|
1058
1605
|
return [value];
|
|
1059
1606
|
}
|
|
1060
1607
|
return [];
|
|
1061
1608
|
}
|
|
1062
|
-
function
|
|
1609
|
+
function membershipRoleAtLeast(edgeRole, resolver) {
|
|
1610
|
+
if (typeof edgeRole !== "string") {
|
|
1611
|
+
return false;
|
|
1612
|
+
}
|
|
1613
|
+
const have = resolver.roleOrder.indexOf(edgeRole);
|
|
1614
|
+
const need = resolver.roleOrder.indexOf(resolver.minRole);
|
|
1615
|
+
return have >= 0 && need >= 0 && have >= need;
|
|
1616
|
+
}
|
|
1617
|
+
function isDid2(value) {
|
|
1063
1618
|
return typeof value === "string" && value.startsWith("did:key:");
|
|
1064
1619
|
}
|
|
1065
1620
|
function parseActions(value) {
|
|
@@ -1079,6 +1634,20 @@ function parseActions(value) {
|
|
|
1079
1634
|
return [];
|
|
1080
1635
|
}
|
|
1081
1636
|
}
|
|
1637
|
+
async function createPolicyEvaluator(options) {
|
|
1638
|
+
const grantIndex = new GrantIndex(options.store, {
|
|
1639
|
+
schemaId: options.grantSchemaId
|
|
1640
|
+
});
|
|
1641
|
+
await grantIndex.initialize();
|
|
1642
|
+
return new DefaultPolicyEvaluator({
|
|
1643
|
+
store: options.store,
|
|
1644
|
+
schemaRegistry: options.schemaRegistry,
|
|
1645
|
+
grantIndex,
|
|
1646
|
+
cache: options.cache,
|
|
1647
|
+
maxDepth: options.maxDepth,
|
|
1648
|
+
maxNodes: options.maxNodes
|
|
1649
|
+
});
|
|
1650
|
+
}
|
|
1082
1651
|
|
|
1083
1652
|
// src/auth/grant-expiration-cleaner.ts
|
|
1084
1653
|
var GrantExpirationCleaner = class _GrantExpirationCleaner {
|
|
@@ -1261,7 +1830,7 @@ var StoreAuth = class {
|
|
|
1261
1830
|
expiresAt,
|
|
1262
1831
|
parentUcanToken: parseString(parentGrant?.properties.ucanToken) ?? void 0
|
|
1263
1832
|
});
|
|
1264
|
-
const
|
|
1833
|
+
const created = await this.options.store.create({
|
|
1265
1834
|
schemaId: GRANT_SCHEMA_IRI,
|
|
1266
1835
|
properties: {
|
|
1267
1836
|
issuer: this.options.actorDid,
|
|
@@ -1292,7 +1861,7 @@ var StoreAuth = class {
|
|
|
1292
1861
|
}
|
|
1293
1862
|
this.options.evaluator.invalidateSubject(input.to);
|
|
1294
1863
|
this.options.evaluator.invalidate(input.resource);
|
|
1295
|
-
const node = await this.getGrantNodeOrThrow(
|
|
1864
|
+
const node = await this.getGrantNodeOrThrow(created.id);
|
|
1296
1865
|
return toGrant(node);
|
|
1297
1866
|
}
|
|
1298
1867
|
async revoke(input) {
|
|
@@ -1578,743 +2147,101 @@ function parseDuration(value) {
|
|
|
1578
2147
|
}
|
|
1579
2148
|
}
|
|
1580
2149
|
|
|
1581
|
-
// src/auth/
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
return { _tag: "property", propertyName };
|
|
1601
|
-
},
|
|
1602
|
-
/**
|
|
1603
|
-
* Role inherited from a related node.
|
|
1604
|
-
* Users who hold `targetRole` on the related node hold this role.
|
|
1605
|
-
*
|
|
1606
|
-
* @param relationName - Name of the relation property
|
|
1607
|
-
* @param targetRole - Role to check on the related node
|
|
1608
|
-
*/
|
|
1609
|
-
relation(relationName, targetRole) {
|
|
1610
|
-
return { _tag: "relation", relationName, targetRole };
|
|
1611
|
-
}
|
|
1612
|
-
};
|
|
1613
|
-
var relation = role.relation;
|
|
1614
|
-
|
|
1615
|
-
// src/auth/recipients.ts
|
|
1616
|
-
var PUBLIC_CONTENT_KEY = new Uint8Array(32);
|
|
1617
|
-
|
|
1618
|
-
// src/schema/define.ts
|
|
1619
|
-
var DEFAULT_SCHEMA_VERSION2 = "1.0.0";
|
|
1620
|
-
function defineSchema(options) {
|
|
1621
|
-
const version = options.version ?? DEFAULT_SCHEMA_VERSION2;
|
|
1622
|
-
const schemaId = `${options.namespace}${options.name}@${version}`;
|
|
1623
|
-
if (process.env.NODE_ENV !== "production") {
|
|
1624
|
-
const REF_NAME_PATTERN = /(?:^target$|^inReplyTo$|^replyTo[A-Z].*(?:Id|Node)$|(?:parent|node|comment|task|page|database)(?:Id|Ref)$)/;
|
|
1625
|
-
for (const [name, builder] of Object.entries(options.properties)) {
|
|
1626
|
-
if (builder.definition.type === "text" && REF_NAME_PATTERN.test(name)) {
|
|
1627
|
-
console.warn(
|
|
1628
|
-
`[xNet Schema] "${options.name}.${name}" is a text() property whose name suggests it stores a reference. Consider using relation() for node references or person() for DIDs.`
|
|
1629
|
-
);
|
|
1630
|
-
}
|
|
1631
|
-
}
|
|
1632
|
-
}
|
|
1633
|
-
const properties = Object.entries(options.properties).map(
|
|
1634
|
-
([name, builder]) => ({
|
|
1635
|
-
"@id": `${schemaId}#${name}`,
|
|
1636
|
-
name,
|
|
1637
|
-
...builder.definition
|
|
1638
|
-
})
|
|
1639
|
-
);
|
|
1640
|
-
const propertyMap = Object.fromEntries(properties.map((property) => [property.name, property]));
|
|
1641
|
-
if (options.authorization) {
|
|
1642
|
-
const authResult = validateAuthorization(options.authorization, propertyMap);
|
|
1643
|
-
if (!authResult.valid) {
|
|
1644
|
-
const message = authResult.errors.map((error) => `${error.path}: [${error.code}] ${error.message}`).join(", ");
|
|
1645
|
-
throw new Error(`Invalid authorization in schema '${options.name}': ${message}`);
|
|
1646
|
-
}
|
|
1647
|
-
}
|
|
1648
|
-
const schema = {
|
|
1649
|
-
"@id": schemaId,
|
|
1650
|
-
"@type": "xnet://xnet.fyi/Schema",
|
|
1651
|
-
name: options.name,
|
|
1652
|
-
namespace: options.namespace,
|
|
1653
|
-
version,
|
|
1654
|
-
migrateFrom: options.migrateFrom,
|
|
1655
|
-
properties,
|
|
1656
|
-
extends: options.extends?.schema["@id"],
|
|
1657
|
-
document: options.document,
|
|
1658
|
-
authorization: options.authorization ? serializeAuthorization(options.authorization) : void 0
|
|
1659
|
-
};
|
|
1660
|
-
function validate(node) {
|
|
2150
|
+
// src/auth/auth-migrator.ts
|
|
2151
|
+
var AuthMigrator = class {
|
|
2152
|
+
constructor(store, schemaRegistry, publicKeyResolver, grantIndex, encryptionLayer) {
|
|
2153
|
+
this.store = store;
|
|
2154
|
+
this.schemaRegistry = schemaRegistry;
|
|
2155
|
+
this.publicKeyResolver = publicKeyResolver;
|
|
2156
|
+
this.grantIndex = grantIndex;
|
|
2157
|
+
this.encryptionLayer = encryptionLayer;
|
|
2158
|
+
}
|
|
2159
|
+
async migrateSchema(schemaIri, options = {}) {
|
|
2160
|
+
const schema = await this.schemaRegistry.get(schemaIri);
|
|
2161
|
+
if (!schema?.schema.authorization) {
|
|
2162
|
+
throw new Error(`Schema ${schemaIri} has no authorization block`);
|
|
2163
|
+
}
|
|
2164
|
+
const nodes = await this.store.list({ schemaId: schemaIri, includeDeleted: false });
|
|
2165
|
+
const total = nodes.length;
|
|
2166
|
+
const batchSize = Math.max(1, options.batchSize ?? 100);
|
|
2167
|
+
let migrated = 0;
|
|
2168
|
+
let failed = 0;
|
|
1661
2169
|
const errors = [];
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
const value = obj[name];
|
|
1684
|
-
const isRequired = builder.definition.required;
|
|
1685
|
-
if (value === void 0 || value === null) {
|
|
1686
|
-
if (isRequired) {
|
|
1687
|
-
errors.push({ path: name, message: `${name} is required` });
|
|
2170
|
+
for (let index = 0; index < nodes.length; index += batchSize) {
|
|
2171
|
+
const batch = nodes.slice(index, index + batchSize);
|
|
2172
|
+
for (const node of batch) {
|
|
2173
|
+
try {
|
|
2174
|
+
const recipients = await computeRecipients(schema.schema, node, {
|
|
2175
|
+
getNode: (nodeId) => this.store.get(nodeId),
|
|
2176
|
+
getSchema: (schemaId) => this.resolveSchema(schemaId),
|
|
2177
|
+
grantIndex: this.grantIndex
|
|
2178
|
+
});
|
|
2179
|
+
const didRecipients = recipients.filter(
|
|
2180
|
+
(recipient) => recipient !== "PUBLIC"
|
|
2181
|
+
);
|
|
2182
|
+
const recipientKeys = await this.publicKeyResolver.resolveBatch(didRecipients);
|
|
2183
|
+
await this.encryptionLayer.encryptAndStoreNode(node, recipientKeys);
|
|
2184
|
+
migrated++;
|
|
2185
|
+
} catch (error) {
|
|
2186
|
+
failed++;
|
|
2187
|
+
errors.push({
|
|
2188
|
+
nodeId: node.id,
|
|
2189
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2190
|
+
});
|
|
1688
2191
|
}
|
|
1689
|
-
} else if (!builder.validate(value)) {
|
|
1690
|
-
errors.push({ path: name, message: `${name} has invalid value`, value });
|
|
1691
|
-
}
|
|
1692
|
-
}
|
|
1693
|
-
return { valid: errors.length === 0, errors };
|
|
1694
|
-
}
|
|
1695
|
-
function create(props, createOptions) {
|
|
1696
|
-
const now = createOptions.createdAt ?? Date.now();
|
|
1697
|
-
const id = createOptions.id ?? createNodeId();
|
|
1698
|
-
const node = {
|
|
1699
|
-
id,
|
|
1700
|
-
schemaId,
|
|
1701
|
-
createdAt: now,
|
|
1702
|
-
createdBy: createOptions.createdBy
|
|
1703
|
-
};
|
|
1704
|
-
for (const [name, builder] of Object.entries(options.properties)) {
|
|
1705
|
-
const rawValue = props[name];
|
|
1706
|
-
const coerced = builder.coerce(rawValue);
|
|
1707
|
-
if (coerced !== null) {
|
|
1708
|
-
node[name] = coerced;
|
|
1709
2192
|
}
|
|
2193
|
+
options.onProgress?.(migrated + failed, total);
|
|
1710
2194
|
}
|
|
1711
|
-
return
|
|
2195
|
+
return { total, migrated, failed, errors };
|
|
1712
2196
|
}
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
return {
|
|
1717
|
-
schema,
|
|
1718
|
-
validate,
|
|
1719
|
-
create,
|
|
1720
|
-
is,
|
|
1721
|
-
_schemaId: schemaId,
|
|
1722
|
-
_properties: options.properties
|
|
1723
|
-
};
|
|
1724
|
-
}
|
|
1725
|
-
|
|
1726
|
-
// src/schema/properties/text.ts
|
|
1727
|
-
function text(options = {}) {
|
|
1728
|
-
return {
|
|
1729
|
-
definition: {
|
|
1730
|
-
type: "text",
|
|
1731
|
-
required: options.required ?? false,
|
|
1732
|
-
config: {
|
|
1733
|
-
minLength: options.minLength,
|
|
1734
|
-
maxLength: options.maxLength,
|
|
1735
|
-
pattern: options.pattern?.source,
|
|
1736
|
-
placeholder: options.placeholder
|
|
1737
|
-
}
|
|
1738
|
-
},
|
|
1739
|
-
validate(value) {
|
|
1740
|
-
if (value === null || value === void 0) {
|
|
1741
|
-
return !options.required;
|
|
1742
|
-
}
|
|
1743
|
-
if (typeof value !== "string") return false;
|
|
1744
|
-
if (options.minLength !== void 0 && value.length < options.minLength) return false;
|
|
1745
|
-
if (options.maxLength !== void 0 && value.length > options.maxLength) return false;
|
|
1746
|
-
if (options.pattern && !options.pattern.test(value)) return false;
|
|
1747
|
-
return true;
|
|
1748
|
-
},
|
|
1749
|
-
coerce(value) {
|
|
1750
|
-
if (value === null || value === void 0) return null;
|
|
1751
|
-
return String(value);
|
|
1752
|
-
},
|
|
1753
|
-
_type: ""
|
|
1754
|
-
};
|
|
1755
|
-
}
|
|
1756
|
-
|
|
1757
|
-
// src/schema/properties/number.ts
|
|
1758
|
-
function number(options = {}) {
|
|
1759
|
-
return {
|
|
1760
|
-
definition: {
|
|
1761
|
-
type: "number",
|
|
1762
|
-
required: options.required ?? false,
|
|
1763
|
-
config: {
|
|
1764
|
-
min: options.min,
|
|
1765
|
-
max: options.max,
|
|
1766
|
-
integer: options.integer
|
|
1767
|
-
}
|
|
1768
|
-
},
|
|
1769
|
-
validate(value) {
|
|
1770
|
-
if (value === null || value === void 0) {
|
|
1771
|
-
return !options.required;
|
|
1772
|
-
}
|
|
1773
|
-
if (typeof value !== "number" || isNaN(value)) return false;
|
|
1774
|
-
if (options.min !== void 0 && value < options.min) return false;
|
|
1775
|
-
if (options.max !== void 0 && value > options.max) return false;
|
|
1776
|
-
if (options.integer && !Number.isInteger(value)) return false;
|
|
1777
|
-
return true;
|
|
1778
|
-
},
|
|
1779
|
-
coerce(value) {
|
|
1780
|
-
if (value === null || value === void 0) return null;
|
|
1781
|
-
const num = Number(value);
|
|
1782
|
-
if (isNaN(num)) return null;
|
|
1783
|
-
if (options.integer) return Math.round(num);
|
|
1784
|
-
return num;
|
|
1785
|
-
},
|
|
1786
|
-
_type: 0
|
|
1787
|
-
};
|
|
1788
|
-
}
|
|
1789
|
-
|
|
1790
|
-
// src/schema/properties/checkbox.ts
|
|
1791
|
-
function checkbox(options = {}) {
|
|
1792
|
-
return {
|
|
1793
|
-
definition: {
|
|
1794
|
-
type: "checkbox",
|
|
1795
|
-
required: options.required ?? false,
|
|
1796
|
-
config: {
|
|
1797
|
-
default: options.default
|
|
1798
|
-
}
|
|
1799
|
-
},
|
|
1800
|
-
validate(value) {
|
|
1801
|
-
if (value === null || value === void 0) {
|
|
1802
|
-
return !options.required;
|
|
1803
|
-
}
|
|
1804
|
-
return typeof value === "boolean";
|
|
1805
|
-
},
|
|
1806
|
-
coerce(value) {
|
|
1807
|
-
if (value === null || value === void 0) {
|
|
1808
|
-
return options.default ?? null;
|
|
1809
|
-
}
|
|
1810
|
-
if (typeof value === "boolean") return value;
|
|
1811
|
-
if (value === "true" || value === 1) return true;
|
|
1812
|
-
if (value === "false" || value === 0) return false;
|
|
1813
|
-
return Boolean(value);
|
|
1814
|
-
},
|
|
1815
|
-
_type: false
|
|
1816
|
-
};
|
|
1817
|
-
}
|
|
1818
|
-
|
|
1819
|
-
// src/schema/properties/date.ts
|
|
1820
|
-
function date(options = {}) {
|
|
1821
|
-
return {
|
|
1822
|
-
definition: {
|
|
1823
|
-
type: "date",
|
|
1824
|
-
required: options.required ?? false,
|
|
1825
|
-
config: {
|
|
1826
|
-
includeTime: options.includeTime ?? false
|
|
1827
|
-
}
|
|
1828
|
-
},
|
|
1829
|
-
validate(value) {
|
|
1830
|
-
if (value === null || value === void 0) {
|
|
1831
|
-
return !options.required;
|
|
1832
|
-
}
|
|
1833
|
-
if (typeof value !== "number") return false;
|
|
1834
|
-
return value >= 0 && value < 3250368e7;
|
|
1835
|
-
},
|
|
1836
|
-
coerce(value) {
|
|
1837
|
-
if (value === null || value === void 0) return null;
|
|
1838
|
-
if (typeof value === "number") {
|
|
1839
|
-
return value;
|
|
1840
|
-
}
|
|
1841
|
-
if (value instanceof Date) {
|
|
1842
|
-
return value.getTime();
|
|
1843
|
-
}
|
|
1844
|
-
if (typeof value === "string") {
|
|
1845
|
-
const parsed = Date.parse(value);
|
|
1846
|
-
if (!isNaN(parsed)) return parsed;
|
|
1847
|
-
}
|
|
1848
|
-
return null;
|
|
1849
|
-
},
|
|
1850
|
-
_type: 0
|
|
1851
|
-
};
|
|
1852
|
-
}
|
|
1853
|
-
|
|
1854
|
-
// src/schema/properties/dateRange.ts
|
|
1855
|
-
function isValidDateRange(value) {
|
|
1856
|
-
if (typeof value !== "object" || value === null) return false;
|
|
1857
|
-
const obj = value;
|
|
1858
|
-
if (typeof obj.start !== "string") return false;
|
|
1859
|
-
if (!isValidDateString(obj.start)) return false;
|
|
1860
|
-
if (obj.end !== void 0) {
|
|
1861
|
-
if (typeof obj.end !== "string") return false;
|
|
1862
|
-
if (!isValidDateString(obj.end)) return false;
|
|
2197
|
+
async resolveSchema(schemaId) {
|
|
2198
|
+
const schema = await this.schemaRegistry.get(schemaId);
|
|
2199
|
+
return schema?.schema;
|
|
1863
2200
|
}
|
|
1864
|
-
|
|
1865
|
-
}
|
|
1866
|
-
function isValidDateString(str) {
|
|
1867
|
-
const d = new Date(str);
|
|
1868
|
-
return !isNaN(d.getTime());
|
|
1869
|
-
}
|
|
1870
|
-
function dateRange(options = {}) {
|
|
1871
|
-
return {
|
|
1872
|
-
definition: {
|
|
1873
|
-
type: "dateRange",
|
|
1874
|
-
required: options.required ?? false,
|
|
1875
|
-
config: {
|
|
1876
|
-
includeTime: options.includeTime ?? false
|
|
1877
|
-
}
|
|
1878
|
-
},
|
|
1879
|
-
validate(value) {
|
|
1880
|
-
if (value === null || value === void 0) {
|
|
1881
|
-
return !options.required;
|
|
1882
|
-
}
|
|
1883
|
-
return isValidDateRange(value);
|
|
1884
|
-
},
|
|
1885
|
-
coerce(value) {
|
|
1886
|
-
if (value === null || value === void 0) return null;
|
|
1887
|
-
if (isValidDateRange(value)) {
|
|
1888
|
-
return value;
|
|
1889
|
-
}
|
|
1890
|
-
if (typeof value === "object" && value !== null) {
|
|
1891
|
-
const obj = value;
|
|
1892
|
-
if (obj.start) {
|
|
1893
|
-
const start = new Date(obj.start);
|
|
1894
|
-
if (!isNaN(start.getTime())) {
|
|
1895
|
-
const result = { start: start.toISOString() };
|
|
1896
|
-
if (obj.end) {
|
|
1897
|
-
const end = new Date(obj.end);
|
|
1898
|
-
if (!isNaN(end.getTime())) {
|
|
1899
|
-
result.end = end.toISOString();
|
|
1900
|
-
}
|
|
1901
|
-
}
|
|
1902
|
-
return result;
|
|
1903
|
-
}
|
|
1904
|
-
}
|
|
1905
|
-
}
|
|
1906
|
-
return null;
|
|
1907
|
-
},
|
|
1908
|
-
_type: {}
|
|
1909
|
-
};
|
|
1910
|
-
}
|
|
1911
|
-
|
|
1912
|
-
// src/schema/properties/select.ts
|
|
1913
|
-
function select(options) {
|
|
1914
|
-
const validIds = new Set(options.options.map((o) => o.id));
|
|
1915
|
-
return {
|
|
1916
|
-
definition: {
|
|
1917
|
-
type: "select",
|
|
1918
|
-
required: options.required ?? false,
|
|
1919
|
-
config: {
|
|
1920
|
-
options: options.options,
|
|
1921
|
-
default: options.default
|
|
1922
|
-
}
|
|
1923
|
-
},
|
|
1924
|
-
validate(value) {
|
|
1925
|
-
if (value === null || value === void 0) {
|
|
1926
|
-
return !options.required;
|
|
1927
|
-
}
|
|
1928
|
-
return typeof value === "string" && validIds.has(value);
|
|
1929
|
-
},
|
|
1930
|
-
coerce(value) {
|
|
1931
|
-
if (value === null || value === void 0) {
|
|
1932
|
-
return options.default ?? null;
|
|
1933
|
-
}
|
|
1934
|
-
if (typeof value === "string" && validIds.has(value)) {
|
|
1935
|
-
return value;
|
|
1936
|
-
}
|
|
1937
|
-
const byName = options.options.find(
|
|
1938
|
-
(o) => o.name.toLowerCase() === String(value).toLowerCase()
|
|
1939
|
-
);
|
|
1940
|
-
if (byName) return byName.id;
|
|
1941
|
-
return null;
|
|
1942
|
-
},
|
|
1943
|
-
_type: ""
|
|
1944
|
-
};
|
|
1945
|
-
}
|
|
1946
|
-
|
|
1947
|
-
// src/schema/properties/multiSelect.ts
|
|
1948
|
-
function multiSelect(options) {
|
|
1949
|
-
const validIds = new Set(options.options.map((o) => o.id));
|
|
1950
|
-
return {
|
|
1951
|
-
definition: {
|
|
1952
|
-
type: "multiSelect",
|
|
1953
|
-
required: options.required ?? false,
|
|
1954
|
-
config: {
|
|
1955
|
-
options: options.options,
|
|
1956
|
-
default: options.default
|
|
1957
|
-
}
|
|
1958
|
-
},
|
|
1959
|
-
validate(value) {
|
|
1960
|
-
if (value === null || value === void 0) {
|
|
1961
|
-
return !options.required;
|
|
1962
|
-
}
|
|
1963
|
-
if (!Array.isArray(value)) return false;
|
|
1964
|
-
return value.every((v) => typeof v === "string" && validIds.has(v));
|
|
1965
|
-
},
|
|
1966
|
-
coerce(value) {
|
|
1967
|
-
if (value === null || value === void 0) {
|
|
1968
|
-
return options.default ?? [];
|
|
1969
|
-
}
|
|
1970
|
-
if (!Array.isArray(value)) {
|
|
1971
|
-
if (typeof value === "string" && validIds.has(value)) {
|
|
1972
|
-
return [value];
|
|
1973
|
-
}
|
|
1974
|
-
return null;
|
|
1975
|
-
}
|
|
1976
|
-
const result = [];
|
|
1977
|
-
for (const v of value) {
|
|
1978
|
-
if (typeof v === "string" && validIds.has(v)) {
|
|
1979
|
-
result.push(v);
|
|
1980
|
-
} else {
|
|
1981
|
-
const byName = options.options.find(
|
|
1982
|
-
(o) => o.name.toLowerCase() === String(v).toLowerCase()
|
|
1983
|
-
);
|
|
1984
|
-
if (byName) result.push(byName.id);
|
|
1985
|
-
}
|
|
1986
|
-
}
|
|
1987
|
-
return result;
|
|
1988
|
-
},
|
|
1989
|
-
_type: []
|
|
1990
|
-
};
|
|
1991
|
-
}
|
|
1992
|
-
|
|
1993
|
-
// src/schema/properties/person.ts
|
|
1994
|
-
var DID_PATTERN = /^did:[a-z]+:[a-zA-Z0-9._:-]+$/;
|
|
1995
|
-
function person(options = {}) {
|
|
1996
|
-
const isMultiple = options.multiple ?? false;
|
|
1997
|
-
return {
|
|
1998
|
-
definition: {
|
|
1999
|
-
type: "person",
|
|
2000
|
-
required: options.required ?? false,
|
|
2001
|
-
config: {
|
|
2002
|
-
multiple: isMultiple
|
|
2003
|
-
}
|
|
2004
|
-
},
|
|
2005
|
-
validate(value) {
|
|
2006
|
-
if (value === null || value === void 0) {
|
|
2007
|
-
return !options.required;
|
|
2008
|
-
}
|
|
2009
|
-
if (isMultiple) {
|
|
2010
|
-
if (!Array.isArray(value)) return false;
|
|
2011
|
-
return value.every((v) => typeof v === "string" && DID_PATTERN.test(v));
|
|
2012
|
-
} else {
|
|
2013
|
-
return typeof value === "string" && DID_PATTERN.test(value);
|
|
2014
|
-
}
|
|
2015
|
-
},
|
|
2016
|
-
coerce(value) {
|
|
2017
|
-
if (value === null || value === void 0) {
|
|
2018
|
-
return isMultiple ? [] : null;
|
|
2019
|
-
}
|
|
2020
|
-
if (isMultiple) {
|
|
2021
|
-
const arr = Array.isArray(value) ? value : [value];
|
|
2022
|
-
return arr.filter((v) => typeof v === "string" && DID_PATTERN.test(v));
|
|
2023
|
-
} else {
|
|
2024
|
-
if (typeof value === "string" && DID_PATTERN.test(value)) {
|
|
2025
|
-
return value;
|
|
2026
|
-
}
|
|
2027
|
-
return null;
|
|
2028
|
-
}
|
|
2029
|
-
},
|
|
2030
|
-
_type: isMultiple ? [] : ""
|
|
2031
|
-
};
|
|
2032
|
-
}
|
|
2033
|
-
|
|
2034
|
-
// src/schema/properties/relation.ts
|
|
2035
|
-
function relation2(options = {}) {
|
|
2036
|
-
const isMultiple = options.multiple ?? false;
|
|
2037
|
-
return {
|
|
2038
|
-
definition: {
|
|
2039
|
-
type: "relation",
|
|
2040
|
-
required: options.required ?? false,
|
|
2041
|
-
config: {
|
|
2042
|
-
...options.target !== void 0 && { target: options.target },
|
|
2043
|
-
multiple: isMultiple
|
|
2044
|
-
}
|
|
2045
|
-
},
|
|
2046
|
-
validate(value) {
|
|
2047
|
-
if (value === null || value === void 0) {
|
|
2048
|
-
return !options.required;
|
|
2049
|
-
}
|
|
2050
|
-
if (isMultiple) {
|
|
2051
|
-
if (!Array.isArray(value)) return false;
|
|
2052
|
-
return value.every((v) => typeof v === "string" && v.length > 0);
|
|
2053
|
-
} else {
|
|
2054
|
-
return typeof value === "string" && value.length > 0;
|
|
2055
|
-
}
|
|
2056
|
-
},
|
|
2057
|
-
coerce(value) {
|
|
2058
|
-
if (value === null || value === void 0) {
|
|
2059
|
-
return isMultiple ? [] : null;
|
|
2060
|
-
}
|
|
2061
|
-
if (isMultiple) {
|
|
2062
|
-
const arr = Array.isArray(value) ? value : [value];
|
|
2063
|
-
return arr.filter((v) => typeof v === "string" && v.length > 0);
|
|
2064
|
-
} else {
|
|
2065
|
-
if (typeof value === "string" && value.length > 0) {
|
|
2066
|
-
return value;
|
|
2067
|
-
}
|
|
2068
|
-
return null;
|
|
2069
|
-
}
|
|
2070
|
-
},
|
|
2071
|
-
_type: isMultiple ? [] : ""
|
|
2072
|
-
};
|
|
2073
|
-
}
|
|
2074
|
-
|
|
2075
|
-
// src/schema/properties/url.ts
|
|
2076
|
-
var URL_PATTERN = /^https?:\/\/.+/i;
|
|
2077
|
-
function url(options = {}) {
|
|
2078
|
-
return {
|
|
2079
|
-
definition: {
|
|
2080
|
-
type: "url",
|
|
2081
|
-
required: options.required ?? false,
|
|
2082
|
-
config: {
|
|
2083
|
-
placeholder: options.placeholder
|
|
2084
|
-
}
|
|
2085
|
-
},
|
|
2086
|
-
validate(value) {
|
|
2087
|
-
if (value === null || value === void 0 || value === "") {
|
|
2088
|
-
return !options.required;
|
|
2089
|
-
}
|
|
2090
|
-
if (typeof value !== "string") return false;
|
|
2091
|
-
return URL_PATTERN.test(value);
|
|
2092
|
-
},
|
|
2093
|
-
coerce(value) {
|
|
2094
|
-
if (value === null || value === void 0 || value === "") return null;
|
|
2095
|
-
const str = String(value).trim();
|
|
2096
|
-
if (str && !str.match(/^https?:\/\//i)) {
|
|
2097
|
-
return `https://${str}`;
|
|
2098
|
-
}
|
|
2099
|
-
return str || null;
|
|
2100
|
-
},
|
|
2101
|
-
_type: ""
|
|
2102
|
-
};
|
|
2103
|
-
}
|
|
2104
|
-
|
|
2105
|
-
// src/schema/properties/email.ts
|
|
2106
|
-
var EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
2107
|
-
function email(options = {}) {
|
|
2108
|
-
return {
|
|
2109
|
-
definition: {
|
|
2110
|
-
type: "email",
|
|
2111
|
-
required: options.required ?? false,
|
|
2112
|
-
config: {
|
|
2113
|
-
placeholder: options.placeholder
|
|
2114
|
-
}
|
|
2115
|
-
},
|
|
2116
|
-
validate(value) {
|
|
2117
|
-
if (value === null || value === void 0 || value === "") {
|
|
2118
|
-
return !options.required;
|
|
2119
|
-
}
|
|
2120
|
-
if (typeof value !== "string") return false;
|
|
2121
|
-
return EMAIL_PATTERN.test(value);
|
|
2122
|
-
},
|
|
2123
|
-
coerce(value) {
|
|
2124
|
-
if (value === null || value === void 0 || value === "") return null;
|
|
2125
|
-
const str = String(value).trim().toLowerCase();
|
|
2126
|
-
return str || null;
|
|
2127
|
-
},
|
|
2128
|
-
_type: ""
|
|
2129
|
-
};
|
|
2130
|
-
}
|
|
2131
|
-
|
|
2132
|
-
// src/schema/properties/phone.ts
|
|
2133
|
-
var PHONE_PATTERN = /^[+]?[(]?[0-9]{1,4}[)]?[-\s./0-9]*$/;
|
|
2134
|
-
function phone(options = {}) {
|
|
2135
|
-
return {
|
|
2136
|
-
definition: {
|
|
2137
|
-
type: "phone",
|
|
2138
|
-
required: options.required ?? false,
|
|
2139
|
-
config: {
|
|
2140
|
-
placeholder: options.placeholder
|
|
2141
|
-
}
|
|
2142
|
-
},
|
|
2143
|
-
validate(value) {
|
|
2144
|
-
if (value === null || value === void 0 || value === "") {
|
|
2145
|
-
return !options.required;
|
|
2146
|
-
}
|
|
2147
|
-
if (typeof value !== "string") return false;
|
|
2148
|
-
const digitCount = value.replace(/\D/g, "").length;
|
|
2149
|
-
return PHONE_PATTERN.test(value) && digitCount >= 7;
|
|
2150
|
-
},
|
|
2151
|
-
coerce(value) {
|
|
2152
|
-
if (value === null || value === void 0 || value === "") return null;
|
|
2153
|
-
const str = String(value).trim().replace(/\s+/g, " ");
|
|
2154
|
-
return str || null;
|
|
2155
|
-
},
|
|
2156
|
-
_type: ""
|
|
2157
|
-
};
|
|
2158
|
-
}
|
|
2159
|
-
|
|
2160
|
-
// src/schema/properties/file.ts
|
|
2161
|
-
function isValidFileRef(value) {
|
|
2162
|
-
if (typeof value !== "object" || value === null) return false;
|
|
2163
|
-
const obj = value;
|
|
2164
|
-
return typeof obj.cid === "string" && typeof obj.name === "string" && typeof obj.mimeType === "string" && typeof obj.size === "number";
|
|
2165
|
-
}
|
|
2166
|
-
function file(options = {}) {
|
|
2167
|
-
const isMultiple = options.multiple ?? false;
|
|
2168
|
-
return {
|
|
2169
|
-
definition: {
|
|
2170
|
-
type: "file",
|
|
2171
|
-
required: options.required ?? false,
|
|
2172
|
-
config: {
|
|
2173
|
-
multiple: isMultiple,
|
|
2174
|
-
accept: options.accept,
|
|
2175
|
-
maxSize: options.maxSize
|
|
2176
|
-
}
|
|
2177
|
-
},
|
|
2178
|
-
validate(value) {
|
|
2179
|
-
if (value === null || value === void 0) {
|
|
2180
|
-
return !options.required;
|
|
2181
|
-
}
|
|
2182
|
-
if (isMultiple) {
|
|
2183
|
-
if (!Array.isArray(value)) return false;
|
|
2184
|
-
return value.every(isValidFileRef);
|
|
2185
|
-
} else {
|
|
2186
|
-
return isValidFileRef(value);
|
|
2187
|
-
}
|
|
2188
|
-
},
|
|
2189
|
-
coerce(value) {
|
|
2190
|
-
if (value === null || value === void 0) {
|
|
2191
|
-
return isMultiple ? [] : null;
|
|
2192
|
-
}
|
|
2193
|
-
if (isMultiple) {
|
|
2194
|
-
const arr = Array.isArray(value) ? value : [value];
|
|
2195
|
-
return arr.filter(isValidFileRef);
|
|
2196
|
-
} else {
|
|
2197
|
-
if (isValidFileRef(value)) {
|
|
2198
|
-
return value;
|
|
2199
|
-
}
|
|
2200
|
-
return null;
|
|
2201
|
-
}
|
|
2202
|
-
},
|
|
2203
|
-
_type: isMultiple ? [] : {}
|
|
2204
|
-
};
|
|
2205
|
-
}
|
|
2206
|
-
|
|
2207
|
-
// src/schema/properties/created.ts
|
|
2208
|
-
function created(options = {}) {
|
|
2209
|
-
return {
|
|
2210
|
-
definition: {
|
|
2211
|
-
type: "created",
|
|
2212
|
-
required: false,
|
|
2213
|
-
// Auto-populated, not user-required
|
|
2214
|
-
config: {
|
|
2215
|
-
label: options.label,
|
|
2216
|
-
auto: true,
|
|
2217
|
-
readonly: true
|
|
2218
|
-
}
|
|
2219
|
-
},
|
|
2220
|
-
validate(value) {
|
|
2221
|
-
if (value === null || value === void 0) {
|
|
2222
|
-
return true;
|
|
2223
|
-
}
|
|
2224
|
-
return typeof value === "number" && value > 0 && Number.isFinite(value);
|
|
2225
|
-
},
|
|
2226
|
-
coerce(value) {
|
|
2227
|
-
if (value === null || value === void 0) {
|
|
2228
|
-
return Date.now();
|
|
2229
|
-
}
|
|
2230
|
-
if (typeof value === "number" && value > 0) {
|
|
2231
|
-
return value;
|
|
2232
|
-
}
|
|
2233
|
-
if (typeof value === "string") {
|
|
2234
|
-
const parsed = Date.parse(value);
|
|
2235
|
-
if (!isNaN(parsed)) return parsed;
|
|
2236
|
-
}
|
|
2237
|
-
return Date.now();
|
|
2238
|
-
},
|
|
2239
|
-
_type: 0
|
|
2240
|
-
};
|
|
2241
|
-
}
|
|
2242
|
-
|
|
2243
|
-
// src/schema/properties/updated.ts
|
|
2244
|
-
function updated(options = {}) {
|
|
2245
|
-
return {
|
|
2246
|
-
definition: {
|
|
2247
|
-
type: "updated",
|
|
2248
|
-
required: false,
|
|
2249
|
-
// Auto-populated, not user-required
|
|
2250
|
-
config: {
|
|
2251
|
-
label: options.label,
|
|
2252
|
-
auto: true,
|
|
2253
|
-
readonly: true
|
|
2254
|
-
}
|
|
2255
|
-
},
|
|
2256
|
-
validate(value) {
|
|
2257
|
-
if (value === null || value === void 0) {
|
|
2258
|
-
return true;
|
|
2259
|
-
}
|
|
2260
|
-
return typeof value === "number" && value > 0 && Number.isFinite(value);
|
|
2261
|
-
},
|
|
2262
|
-
coerce(value) {
|
|
2263
|
-
if (value === null || value === void 0) {
|
|
2264
|
-
return Date.now();
|
|
2265
|
-
}
|
|
2266
|
-
if (typeof value === "number" && value > 0) {
|
|
2267
|
-
return value;
|
|
2268
|
-
}
|
|
2269
|
-
if (typeof value === "string") {
|
|
2270
|
-
const parsed = Date.parse(value);
|
|
2271
|
-
if (!isNaN(parsed)) return parsed;
|
|
2272
|
-
}
|
|
2273
|
-
return Date.now();
|
|
2274
|
-
},
|
|
2275
|
-
_type: 0
|
|
2276
|
-
};
|
|
2277
|
-
}
|
|
2201
|
+
};
|
|
2278
2202
|
|
|
2279
|
-
// src/
|
|
2280
|
-
var
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
config: {
|
|
2288
|
-
label: options.label,
|
|
2289
|
-
auto: true,
|
|
2290
|
-
readonly: true
|
|
2291
|
-
}
|
|
2292
|
-
},
|
|
2293
|
-
validate(value) {
|
|
2294
|
-
if (value === null || value === void 0) {
|
|
2295
|
-
return true;
|
|
2296
|
-
}
|
|
2297
|
-
return typeof value === "string" && DID_PATTERN2.test(value);
|
|
2298
|
-
},
|
|
2299
|
-
coerce(value) {
|
|
2300
|
-
if (value === null || value === void 0) {
|
|
2301
|
-
return null;
|
|
2302
|
-
}
|
|
2303
|
-
if (typeof value === "string" && DID_PATTERN2.test(value)) {
|
|
2304
|
-
return value;
|
|
2305
|
-
}
|
|
2306
|
-
return null;
|
|
2307
|
-
},
|
|
2308
|
-
_type: ""
|
|
2309
|
-
};
|
|
2310
|
-
}
|
|
2203
|
+
// src/auth/rollout.ts
|
|
2204
|
+
var AUTH_FEATURE_FLAGS = {
|
|
2205
|
+
enforceLocal: false,
|
|
2206
|
+
enforceRemote: false,
|
|
2207
|
+
enforceHub: false,
|
|
2208
|
+
enforceEncryption: false,
|
|
2209
|
+
logDecisions: true
|
|
2210
|
+
};
|
|
2311
2211
|
|
|
2312
2212
|
export {
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2213
|
+
allow,
|
|
2214
|
+
deny,
|
|
2215
|
+
and,
|
|
2216
|
+
or,
|
|
2217
|
+
not,
|
|
2218
|
+
PUBLIC,
|
|
2219
|
+
AUTHENTICATED,
|
|
2220
|
+
role,
|
|
2221
|
+
relation,
|
|
2222
|
+
BUILTIN_ROLES,
|
|
2223
|
+
extractRoleRefs,
|
|
2224
|
+
countExpressionNodes,
|
|
2225
|
+
hasPublicAccess,
|
|
2226
|
+
MAX_EXPRESSION_NODES,
|
|
2227
|
+
validateAuthorization,
|
|
2228
|
+
serializeAuthExpression,
|
|
2229
|
+
deserializeAuthExpression,
|
|
2230
|
+
serializeAuthorization,
|
|
2231
|
+
deserializeAuthorization,
|
|
2232
|
+
presets,
|
|
2233
|
+
describeRoleResolver,
|
|
2234
|
+
buildPermissionMatrix,
|
|
2235
|
+
getAuthMode,
|
|
2236
|
+
warnLegacySchema,
|
|
2237
|
+
schemaToHubPolicy,
|
|
2238
|
+
hubActionsForSpaceRole,
|
|
2239
|
+
PUBLIC_CONTENT_KEY,
|
|
2240
|
+
PUBLIC_RECIPIENT,
|
|
2241
|
+
computeRecipients,
|
|
2242
|
+
hasRecipientsChanged,
|
|
2243
|
+
canTransitionToPublic,
|
|
2244
|
+
handleAuthMigration,
|
|
2318
2245
|
GRANT_SCHEMA_IRI,
|
|
2319
2246
|
isGrantActive,
|
|
2320
2247
|
GrantIndex,
|
|
@@ -2322,26 +2249,14 @@ export {
|
|
|
2322
2249
|
mergeOfflinePolicy,
|
|
2323
2250
|
DecisionCache,
|
|
2324
2251
|
DefaultRoleResolver,
|
|
2252
|
+
evaluateExpression,
|
|
2325
2253
|
DefaultPolicyEvaluator,
|
|
2254
|
+
createPolicyEvaluator,
|
|
2326
2255
|
GrantExpirationCleaner,
|
|
2327
2256
|
GrantRateLimiter,
|
|
2328
2257
|
StoreAuthError,
|
|
2329
2258
|
StoreAuth,
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
checkbox,
|
|
2334
|
-
date,
|
|
2335
|
-
dateRange,
|
|
2336
|
-
select,
|
|
2337
|
-
multiSelect,
|
|
2338
|
-
person,
|
|
2339
|
-
relation2 as relation,
|
|
2340
|
-
url,
|
|
2341
|
-
email,
|
|
2342
|
-
phone,
|
|
2343
|
-
file,
|
|
2344
|
-
created,
|
|
2345
|
-
updated,
|
|
2346
|
-
createdBy
|
|
2259
|
+
AuthMigrator,
|
|
2260
|
+
AUTH_FEATURE_FLAGS,
|
|
2261
|
+
AUTH_ACTIONS2 as AUTH_ACTIONS
|
|
2347
2262
|
};
|