@xnetjs/data 0.0.2 → 0.0.3
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-BZHETMC4.js +12 -0
- package/dist/auth/index.d.ts +413 -0
- package/dist/auth/index.js +102 -0
- package/dist/budget-WR2Q4YH3.js +12 -0
- package/dist/canvas-HFMMVQLD.js +10 -0
- package/dist/channel-DXDYJ3ZH.js +12 -0
- package/dist/chat-message-QUUQGXEW.js +10 -0
- package/dist/chunk-2DSDH2ZH.js +52 -0
- package/dist/chunk-337E3ZOB.js +54 -0
- package/dist/chunk-37267RSF.js +41 -0
- package/dist/chunk-3JKW6TDE.js +66 -0
- package/dist/chunk-3RB2FMMO.js +148 -0
- package/dist/{chunk-BQBPA5HS.js → chunk-4U4KBCDN.js} +16 -2
- package/dist/chunk-4UQTURCD.js +722 -0
- package/dist/chunk-54Y7QRZN.js +109 -0
- package/dist/chunk-5FHJE7RC.js +46 -0
- package/dist/chunk-64AM6JPM.js +40 -0
- package/dist/chunk-6CN53PON.js +143 -0
- package/dist/chunk-6GB7PBCR.js +40 -0
- package/dist/chunk-6OPZJ7MG.js +78 -0
- package/dist/{chunk-IDMBCRUC.js → chunk-77UDLVQY.js} +1 -1
- package/dist/chunk-7GMJOAPF.js +52 -0
- package/dist/chunk-7PDRDSFH.js +69 -0
- package/dist/chunk-C5IRSEEZ.js +69 -0
- package/dist/chunk-CHZUSBSB.js +45 -0
- package/dist/chunk-DOZJXPQ6.js +232 -0
- package/dist/chunk-DTPSTMUD.js +105 -0
- package/dist/chunk-E6S557NC.js +402 -0
- package/dist/chunk-EIQX7INF.js +45 -0
- package/dist/chunk-ENTHVNOC.js +57 -0
- package/dist/chunk-EZA5UT4Y.js +5543 -0
- package/dist/chunk-GR3NDMBY.js +76 -0
- package/dist/chunk-GWYVY2K4.js +63 -0
- package/dist/chunk-IJ675GJR.js +42 -0
- package/dist/chunk-IP4BEVVD.js +57 -0
- package/dist/chunk-J4IVJA5M.js +102 -0
- package/dist/chunk-J6EDPPH4.js +84 -0
- package/dist/{chunk-SZC345Z2.js → chunk-JESSJ5CZ.js} +1015 -1110
- package/dist/chunk-JO6Y4E5V.js +73 -0
- package/dist/chunk-KPVBKKZS.js +47 -0
- package/dist/chunk-KTJLFJEQ.js +43 -0
- package/dist/chunk-L66WXPTW.js +32 -0
- package/dist/chunk-LGD72FVD.js +48 -0
- package/dist/chunk-O64EK5FK.js +887 -0
- package/dist/chunk-PMUQACPY.js +33 -0
- package/dist/chunk-PSUJEKVV.js +1722 -0
- package/dist/chunk-QVZXXJSP.js +6122 -0
- package/dist/chunk-QWFTRZQT.js +142 -0
- package/dist/chunk-RSSZXCLL.js +51 -0
- package/dist/chunk-S4B5OPZU.js +415 -0
- package/dist/{chunk-VYR5GPJP.js → chunk-SPFKSTPT.js} +9 -3
- package/dist/chunk-U6CBYDVZ.js +50 -0
- package/dist/chunk-UDUPQKRB.js +49 -0
- package/dist/chunk-V6HZ3LF3.js +380 -0
- package/dist/chunk-WMSCEBFH.js +64 -0
- package/dist/chunk-WUR3WWQM.js +65 -0
- package/dist/chunk-XHEA5UER.js +75 -0
- package/dist/chunk-XTUGZSYQ.js +75 -0
- package/dist/chunk-XWA3CJQ6.js +45 -0
- package/dist/chunk-YEQ6LLUY.js +49 -0
- package/dist/chunk-YWN27MLD.js +47 -0
- package/dist/chunk-YXZGYLKV.js +65 -0
- package/dist/chunk-ZICIXDEE.js +54 -0
- package/dist/chunk-ZMCO3O2Z.js +70 -0
- package/dist/clone-DpbVKLUu.d.ts +2205 -0
- package/dist/comment-Y2Y3QYYI.js +10 -0
- package/dist/crm-AAJXYIJY.js +68 -0
- package/dist/dashboard-3DC6B4BJ.js +10 -0
- package/dist/database/index.d.ts +882 -0
- package/dist/database/index.js +523 -0
- package/dist/database-DRGL5UYS.js +10 -0
- package/dist/database-field-F47FY3XX.js +10 -0
- package/dist/database-row-77OXW2NF.js +10 -0
- package/dist/database-select-option-6S3TTA7A.js +10 -0
- package/dist/database-view-YW7DQG5X.js +10 -0
- package/dist/experiment-TJOAL3OH.js +10 -0
- package/dist/external-item-D654PKLI.js +14 -0
- package/dist/external-reference-K5SUJIQN.js +8 -0
- package/dist/feed-L23XTNDI.js +12 -0
- package/dist/feed-item-GSO242J6.js +12 -0
- package/dist/folder-ONKSWHUT.js +21 -0
- package/dist/game-BF7DP5JU.js +50 -0
- package/dist/grant-NNKMWXPX.js +8 -0
- package/dist/grant-expiration-cleaner-DBiNTWbD.d.ts +249 -0
- package/dist/import-batch-JMESZ4VI.js +12 -0
- package/dist/inbox-state-AN6FPCIY.js +12 -0
- package/dist/index.d.ts +224 -4548
- package/dist/index.js +2206 -4754
- package/dist/map-AUV65YS3.js +10 -0
- package/dist/media-asset-4I5YPR4R.js +8 -0
- package/dist/memory-NV6EVJBE.js +12 -0
- package/dist/metric-X7W55DLC.js +10 -0
- package/dist/milestone-JJGOGK4R.js +12 -0
- package/dist/moderation-QSUVBCNS.js +30 -0
- package/dist/observation-UZSXXGBC.js +10 -0
- package/dist/page-NI7XODS6.js +10 -0
- package/dist/posting-FZOYVGH2.js +12 -0
- package/dist/profile-42A43GES.js +8 -0
- package/dist/project-G2CTLDXY.js +10 -0
- package/dist/query-ast-CWo6_30h.d.ts +226 -0
- package/dist/reaction-VAQBVDUD.js +10 -0
- package/dist/registry-CQoN_p8U.d.ts +99 -0
- package/dist/saved-view-AOQHGHNM.js +10 -0
- package/dist/schema/index.d.ts +6182 -0
- package/dist/schema/index.js +631 -0
- package/dist/schema-extension-5BUXRDPB.js +18 -0
- package/dist/space-PZ737CIS.js +42 -0
- package/dist/space-membership-N35NRX6F.js +14 -0
- package/dist/store/index.d.ts +391 -0
- package/dist/store/index.js +135 -0
- package/dist/store-CvgiydME.d.ts +338 -0
- package/dist/sync/awareness.d.ts +69 -0
- package/dist/sync/awareness.js +20 -0
- package/dist/system-Q5I6STDC.js +38 -0
- package/dist/tag-C677GKQW.js +16 -0
- package/dist/task-WQLMPN5U.js +16 -0
- package/dist/task-view-T5S32V4L.js +10 -0
- package/dist/transaction-U3ITRGJP.js +12 -0
- package/dist/transcription-F3T23LLO.js +12 -0
- package/dist/types-Def-5Nka.d.ts +1525 -0
- package/dist/updates.d.ts +44 -0
- package/dist/updates.js +16 -0
- package/dist/user-widget-4SJJFPG5.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,622 @@ 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
|
+
async function computeRecipients(schema, node, dependencies) {
|
|
622
|
+
const recipients = /* @__PURE__ */ new Set();
|
|
623
|
+
recipients.add(node.createdBy);
|
|
624
|
+
const addGrantRecipients = () => {
|
|
625
|
+
const grants = dependencies.grantIndex?.findGrantsForResource(node.id) ?? [];
|
|
626
|
+
for (const grant of grants) {
|
|
627
|
+
const actions = parseGrantActions(grant.properties.actions);
|
|
628
|
+
if (actions.includes("read") || actions.includes("write")) {
|
|
629
|
+
const grantee = grant.properties.grantee;
|
|
630
|
+
if (typeof grantee === "string" && grantee.startsWith("did:key:")) {
|
|
631
|
+
recipients.add(grantee);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
if (!schema.authorization) {
|
|
637
|
+
addGrantRecipients();
|
|
638
|
+
return [...recipients];
|
|
639
|
+
}
|
|
640
|
+
const auth = deserializeAuthorization(schema.authorization);
|
|
641
|
+
const readExpr = auth.actions.read;
|
|
642
|
+
if (!readExpr) {
|
|
643
|
+
addGrantRecipients();
|
|
644
|
+
return [...recipients];
|
|
645
|
+
}
|
|
646
|
+
if (hasPublicAccess(readExpr)) {
|
|
647
|
+
return [PUBLIC_RECIPIENT];
|
|
648
|
+
}
|
|
649
|
+
const readRoles = extractRoleRefs(readExpr);
|
|
650
|
+
for (const roleName of readRoles) {
|
|
651
|
+
const resolver = auth.roles[roleName];
|
|
652
|
+
if (!resolver) continue;
|
|
653
|
+
const members = await resolveRoleMembers(resolver, node, schema, dependencies, 0);
|
|
654
|
+
for (const did of members) {
|
|
655
|
+
recipients.add(did);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
addGrantRecipients();
|
|
659
|
+
return [...recipients];
|
|
660
|
+
}
|
|
661
|
+
async function resolveRoleMembers(resolver, node, _schema, dependencies, depth) {
|
|
662
|
+
if (depth > (dependencies.maxDepth ?? 3)) {
|
|
663
|
+
return [];
|
|
664
|
+
}
|
|
665
|
+
switch (resolver._tag) {
|
|
666
|
+
case "creator":
|
|
667
|
+
return [node.createdBy];
|
|
668
|
+
case "property":
|
|
669
|
+
return readDidProperty(node.properties[resolver.propertyName]);
|
|
670
|
+
case "relation": {
|
|
671
|
+
const targetId = node.properties[resolver.relationName];
|
|
672
|
+
if (typeof targetId !== "string") {
|
|
673
|
+
return [];
|
|
674
|
+
}
|
|
675
|
+
const targetNode = await dependencies.getNode(targetId);
|
|
676
|
+
if (!targetNode || !dependencies.getSchema) {
|
|
677
|
+
return [];
|
|
678
|
+
}
|
|
679
|
+
const targetSchema = await dependencies.getSchema(targetNode.schemaId);
|
|
680
|
+
if (!targetSchema?.authorization) {
|
|
681
|
+
return [];
|
|
682
|
+
}
|
|
683
|
+
const targetAuth = deserializeAuthorization(targetSchema.authorization);
|
|
684
|
+
const targetResolver = targetAuth.roles[resolver.targetRole];
|
|
685
|
+
if (!targetResolver) {
|
|
686
|
+
return [];
|
|
687
|
+
}
|
|
688
|
+
return resolveRoleMembers(targetResolver, targetNode, targetSchema, dependencies, depth + 1);
|
|
689
|
+
}
|
|
690
|
+
case "membership": {
|
|
691
|
+
if (!dependencies.listNodes) {
|
|
692
|
+
return [];
|
|
693
|
+
}
|
|
694
|
+
const containerIds = await collectContainerIds(node, resolver.parentProp, dependencies);
|
|
695
|
+
const edges = await dependencies.listNodes(resolver.edgeSchema);
|
|
696
|
+
const members = /* @__PURE__ */ new Set();
|
|
697
|
+
for (const edge of edges) {
|
|
698
|
+
if (edge.deleted) continue;
|
|
699
|
+
const container = edge.properties[resolver.containerProp];
|
|
700
|
+
if (typeof container !== "string" || !containerIds.has(container)) continue;
|
|
701
|
+
const edgeRole = edge.properties[resolver.roleProp];
|
|
702
|
+
if (typeof edgeRole !== "string") continue;
|
|
703
|
+
const have = resolver.roleOrder.indexOf(edgeRole);
|
|
704
|
+
const need = resolver.roleOrder.indexOf(resolver.minRole);
|
|
705
|
+
if (have < 0 || need < 0 || have < need) continue;
|
|
706
|
+
for (const did of readDidProperty(edge.properties[resolver.memberProp])) {
|
|
707
|
+
members.add(did);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
return [...members];
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
async function collectContainerIds(node, parentProp, dependencies) {
|
|
715
|
+
const ids = /* @__PURE__ */ new Set([node.id]);
|
|
716
|
+
if (!parentProp) {
|
|
717
|
+
return ids;
|
|
718
|
+
}
|
|
719
|
+
let current = node;
|
|
720
|
+
let depth = 0;
|
|
721
|
+
while (current && depth < MAX_CONTAINER_DEPTH) {
|
|
722
|
+
const parentId = current.properties[parentProp];
|
|
723
|
+
if (typeof parentId !== "string" || ids.has(parentId)) {
|
|
724
|
+
break;
|
|
725
|
+
}
|
|
726
|
+
ids.add(parentId);
|
|
727
|
+
current = await dependencies.getNode(parentId);
|
|
728
|
+
depth += 1;
|
|
729
|
+
}
|
|
730
|
+
return ids;
|
|
731
|
+
}
|
|
732
|
+
function readDidProperty(value) {
|
|
733
|
+
if (Array.isArray(value)) {
|
|
734
|
+
return value.filter(isDid);
|
|
735
|
+
}
|
|
736
|
+
if (isDid(value)) {
|
|
737
|
+
return [value];
|
|
738
|
+
}
|
|
739
|
+
return [];
|
|
740
|
+
}
|
|
741
|
+
function isDid(value) {
|
|
742
|
+
return typeof value === "string" && value.startsWith("did:key:");
|
|
743
|
+
}
|
|
744
|
+
function parseGrantActions(value) {
|
|
745
|
+
if (Array.isArray(value)) {
|
|
746
|
+
return value.filter((entry) => typeof entry === "string");
|
|
747
|
+
}
|
|
748
|
+
if (typeof value !== "string") {
|
|
749
|
+
return [];
|
|
750
|
+
}
|
|
751
|
+
try {
|
|
752
|
+
const parsed = JSON.parse(value);
|
|
753
|
+
if (!Array.isArray(parsed)) {
|
|
754
|
+
return [];
|
|
755
|
+
}
|
|
756
|
+
return parsed.filter((entry) => typeof entry === "string");
|
|
757
|
+
} catch {
|
|
758
|
+
return [];
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
function hasRecipientsChanged(previous, next) {
|
|
762
|
+
if (previous.length !== next.length) {
|
|
763
|
+
return true;
|
|
764
|
+
}
|
|
765
|
+
const nextSet = new Set(next);
|
|
766
|
+
return previous.some((recipient) => !nextSet.has(recipient));
|
|
767
|
+
}
|
|
768
|
+
function canTransitionToPublic(readExpr) {
|
|
769
|
+
if (!readExpr) {
|
|
770
|
+
return false;
|
|
771
|
+
}
|
|
772
|
+
return hasPublicAccess(readExpr);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// src/auth/migration.ts
|
|
776
|
+
async function handleAuthMigration(oldSchema, newSchema, node, dependencies) {
|
|
777
|
+
const oldAuth = oldSchema.authorization;
|
|
778
|
+
const newAuth = newSchema.authorization;
|
|
779
|
+
if (!oldAuth && newAuth) {
|
|
780
|
+
await dependencies.encryptExistingNode(node, newSchema);
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
if (oldAuth && !newAuth) {
|
|
784
|
+
console.warn(
|
|
785
|
+
`[xnet:auth] Schema '${newSchema.name}' removed authorization block. Existing nodes remain encrypted until manually migrated.`
|
|
786
|
+
);
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
if (!oldAuth || !newAuth) {
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
const oldRecipients = await computeRecipients(oldSchema, node, dependencies);
|
|
793
|
+
const newRecipients = await computeRecipients(newSchema, node, dependencies);
|
|
794
|
+
if (!hasRecipientsChanged(oldRecipients, newRecipients)) {
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
await dependencies.rotateContentKeyForNode(node.id, newRecipients);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// src/auth/grants.ts
|
|
801
|
+
var GRANT_SCHEMA_IRI = "xnet://xnet.fyi/Grant";
|
|
802
|
+
function isGrantActive(grant, now = Date.now()) {
|
|
803
|
+
const revokedAt = asNumber(grant.properties.revokedAt);
|
|
804
|
+
if (revokedAt > 0) {
|
|
805
|
+
return false;
|
|
806
|
+
}
|
|
807
|
+
const expiresAt = asNumber(grant.properties.expiresAt);
|
|
808
|
+
if (expiresAt > 0 && expiresAt <= now) {
|
|
809
|
+
return false;
|
|
810
|
+
}
|
|
811
|
+
return true;
|
|
812
|
+
}
|
|
813
|
+
var GrantIndex = class {
|
|
814
|
+
constructor(store, options = {}) {
|
|
815
|
+
this.store = store;
|
|
816
|
+
this.schemaId = options.schemaId ?? GRANT_SCHEMA_IRI;
|
|
817
|
+
this.clock = options.clock ?? Date.now;
|
|
818
|
+
}
|
|
819
|
+
byResourceAndGrantee = /* @__PURE__ */ new Map();
|
|
820
|
+
byResource = /* @__PURE__ */ new Map();
|
|
821
|
+
grantsById = /* @__PURE__ */ new Map();
|
|
822
|
+
unsubscribe = null;
|
|
823
|
+
schemaId;
|
|
824
|
+
clock;
|
|
825
|
+
async initialize() {
|
|
826
|
+
const grants = await this.loadGrantNodes();
|
|
827
|
+
for (const grant of grants) {
|
|
828
|
+
this.indexGrant(grant);
|
|
829
|
+
}
|
|
830
|
+
this.unsubscribe = this.store.subscribe((event) => {
|
|
831
|
+
const node = event.node;
|
|
832
|
+
if (!node || !this.isGrantSchema(node.schemaId)) {
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
this.removeGrant(node.id);
|
|
836
|
+
if (!node.deleted) {
|
|
837
|
+
this.indexGrant(node);
|
|
838
|
+
}
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
findGrants(resource, grantee) {
|
|
842
|
+
const grantsByGrantee = this.byResourceAndGrantee.get(resource);
|
|
843
|
+
if (!grantsByGrantee) return [];
|
|
844
|
+
const grantIds = grantsByGrantee.get(grantee);
|
|
845
|
+
if (!grantIds) return [];
|
|
846
|
+
const now = this.clock();
|
|
847
|
+
const grants = [];
|
|
848
|
+
for (const grantId of grantIds) {
|
|
849
|
+
const grant = this.grantsById.get(grantId);
|
|
850
|
+
if (grant && isGrantActive(grant, now)) {
|
|
851
|
+
grants.push(grant);
|
|
422
852
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
count += countExpressionNodes(expr.expr);
|
|
426
|
-
break;
|
|
853
|
+
}
|
|
854
|
+
return grants;
|
|
427
855
|
}
|
|
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
|
-
});
|
|
856
|
+
findGrantsForResource(resource) {
|
|
857
|
+
const grantIds = this.byResource.get(resource);
|
|
858
|
+
if (!grantIds) return [];
|
|
859
|
+
const now = this.clock();
|
|
860
|
+
const grants = [];
|
|
861
|
+
for (const grantId of grantIds) {
|
|
862
|
+
const grant = this.grantsById.get(grantId);
|
|
863
|
+
if (grant && isGrantActive(grant, now)) {
|
|
864
|
+
grants.push(grant);
|
|
449
865
|
}
|
|
450
866
|
}
|
|
867
|
+
return grants;
|
|
451
868
|
}
|
|
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
|
-
});
|
|
869
|
+
findAllGrantsForResource(resource) {
|
|
870
|
+
const grantIds = this.byResource.get(resource);
|
|
871
|
+
if (!grantIds) return [];
|
|
872
|
+
const grants = [];
|
|
873
|
+
for (const grantId of grantIds) {
|
|
874
|
+
const grant = this.grantsById.get(grantId);
|
|
875
|
+
if (grant) {
|
|
876
|
+
grants.push(grant);
|
|
467
877
|
}
|
|
468
878
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
});
|
|
879
|
+
return grants;
|
|
880
|
+
}
|
|
881
|
+
findGrantsForGrantee(grantee) {
|
|
882
|
+
const grants = [];
|
|
883
|
+
const now = this.clock();
|
|
884
|
+
for (const grantsByGrantee of this.byResourceAndGrantee.values()) {
|
|
885
|
+
const grantIds = grantsByGrantee.get(grantee);
|
|
886
|
+
if (!grantIds) continue;
|
|
887
|
+
for (const grantId of grantIds) {
|
|
888
|
+
const grant = this.grantsById.get(grantId);
|
|
889
|
+
if (grant && isGrantActive(grant, now)) {
|
|
890
|
+
grants.push(grant);
|
|
891
|
+
}
|
|
483
892
|
}
|
|
484
893
|
}
|
|
894
|
+
return grants;
|
|
485
895
|
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
896
|
+
dispose() {
|
|
897
|
+
this.unsubscribe?.();
|
|
898
|
+
this.unsubscribe = null;
|
|
899
|
+
this.byResourceAndGrantee.clear();
|
|
900
|
+
this.byResource.clear();
|
|
901
|
+
this.grantsById.clear();
|
|
902
|
+
}
|
|
903
|
+
async loadGrantNodes() {
|
|
904
|
+
const directMatches = await this.store.list({ schemaId: this.schemaId, includeDeleted: false });
|
|
905
|
+
if (directMatches.length > 0 || this.schemaId.includes("@")) {
|
|
906
|
+
return directMatches;
|
|
495
907
|
}
|
|
908
|
+
const allNodes = await this.store.list({ includeDeleted: false });
|
|
909
|
+
return allNodes.filter((node) => this.isGrantSchema(node.schemaId));
|
|
496
910
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
911
|
+
isGrantSchema(schemaId) {
|
|
912
|
+
return schemaId === this.schemaId || schemaId.startsWith(`${this.schemaId}@`);
|
|
913
|
+
}
|
|
914
|
+
indexGrant(node) {
|
|
915
|
+
const resource = asString(node.properties.resource);
|
|
916
|
+
const grantee = asDid(node.properties.grantee);
|
|
917
|
+
if (!resource || !grantee) {
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
const grant = {
|
|
921
|
+
id: node.id,
|
|
922
|
+
properties: node.properties,
|
|
923
|
+
deleted: node.deleted
|
|
924
|
+
};
|
|
925
|
+
this.grantsById.set(node.id, grant);
|
|
926
|
+
let byGrantee = this.byResourceAndGrantee.get(resource);
|
|
927
|
+
if (!byGrantee) {
|
|
928
|
+
byGrantee = /* @__PURE__ */ new Map();
|
|
929
|
+
this.byResourceAndGrantee.set(resource, byGrantee);
|
|
930
|
+
}
|
|
931
|
+
let grantIdsForGrantee = byGrantee.get(grantee);
|
|
932
|
+
if (!grantIdsForGrantee) {
|
|
933
|
+
grantIdsForGrantee = /* @__PURE__ */ new Set();
|
|
934
|
+
byGrantee.set(grantee, grantIdsForGrantee);
|
|
935
|
+
}
|
|
936
|
+
grantIdsForGrantee.add(node.id);
|
|
937
|
+
let grantIdsForResource = this.byResource.get(resource);
|
|
938
|
+
if (!grantIdsForResource) {
|
|
939
|
+
grantIdsForResource = /* @__PURE__ */ new Set();
|
|
940
|
+
this.byResource.set(resource, grantIdsForResource);
|
|
505
941
|
}
|
|
942
|
+
grantIdsForResource.add(node.id);
|
|
506
943
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
944
|
+
removeGrant(grantId) {
|
|
945
|
+
const existing = this.grantsById.get(grantId);
|
|
946
|
+
if (!existing) {
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
const resource = asString(existing.properties.resource);
|
|
950
|
+
const grantee = asDid(existing.properties.grantee);
|
|
951
|
+
this.grantsById.delete(grantId);
|
|
952
|
+
if (!resource || !grantee) {
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
const byGrantee = this.byResourceAndGrantee.get(resource);
|
|
956
|
+
if (byGrantee) {
|
|
957
|
+
const grantIds = byGrantee.get(grantee);
|
|
958
|
+
if (grantIds) {
|
|
959
|
+
grantIds.delete(grantId);
|
|
960
|
+
if (grantIds.size === 0) {
|
|
961
|
+
byGrantee.delete(grantee);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
if (byGrantee.size === 0) {
|
|
965
|
+
this.byResourceAndGrantee.delete(resource);
|
|
515
966
|
}
|
|
516
967
|
}
|
|
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
|
-
});
|
|
968
|
+
const byResource = this.byResource.get(resource);
|
|
969
|
+
if (byResource) {
|
|
970
|
+
byResource.delete(grantId);
|
|
971
|
+
if (byResource.size === 0) {
|
|
972
|
+
this.byResource.delete(resource);
|
|
973
|
+
}
|
|
527
974
|
}
|
|
528
975
|
}
|
|
529
|
-
|
|
976
|
+
};
|
|
977
|
+
function asDid(value) {
|
|
978
|
+
if (typeof value === "string" && value.startsWith("did:key:")) {
|
|
979
|
+
return value;
|
|
980
|
+
}
|
|
981
|
+
return null;
|
|
982
|
+
}
|
|
983
|
+
function asString(value) {
|
|
984
|
+
return typeof value === "string" ? value : null;
|
|
985
|
+
}
|
|
986
|
+
function asNumber(value) {
|
|
987
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
// src/auth/offline-policy.ts
|
|
991
|
+
var DEFAULT_OFFLINE_POLICY = {
|
|
992
|
+
decisionCacheTTL: 5 * 60 * 1e3,
|
|
993
|
+
maxStaleness: 60 * 60 * 1e3,
|
|
994
|
+
revalidation: "hybrid",
|
|
995
|
+
allowOfflineGrants: true
|
|
996
|
+
};
|
|
997
|
+
function mergeOfflinePolicy(current, patch) {
|
|
998
|
+
return {
|
|
999
|
+
...current,
|
|
1000
|
+
...patch
|
|
1001
|
+
};
|
|
530
1002
|
}
|
|
531
1003
|
|
|
532
1004
|
// src/auth/evaluator.ts
|
|
1005
|
+
var MAX_CONTAINER_DEPTH2 = 32;
|
|
533
1006
|
var DecisionCache = class {
|
|
534
1007
|
cache = /* @__PURE__ */ new Map();
|
|
535
1008
|
ttlMs;
|
|
@@ -632,7 +1105,7 @@ var DefaultRoleResolver = class {
|
|
|
632
1105
|
case "creator":
|
|
633
1106
|
return [node.createdBy];
|
|
634
1107
|
case "property":
|
|
635
|
-
return
|
|
1108
|
+
return readDidProperty2(node.properties[resolver.propertyName]);
|
|
636
1109
|
case "relation": {
|
|
637
1110
|
const relatedNodes = await this.loadRelatedNodes(node, resolver.relationName);
|
|
638
1111
|
if (relatedNodes.length === 0) {
|
|
@@ -661,6 +1134,20 @@ var DefaultRoleResolver = class {
|
|
|
661
1134
|
}
|
|
662
1135
|
return [...members];
|
|
663
1136
|
}
|
|
1137
|
+
case "membership": {
|
|
1138
|
+
const containerIds = await this.collectContainerIds(node, resolver.parentProp);
|
|
1139
|
+
const edges = await this.store.list({ schemaId: resolver.edgeSchema });
|
|
1140
|
+
const members = /* @__PURE__ */ new Set();
|
|
1141
|
+
for (const edge of edges) {
|
|
1142
|
+
if (edge.deleted) continue;
|
|
1143
|
+
if (!this.membershipMatchesContainer(edge, resolver, containerIds)) continue;
|
|
1144
|
+
if (!membershipRoleAtLeast(edge.properties[resolver.roleProp], resolver)) continue;
|
|
1145
|
+
for (const did of readDidProperty2(edge.properties[resolver.memberProp])) {
|
|
1146
|
+
members.add(did);
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
return [...members];
|
|
1150
|
+
}
|
|
664
1151
|
}
|
|
665
1152
|
}
|
|
666
1153
|
async checkRole(did, resolver, node, depth, visited) {
|
|
@@ -676,7 +1163,7 @@ var DefaultRoleResolver = class {
|
|
|
676
1163
|
case "creator":
|
|
677
1164
|
return did === node.createdBy;
|
|
678
1165
|
case "property":
|
|
679
|
-
return
|
|
1166
|
+
return readDidProperty2(node.properties[resolver.propertyName]).includes(did);
|
|
680
1167
|
case "relation": {
|
|
681
1168
|
const relatedNodes = await this.loadRelatedNodes(node, resolver.relationName);
|
|
682
1169
|
for (const relatedNode of relatedNodes) {
|
|
@@ -696,7 +1183,47 @@ var DefaultRoleResolver = class {
|
|
|
696
1183
|
}
|
|
697
1184
|
return false;
|
|
698
1185
|
}
|
|
1186
|
+
case "membership": {
|
|
1187
|
+
const containerIds = await this.collectContainerIds(node, resolver.parentProp);
|
|
1188
|
+
const edges = await this.store.list({ schemaId: resolver.edgeSchema });
|
|
1189
|
+
for (const edge of edges) {
|
|
1190
|
+
if (edge.deleted) continue;
|
|
1191
|
+
if (!this.membershipMatchesContainer(edge, resolver, containerIds)) continue;
|
|
1192
|
+
if (!readDidProperty2(edge.properties[resolver.memberProp]).includes(did)) continue;
|
|
1193
|
+
if (membershipRoleAtLeast(edge.properties[resolver.roleProp], resolver)) {
|
|
1194
|
+
return true;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
return false;
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
membershipMatchesContainer(edge, resolver, containerIds) {
|
|
1202
|
+
const value = edge.properties[resolver.containerProp];
|
|
1203
|
+
return typeof value === "string" && containerIds.has(value);
|
|
1204
|
+
}
|
|
1205
|
+
/**
|
|
1206
|
+
* The node's own id plus the ids of every ancestor reachable by walking
|
|
1207
|
+
* `parentProp`. Cycle- and depth-bounded. When `parentProp` is undefined the
|
|
1208
|
+
* set is just the node itself (no inheritance).
|
|
1209
|
+
*/
|
|
1210
|
+
async collectContainerIds(node, parentProp) {
|
|
1211
|
+
const ids = /* @__PURE__ */ new Set([node.id]);
|
|
1212
|
+
if (!parentProp) {
|
|
1213
|
+
return ids;
|
|
1214
|
+
}
|
|
1215
|
+
let current = node;
|
|
1216
|
+
let depth = 0;
|
|
1217
|
+
while (current && depth < MAX_CONTAINER_DEPTH2) {
|
|
1218
|
+
const parentId = current.properties[parentProp];
|
|
1219
|
+
if (typeof parentId !== "string" || ids.has(parentId)) {
|
|
1220
|
+
break;
|
|
1221
|
+
}
|
|
1222
|
+
ids.add(parentId);
|
|
1223
|
+
current = await this.store.get(parentId);
|
|
1224
|
+
depth += 1;
|
|
699
1225
|
}
|
|
1226
|
+
return ids;
|
|
700
1227
|
}
|
|
701
1228
|
async loadRelatedNodes(node, relationName) {
|
|
702
1229
|
const value = node.properties[relationName];
|
|
@@ -766,7 +1293,8 @@ var DefaultPolicyEvaluator = class {
|
|
|
766
1293
|
this.emitDecision(decision2);
|
|
767
1294
|
return decision2;
|
|
768
1295
|
}
|
|
769
|
-
const
|
|
1296
|
+
const useCache = !input.patch;
|
|
1297
|
+
const cached = useCache ? this.cache.get(input.subject, input.action, input.nodeId) : void 0;
|
|
770
1298
|
if (cached) {
|
|
771
1299
|
const decision2 = {
|
|
772
1300
|
...cached,
|
|
@@ -789,15 +1317,24 @@ var DefaultPolicyEvaluator = class {
|
|
|
789
1317
|
return decision2;
|
|
790
1318
|
}
|
|
791
1319
|
const mode = getAuthMode(schema.schema);
|
|
792
|
-
if (mode === "legacy") {
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
1320
|
+
if (mode === "legacy" || !schema.schema.authorization) {
|
|
1321
|
+
if (node.createdBy === input.subject) {
|
|
1322
|
+
const decision3 = this.decision(input, true, ["owner"], start);
|
|
1323
|
+
if (useCache) this.cache.set(input.subject, input.action, input.nodeId, decision3);
|
|
1324
|
+
this.emitDecision(decision3);
|
|
1325
|
+
return decision3;
|
|
1326
|
+
}
|
|
1327
|
+
const grant2 = this.findMatchingGrant(
|
|
1328
|
+
input,
|
|
1329
|
+
this.grantIndex?.findGrants(input.nodeId, input.subject) ?? []
|
|
1330
|
+
);
|
|
1331
|
+
if (grant2) {
|
|
1332
|
+
const decision3 = this.decision(input, true, [], start, [grant2.id]);
|
|
1333
|
+
if (useCache) this.cache.set(input.subject, input.action, input.nodeId, decision3);
|
|
1334
|
+
this.emitDecision(decision3);
|
|
1335
|
+
return decision3;
|
|
1336
|
+
}
|
|
1337
|
+
const decision2 = this.deny(input, ["DENY_NO_ROLE_MATCH", "DENY_NO_GRANT"], start);
|
|
801
1338
|
this.emitDecision(decision2);
|
|
802
1339
|
return decision2;
|
|
803
1340
|
}
|
|
@@ -823,7 +1360,7 @@ var DefaultPolicyEvaluator = class {
|
|
|
823
1360
|
const allowedByRole = evaluateExpression(actionExpr, roles, true);
|
|
824
1361
|
if (allowedByRole) {
|
|
825
1362
|
const decision2 = this.decision(input, true, [...roles], start);
|
|
826
|
-
this.cache.set(input.subject, input.action, input.nodeId, decision2);
|
|
1363
|
+
if (useCache) this.cache.set(input.subject, input.action, input.nodeId, decision2);
|
|
827
1364
|
this.emitDecision(decision2);
|
|
828
1365
|
return decision2;
|
|
829
1366
|
}
|
|
@@ -833,7 +1370,7 @@ var DefaultPolicyEvaluator = class {
|
|
|
833
1370
|
);
|
|
834
1371
|
if (grant) {
|
|
835
1372
|
const decision2 = this.decision(input, true, [...roles], start, [grant.id]);
|
|
836
|
-
this.cache.set(input.subject, input.action, input.nodeId, decision2);
|
|
1373
|
+
if (useCache) this.cache.set(input.subject, input.action, input.nodeId, decision2);
|
|
837
1374
|
this.emitDecision(decision2);
|
|
838
1375
|
return decision2;
|
|
839
1376
|
}
|
|
@@ -1050,16 +1587,24 @@ function matchesDenyExpression(expr, roles, isAuthenticated) {
|
|
|
1050
1587
|
return false;
|
|
1051
1588
|
}
|
|
1052
1589
|
}
|
|
1053
|
-
function
|
|
1590
|
+
function readDidProperty2(value) {
|
|
1054
1591
|
if (Array.isArray(value)) {
|
|
1055
|
-
return value.filter(
|
|
1592
|
+
return value.filter(isDid2);
|
|
1056
1593
|
}
|
|
1057
|
-
if (
|
|
1594
|
+
if (isDid2(value)) {
|
|
1058
1595
|
return [value];
|
|
1059
1596
|
}
|
|
1060
1597
|
return [];
|
|
1061
1598
|
}
|
|
1062
|
-
function
|
|
1599
|
+
function membershipRoleAtLeast(edgeRole, resolver) {
|
|
1600
|
+
if (typeof edgeRole !== "string") {
|
|
1601
|
+
return false;
|
|
1602
|
+
}
|
|
1603
|
+
const have = resolver.roleOrder.indexOf(edgeRole);
|
|
1604
|
+
const need = resolver.roleOrder.indexOf(resolver.minRole);
|
|
1605
|
+
return have >= 0 && need >= 0 && have >= need;
|
|
1606
|
+
}
|
|
1607
|
+
function isDid2(value) {
|
|
1063
1608
|
return typeof value === "string" && value.startsWith("did:key:");
|
|
1064
1609
|
}
|
|
1065
1610
|
function parseActions(value) {
|
|
@@ -1079,6 +1624,20 @@ function parseActions(value) {
|
|
|
1079
1624
|
return [];
|
|
1080
1625
|
}
|
|
1081
1626
|
}
|
|
1627
|
+
async function createPolicyEvaluator(options) {
|
|
1628
|
+
const grantIndex = new GrantIndex(options.store, {
|
|
1629
|
+
schemaId: options.grantSchemaId
|
|
1630
|
+
});
|
|
1631
|
+
await grantIndex.initialize();
|
|
1632
|
+
return new DefaultPolicyEvaluator({
|
|
1633
|
+
store: options.store,
|
|
1634
|
+
schemaRegistry: options.schemaRegistry,
|
|
1635
|
+
grantIndex,
|
|
1636
|
+
cache: options.cache,
|
|
1637
|
+
maxDepth: options.maxDepth,
|
|
1638
|
+
maxNodes: options.maxNodes
|
|
1639
|
+
});
|
|
1640
|
+
}
|
|
1082
1641
|
|
|
1083
1642
|
// src/auth/grant-expiration-cleaner.ts
|
|
1084
1643
|
var GrantExpirationCleaner = class _GrantExpirationCleaner {
|
|
@@ -1261,7 +1820,7 @@ var StoreAuth = class {
|
|
|
1261
1820
|
expiresAt,
|
|
1262
1821
|
parentUcanToken: parseString(parentGrant?.properties.ucanToken) ?? void 0
|
|
1263
1822
|
});
|
|
1264
|
-
const
|
|
1823
|
+
const created = await this.options.store.create({
|
|
1265
1824
|
schemaId: GRANT_SCHEMA_IRI,
|
|
1266
1825
|
properties: {
|
|
1267
1826
|
issuer: this.options.actorDid,
|
|
@@ -1292,7 +1851,7 @@ var StoreAuth = class {
|
|
|
1292
1851
|
}
|
|
1293
1852
|
this.options.evaluator.invalidateSubject(input.to);
|
|
1294
1853
|
this.options.evaluator.invalidate(input.resource);
|
|
1295
|
-
const node = await this.getGrantNodeOrThrow(
|
|
1854
|
+
const node = await this.getGrantNodeOrThrow(created.id);
|
|
1296
1855
|
return toGrant(node);
|
|
1297
1856
|
}
|
|
1298
1857
|
async revoke(input) {
|
|
@@ -1578,743 +2137,101 @@ function parseDuration(value) {
|
|
|
1578
2137
|
}
|
|
1579
2138
|
}
|
|
1580
2139
|
|
|
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) {
|
|
2140
|
+
// src/auth/auth-migrator.ts
|
|
2141
|
+
var AuthMigrator = class {
|
|
2142
|
+
constructor(store, schemaRegistry, publicKeyResolver, grantIndex, encryptionLayer) {
|
|
2143
|
+
this.store = store;
|
|
2144
|
+
this.schemaRegistry = schemaRegistry;
|
|
2145
|
+
this.publicKeyResolver = publicKeyResolver;
|
|
2146
|
+
this.grantIndex = grantIndex;
|
|
2147
|
+
this.encryptionLayer = encryptionLayer;
|
|
2148
|
+
}
|
|
2149
|
+
async migrateSchema(schemaIri, options = {}) {
|
|
2150
|
+
const schema = await this.schemaRegistry.get(schemaIri);
|
|
2151
|
+
if (!schema?.schema.authorization) {
|
|
2152
|
+
throw new Error(`Schema ${schemaIri} has no authorization block`);
|
|
2153
|
+
}
|
|
2154
|
+
const nodes = await this.store.list({ schemaId: schemaIri, includeDeleted: false });
|
|
2155
|
+
const total = nodes.length;
|
|
2156
|
+
const batchSize = Math.max(1, options.batchSize ?? 100);
|
|
2157
|
+
let migrated = 0;
|
|
2158
|
+
let failed = 0;
|
|
1661
2159
|
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` });
|
|
2160
|
+
for (let index = 0; index < nodes.length; index += batchSize) {
|
|
2161
|
+
const batch = nodes.slice(index, index + batchSize);
|
|
2162
|
+
for (const node of batch) {
|
|
2163
|
+
try {
|
|
2164
|
+
const recipients = await computeRecipients(schema.schema, node, {
|
|
2165
|
+
getNode: (nodeId) => this.store.get(nodeId),
|
|
2166
|
+
getSchema: (schemaId) => this.resolveSchema(schemaId),
|
|
2167
|
+
grantIndex: this.grantIndex
|
|
2168
|
+
});
|
|
2169
|
+
const didRecipients = recipients.filter(
|
|
2170
|
+
(recipient) => recipient !== "PUBLIC"
|
|
2171
|
+
);
|
|
2172
|
+
const recipientKeys = await this.publicKeyResolver.resolveBatch(didRecipients);
|
|
2173
|
+
await this.encryptionLayer.encryptAndStoreNode(node, recipientKeys);
|
|
2174
|
+
migrated++;
|
|
2175
|
+
} catch (error) {
|
|
2176
|
+
failed++;
|
|
2177
|
+
errors.push({
|
|
2178
|
+
nodeId: node.id,
|
|
2179
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2180
|
+
});
|
|
1688
2181
|
}
|
|
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
2182
|
}
|
|
2183
|
+
options.onProgress?.(migrated + failed, total);
|
|
1710
2184
|
}
|
|
1711
|
-
return
|
|
2185
|
+
return { total, migrated, failed, errors };
|
|
1712
2186
|
}
|
|
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;
|
|
2187
|
+
async resolveSchema(schemaId) {
|
|
2188
|
+
const schema = await this.schemaRegistry.get(schemaId);
|
|
2189
|
+
return schema?.schema;
|
|
1863
2190
|
}
|
|
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
|
-
}
|
|
2191
|
+
};
|
|
2278
2192
|
|
|
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
|
-
}
|
|
2193
|
+
// src/auth/rollout.ts
|
|
2194
|
+
var AUTH_FEATURE_FLAGS = {
|
|
2195
|
+
enforceLocal: false,
|
|
2196
|
+
enforceRemote: false,
|
|
2197
|
+
enforceHub: false,
|
|
2198
|
+
enforceEncryption: false,
|
|
2199
|
+
logDecisions: true
|
|
2200
|
+
};
|
|
2311
2201
|
|
|
2312
2202
|
export {
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2203
|
+
allow,
|
|
2204
|
+
deny,
|
|
2205
|
+
and,
|
|
2206
|
+
or,
|
|
2207
|
+
not,
|
|
2208
|
+
PUBLIC,
|
|
2209
|
+
AUTHENTICATED,
|
|
2210
|
+
role,
|
|
2211
|
+
relation,
|
|
2212
|
+
BUILTIN_ROLES,
|
|
2213
|
+
extractRoleRefs,
|
|
2214
|
+
countExpressionNodes,
|
|
2215
|
+
hasPublicAccess,
|
|
2216
|
+
MAX_EXPRESSION_NODES,
|
|
2217
|
+
validateAuthorization,
|
|
2218
|
+
serializeAuthExpression,
|
|
2219
|
+
deserializeAuthExpression,
|
|
2220
|
+
serializeAuthorization,
|
|
2221
|
+
deserializeAuthorization,
|
|
2222
|
+
presets,
|
|
2223
|
+
describeRoleResolver,
|
|
2224
|
+
buildPermissionMatrix,
|
|
2225
|
+
getAuthMode,
|
|
2226
|
+
warnLegacySchema,
|
|
2227
|
+
schemaToHubPolicy,
|
|
2228
|
+
hubActionsForSpaceRole,
|
|
2229
|
+
PUBLIC_CONTENT_KEY,
|
|
2230
|
+
PUBLIC_RECIPIENT,
|
|
2231
|
+
computeRecipients,
|
|
2232
|
+
hasRecipientsChanged,
|
|
2233
|
+
canTransitionToPublic,
|
|
2234
|
+
handleAuthMigration,
|
|
2318
2235
|
GRANT_SCHEMA_IRI,
|
|
2319
2236
|
isGrantActive,
|
|
2320
2237
|
GrantIndex,
|
|
@@ -2322,26 +2239,14 @@ export {
|
|
|
2322
2239
|
mergeOfflinePolicy,
|
|
2323
2240
|
DecisionCache,
|
|
2324
2241
|
DefaultRoleResolver,
|
|
2242
|
+
evaluateExpression,
|
|
2325
2243
|
DefaultPolicyEvaluator,
|
|
2244
|
+
createPolicyEvaluator,
|
|
2326
2245
|
GrantExpirationCleaner,
|
|
2327
2246
|
GrantRateLimiter,
|
|
2328
2247
|
StoreAuthError,
|
|
2329
2248
|
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
|
|
2249
|
+
AuthMigrator,
|
|
2250
|
+
AUTH_FEATURE_FLAGS,
|
|
2251
|
+
AUTH_ACTIONS2 as AUTH_ACTIONS
|
|
2347
2252
|
};
|