@nest-boot/permission 7.3.6 → 8.0.0-beta.1

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.
Files changed (69) hide show
  1. package/dist/decorators/can.decorator.d.ts +4 -17
  2. package/dist/decorators/can.decorator.js +5 -15
  3. package/dist/decorators/can.decorator.js.map +1 -1
  4. package/dist/enums/permission-action.enum.d.ts +0 -6
  5. package/dist/enums/permission-action.enum.js +2 -11
  6. package/dist/enums/permission-action.enum.js.map +1 -1
  7. package/dist/index.d.ts +16 -16
  8. package/dist/index.js +16 -32
  9. package/dist/index.js.map +1 -1
  10. package/dist/interfaces/can-options.interface.d.ts +2 -5
  11. package/dist/interfaces/can-options.interface.js +1 -2
  12. package/dist/interfaces/permission-module-options.interface.d.ts +1 -3
  13. package/dist/interfaces/permission-module-options.interface.js +1 -2
  14. package/dist/interfaces/route-argument-metadata-value.interface.d.ts +0 -4
  15. package/dist/interfaces/route-argument-metadata-value.interface.js +1 -2
  16. package/dist/permission.ability-builder.d.ts +1 -3
  17. package/dist/permission.ability-builder.js +3 -9
  18. package/dist/permission.ability-builder.js.map +1 -1
  19. package/dist/permission.constants.d.ts +0 -24
  20. package/dist/permission.constants.js +7 -34
  21. package/dist/permission.constants.js.map +1 -1
  22. package/dist/permission.guard.d.ts +2 -22
  23. package/dist/permission.guard.js +40 -57
  24. package/dist/permission.guard.js.map +1 -1
  25. package/dist/permission.module-definition.d.ts +1 -3
  26. package/dist/permission.module-definition.js +4 -9
  27. package/dist/permission.module-definition.js.map +1 -1
  28. package/dist/permission.module.d.ts +1 -2
  29. package/dist/permission.module.js +10 -14
  30. package/dist/permission.module.js.map +1 -1
  31. package/dist/types/build-ability-callback.type.d.ts +1 -2
  32. package/dist/types/build-ability-callback.type.js +1 -2
  33. package/dist/types/can-subject-factory.type.d.ts +0 -1
  34. package/dist/types/can-subject-factory.type.js +1 -2
  35. package/dist/types/can-subject.type.d.ts +1 -2
  36. package/dist/types/can-subject.type.js +1 -2
  37. package/dist/types/permission-ability.type.d.ts +1 -2
  38. package/dist/types/permission-ability.type.js +1 -2
  39. package/dist/types/route-argument-metadata.type.d.ts +1 -2
  40. package/dist/types/route-argument-metadata.type.js +1 -2
  41. package/dist/utils/can.util.d.ts +1 -2
  42. package/dist/utils/can.util.js +3 -7
  43. package/dist/utils/can.util.js.map +1 -1
  44. package/dist/utils/get-permission-ability.util.d.ts +1 -2
  45. package/dist/utils/get-permission-ability.util.js +6 -11
  46. package/dist/utils/get-permission-ability.util.js.map +1 -1
  47. package/package.json +33 -25
  48. package/dist/decorators/can.decorator.spec.d.ts +0 -1
  49. package/dist/decorators/can.decorator.spec.js +0 -50
  50. package/dist/decorators/can.decorator.spec.js.map +0 -1
  51. package/dist/index.spec.d.ts +0 -0
  52. package/dist/index.spec.js +0 -47
  53. package/dist/index.spec.js.map +0 -1
  54. package/dist/permission.ability-builder.spec.d.ts +0 -1
  55. package/dist/permission.ability-builder.spec.js +0 -16
  56. package/dist/permission.ability-builder.spec.js.map +0 -1
  57. package/dist/permission.guard.spec.d.ts +0 -1
  58. package/dist/permission.guard.spec.js +0 -577
  59. package/dist/permission.guard.spec.js.map +0 -1
  60. package/dist/permission.module.spec.d.ts +0 -1
  61. package/dist/permission.module.spec.js +0 -12
  62. package/dist/permission.module.spec.js.map +0 -1
  63. package/dist/tsconfig.build.tsbuildinfo +0 -1
  64. package/dist/utils/can.util.spec.d.ts +0 -1
  65. package/dist/utils/can.util.spec.js +0 -34
  66. package/dist/utils/can.util.spec.js.map +0 -1
  67. package/dist/utils/get-permission-ability.util.spec.d.ts +0 -1
  68. package/dist/utils/get-permission-ability.util.spec.js +0 -27
  69. package/dist/utils/get-permission-ability.util.spec.js.map +0 -1
@@ -1,47 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- describe("public API", () => {
36
- it("should export permission package APIs", async () => {
37
- const api = await Promise.resolve().then(() => __importStar(require(".")));
38
- expect(api.Can).toBeDefined();
39
- expect(api.PermissionAction).toBeDefined();
40
- expect(api.PermissionAbilityBuilder).toBeDefined();
41
- expect(api.PermissionGuard).toBeDefined();
42
- expect(api.PermissionModule).toBeDefined();
43
- expect(api.can).toBeDefined();
44
- expect(api.getPermissionAbility).toBeDefined();
45
- });
46
- });
47
- //# sourceMappingURL=index.spec.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.spec.js","sourceRoot":"","sources":["../src/index.spec.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC1B,EAAE,CAAC,uCAAuC,EAAE,KAAK,IAAI,EAAE;QACrD,MAAM,GAAG,GAAG,wDAAa,GAAG,GAAC,CAAC;QAE9B,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;QAC9B,MAAM,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,WAAW,EAAE,CAAC;QAC3C,MAAM,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC,WAAW,EAAE,CAAC;QACnD,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,WAAW,EAAE,CAAC;QAC1C,MAAM,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,WAAW,EAAE,CAAC;QAC3C,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;QAC9B,MAAM,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC,WAAW,EAAE,CAAC;IACjD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -1 +0,0 @@
1
- export {};
@@ -1,16 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const permission_action_enum_1 = require("./enums/permission-action.enum");
4
- const permission_ability_builder_1 = require("./permission.ability-builder");
5
- class Post {
6
- }
7
- describe("PermissionAbilityBuilder", () => {
8
- it("should build a CASL mongo ability", () => {
9
- const builder = new permission_ability_builder_1.PermissionAbilityBuilder();
10
- builder.can(permission_action_enum_1.PermissionAction.READ, Post);
11
- const ability = builder.build();
12
- expect(ability.can(permission_action_enum_1.PermissionAction.READ, Post)).toBe(true);
13
- expect(ability.can(permission_action_enum_1.PermissionAction.DELETE, Post)).toBe(false);
14
- });
15
- });
16
- //# sourceMappingURL=permission.ability-builder.spec.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"permission.ability-builder.spec.js","sourceRoot":"","sources":["../src/permission.ability-builder.spec.ts"],"names":[],"mappings":";;AAAA,2EAAkE;AAClE,6EAAwE;AAExE,MAAM,IAAI;CAAG;AAEb,QAAQ,CAAC,0BAA0B,EAAE,GAAG,EAAE;IACxC,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;QAC3C,MAAM,OAAO,GAAG,IAAI,qDAAwB,EAAE,CAAC;QAE/C,OAAO,CAAC,GAAG,CAAC,yCAAgB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACzC,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;QAEhC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,yCAAgB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5D,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,yCAAgB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -1 +0,0 @@
1
- export {};
@@ -1,577 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const request_context_1 = require("@nest-boot/request-context");
4
- const common_1 = require("@nestjs/common");
5
- const core_1 = require("@nestjs/core");
6
- const testing_1 = require("@nestjs/testing");
7
- const permission_action_enum_1 = require("./enums/permission-action.enum");
8
- const permission_constants_1 = require("./permission.constants");
9
- const permission_guard_1 = require("./permission.guard");
10
- const permission_module_definition_1 = require("./permission.module-definition");
11
- const get_permission_ability_util_1 = require("./utils/get-permission-ability.util");
12
- class Subject {
13
- }
14
- class Controller {
15
- }
16
- describe("PermissionGuard", () => {
17
- afterEach(() => {
18
- Reflect.deleteMetadata(permission_constants_1.ROUTE_ARGS_METADATA, Controller, "handler");
19
- });
20
- it("allows requests without permission metadata", async () => {
21
- const { guard, reflector, buildAbility } = await createGuard();
22
- reflector.getAllAndOverride.mockReturnValue(undefined);
23
- await expect(guard.canActivate(createContext())).resolves.toBe(true);
24
- expect(buildAbility).not.toHaveBeenCalled();
25
- });
26
- it("throws when permission metadata exists but no ability is available", async () => {
27
- const { guard, reflector, buildAbility } = await createGuard();
28
- reflector.getAllAndOverride.mockReturnValue({
29
- action: permission_action_enum_1.PermissionAction.READ,
30
- subject: Subject,
31
- });
32
- await request_context_1.RequestContext.run(new request_context_1.RequestContext({ type: "http" }), async () => {
33
- await expect(guard.canActivate(createContext())).rejects.toBeInstanceOf(common_1.ForbiddenException);
34
- await expect(guard.canActivate(createContext())).rejects.toBeInstanceOf(common_1.ForbiddenException);
35
- });
36
- expect(buildAbility).toHaveBeenCalledTimes(1);
37
- });
38
- it("builds, caches, and checks ability against configured permission metadata", async () => {
39
- const canMock = jest.fn(() => true);
40
- const ability = {
41
- can: canMock,
42
- };
43
- const { guard, reflector, buildAbility, req, res } = await createGuard(ability);
44
- reflector.getAllAndOverride.mockReturnValue({
45
- action: permission_action_enum_1.PermissionAction.UPDATE,
46
- subject: Subject,
47
- });
48
- const context = createContext(req, res);
49
- await request_context_1.RequestContext.run(new request_context_1.RequestContext({ type: "http" }), async () => {
50
- await expect(guard.canActivate(context)).resolves.toBe(true);
51
- await expect(request_context_1.RequestContext.get(permission_constants_1.PERMISSION_ABILITY_PROMISE)).resolves.toBe(ability);
52
- expect(request_context_1.RequestContext.get(permission_constants_1.PERMISSION_ABILITY)).toBe(ability);
53
- expect((0, get_permission_ability_util_1.getPermissionAbility)()).toBe(ability);
54
- });
55
- expect(buildAbility).toHaveBeenCalledWith(context);
56
- expect(canMock).toHaveBeenCalledWith(permission_action_enum_1.PermissionAction.UPDATE, Subject);
57
- });
58
- it("passes GraphQL execution context to buildAbility", async () => {
59
- const canMock = jest.fn(() => true);
60
- const ability = {
61
- can: canMock,
62
- };
63
- const { guard, reflector, buildAbility, req, res } = await createGuard(ability);
64
- const gqlContext = { req, res };
65
- reflector.getAllAndOverride.mockReturnValue({
66
- action: permission_action_enum_1.PermissionAction.UPDATE,
67
- subject: Subject,
68
- });
69
- const context = createContext(undefined, undefined, [undefined, {}, gqlContext, {}], "graphql");
70
- await request_context_1.RequestContext.run(new request_context_1.RequestContext({ type: "graphql" }), async () => {
71
- await expect(guard.canActivate(context)).resolves.toBe(true);
72
- });
73
- expect(buildAbility).toHaveBeenCalledWith(context);
74
- });
75
- it("uses cached ability before building a new one", async () => {
76
- const canMock = jest.fn(() => true);
77
- const ability = {
78
- can: canMock,
79
- };
80
- const { guard, reflector, buildAbility } = await createGuard();
81
- reflector.getAllAndOverride.mockReturnValue({
82
- action: permission_action_enum_1.PermissionAction.UPDATE,
83
- subject: Subject,
84
- });
85
- await request_context_1.RequestContext.run(new request_context_1.RequestContext({ type: "http" }), () => {
86
- request_context_1.RequestContext.set(permission_constants_1.PERMISSION_ABILITY, ability);
87
- return expect(guard.canActivate(createContext())).resolves.toBe(true);
88
- });
89
- expect(buildAbility).not.toHaveBeenCalled();
90
- expect(canMock).toHaveBeenCalledWith(permission_action_enum_1.PermissionAction.UPDATE, Subject);
91
- });
92
- it("checks ability against subject resolved from self and decorated method args", async () => {
93
- const subjectInstance = new Subject();
94
- const input = { id: 123 };
95
- const handlerThis = {
96
- workspaceMemberService: {
97
- findOne: jest.fn((_id) => Promise.resolve(subjectInstance)),
98
- },
99
- };
100
- const subjectFactory = jest.fn((self, params) => self.workspaceMemberService.findOne(params.input.id));
101
- const canMock = jest.fn(() => true);
102
- const ability = {
103
- can: canMock,
104
- };
105
- const { guard, reflector, moduleRef } = await createGuard(ability, handlerThis);
106
- setRouteArgsMetadata({
107
- "3:0": {
108
- index: 0,
109
- },
110
- });
111
- reflector.getAllAndOverride.mockReturnValue({
112
- action: permission_action_enum_1.PermissionAction.UPDATE,
113
- subject: subjectFactory,
114
- });
115
- await request_context_1.RequestContext.run(new request_context_1.RequestContext({ type: "http" }), async () => {
116
- await expect(guard.canActivate(createContext({ headers: {}, body: { input } }, {}, []))).resolves.toBe(true);
117
- });
118
- expect(moduleRef.resolve).toHaveBeenCalledWith(Controller, expect.any(Object), { strict: false });
119
- expect(subjectFactory.mock.contexts[0]).toBeUndefined();
120
- expect(subjectFactory).toHaveBeenCalledWith(handlerThis, { input });
121
- expect(handlerThis.workspaceMemberService.findOne).toHaveBeenCalledWith(123);
122
- expect(canMock).toHaveBeenCalledWith(permission_action_enum_1.PermissionAction.UPDATE, subjectInstance);
123
- });
124
- it("passes GraphQL resolver arguments in decorated parameter order", async () => {
125
- const input = { title: "New title" };
126
- const subjectInstance = new Subject();
127
- const handlerThis = {
128
- postService: {
129
- findOneOrFail: jest.fn((_id, _input) => subjectInstance),
130
- },
131
- };
132
- const subjectFactory = jest.fn((self, id, params) => self.postService.findOneOrFail(id, params));
133
- const canMock = jest.fn(() => true);
134
- const ability = {
135
- can: canMock,
136
- };
137
- const { guard, reflector, req, res } = await createGuard(ability, handlerThis);
138
- setRouteArgsMetadata({
139
- "3:0": {
140
- index: 0,
141
- data: "id",
142
- },
143
- "3:1": {
144
- index: 1,
145
- data: "input",
146
- },
147
- });
148
- reflector.getAllAndOverride.mockReturnValue({
149
- action: permission_action_enum_1.PermissionAction.UPDATE,
150
- subject: subjectFactory,
151
- });
152
- await request_context_1.RequestContext.run(new request_context_1.RequestContext({ type: "graphql" }), async () => {
153
- await expect(guard.canActivate(createContext(undefined, undefined, [undefined, { input, id: "post-1" }, { req, res }, {}], "graphql"))).resolves.toBe(true);
154
- });
155
- expect(subjectFactory).toHaveBeenCalledWith(handlerThis, "post-1", input);
156
- expect(handlerThis.postService.findOneOrFail).toHaveBeenCalledWith("post-1", input);
157
- });
158
- it("passes every GraphQL route argument source to subject factories", async () => {
159
- const root = { root: true };
160
- const info = { fieldName: "post" };
161
- const subjectInstance = new Subject();
162
- const handlerThis = {};
163
- const subjectFactory = jest.fn(() => subjectInstance);
164
- const canMock = jest.fn(() => true);
165
- const ability = {
166
- can: canMock,
167
- };
168
- const { guard, reflector, req } = await createGuard(ability, handlerThis);
169
- setRouteArgsMetadata({
170
- "0:0": {
171
- index: 0,
172
- },
173
- "3:1": {
174
- index: 1,
175
- data: "id",
176
- },
177
- "1:2": {
178
- index: 2,
179
- data: "viewer",
180
- },
181
- "2:3": {
182
- index: 3,
183
- },
184
- "99:4": {
185
- index: 4,
186
- },
187
- });
188
- reflector.getAllAndOverride.mockReturnValue({
189
- action: permission_action_enum_1.PermissionAction.READ,
190
- subject: subjectFactory,
191
- });
192
- await request_context_1.RequestContext.run(new request_context_1.RequestContext({ type: "graphql" }), async () => {
193
- await expect(guard.canActivate(createContext(undefined, undefined, [root, { id: "post-1" }, { req, viewer: "user-1" }, info], "graphql"))).resolves.toBe(true);
194
- });
195
- expect(subjectFactory).toHaveBeenCalledWith(handlerThis, root, "post-1", "user-1", info, undefined);
196
- expect(canMock).toHaveBeenCalledWith(permission_action_enum_1.PermissionAction.READ, subjectInstance);
197
- });
198
- it("passes HTTP controller arguments in decorated parameter order", async () => {
199
- const input = { title: "New title" };
200
- const subjectInstance = new Subject();
201
- const handlerThis = {
202
- postService: {
203
- findOneOrFail: jest.fn((_id, _input) => subjectInstance),
204
- },
205
- };
206
- const subjectFactory = jest.fn((self, id, params) => self.postService.findOneOrFail(id, params));
207
- const canMock = jest.fn(() => true);
208
- const ability = {
209
- can: canMock,
210
- };
211
- const { guard, reflector } = await createGuard(ability, handlerThis);
212
- setRouteArgsMetadata({
213
- "5:0": {
214
- index: 0,
215
- data: "id",
216
- },
217
- "3:1": {
218
- index: 1,
219
- data: "input",
220
- },
221
- });
222
- reflector.getAllAndOverride.mockReturnValue({
223
- action: permission_action_enum_1.PermissionAction.UPDATE,
224
- subject: subjectFactory,
225
- });
226
- await request_context_1.RequestContext.run(new request_context_1.RequestContext({ type: "http" }), async () => {
227
- await expect(guard.canActivate(createContext({
228
- headers: {},
229
- params: { id: "post-1" },
230
- body: { input },
231
- }, {}, []))).resolves.toBe(true);
232
- });
233
- expect(subjectFactory).toHaveBeenCalledWith(handlerThis, "post-1", input);
234
- expect(handlerThis.postService.findOneOrFail).toHaveBeenCalledWith("post-1", input);
235
- });
236
- it("passes every HTTP route argument source to subject factories", async () => {
237
- const req = {
238
- body: { input: { title: "Draft" } },
239
- files: ["first-file"],
240
- headers: { "x-user-id": "user-1" },
241
- hosts: { account: "acme" },
242
- ip: "127.0.0.1",
243
- params: { id: "post-1" },
244
- query: { preview: "true" },
245
- rawBody: Buffer.from("raw"),
246
- session: { id: "session-1" },
247
- upload: { filename: "avatar.png" },
248
- };
249
- const res = {
250
- locals: {},
251
- };
252
- const next = jest.fn();
253
- const subjectInstance = new Subject();
254
- const handlerThis = {};
255
- const subjectFactory = jest.fn(() => subjectInstance);
256
- const canMock = jest.fn(() => true);
257
- const ability = {
258
- can: canMock,
259
- };
260
- const { guard, reflector } = await createGuard(ability, handlerThis);
261
- setRouteArgsMetadata({
262
- "0:0": {
263
- index: 0,
264
- },
265
- "1:1": {
266
- index: 1,
267
- },
268
- "2:2": {
269
- index: 2,
270
- },
271
- "3:3": {
272
- index: 3,
273
- },
274
- "12:4": {
275
- index: 4,
276
- },
277
- "5:5": {
278
- index: 5,
279
- data: "id",
280
- },
281
- "10:6": {
282
- index: 6,
283
- data: "account",
284
- },
285
- "4:7": {
286
- index: 7,
287
- data: "preview",
288
- },
289
- "6:8": {
290
- index: 8,
291
- data: "X-USER-ID",
292
- },
293
- "7:9": {
294
- index: 9,
295
- },
296
- "8:10": {
297
- index: 10,
298
- data: "upload",
299
- },
300
- "8:11": {
301
- index: 11,
302
- },
303
- "9:12": {
304
- index: 12,
305
- },
306
- "11:13": {
307
- index: 13,
308
- },
309
- "99:14": {
310
- index: 14,
311
- },
312
- });
313
- reflector.getAllAndOverride.mockReturnValue({
314
- action: permission_action_enum_1.PermissionAction.READ,
315
- subject: subjectFactory,
316
- });
317
- await request_context_1.RequestContext.run(new request_context_1.RequestContext({ type: "http" }), async () => {
318
- await expect(guard.canActivate(createContext(req, res, [undefined, undefined, next]))).resolves.toBe(true);
319
- });
320
- expect(subjectFactory).toHaveBeenCalledWith(handlerThis, req, res, next, req.body, req.rawBody, "post-1", "acme", "true", "user-1", req.session, req.upload, undefined, req.files, req.ip, undefined);
321
- expect(canMock).toHaveBeenCalledWith(permission_action_enum_1.PermissionAction.READ, subjectInstance);
322
- });
323
- it("passes custom HTTP controller arguments to subject factories", async () => {
324
- const workspace = { id: "workspace-1" };
325
- const subjectInstance = new Subject();
326
- const customFactory = jest.fn((_data, context) => context.switchToHttp().getRequest().headers["x-workspace-id"] === workspace.id
327
- ? workspace
328
- : null);
329
- const handlerThis = {
330
- workspaceService: {
331
- findSubject: jest.fn((_customWorkspace, _id) => subjectInstance),
332
- },
333
- };
334
- const subjectFactory = jest.fn((self, currentWorkspace, id) => self.workspaceService.findSubject(currentWorkspace, id));
335
- const canMock = jest.fn(() => true);
336
- const ability = {
337
- can: canMock,
338
- };
339
- const { guard, reflector } = await createGuard(ability, handlerThis);
340
- setRouteArgsMetadata({
341
- [`workspace${permission_constants_1.CUSTOM_ROUTE_ARGS_METADATA}:0`]: {
342
- index: 0,
343
- data: "workspace",
344
- factory: customFactory,
345
- },
346
- "5:1": {
347
- index: 1,
348
- data: "id",
349
- },
350
- });
351
- reflector.getAllAndOverride.mockReturnValue({
352
- action: permission_action_enum_1.PermissionAction.UPDATE,
353
- subject: subjectFactory,
354
- });
355
- await request_context_1.RequestContext.run(new request_context_1.RequestContext({ type: "http" }), async () => {
356
- await expect(guard.canActivate(createContext({
357
- headers: { "x-workspace-id": workspace.id },
358
- params: { id: "post-1" },
359
- }, {}, []))).resolves.toBe(true);
360
- });
361
- expect(customFactory).toHaveBeenCalledWith("workspace", expect.any(Object));
362
- expect(subjectFactory).toHaveBeenCalledWith(handlerThis, workspace, "post-1");
363
- expect(handlerThis.workspaceService.findSubject).toHaveBeenCalledWith(workspace, "post-1");
364
- expect(canMock).toHaveBeenCalledWith(permission_action_enum_1.PermissionAction.UPDATE, subjectInstance);
365
- });
366
- it("awaits async custom controller arguments before invoking subject factories", async () => {
367
- const workspace = { id: "workspace-1" };
368
- const subjectInstance = new Subject();
369
- const customFactory = jest.fn(() => Promise.resolve(workspace));
370
- const handlerThis = {
371
- workspaceService: {
372
- findSubject: jest.fn((_customWorkspace, _id) => subjectInstance),
373
- },
374
- };
375
- const subjectFactory = jest.fn((self, currentWorkspace, id) => self.workspaceService.findSubject(currentWorkspace, id));
376
- const canMock = jest.fn(() => true);
377
- const ability = {
378
- can: canMock,
379
- };
380
- const { guard, reflector } = await createGuard(ability, handlerThis);
381
- setRouteArgsMetadata({
382
- [`workspace${permission_constants_1.CUSTOM_ROUTE_ARGS_METADATA}:0`]: {
383
- index: 0,
384
- data: "workspace",
385
- factory: customFactory,
386
- },
387
- "5:1": {
388
- index: 1,
389
- data: "id",
390
- },
391
- });
392
- reflector.getAllAndOverride.mockReturnValue({
393
- action: permission_action_enum_1.PermissionAction.UPDATE,
394
- subject: subjectFactory,
395
- });
396
- await request_context_1.RequestContext.run(new request_context_1.RequestContext({ type: "http" }), async () => {
397
- await expect(guard.canActivate(createContext({
398
- headers: {},
399
- params: { id: "post-1" },
400
- }, {}, []))).resolves.toBe(true);
401
- });
402
- expect(subjectFactory).toHaveBeenCalledWith(handlerThis, workspace, "post-1");
403
- expect(handlerThis.workspaceService.findSubject).toHaveBeenCalledWith(workspace, "post-1");
404
- });
405
- it("shares a pending ability build across concurrent checks", async () => {
406
- const canMock = jest.fn(() => true);
407
- const ability = {
408
- can: canMock,
409
- };
410
- const { guard, reflector, buildAbility } = await createGuard();
411
- let resolveAbility;
412
- const abilityPromise = new Promise((resolve) => {
413
- resolveAbility = resolve;
414
- });
415
- buildAbility.mockImplementation(() => abilityPromise);
416
- reflector.getAllAndOverride.mockReturnValue({
417
- action: permission_action_enum_1.PermissionAction.UPDATE,
418
- subject: Subject,
419
- });
420
- await request_context_1.RequestContext.run(new request_context_1.RequestContext({ type: "graphql" }), async () => {
421
- const first = guard.canActivate(createContext());
422
- const second = guard.canActivate(createContext());
423
- await Promise.resolve();
424
- expect(buildAbility).toHaveBeenCalledTimes(1);
425
- resolveAbility(ability);
426
- await expect(Promise.all([first, second])).resolves.toEqual([
427
- true,
428
- true,
429
- ]);
430
- });
431
- expect(canMock).toHaveBeenCalledTimes(2);
432
- });
433
- it("passes no subject factory args when route metadata is missing", async () => {
434
- const subjectInstance = new Subject();
435
- const handlerThis = {};
436
- const subjectFactory = jest.fn(() => subjectInstance);
437
- const canMock = jest.fn(() => true);
438
- const { guard, reflector, moduleRef } = await createGuard({
439
- can: canMock,
440
- }, handlerThis);
441
- reflector.getAllAndOverride.mockReturnValue({
442
- action: permission_action_enum_1.PermissionAction.READ,
443
- subject: subjectFactory,
444
- });
445
- await request_context_1.RequestContext.run(new request_context_1.RequestContext({ type: "rpc" }), async () => {
446
- await expect(guard.canActivate(createContext(undefined, undefined, [], "rpc"))).resolves.toBe(true);
447
- });
448
- expect(moduleRef.resolve).toHaveBeenCalledWith(Controller, undefined, {
449
- strict: false,
450
- });
451
- expect(subjectFactory).toHaveBeenCalledWith(handlerThis);
452
- });
453
- it("falls back to prototype lookup and null when handler names are unavailable", async () => {
454
- const subjectInstance = new Subject();
455
- const handlerThis = {};
456
- const subjectFactory = jest.fn(() => subjectInstance);
457
- const canMock = jest.fn(() => true);
458
- const { guard, reflector } = await createGuard({
459
- can: canMock,
460
- }, handlerThis);
461
- const matchedHandler = createUnnamedHandler();
462
- const unmatchedHandler = createUnnamedHandler();
463
- Object.defineProperty(Controller.prototype, "matched", {
464
- configurable: true,
465
- value: matchedHandler,
466
- });
467
- Reflect.defineMetadata(permission_constants_1.ROUTE_ARGS_METADATA, {
468
- "3:0": {
469
- index: 0,
470
- data: 1,
471
- },
472
- }, Controller, "matched");
473
- reflector.getAllAndOverride.mockReturnValue({
474
- action: permission_action_enum_1.PermissionAction.READ,
475
- subject: subjectFactory,
476
- });
477
- await request_context_1.RequestContext.run(new request_context_1.RequestContext({ type: "http" }), async () => {
478
- const matchedContext = createContext({
479
- body: { input: true },
480
- headers: {},
481
- }, {});
482
- matchedContext.getHandler = jest.fn(() => matchedHandler);
483
- await expect(guard.canActivate(matchedContext)).resolves.toBe(true);
484
- const unmatchedContext = createContext({
485
- body: "not-an-object",
486
- headers: {},
487
- }, {});
488
- unmatchedContext.getHandler = jest.fn(() => unmatchedHandler);
489
- await expect(guard.canActivate(unmatchedContext)).resolves.toBe(true);
490
- });
491
- expect(subjectFactory).toHaveBeenNthCalledWith(1, handlerThis, {
492
- input: true,
493
- });
494
- expect(subjectFactory).toHaveBeenNthCalledWith(2, handlerThis);
495
- Reflect.deleteMetadata(permission_constants_1.ROUTE_ARGS_METADATA, Controller, "matched");
496
- delete Controller.prototype.matched;
497
- });
498
- it("returns false when ability denies the permission", async () => {
499
- const canMock = jest.fn(() => false);
500
- const { guard, reflector } = await createGuard({
501
- can: canMock,
502
- });
503
- reflector.getAllAndOverride.mockReturnValue({
504
- action: permission_action_enum_1.PermissionAction.DELETE,
505
- subject: Subject,
506
- });
507
- await request_context_1.RequestContext.run(new request_context_1.RequestContext({ type: "http" }), async () => {
508
- await expect(guard.canActivate(createContext())).resolves.toBe(false);
509
- });
510
- });
511
- });
512
- async function createGuard(ability = null, handlerThis = {}) {
513
- const reflector = {
514
- getAllAndOverride: jest.fn(),
515
- };
516
- const buildAbility = jest.fn((_ctx) => ability);
517
- const moduleRefMock = {
518
- resolve: jest.fn(() => Promise.resolve(handlerThis)),
519
- };
520
- const req = {
521
- headers: {},
522
- };
523
- const res = {};
524
- const testingModule = await testing_1.Test.createTestingModule({
525
- providers: [
526
- permission_guard_1.PermissionGuard,
527
- {
528
- provide: core_1.Reflector,
529
- useValue: reflector,
530
- },
531
- {
532
- provide: permission_module_definition_1.MODULE_OPTIONS_TOKEN,
533
- useValue: {
534
- buildAbility,
535
- },
536
- },
537
- {
538
- provide: core_1.ModuleRef,
539
- useValue: moduleRefMock,
540
- },
541
- ],
542
- }).compile();
543
- return {
544
- guard: testingModule.get(permission_guard_1.PermissionGuard),
545
- reflector,
546
- buildAbility,
547
- moduleRef: moduleRefMock,
548
- req,
549
- res,
550
- };
551
- }
552
- function createContext(req, res, args = [], type = "http") {
553
- const resolvedReq = arguments.length >= 1 ? req : { headers: {} };
554
- const resolvedRes = arguments.length >= 2 ? res : {};
555
- return {
556
- getType: jest.fn(() => type),
557
- switchToHttp: () => ({
558
- getRequest: () => resolvedReq,
559
- getResponse: () => resolvedRes,
560
- getNext: () => args[2],
561
- }),
562
- getArgs: jest.fn(() => args),
563
- getHandler: jest.fn(() => function handler() {
564
- return undefined;
565
- }),
566
- getClass: jest.fn(() => Controller),
567
- };
568
- }
569
- function setRouteArgsMetadata(metadata) {
570
- Reflect.defineMetadata(permission_constants_1.ROUTE_ARGS_METADATA, metadata, Controller, "handler");
571
- }
572
- function createUnnamedHandler() {
573
- return function () {
574
- return undefined;
575
- };
576
- }
577
- //# sourceMappingURL=permission.guard.spec.js.map