@powerhousedao/vetra 6.0.0-dev.59 → 6.0.0-dev.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/subgraphs/__tests__/permission-utils.test.js +20 -177
- package/dist/subgraphs/__tests__/vetra-read-model-permissions.test.js +1 -31
- package/dist/subgraphs/permission-utils.d.ts +8 -9
- package/dist/subgraphs/permission-utils.d.ts.map +1 -1
- package/dist/subgraphs/permission-utils.js +31 -35
- package/dist/subgraphs/vetra-read-model/resolvers.js +2 -2
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +13 -13
|
@@ -1,144 +1,30 @@
|
|
|
1
1
|
import { GraphQLError } from "graphql";
|
|
2
|
-
import {
|
|
3
|
-
import { assertCanExecuteOperation, assertCanRead, assertCanWrite, canReadDocument, canWriteDocument,
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { assertCanExecuteOperation, assertCanRead, assertCanWrite, canReadDocument, canWriteDocument, hasGlobalAdminAccess, } from "../permission-utils.js";
|
|
4
4
|
describe("permission-utils", () => {
|
|
5
5
|
// Helper to create context with different permission levels
|
|
6
6
|
const createContext = (options) => ({
|
|
7
7
|
user: options.userAddress ? { address: options.userAddress } : undefined,
|
|
8
8
|
isAdmin: vi.fn().mockReturnValue(options.isAdmin ?? false),
|
|
9
|
-
isUser: vi.fn().mockReturnValue(options.isUser ?? false),
|
|
10
|
-
isGuest: vi.fn().mockReturnValue(options.isGuest ?? false),
|
|
11
9
|
});
|
|
12
|
-
describe("
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const ctx = createContext({ isAdmin: true, userAddress: "0xadmin" });
|
|
19
|
-
const result = hasGlobalReadAccess(ctx);
|
|
20
|
-
expect(result).toBe(true);
|
|
21
|
-
expect(ctx.isAdmin).toHaveBeenCalledWith("0xadmin");
|
|
22
|
-
});
|
|
23
|
-
it("should return true when user is global user", () => {
|
|
24
|
-
const ctx = createContext({ isUser: true, userAddress: "0xuser" });
|
|
25
|
-
const result = hasGlobalReadAccess(ctx);
|
|
26
|
-
expect(result).toBe(true);
|
|
27
|
-
expect(ctx.isUser).toHaveBeenCalledWith("0xuser");
|
|
28
|
-
});
|
|
29
|
-
it("should return true when user is global guest", () => {
|
|
30
|
-
const ctx = createContext({ isGuest: true, userAddress: "0xguest" });
|
|
31
|
-
const result = hasGlobalReadAccess(ctx);
|
|
32
|
-
expect(result).toBe(true);
|
|
33
|
-
expect(ctx.isGuest).toHaveBeenCalledWith("0xguest");
|
|
34
|
-
});
|
|
35
|
-
it("should return false when user has no global role", () => {
|
|
36
|
-
const ctx = createContext({ userAddress: "0xnorole" });
|
|
37
|
-
const result = hasGlobalReadAccess(ctx);
|
|
38
|
-
expect(result).toBe(false);
|
|
39
|
-
});
|
|
40
|
-
it("should return false when user is not authenticated (no address)", () => {
|
|
41
|
-
const ctx = createContext({});
|
|
42
|
-
const result = hasGlobalReadAccess(ctx);
|
|
43
|
-
expect(result).toBe(false);
|
|
44
|
-
// Should call with empty string when no user address
|
|
45
|
-
expect(ctx.isAdmin).toHaveBeenCalledWith("");
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
describe("FREE_ENTRY environment variable", () => {
|
|
49
|
-
it("should return true when FREE_ENTRY is 'true' regardless of roles", () => {
|
|
50
|
-
process.env.FREE_ENTRY = "true";
|
|
51
|
-
const ctx = createContext({ userAddress: "0xanyone" });
|
|
52
|
-
const result = hasGlobalReadAccess(ctx);
|
|
53
|
-
expect(result).toBe(true);
|
|
54
|
-
});
|
|
55
|
-
it("should return true when FREE_ENTRY is 'true' even without authentication", () => {
|
|
56
|
-
process.env.FREE_ENTRY = "true";
|
|
57
|
-
const ctx = createContext({});
|
|
58
|
-
const result = hasGlobalReadAccess(ctx);
|
|
59
|
-
expect(result).toBe(true);
|
|
60
|
-
});
|
|
61
|
-
it("should not grant access when FREE_ENTRY is 'false'", () => {
|
|
62
|
-
process.env.FREE_ENTRY = "false";
|
|
63
|
-
const ctx = createContext({ userAddress: "0xanyone" });
|
|
64
|
-
const result = hasGlobalReadAccess(ctx);
|
|
65
|
-
expect(result).toBe(false);
|
|
66
|
-
});
|
|
67
|
-
it("should not grant access when FREE_ENTRY is not set", () => {
|
|
68
|
-
const ctx = createContext({ userAddress: "0xanyone" });
|
|
69
|
-
const result = hasGlobalReadAccess(ctx);
|
|
70
|
-
expect(result).toBe(false);
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
describe("Combined roles", () => {
|
|
74
|
-
it("should return true when user has multiple roles (admin + user)", () => {
|
|
75
|
-
const ctx = createContext({
|
|
76
|
-
isAdmin: true,
|
|
77
|
-
isUser: true,
|
|
78
|
-
userAddress: "0xmultirole",
|
|
79
|
-
});
|
|
80
|
-
const result = hasGlobalReadAccess(ctx);
|
|
81
|
-
expect(result).toBe(true);
|
|
82
|
-
});
|
|
83
|
-
it("should return true when all roles are true (AUTH_ENABLED=false scenario)", () => {
|
|
84
|
-
const ctx = createContext({
|
|
85
|
-
isAdmin: true,
|
|
86
|
-
isUser: true,
|
|
87
|
-
isGuest: true,
|
|
88
|
-
userAddress: "0xanyone",
|
|
89
|
-
});
|
|
90
|
-
const result = hasGlobalReadAccess(ctx);
|
|
91
|
-
expect(result).toBe(true);
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
describe("hasGlobalWriteAccess", () => {
|
|
96
|
-
describe("Role-based access", () => {
|
|
97
|
-
it("should return true when user is global admin", () => {
|
|
98
|
-
const ctx = createContext({ isAdmin: true, userAddress: "0xadmin" });
|
|
99
|
-
const result = hasGlobalWriteAccess(ctx);
|
|
100
|
-
expect(result).toBe(true);
|
|
101
|
-
});
|
|
102
|
-
it("should return true when user is global user", () => {
|
|
103
|
-
const ctx = createContext({ isUser: true, userAddress: "0xuser" });
|
|
104
|
-
const result = hasGlobalWriteAccess(ctx);
|
|
105
|
-
expect(result).toBe(true);
|
|
106
|
-
});
|
|
107
|
-
it("should return false when user is only global guest", () => {
|
|
108
|
-
const ctx = createContext({ isGuest: true, userAddress: "0xguest" });
|
|
109
|
-
const result = hasGlobalWriteAccess(ctx);
|
|
110
|
-
expect(result).toBe(false);
|
|
111
|
-
});
|
|
112
|
-
it("should return false when user has no global role", () => {
|
|
113
|
-
const ctx = createContext({ userAddress: "0xnorole" });
|
|
114
|
-
const result = hasGlobalWriteAccess(ctx);
|
|
115
|
-
expect(result).toBe(false);
|
|
116
|
-
});
|
|
117
|
-
it("should return false when user is not authenticated", () => {
|
|
118
|
-
const ctx = createContext({});
|
|
119
|
-
const result = hasGlobalWriteAccess(ctx);
|
|
120
|
-
expect(result).toBe(false);
|
|
121
|
-
});
|
|
10
|
+
describe("hasGlobalAdminAccess", () => {
|
|
11
|
+
it("should return true when user is global admin", () => {
|
|
12
|
+
const ctx = createContext({ isAdmin: true, userAddress: "0xadmin" });
|
|
13
|
+
const result = hasGlobalAdminAccess(ctx);
|
|
14
|
+
expect(result).toBe(true);
|
|
15
|
+
expect(ctx.isAdmin).toHaveBeenCalledWith("0xadmin");
|
|
122
16
|
});
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const result = hasGlobalWriteAccess(ctx);
|
|
128
|
-
expect(result).toBe(false);
|
|
129
|
-
delete process.env.FREE_ENTRY;
|
|
130
|
-
});
|
|
17
|
+
it("should return false when user is not admin", () => {
|
|
18
|
+
const ctx = createContext({ userAddress: "0xnorole" });
|
|
19
|
+
const result = hasGlobalAdminAccess(ctx);
|
|
20
|
+
expect(result).toBe(false);
|
|
131
21
|
});
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
});
|
|
139
|
-
const result = hasGlobalWriteAccess(ctx);
|
|
140
|
-
expect(result).toBe(true);
|
|
141
|
-
});
|
|
22
|
+
it("should return false when user is not authenticated (no address)", () => {
|
|
23
|
+
const ctx = createContext({});
|
|
24
|
+
const result = hasGlobalAdminAccess(ctx);
|
|
25
|
+
expect(result).toBe(false);
|
|
26
|
+
// Should call with empty string when no user address
|
|
27
|
+
expect(ctx.isAdmin).toHaveBeenCalledWith("");
|
|
142
28
|
});
|
|
143
29
|
});
|
|
144
30
|
describe("canReadDocument", () => {
|
|
@@ -162,18 +48,12 @@ describe("permission-utils", () => {
|
|
|
162
48
|
};
|
|
163
49
|
});
|
|
164
50
|
describe("Global access bypass", () => {
|
|
165
|
-
it("should return true immediately when user has global
|
|
51
|
+
it("should return true immediately when user has global admin access", async () => {
|
|
166
52
|
const ctx = createContext({ isAdmin: true, userAddress: "0xadmin" });
|
|
167
53
|
const result = await canReadDocument(mockSubgraph, "doc-123", ctx);
|
|
168
54
|
expect(result).toBe(true);
|
|
169
55
|
expect(mockDocumentPermissionService.canRead).not.toHaveBeenCalled();
|
|
170
56
|
});
|
|
171
|
-
it("should return true for guest without checking document permissions", async () => {
|
|
172
|
-
const ctx = createContext({ isGuest: true, userAddress: "0xguest" });
|
|
173
|
-
const result = await canReadDocument(mockSubgraph, "doc-123", ctx);
|
|
174
|
-
expect(result).toBe(true);
|
|
175
|
-
expect(mockDocumentPermissionService.canRead).not.toHaveBeenCalled();
|
|
176
|
-
});
|
|
177
57
|
});
|
|
178
58
|
describe("Document-level permissions", () => {
|
|
179
59
|
it("should check document permission service when no global access", async () => {
|
|
@@ -267,19 +147,6 @@ describe("permission-utils", () => {
|
|
|
267
147
|
expect(result).toBe(true);
|
|
268
148
|
expect(mockDocumentPermissionService.canWrite).not.toHaveBeenCalled();
|
|
269
149
|
});
|
|
270
|
-
it("should return true immediately when user is global user", async () => {
|
|
271
|
-
const ctx = createContext({ isUser: true, userAddress: "0xuser" });
|
|
272
|
-
const result = await canWriteDocument(mockSubgraph, "doc-123", ctx);
|
|
273
|
-
expect(result).toBe(true);
|
|
274
|
-
expect(mockDocumentPermissionService.canWrite).not.toHaveBeenCalled();
|
|
275
|
-
});
|
|
276
|
-
it("should NOT return true for global guest (guests cannot write)", async () => {
|
|
277
|
-
const ctx = createContext({ isGuest: true, userAddress: "0xguest" });
|
|
278
|
-
const result = await canWriteDocument(mockSubgraph, "doc-123", ctx);
|
|
279
|
-
// Guest has no global write access, so should check document permissions
|
|
280
|
-
expect(mockDocumentPermissionService.canWrite).toHaveBeenCalled();
|
|
281
|
-
expect(result).toBe(false);
|
|
282
|
-
});
|
|
283
150
|
});
|
|
284
151
|
describe("Document-level permissions", () => {
|
|
285
152
|
it("should check document permission service when no global write access", async () => {
|
|
@@ -385,10 +252,6 @@ describe("permission-utils", () => {
|
|
|
385
252
|
const ctx = createContext({ userAddress: "0xunpermitted" });
|
|
386
253
|
await expect(assertCanWrite(mockSubgraph, "doc-123", ctx)).rejects.toThrow("Forbidden: insufficient permissions to write to this document");
|
|
387
254
|
});
|
|
388
|
-
it("should throw for guest user (guests cannot write)", async () => {
|
|
389
|
-
const ctx = createContext({ isGuest: true, userAddress: "0xguest" });
|
|
390
|
-
await expect(assertCanWrite(mockSubgraph, "doc-123", ctx)).rejects.toThrow("Forbidden");
|
|
391
|
-
});
|
|
392
255
|
});
|
|
393
256
|
describe("assertCanExecuteOperation", () => {
|
|
394
257
|
let mockSubgraph;
|
|
@@ -457,30 +320,10 @@ describe("permission-utils", () => {
|
|
|
457
320
|
});
|
|
458
321
|
});
|
|
459
322
|
describe("Edge Cases", () => {
|
|
460
|
-
describe("Null/undefined context fields", () => {
|
|
461
|
-
it("hasGlobalReadAccess should handle context without isAdmin function", () => {
|
|
462
|
-
const ctx = {
|
|
463
|
-
user: { address: "0xuser" },
|
|
464
|
-
isUser: vi.fn().mockReturnValue(true),
|
|
465
|
-
isGuest: vi.fn().mockReturnValue(false),
|
|
466
|
-
};
|
|
467
|
-
const result = hasGlobalReadAccess(ctx);
|
|
468
|
-
expect(result).toBe(true);
|
|
469
|
-
});
|
|
470
|
-
it("hasGlobalWriteAccess should handle context without isUser function", () => {
|
|
471
|
-
const ctx = {
|
|
472
|
-
user: { address: "0xadmin" },
|
|
473
|
-
isAdmin: vi.fn().mockReturnValue(true),
|
|
474
|
-
isGuest: vi.fn().mockReturnValue(false),
|
|
475
|
-
};
|
|
476
|
-
const result = hasGlobalWriteAccess(ctx);
|
|
477
|
-
expect(result).toBe(true);
|
|
478
|
-
});
|
|
479
|
-
});
|
|
480
323
|
describe("Empty string user address", () => {
|
|
481
324
|
it("should handle empty string user address", () => {
|
|
482
325
|
const ctx = createContext({ userAddress: "" });
|
|
483
|
-
const result =
|
|
326
|
+
const result = hasGlobalAdminAccess(ctx);
|
|
484
327
|
expect(result).toBe(false);
|
|
485
328
|
expect(ctx.isAdmin).toHaveBeenCalledWith("");
|
|
486
329
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
2
|
import { getResolvers } from "../vetra-read-model/resolvers.js";
|
|
3
3
|
// Mock the VetraReadModelProcessorLegacy
|
|
4
4
|
vi.mock("../../processors/vetra-read-model/index.legacy.js", () => ({
|
|
@@ -65,8 +65,6 @@ describe("VetraReadModel Subgraph Permission Checks", () => {
|
|
|
65
65
|
const createContext = (options) => ({
|
|
66
66
|
user: options.userAddress ? { address: options.userAddress } : undefined,
|
|
67
67
|
isAdmin: vi.fn().mockReturnValue(options.isAdmin ?? false),
|
|
68
|
-
isUser: vi.fn().mockReturnValue(options.isUser ?? false),
|
|
69
|
-
isGuest: vi.fn().mockReturnValue(options.isGuest ?? false),
|
|
70
68
|
});
|
|
71
69
|
// Setup mock query chain
|
|
72
70
|
const setupMockQuery = (packages) => {
|
|
@@ -92,7 +90,6 @@ describe("VetraReadModel Subgraph Permission Checks", () => {
|
|
|
92
90
|
};
|
|
93
91
|
beforeEach(() => {
|
|
94
92
|
vi.clearAllMocks();
|
|
95
|
-
delete process.env.FREE_ENTRY;
|
|
96
93
|
// Create mock DocumentPermissionService
|
|
97
94
|
mockDocumentPermissionService = {
|
|
98
95
|
canRead: vi.fn().mockResolvedValue(false),
|
|
@@ -116,9 +113,6 @@ describe("VetraReadModel Subgraph Permission Checks", () => {
|
|
|
116
113
|
// Get resolvers
|
|
117
114
|
resolvers = getResolvers(mockSubgraph);
|
|
118
115
|
});
|
|
119
|
-
afterEach(() => {
|
|
120
|
-
delete process.env.FREE_ENTRY;
|
|
121
|
-
});
|
|
122
116
|
describe("Query: vetraPackages", () => {
|
|
123
117
|
const callVetraPackages = async (ctx, args = {}) => {
|
|
124
118
|
const query = resolvers.Query?.vetraPackages;
|
|
@@ -132,28 +126,6 @@ describe("VetraReadModel Subgraph Permission Checks", () => {
|
|
|
132
126
|
expect(result).toHaveLength(3);
|
|
133
127
|
expect(mockDocumentPermissionService.canRead).not.toHaveBeenCalled();
|
|
134
128
|
});
|
|
135
|
-
it("should return all packages when user is global user", async () => {
|
|
136
|
-
setupMockQuery(mockPackages);
|
|
137
|
-
const ctx = createContext({ isUser: true, userAddress: "0xuser" });
|
|
138
|
-
const result = await callVetraPackages(ctx);
|
|
139
|
-
expect(result).toHaveLength(3);
|
|
140
|
-
expect(mockDocumentPermissionService.canRead).not.toHaveBeenCalled();
|
|
141
|
-
});
|
|
142
|
-
it("should return all packages when user is global guest", async () => {
|
|
143
|
-
setupMockQuery(mockPackages);
|
|
144
|
-
const ctx = createContext({ isGuest: true, userAddress: "0xguest" });
|
|
145
|
-
const result = await callVetraPackages(ctx);
|
|
146
|
-
expect(result).toHaveLength(3);
|
|
147
|
-
expect(mockDocumentPermissionService.canRead).not.toHaveBeenCalled();
|
|
148
|
-
});
|
|
149
|
-
it("should return all packages when FREE_ENTRY is true", async () => {
|
|
150
|
-
process.env.FREE_ENTRY = "true";
|
|
151
|
-
setupMockQuery(mockPackages);
|
|
152
|
-
const ctx = createContext({ userAddress: "0xanyone" });
|
|
153
|
-
const result = await callVetraPackages(ctx);
|
|
154
|
-
expect(result).toHaveLength(3);
|
|
155
|
-
expect(mockDocumentPermissionService.canRead).not.toHaveBeenCalled();
|
|
156
|
-
});
|
|
157
129
|
});
|
|
158
130
|
describe("Document Permission Filtering", () => {
|
|
159
131
|
it("should filter packages based on permissions when no global access", async () => {
|
|
@@ -276,8 +248,6 @@ describe("VetraReadModel Subgraph Permission Checks", () => {
|
|
|
276
248
|
setupMockQuery(mockPackages);
|
|
277
249
|
const ctx = createContext({
|
|
278
250
|
isAdmin: true,
|
|
279
|
-
isUser: true,
|
|
280
|
-
isGuest: true,
|
|
281
251
|
userAddress: "0xanyone",
|
|
282
252
|
});
|
|
283
253
|
const result = await (resolvers.Query?.vetraPackages)(null, {}, ctx);
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import type { BaseSubgraph, Context } from "@powerhousedao/reactor-api";
|
|
2
2
|
/**
|
|
3
|
-
* Check if user has global
|
|
3
|
+
* Check if user has global admin access.
|
|
4
|
+
* Legacy fallback when authorizationService is not available.
|
|
4
5
|
*/
|
|
5
|
-
export declare function
|
|
6
|
+
export declare function hasGlobalAdminAccess(ctx: Context): boolean;
|
|
6
7
|
/**
|
|
7
|
-
* Check if user
|
|
8
|
-
|
|
9
|
-
export declare function hasGlobalWriteAccess(ctx: Context): boolean;
|
|
10
|
-
/**
|
|
11
|
-
* Check if user can read a document (with hierarchy)
|
|
8
|
+
* Check if user can read a document (with hierarchy).
|
|
9
|
+
* Delegates to AuthorizationService when available.
|
|
12
10
|
*/
|
|
13
11
|
export declare function canReadDocument(subgraph: BaseSubgraph, documentId: string, ctx: Context): Promise<boolean>;
|
|
14
12
|
/**
|
|
15
|
-
* Check if user can write to a document (with hierarchy)
|
|
13
|
+
* Check if user can write to a document (with hierarchy).
|
|
14
|
+
* Delegates to AuthorizationService when available.
|
|
16
15
|
*/
|
|
17
16
|
export declare function canWriteDocument(subgraph: BaseSubgraph, documentId: string, ctx: Context): Promise<boolean>;
|
|
18
17
|
/**
|
|
@@ -25,7 +24,7 @@ export declare function assertCanRead(subgraph: BaseSubgraph, documentId: string
|
|
|
25
24
|
export declare function assertCanWrite(subgraph: BaseSubgraph, documentId: string, ctx: Context): Promise<void>;
|
|
26
25
|
/**
|
|
27
26
|
* Check if user can execute a specific operation on a document.
|
|
28
|
-
*
|
|
27
|
+
* Delegates to AuthorizationService.canMutate when available.
|
|
29
28
|
*/
|
|
30
29
|
export declare function assertCanExecuteOperation(subgraph: BaseSubgraph, documentId: string, operationType: string, ctx: Context): Promise<void>;
|
|
31
30
|
//# sourceMappingURL=permission-utils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permission-utils.d.ts","sourceRoot":"","sources":["../../subgraphs/permission-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"permission-utils.d.ts","sourceRoot":"","sources":["../../subgraphs/permission-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAiBxE;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAE1D;AAED;;;GAGG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,YAAY,EACtB,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,OAAO,GACX,OAAO,CAAC,OAAO,CAAC,CAkBlB;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,YAAY,EACtB,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,OAAO,GACX,OAAO,CAAC,OAAO,CAAC,CAkBlB;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,QAAQ,EAAE,YAAY,EACtB,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,OAAO,GACX,OAAO,CAAC,IAAI,CAAC,CAOf;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,YAAY,EACtB,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,OAAO,GACX,OAAO,CAAC,IAAI,CAAC,CAOf;AAED;;;GAGG;AACH,wBAAsB,yBAAyB,CAC7C,QAAQ,EAAE,YAAY,EACtB,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,EACrB,GAAG,EAAE,OAAO,GACX,OAAO,CAAC,IAAI,CAAC,CAwCf"}
|
|
@@ -1,21 +1,4 @@
|
|
|
1
1
|
import { GraphQLError } from "graphql";
|
|
2
|
-
/**
|
|
3
|
-
* Check if user has global read access (admin, user, or guest)
|
|
4
|
-
*/
|
|
5
|
-
export function hasGlobalReadAccess(ctx) {
|
|
6
|
-
const isGlobalAdmin = ctx.isAdmin?.(ctx.user?.address ?? "");
|
|
7
|
-
const isGlobalUser = ctx.isUser?.(ctx.user?.address ?? "");
|
|
8
|
-
const isGlobalGuest = ctx.isGuest?.(ctx.user?.address ?? "") || process.env.FREE_ENTRY === "true";
|
|
9
|
-
return !!(isGlobalAdmin || isGlobalUser || isGlobalGuest);
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Check if user has global write access (admin or user, not guest)
|
|
13
|
-
*/
|
|
14
|
-
export function hasGlobalWriteAccess(ctx) {
|
|
15
|
-
const isGlobalAdmin = ctx.isAdmin?.(ctx.user?.address ?? "");
|
|
16
|
-
const isGlobalUser = ctx.isUser?.(ctx.user?.address ?? "");
|
|
17
|
-
return !!(isGlobalAdmin || isGlobalUser);
|
|
18
|
-
}
|
|
19
2
|
/**
|
|
20
3
|
* Get the parent IDs function for hierarchical permission checks
|
|
21
4
|
*/
|
|
@@ -31,28 +14,39 @@ function getParentIdsFn(subgraph) {
|
|
|
31
14
|
};
|
|
32
15
|
}
|
|
33
16
|
/**
|
|
34
|
-
* Check if user
|
|
17
|
+
* Check if user has global admin access.
|
|
18
|
+
* Legacy fallback when authorizationService is not available.
|
|
19
|
+
*/
|
|
20
|
+
export function hasGlobalAdminAccess(ctx) {
|
|
21
|
+
return !!ctx.isAdmin?.(ctx.user?.address ?? "");
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Check if user can read a document (with hierarchy).
|
|
25
|
+
* Delegates to AuthorizationService when available.
|
|
35
26
|
*/
|
|
36
27
|
export async function canReadDocument(subgraph, documentId, ctx) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return true;
|
|
28
|
+
if (subgraph.authorizationService) {
|
|
29
|
+
return subgraph.authorizationService.canRead(documentId, ctx.user?.address, getParentIdsFn(subgraph));
|
|
40
30
|
}
|
|
41
|
-
//
|
|
31
|
+
// Legacy fallback
|
|
32
|
+
if (hasGlobalAdminAccess(ctx))
|
|
33
|
+
return true;
|
|
42
34
|
if (subgraph.documentPermissionService) {
|
|
43
35
|
return subgraph.documentPermissionService.canRead(documentId, ctx.user?.address, getParentIdsFn(subgraph));
|
|
44
36
|
}
|
|
45
37
|
return false;
|
|
46
38
|
}
|
|
47
39
|
/**
|
|
48
|
-
* Check if user can write to a document (with hierarchy)
|
|
40
|
+
* Check if user can write to a document (with hierarchy).
|
|
41
|
+
* Delegates to AuthorizationService when available.
|
|
49
42
|
*/
|
|
50
43
|
export async function canWriteDocument(subgraph, documentId, ctx) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return true;
|
|
44
|
+
if (subgraph.authorizationService) {
|
|
45
|
+
return subgraph.authorizationService.canWrite(documentId, ctx.user?.address, getParentIdsFn(subgraph));
|
|
54
46
|
}
|
|
55
|
-
//
|
|
47
|
+
// Legacy fallback
|
|
48
|
+
if (hasGlobalAdminAccess(ctx))
|
|
49
|
+
return true;
|
|
56
50
|
if (subgraph.documentPermissionService) {
|
|
57
51
|
return subgraph.documentPermissionService.canWrite(documentId, ctx.user?.address, getParentIdsFn(subgraph));
|
|
58
52
|
}
|
|
@@ -78,21 +72,23 @@ export async function assertCanWrite(subgraph, documentId, ctx) {
|
|
|
78
72
|
}
|
|
79
73
|
/**
|
|
80
74
|
* Check if user can execute a specific operation on a document.
|
|
81
|
-
*
|
|
75
|
+
* Delegates to AuthorizationService.canMutate when available.
|
|
82
76
|
*/
|
|
83
77
|
export async function assertCanExecuteOperation(subgraph, documentId, operationType, ctx) {
|
|
84
|
-
|
|
85
|
-
|
|
78
|
+
if (subgraph.authorizationService) {
|
|
79
|
+
const canMutate = await subgraph.authorizationService.canMutate(documentId, operationType, ctx.user?.address, getParentIdsFn(subgraph));
|
|
80
|
+
if (!canMutate) {
|
|
81
|
+
throw new GraphQLError(`Forbidden: insufficient permissions to execute operation "${operationType}" on this document`);
|
|
82
|
+
}
|
|
86
83
|
return;
|
|
87
84
|
}
|
|
88
|
-
//
|
|
89
|
-
if (
|
|
85
|
+
// Legacy fallback
|
|
86
|
+
if (!subgraph.documentPermissionService)
|
|
87
|
+
return;
|
|
88
|
+
if (ctx.isAdmin?.(ctx.user?.address ?? ""))
|
|
90
89
|
return;
|
|
91
|
-
}
|
|
92
|
-
// Check if this operation has any restrictions set
|
|
93
90
|
const isRestricted = await subgraph.documentPermissionService.isOperationRestricted(documentId, operationType);
|
|
94
91
|
if (isRestricted) {
|
|
95
|
-
// Operation is restricted, check if user has permission
|
|
96
92
|
const canExecute = await subgraph.documentPermissionService.canExecuteOperation(documentId, operationType, ctx.user?.address);
|
|
97
93
|
if (!canExecute) {
|
|
98
94
|
throw new GraphQLError(`Forbidden: insufficient permissions to execute operation "${operationType}" on this document`);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { VetraReadModelProcessorLegacy } from "../../processors/vetra-read-model/index.legacy.js";
|
|
2
|
-
import { canReadDocument,
|
|
2
|
+
import { canReadDocument, hasGlobalAdminAccess } from "../permission-utils.js";
|
|
3
3
|
export const getResolvers = (subgraph) => {
|
|
4
4
|
const db = subgraph.relationalDb;
|
|
5
5
|
return {
|
|
@@ -32,7 +32,7 @@ export const getResolvers = (subgraph) => {
|
|
|
32
32
|
driveId: pkg.drive_id,
|
|
33
33
|
}));
|
|
34
34
|
// If user doesn't have global read access, filter by document-level permissions
|
|
35
|
-
if (!
|
|
35
|
+
if (!hasGlobalAdminAccess(ctx) && subgraph.documentPermissionService) {
|
|
36
36
|
const filteredResults = [];
|
|
37
37
|
for (const pkg of mappedResults) {
|
|
38
38
|
const canRead = await canReadDocument(subgraph, pkg.documentId, ctx);
|