@stonecrop/casl-middleware 0.13.14 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +23 -23
- package/dist/index.js +0 -3
- package/dist/middleware/ability.js +0 -139
- package/dist/middleware/graphql.js +0 -120
- package/dist/middleware/introspection.js +0 -169
- package/dist/middleware/jwt.js +0 -291
- package/dist/middleware/postgraphile.js +0 -80
- package/dist/middleware/yoga.js +0 -32
- package/dist/src/tsdoc-metadata.json +0 -11
- package/dist/tests/ability.test.d.ts +0 -2
- package/dist/tests/ability.test.d.ts.map +0 -1
- package/dist/tests/ability.test.js +0 -402
- package/dist/tests/helpers/test-utils.d.ts +0 -46
- package/dist/tests/helpers/test-utils.d.ts.map +0 -1
- package/dist/tests/helpers/test-utils.js +0 -92
- package/dist/tests/introspection.test.d.ts +0 -2
- package/dist/tests/introspection.test.d.ts.map +0 -1
- package/dist/tests/introspection.test.js +0 -368
- package/dist/tests/jwt.test.d.ts +0 -2
- package/dist/tests/jwt.test.d.ts.map +0 -1
- package/dist/tests/jwt.test.js +0 -700
- package/dist/tests/middleware.test.d.ts +0 -2
- package/dist/tests/middleware.test.d.ts.map +0 -1
- package/dist/tests/middleware.test.js +0 -184
- package/dist/tests/postgraphile-plugin.test.d.ts +0 -2
- package/dist/tests/postgraphile-plugin.test.d.ts.map +0 -1
- package/dist/tests/postgraphile-plugin.test.js +0 -56
- package/dist/tests/setup.d.ts +0 -2
- package/dist/tests/setup.d.ts.map +0 -1
- package/dist/tests/setup.js +0 -11
- package/dist/tests/types.test.d.ts +0 -2
- package/dist/tests/types.test.d.ts.map +0 -1
- package/dist/tests/types.test.js +0 -143
- package/dist/tests/user-roles.test.d.ts +0 -2
- package/dist/tests/user-roles.test.d.ts.map +0 -1
- package/dist/tests/user-roles.test.js +0 -157
- package/dist/tests/yoga-plugin.test.d.ts +0 -2
- package/dist/tests/yoga-plugin.test.d.ts.map +0 -1
- package/dist/tests/yoga-plugin.test.js +0 -125
- package/dist/types/index.js +0 -0
|
@@ -1,402 +0,0 @@
|
|
|
1
|
-
import { AbilityBuilder, PureAbility, subject } from '@casl/ability';
|
|
2
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
3
|
-
import { createAbility, detectSubjectType, defaultAbilityBuilder, createDatabaseAbilityBuilder, createConfigBasedAbilityBuilder, createAbilityFactory, } from '../src/middleware/ability';
|
|
4
|
-
const Ability = PureAbility;
|
|
5
|
-
describe('Ability Creation', () => {
|
|
6
|
-
describe('createAbility', () => {
|
|
7
|
-
// Create a test builder with proper matchers - using the same detectSubjectType as production
|
|
8
|
-
const testBuilder = (user) => {
|
|
9
|
-
const { can, cannot, build } = new AbilityBuilder(Ability);
|
|
10
|
-
if (!user) {
|
|
11
|
-
can('read', 'Query');
|
|
12
|
-
can('read', 'PublicType');
|
|
13
|
-
}
|
|
14
|
-
else {
|
|
15
|
-
can('read', 'Query');
|
|
16
|
-
can('read', 'Mutation');
|
|
17
|
-
if (user.roles?.includes('admin')) {
|
|
18
|
-
can('manage', 'all');
|
|
19
|
-
}
|
|
20
|
-
else {
|
|
21
|
-
can('read', 'User', { id: user.id });
|
|
22
|
-
can('update', 'User', { id: user.id });
|
|
23
|
-
cannot('update', 'User', 'role');
|
|
24
|
-
cannot('update', 'User', 'permissions');
|
|
25
|
-
}
|
|
26
|
-
if (user.roles?.includes('moderator')) {
|
|
27
|
-
can('update', 'Post');
|
|
28
|
-
can('delete', 'Comment');
|
|
29
|
-
}
|
|
30
|
-
if (user.roles?.includes('editor')) {
|
|
31
|
-
can('create', 'Post');
|
|
32
|
-
can('update', 'Post');
|
|
33
|
-
can('delete', 'Post');
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
return build({
|
|
37
|
-
detectSubjectType,
|
|
38
|
-
fieldMatcher: fields => field => {
|
|
39
|
-
if (!fields || !field)
|
|
40
|
-
return false;
|
|
41
|
-
return Array.isArray(fields) ? fields.includes(field) : fields === field;
|
|
42
|
-
},
|
|
43
|
-
conditionsMatcher: conditions => object => {
|
|
44
|
-
if (!conditions || !object)
|
|
45
|
-
return true;
|
|
46
|
-
return Object.keys(conditions).every(key => object[key] === conditions[key]);
|
|
47
|
-
},
|
|
48
|
-
});
|
|
49
|
-
};
|
|
50
|
-
it('should create a basic ability for unauthenticated users', async () => {
|
|
51
|
-
const ability = await createAbility(undefined, testBuilder);
|
|
52
|
-
expect(ability).toBeDefined();
|
|
53
|
-
expect(ability.can('read', 'Query')).toBe(true);
|
|
54
|
-
expect(ability.can('read', 'PublicType')).toBe(true);
|
|
55
|
-
expect(ability.can('read', 'Mutation')).toBe(false);
|
|
56
|
-
expect(ability.can('manage', 'all')).toBe(false);
|
|
57
|
-
});
|
|
58
|
-
it('should create abilities for authenticated users without roles', async () => {
|
|
59
|
-
const user = { id: '123', roles: [] };
|
|
60
|
-
const ability = await createAbility(user, testBuilder);
|
|
61
|
-
expect(ability.can('read', 'Query')).toBe(true);
|
|
62
|
-
expect(ability.can('read', 'Mutation')).toBe(true);
|
|
63
|
-
expect(ability.can('read', subject('User', { id: '123' }))).toBe(true);
|
|
64
|
-
expect(ability.can('update', subject('User', { id: '123' }))).toBe(true);
|
|
65
|
-
expect(ability.can('update', subject('User', { id: '456' }))).toBe(false);
|
|
66
|
-
// Check field-level restrictions
|
|
67
|
-
expect(ability.can('update', 'User', 'role')).toBe(false);
|
|
68
|
-
expect(ability.can('update', 'User', 'permissions')).toBe(false);
|
|
69
|
-
});
|
|
70
|
-
it('should create admin abilities for users with admin role', async () => {
|
|
71
|
-
const user = { id: '123', roles: ['admin'] };
|
|
72
|
-
const ability = await createAbility(user, testBuilder);
|
|
73
|
-
expect(ability.can('manage', 'all')).toBe(true);
|
|
74
|
-
expect(ability.can('read', 'Query')).toBe(true);
|
|
75
|
-
expect(ability.can('update', subject('User', { id: '456' }))).toBe(true);
|
|
76
|
-
expect(ability.can('delete', 'User')).toBe(true);
|
|
77
|
-
});
|
|
78
|
-
it('should handle mixed roles correctly', async () => {
|
|
79
|
-
const user = { id: '123', roles: ['user', 'moderator'] };
|
|
80
|
-
const ability = await createAbility(user, testBuilder);
|
|
81
|
-
expect(ability.can('read', 'Query')).toBe(true);
|
|
82
|
-
expect(ability.can('read', 'Mutation')).toBe(true);
|
|
83
|
-
expect(ability.can('manage', 'all')).toBe(false);
|
|
84
|
-
expect(ability.can('read', subject('User', { id: '123' }))).toBe(true);
|
|
85
|
-
expect(ability.can('update', subject('User', { id: '123' }))).toBe(true);
|
|
86
|
-
// Check field restrictions
|
|
87
|
-
expect(ability.can('update', 'User', 'role')).toBe(false);
|
|
88
|
-
expect(ability.can('update', 'User', 'permissions')).toBe(false);
|
|
89
|
-
// Check moderator permissions
|
|
90
|
-
expect(ability.can('update', 'Post')).toBe(true);
|
|
91
|
-
expect(ability.can('delete', 'Comment')).toBe(true);
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
describe('Custom Ability Builder', () => {
|
|
95
|
-
it('should allow custom ability definitions', () => {
|
|
96
|
-
const customCreateAbility = (user) => {
|
|
97
|
-
const { can, build } = new AbilityBuilder(Ability);
|
|
98
|
-
if (user?.permissions?.includes('read:posts')) {
|
|
99
|
-
can('read', 'Post');
|
|
100
|
-
}
|
|
101
|
-
if (user?.permissions?.includes('write:posts')) {
|
|
102
|
-
can(['create', 'update'], 'Post', { authorId: user.id });
|
|
103
|
-
}
|
|
104
|
-
if (user?.permissions?.includes('delete:posts')) {
|
|
105
|
-
can('delete', 'Post', { authorId: user.id });
|
|
106
|
-
}
|
|
107
|
-
return build({
|
|
108
|
-
detectSubjectType,
|
|
109
|
-
conditionsMatcher: conditions => object => {
|
|
110
|
-
if (!conditions || !object)
|
|
111
|
-
return true;
|
|
112
|
-
return Object.keys(conditions).every(key => object[key] === conditions[key]);
|
|
113
|
-
},
|
|
114
|
-
});
|
|
115
|
-
};
|
|
116
|
-
const user = { id: '123', permissions: ['read:posts', 'write:posts'] };
|
|
117
|
-
const ability = customCreateAbility(user);
|
|
118
|
-
expect(ability.can('read', 'Post')).toBe(true);
|
|
119
|
-
expect(ability.can('create', subject('Post', { authorId: '123' }))).toBe(true);
|
|
120
|
-
expect(ability.can('update', subject('Post', { authorId: '123' }))).toBe(true);
|
|
121
|
-
expect(ability.can('delete', subject('Post', { authorId: '123' }))).toBe(false);
|
|
122
|
-
expect(ability.can('update', subject('Post', { authorId: '456' }))).toBe(false);
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
describe('detectSubjectType', () => {
|
|
126
|
-
it('should detect __caslSubjectType__ property', () => {
|
|
127
|
-
const obj = { __caslSubjectType__: 'CustomType', data: 'test' };
|
|
128
|
-
expect(detectSubjectType(obj)).toBe('CustomType');
|
|
129
|
-
});
|
|
130
|
-
it('should fallback to type property', () => {
|
|
131
|
-
const obj = { type: 'Post', title: 'Test' };
|
|
132
|
-
expect(detectSubjectType(obj)).toBe('Post');
|
|
133
|
-
});
|
|
134
|
-
it('should fallback to constructor name', () => {
|
|
135
|
-
class User {
|
|
136
|
-
name;
|
|
137
|
-
constructor(name) {
|
|
138
|
-
this.name = name;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
const user = new User('John');
|
|
142
|
-
expect(detectSubjectType(user)).toBe('User');
|
|
143
|
-
});
|
|
144
|
-
it('should return Unknown for plain objects', () => {
|
|
145
|
-
const obj = { data: 'test' };
|
|
146
|
-
expect(detectSubjectType(obj)).toBe('Unknown');
|
|
147
|
-
});
|
|
148
|
-
it('should return Unknown for null or undefined', () => {
|
|
149
|
-
expect(detectSubjectType(null)).toBe('Unknown');
|
|
150
|
-
expect(detectSubjectType(undefined)).toBe('Unknown');
|
|
151
|
-
});
|
|
152
|
-
it('should return Unknown for primitives', () => {
|
|
153
|
-
expect(detectSubjectType('string')).toBe('Unknown');
|
|
154
|
-
expect(detectSubjectType(123)).toBe('Unknown');
|
|
155
|
-
expect(detectSubjectType(true)).toBe('Unknown');
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
describe('defaultAbilityBuilder', () => {
|
|
159
|
-
it('should create ability with only Query read access for no user', () => {
|
|
160
|
-
const ability = defaultAbilityBuilder();
|
|
161
|
-
expect(ability.can('read', 'Query')).toBe(true);
|
|
162
|
-
expect(ability.can('read', 'Mutation')).toBe(false);
|
|
163
|
-
expect(ability.can('manage', 'all')).toBe(false);
|
|
164
|
-
});
|
|
165
|
-
it('should create ability with only Query read access for user without roles', () => {
|
|
166
|
-
const user = { id: '123', roles: [] };
|
|
167
|
-
const ability = defaultAbilityBuilder(user);
|
|
168
|
-
expect(ability.can('read', 'Query')).toBe(true);
|
|
169
|
-
expect(ability.can('read', 'Mutation')).toBe(false);
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
describe('createDatabaseAbilityBuilder', () => {
|
|
173
|
-
it('should build ability from database rules', async () => {
|
|
174
|
-
const mockFetchRules = vi.fn().mockResolvedValue([
|
|
175
|
-
{ action: 'read', subject: 'Post', inverted: false },
|
|
176
|
-
{ action: 'create', subject: 'Post', inverted: false },
|
|
177
|
-
{ action: 'update', subject: 'Post', fields: ['title'], inverted: false },
|
|
178
|
-
{ action: 'delete', subject: 'Post', inverted: true },
|
|
179
|
-
]);
|
|
180
|
-
const builder = createDatabaseAbilityBuilder(mockFetchRules);
|
|
181
|
-
const user = { id: '123', roles: ['user'] };
|
|
182
|
-
const ability = await builder(user);
|
|
183
|
-
expect(mockFetchRules).toHaveBeenCalledWith('123');
|
|
184
|
-
expect(ability.can('read', 'Post')).toBe(true);
|
|
185
|
-
expect(ability.can('create', 'Post')).toBe(true);
|
|
186
|
-
expect(ability.can('update', 'Post', 'title')).toBe(true);
|
|
187
|
-
expect(ability.can('delete', 'Post')).toBe(false);
|
|
188
|
-
});
|
|
189
|
-
it('should handle rules with conditions', async () => {
|
|
190
|
-
const mockFetchRules = vi.fn().mockResolvedValue([
|
|
191
|
-
{
|
|
192
|
-
action: 'update',
|
|
193
|
-
subject: 'Post',
|
|
194
|
-
conditions: { authorId: '123' },
|
|
195
|
-
inverted: false,
|
|
196
|
-
},
|
|
197
|
-
]);
|
|
198
|
-
const builder = createDatabaseAbilityBuilder(mockFetchRules);
|
|
199
|
-
const user = { id: '123', roles: ['user'] };
|
|
200
|
-
const ability = await builder(user);
|
|
201
|
-
expect(ability.can('update', subject('Post', { authorId: '123' }))).toBe(true);
|
|
202
|
-
expect(ability.can('update', subject('Post', { authorId: '456' }))).toBe(false);
|
|
203
|
-
});
|
|
204
|
-
it('should only allow Query read for user without id', async () => {
|
|
205
|
-
const mockFetchRules = vi.fn();
|
|
206
|
-
const builder = createDatabaseAbilityBuilder(mockFetchRules);
|
|
207
|
-
const ability = await builder();
|
|
208
|
-
expect(mockFetchRules).not.toHaveBeenCalled();
|
|
209
|
-
expect(ability.can('read', 'Query')).toBe(true);
|
|
210
|
-
expect(ability.can('read', 'Post')).toBe(false);
|
|
211
|
-
});
|
|
212
|
-
it('should handle empty rules array', async () => {
|
|
213
|
-
const mockFetchRules = vi.fn().mockResolvedValue([]);
|
|
214
|
-
const builder = createDatabaseAbilityBuilder(mockFetchRules);
|
|
215
|
-
const user = { id: '123', roles: [] };
|
|
216
|
-
const ability = await builder(user);
|
|
217
|
-
expect(mockFetchRules).toHaveBeenCalledWith('123');
|
|
218
|
-
expect(ability.can('read', 'Query')).toBe(false);
|
|
219
|
-
});
|
|
220
|
-
});
|
|
221
|
-
describe('createConfigBasedAbilityBuilder', () => {
|
|
222
|
-
it('should build ability from role-based config', async () => {
|
|
223
|
-
const config = {
|
|
224
|
-
roles: {
|
|
225
|
-
editor: [
|
|
226
|
-
{ action: 'read', subject: 'Post' },
|
|
227
|
-
{ action: 'create', subject: 'Post' },
|
|
228
|
-
{ action: 'update', subject: 'Post' },
|
|
229
|
-
],
|
|
230
|
-
viewer: [{ action: 'read', subject: 'Post' }],
|
|
231
|
-
},
|
|
232
|
-
};
|
|
233
|
-
const builder = createConfigBasedAbilityBuilder(config);
|
|
234
|
-
const user = { id: '123', roles: ['editor'] };
|
|
235
|
-
const ability = await builder(user);
|
|
236
|
-
expect(ability.can('read', 'Post')).toBe(true);
|
|
237
|
-
expect(ability.can('create', 'Post')).toBe(true);
|
|
238
|
-
expect(ability.can('update', 'Post')).toBe(true);
|
|
239
|
-
expect(ability.can('delete', 'Post')).toBe(false);
|
|
240
|
-
});
|
|
241
|
-
it('should apply default rules', async () => {
|
|
242
|
-
const config = {
|
|
243
|
-
roles: {},
|
|
244
|
-
defaultRules: [
|
|
245
|
-
{ action: 'read', subject: 'Query' },
|
|
246
|
-
{ action: 'read', subject: 'PublicData' },
|
|
247
|
-
],
|
|
248
|
-
};
|
|
249
|
-
const builder = createConfigBasedAbilityBuilder(config);
|
|
250
|
-
const ability = await builder();
|
|
251
|
-
expect(ability.can('read', 'Query')).toBe(true);
|
|
252
|
-
expect(ability.can('read', 'PublicData')).toBe(true);
|
|
253
|
-
});
|
|
254
|
-
it('should combine multiple roles', async () => {
|
|
255
|
-
const config = {
|
|
256
|
-
roles: {
|
|
257
|
-
editor: [
|
|
258
|
-
{ action: 'create', subject: 'Post' },
|
|
259
|
-
{ action: 'update', subject: 'Post' },
|
|
260
|
-
],
|
|
261
|
-
moderator: [
|
|
262
|
-
{ action: 'delete', subject: 'Comment' },
|
|
263
|
-
{ action: 'update', subject: 'Comment' },
|
|
264
|
-
],
|
|
265
|
-
},
|
|
266
|
-
};
|
|
267
|
-
const builder = createConfigBasedAbilityBuilder(config);
|
|
268
|
-
const user = { id: '123', roles: ['editor', 'moderator'] };
|
|
269
|
-
const ability = await builder(user);
|
|
270
|
-
expect(ability.can('create', 'Post')).toBe(true);
|
|
271
|
-
expect(ability.can('update', 'Post')).toBe(true);
|
|
272
|
-
expect(ability.can('delete', 'Comment')).toBe(true);
|
|
273
|
-
expect(ability.can('update', 'Comment')).toBe(true);
|
|
274
|
-
});
|
|
275
|
-
it('should handle field-level permissions', async () => {
|
|
276
|
-
const config = {
|
|
277
|
-
roles: {
|
|
278
|
-
user: [
|
|
279
|
-
{ action: 'update', subject: 'User', fields: ['name', 'email'] },
|
|
280
|
-
{ action: 'update', subject: 'User', fields: 'role', inverted: true },
|
|
281
|
-
],
|
|
282
|
-
},
|
|
283
|
-
};
|
|
284
|
-
const builder = createConfigBasedAbilityBuilder(config);
|
|
285
|
-
const user = { id: '123', roles: ['user'] };
|
|
286
|
-
const ability = await builder(user);
|
|
287
|
-
expect(ability.can('update', 'User', 'name')).toBe(true);
|
|
288
|
-
expect(ability.can('update', 'User', 'email')).toBe(true);
|
|
289
|
-
expect(ability.can('update', 'User', 'role')).toBe(false);
|
|
290
|
-
});
|
|
291
|
-
it('should process userId template variables in conditions', async () => {
|
|
292
|
-
const config = {
|
|
293
|
-
roles: {
|
|
294
|
-
user: [
|
|
295
|
-
{
|
|
296
|
-
action: 'update',
|
|
297
|
-
subject: 'Post',
|
|
298
|
-
conditions: { authorId: '{{userId}}' },
|
|
299
|
-
},
|
|
300
|
-
],
|
|
301
|
-
},
|
|
302
|
-
};
|
|
303
|
-
const builder = createConfigBasedAbilityBuilder(config);
|
|
304
|
-
const user = { id: '123', roles: ['user'] };
|
|
305
|
-
const ability = await builder(user);
|
|
306
|
-
expect(ability.can('update', subject('Post', { authorId: '123' }))).toBe(true);
|
|
307
|
-
expect(ability.can('update', subject('Post', { authorId: '456' }))).toBe(false);
|
|
308
|
-
});
|
|
309
|
-
it('should handle user without roles', async () => {
|
|
310
|
-
const config = {
|
|
311
|
-
roles: {
|
|
312
|
-
user: [{ action: 'read', subject: 'Post' }],
|
|
313
|
-
},
|
|
314
|
-
defaultRules: [{ action: 'read', subject: 'Query' }],
|
|
315
|
-
};
|
|
316
|
-
const builder = createConfigBasedAbilityBuilder(config);
|
|
317
|
-
const ability = await builder({ id: '123', roles: [] });
|
|
318
|
-
expect(ability.can('read', 'Query')).toBe(true);
|
|
319
|
-
expect(ability.can('read', 'Post')).toBe(false);
|
|
320
|
-
});
|
|
321
|
-
it('should handle undefined user', async () => {
|
|
322
|
-
const config = {
|
|
323
|
-
roles: {
|
|
324
|
-
user: [{ action: 'read', subject: 'Post' }],
|
|
325
|
-
},
|
|
326
|
-
defaultRules: [{ action: 'read', subject: 'Query' }],
|
|
327
|
-
};
|
|
328
|
-
const builder = createConfigBasedAbilityBuilder(config);
|
|
329
|
-
const ability = await builder();
|
|
330
|
-
expect(ability.can('read', 'Query')).toBe(true);
|
|
331
|
-
expect(ability.can('read', 'Post')).toBe(false);
|
|
332
|
-
});
|
|
333
|
-
it('should handle role not defined in config', async () => {
|
|
334
|
-
const config = {
|
|
335
|
-
roles: {
|
|
336
|
-
admin: [{ action: 'manage', subject: 'all' }],
|
|
337
|
-
},
|
|
338
|
-
};
|
|
339
|
-
const builder = createConfigBasedAbilityBuilder(config);
|
|
340
|
-
const user = { id: '123', roles: ['unknownRole'] };
|
|
341
|
-
const ability = await builder(user);
|
|
342
|
-
expect(ability.can('manage', 'all')).toBe(false);
|
|
343
|
-
});
|
|
344
|
-
it('should handle conditions without userId in template', async () => {
|
|
345
|
-
const config = {
|
|
346
|
-
roles: {
|
|
347
|
-
user: [
|
|
348
|
-
{
|
|
349
|
-
action: 'update',
|
|
350
|
-
subject: 'Post',
|
|
351
|
-
conditions: { status: 'draft' },
|
|
352
|
-
},
|
|
353
|
-
],
|
|
354
|
-
},
|
|
355
|
-
};
|
|
356
|
-
const builder = createConfigBasedAbilityBuilder(config);
|
|
357
|
-
const user = { id: '123', roles: ['user'] };
|
|
358
|
-
const ability = await builder(user);
|
|
359
|
-
expect(ability.can('update', subject('Post', { status: 'draft' }))).toBe(true);
|
|
360
|
-
expect(ability.can('update', subject('Post', { status: 'published' }))).toBe(false);
|
|
361
|
-
});
|
|
362
|
-
});
|
|
363
|
-
describe('createAbilityFactory', () => {
|
|
364
|
-
it('should create a factory function', () => {
|
|
365
|
-
const builderFn = (user) => {
|
|
366
|
-
const { can, build } = new AbilityBuilder(Ability);
|
|
367
|
-
can('read', 'Query');
|
|
368
|
-
if (user?.roles?.includes('admin')) {
|
|
369
|
-
can('manage', 'all');
|
|
370
|
-
}
|
|
371
|
-
return build({ detectSubjectType });
|
|
372
|
-
};
|
|
373
|
-
const factory = createAbilityFactory(builderFn);
|
|
374
|
-
expect(typeof factory).toBe('function');
|
|
375
|
-
const ability1 = factory();
|
|
376
|
-
expect(ability1.can('read', 'Query')).toBe(true);
|
|
377
|
-
expect(ability1.can('manage', 'all')).toBe(false);
|
|
378
|
-
const ability2 = factory({ id: '123', roles: ['admin'] });
|
|
379
|
-
expect(ability2.can('manage', 'all')).toBe(true);
|
|
380
|
-
});
|
|
381
|
-
});
|
|
382
|
-
describe('createAbility with different builders', () => {
|
|
383
|
-
it('should work with database builder', async () => {
|
|
384
|
-
const mockFetchRules = vi.fn().mockResolvedValue([{ action: 'read', subject: 'Post', inverted: false }]);
|
|
385
|
-
const builder = createDatabaseAbilityBuilder(mockFetchRules);
|
|
386
|
-
const user = { id: '123', roles: ['user'] };
|
|
387
|
-
const ability = await createAbility(user, builder);
|
|
388
|
-
expect(ability.can('read', 'Post')).toBe(true);
|
|
389
|
-
});
|
|
390
|
-
it('should work with config-based builder', async () => {
|
|
391
|
-
const config = {
|
|
392
|
-
roles: {
|
|
393
|
-
admin: [{ action: 'manage', subject: 'all' }],
|
|
394
|
-
},
|
|
395
|
-
};
|
|
396
|
-
const builder = createConfigBasedAbilityBuilder(config);
|
|
397
|
-
const user = { id: '123', roles: ['admin'] };
|
|
398
|
-
const ability = await createAbility(user, builder);
|
|
399
|
-
expect(ability.can('manage', 'all')).toBe(true);
|
|
400
|
-
});
|
|
401
|
-
});
|
|
402
|
-
});
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { PureAbility } from '@casl/ability';
|
|
2
|
-
import { GraphQLSchema } from 'graphql';
|
|
3
|
-
import type { AppAbility } from '../../src/middleware/ability';
|
|
4
|
-
import { Context, User } from '../../src/types';
|
|
5
|
-
/**
|
|
6
|
-
* Create a mock GraphQL context for testing
|
|
7
|
-
*/
|
|
8
|
-
export declare const createMockContext: (user?: User, ability?: AppAbility) => Context;
|
|
9
|
-
/**
|
|
10
|
-
* Create a simple test schema
|
|
11
|
-
*/
|
|
12
|
-
export declare const createTestSchema: () => GraphQLSchema;
|
|
13
|
-
/**
|
|
14
|
-
* Create test users with different roles
|
|
15
|
-
*/
|
|
16
|
-
export declare const testUsers: {
|
|
17
|
-
public: undefined;
|
|
18
|
-
regular: {
|
|
19
|
-
id: string;
|
|
20
|
-
roles: string[];
|
|
21
|
-
};
|
|
22
|
-
moderator: {
|
|
23
|
-
id: string;
|
|
24
|
-
roles: string[];
|
|
25
|
-
};
|
|
26
|
-
editor: {
|
|
27
|
-
id: string;
|
|
28
|
-
roles: string[];
|
|
29
|
-
};
|
|
30
|
-
admin: {
|
|
31
|
-
id: string;
|
|
32
|
-
roles: string[];
|
|
33
|
-
};
|
|
34
|
-
};
|
|
35
|
-
/**
|
|
36
|
-
* Assertion helper for checking abilities
|
|
37
|
-
*/
|
|
38
|
-
export declare const expectAbility: (ability: PureAbility) => {
|
|
39
|
-
toAllow: (action: string, subject: string, conditions?: any) => void;
|
|
40
|
-
toDeny: (action: string, subject: string, conditions?: any) => void;
|
|
41
|
-
};
|
|
42
|
-
/**
|
|
43
|
-
* Mock GraphQL ResolveInfo for testing
|
|
44
|
-
*/
|
|
45
|
-
export declare const createMockResolveInfo: (overrides?: any) => any;
|
|
46
|
-
//# sourceMappingURL=test-utils.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"test-utils.d.ts","sourceRoot":"","sources":["../../../tests/helpers/test-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAE3C,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAEvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAA;AAC9D,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AAE/C;;GAEG;AACH,eAAO,MAAM,iBAAiB,GAAI,OAAO,IAAI,EAAE,UAAU,UAAU,KAAG,OAOrE,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,gBAAgB,QAAO,aA4CnC,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;CAMrB,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,aAAa,GAAI,SAAS,WAAW;sBAC/B,MAAM,WAAW,MAAM,eAAe,GAAG;qBAG1C,MAAM,WAAW,MAAM,eAAe,GAAG;CAGzD,CAAA;AAEF;;GAEG;AACH,eAAO,MAAM,qBAAqB,GAAI,YAAW,GAAQ,KAAG,GAS1D,CAAA"}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import { PureAbility } from '@casl/ability';
|
|
2
|
-
import { makeExecutableSchema } from '@graphql-tools/schema';
|
|
3
|
-
/**
|
|
4
|
-
* Create a mock GraphQL context for testing
|
|
5
|
-
*/
|
|
6
|
-
export const createMockContext = (user, ability) => {
|
|
7
|
-
const defaultAbility = new PureAbility([{ action: 'read', subject: 'Query' }]);
|
|
8
|
-
return {
|
|
9
|
-
user: user || undefined,
|
|
10
|
-
ability: ability || defaultAbility,
|
|
11
|
-
};
|
|
12
|
-
};
|
|
13
|
-
/**
|
|
14
|
-
* Create a simple test schema
|
|
15
|
-
*/
|
|
16
|
-
export const createTestSchema = () => {
|
|
17
|
-
const typeDefs = `
|
|
18
|
-
type User {
|
|
19
|
-
id: ID!
|
|
20
|
-
name: String!
|
|
21
|
-
email: String
|
|
22
|
-
role: String
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
type Post {
|
|
26
|
-
id: ID!
|
|
27
|
-
title: String!
|
|
28
|
-
content: String!
|
|
29
|
-
authorId: String!
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
type Query {
|
|
33
|
-
me: User
|
|
34
|
-
user(id: ID!): User
|
|
35
|
-
posts: [Post!]!
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
type Mutation {
|
|
39
|
-
updateUser(id: ID!, name: String): User
|
|
40
|
-
createPost(title: String!, content: String!): Post
|
|
41
|
-
}
|
|
42
|
-
`;
|
|
43
|
-
const resolvers = {
|
|
44
|
-
Query: {
|
|
45
|
-
me: () => ({ id: '123', name: 'Test User', email: 'test@example.com', role: 'user' }),
|
|
46
|
-
user: (_, { id }) => ({ id, name: `User ${id}`, email: `user${id}@example.com`, role: 'user' }),
|
|
47
|
-
posts: () => [
|
|
48
|
-
{ id: '1', title: 'Post 1', content: 'Content 1', authorId: '123' },
|
|
49
|
-
{ id: '2', title: 'Post 2', content: 'Content 2', authorId: '456' },
|
|
50
|
-
],
|
|
51
|
-
},
|
|
52
|
-
Mutation: {
|
|
53
|
-
updateUser: (_, { id, name }) => ({ id, name: name || 'Updated', email: 'updated@example.com' }),
|
|
54
|
-
createPost: (_, { title, content }) => ({ id: '3', title, content, authorId: '123' }),
|
|
55
|
-
},
|
|
56
|
-
};
|
|
57
|
-
return makeExecutableSchema({ typeDefs, resolvers });
|
|
58
|
-
};
|
|
59
|
-
/**
|
|
60
|
-
* Create test users with different roles
|
|
61
|
-
*/
|
|
62
|
-
export const testUsers = {
|
|
63
|
-
public: undefined,
|
|
64
|
-
regular: { id: '123', roles: ['user'] },
|
|
65
|
-
moderator: { id: '456', roles: ['user', 'moderator'] },
|
|
66
|
-
editor: { id: '789', roles: ['user', 'editor'] },
|
|
67
|
-
admin: { id: '999', roles: ['admin'] },
|
|
68
|
-
};
|
|
69
|
-
/**
|
|
70
|
-
* Assertion helper for checking abilities
|
|
71
|
-
*/
|
|
72
|
-
export const expectAbility = (ability) => ({
|
|
73
|
-
toAllow: (action, subject, conditions) => {
|
|
74
|
-
expect(ability.can(action, subject, conditions)).toBe(true);
|
|
75
|
-
},
|
|
76
|
-
toDeny: (action, subject, conditions) => {
|
|
77
|
-
expect(ability.can(action, subject, conditions)).toBe(false);
|
|
78
|
-
},
|
|
79
|
-
});
|
|
80
|
-
/**
|
|
81
|
-
* Mock GraphQL ResolveInfo for testing
|
|
82
|
-
*/
|
|
83
|
-
export const createMockResolveInfo = (overrides = {}) => ({
|
|
84
|
-
fieldName: 'testField',
|
|
85
|
-
parentType: { name: 'Query' },
|
|
86
|
-
path: { typename: 'Query', key: 'testField' },
|
|
87
|
-
operation: {
|
|
88
|
-
operation: 'query',
|
|
89
|
-
loc: { source: { body: 'query { testField }' } },
|
|
90
|
-
},
|
|
91
|
-
...overrides,
|
|
92
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"introspection.test.d.ts","sourceRoot":"","sources":["../../tests/introspection.test.ts"],"names":[],"mappings":""}
|