@trailmix-cms/cms 0.4.4 → 0.7.2

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 (267) hide show
  1. package/dist/auth.guard.d.ts +5 -13
  2. package/dist/auth.guard.d.ts.map +1 -1
  3. package/dist/auth.guard.js +24 -95
  4. package/dist/auth.guard.js.map +1 -1
  5. package/dist/collections/account.collection.d.ts +5 -3
  6. package/dist/collections/account.collection.d.ts.map +1 -1
  7. package/dist/collections/account.collection.js +15 -8
  8. package/dist/collections/account.collection.js.map +1 -1
  9. package/dist/collections/api-key.collection.d.ts +54 -0
  10. package/dist/collections/api-key.collection.d.ts.map +1 -0
  11. package/dist/collections/api-key.collection.js +142 -0
  12. package/dist/collections/api-key.collection.js.map +1 -0
  13. package/dist/collections/index.d.ts +4 -2
  14. package/dist/collections/index.d.ts.map +1 -1
  15. package/dist/collections/index.js +9 -5
  16. package/dist/collections/index.js.map +1 -1
  17. package/dist/collections/organization.collection.d.ts +20 -0
  18. package/dist/collections/organization.collection.d.ts.map +1 -0
  19. package/dist/collections/{file.collection.js → organization.collection.js} +17 -17
  20. package/dist/collections/organization.collection.js.map +1 -0
  21. package/dist/collections/role.collection.d.ts +32 -0
  22. package/dist/collections/role.collection.d.ts.map +1 -0
  23. package/dist/collections/role.collection.js +90 -0
  24. package/dist/collections/role.collection.js.map +1 -0
  25. package/dist/collections/security-audit.collection.d.ts +30 -0
  26. package/dist/collections/security-audit.collection.d.ts.map +1 -0
  27. package/dist/collections/security-audit.collection.js +79 -0
  28. package/dist/collections/security-audit.collection.js.map +1 -0
  29. package/dist/constants/cms-collection-names.d.ts +4 -2
  30. package/dist/constants/cms-collection-names.d.ts.map +1 -1
  31. package/dist/constants/cms-collection-names.js +4 -2
  32. package/dist/constants/cms-collection-names.js.map +1 -1
  33. package/dist/constants/provider-symbols.d.ts +10 -12
  34. package/dist/constants/provider-symbols.d.ts.map +1 -1
  35. package/dist/constants/provider-symbols.js +10 -12
  36. package/dist/constants/provider-symbols.js.map +1 -1
  37. package/dist/controllers/account.controller.d.ts +11 -15
  38. package/dist/controllers/account.controller.d.ts.map +1 -1
  39. package/dist/controllers/account.controller.js +69 -13
  40. package/dist/controllers/account.controller.js.map +1 -1
  41. package/dist/controllers/api-keys.controller.d.ts +13 -0
  42. package/dist/controllers/api-keys.controller.d.ts.map +1 -0
  43. package/dist/controllers/api-keys.controller.js +125 -0
  44. package/dist/controllers/api-keys.controller.js.map +1 -0
  45. package/dist/controllers/audit.controller.d.ts.map +1 -1
  46. package/dist/controllers/audit.controller.js +3 -3
  47. package/dist/controllers/audit.controller.js.map +1 -1
  48. package/dist/controllers/audits.controller.d.ts +10 -0
  49. package/dist/controllers/audits.controller.d.ts.map +1 -0
  50. package/dist/controllers/audits.controller.js +107 -0
  51. package/dist/controllers/audits.controller.js.map +1 -0
  52. package/dist/controllers/global-roles.controller.d.ts +16 -0
  53. package/dist/controllers/global-roles.controller.d.ts.map +1 -0
  54. package/dist/controllers/global-roles.controller.js +137 -0
  55. package/dist/controllers/global-roles.controller.js.map +1 -0
  56. package/dist/controllers/index.d.ts +6 -1
  57. package/dist/controllers/index.d.ts.map +1 -1
  58. package/dist/controllers/index.js +6 -1
  59. package/dist/controllers/index.js.map +1 -1
  60. package/dist/controllers/organization-roles.controller.d.ts +16 -0
  61. package/dist/controllers/organization-roles.controller.d.ts.map +1 -0
  62. package/dist/controllers/organization-roles.controller.js +145 -0
  63. package/dist/controllers/organization-roles.controller.js.map +1 -0
  64. package/dist/controllers/organizations.controller.d.ts +65 -0
  65. package/dist/controllers/organizations.controller.d.ts.map +1 -0
  66. package/dist/controllers/organizations.controller.js +140 -0
  67. package/dist/controllers/organizations.controller.js.map +1 -0
  68. package/dist/controllers/security-audits.controller.d.ts +11 -0
  69. package/dist/controllers/security-audits.controller.d.ts.map +1 -0
  70. package/dist/controllers/security-audits.controller.js +130 -0
  71. package/dist/controllers/security-audits.controller.js.map +1 -0
  72. package/dist/decorators/account.decorator.d.ts +1 -3
  73. package/dist/decorators/account.decorator.d.ts.map +1 -1
  74. package/dist/decorators/account.decorator.js +3 -10
  75. package/dist/decorators/account.decorator.js.map +1 -1
  76. package/dist/decorators/audit-context.decorator.d.ts +6 -0
  77. package/dist/decorators/audit-context.decorator.d.ts.map +1 -1
  78. package/dist/decorators/audit-context.decorator.js +12 -3
  79. package/dist/decorators/audit-context.decorator.js.map +1 -1
  80. package/dist/decorators/auth.decorator.d.ts +7 -6
  81. package/dist/decorators/auth.decorator.d.ts.map +1 -1
  82. package/dist/decorators/auth.decorator.js +38 -5
  83. package/dist/decorators/auth.decorator.js.map +1 -1
  84. package/dist/decorators/index.d.ts +4 -0
  85. package/dist/decorators/index.d.ts.map +1 -0
  86. package/dist/decorators/index.js +20 -0
  87. package/dist/decorators/index.js.map +1 -0
  88. package/dist/dto/account.dto.d.ts +33 -0
  89. package/dist/dto/account.dto.d.ts.map +1 -0
  90. package/dist/dto/account.dto.js +14 -0
  91. package/dist/dto/account.dto.js.map +1 -0
  92. package/dist/dto/api-key.dto.d.ts +89 -0
  93. package/dist/dto/api-key.dto.d.ts.map +1 -0
  94. package/dist/dto/api-key.dto.js +27 -0
  95. package/dist/dto/api-key.dto.js.map +1 -0
  96. package/dist/dto/audit.dto.d.ts +11 -5
  97. package/dist/dto/audit.dto.d.ts.map +1 -1
  98. package/dist/dto/audit.dto.js +1 -1
  99. package/dist/dto/audit.dto.js.map +1 -1
  100. package/dist/dto/global-role.dto.d.ts +99 -0
  101. package/dist/dto/global-role.dto.d.ts.map +1 -0
  102. package/dist/dto/global-role.dto.js +26 -0
  103. package/dist/dto/global-role.dto.js.map +1 -0
  104. package/dist/dto/organization-role.dto.d.ts +107 -0
  105. package/dist/dto/organization-role.dto.d.ts.map +1 -0
  106. package/dist/dto/organization-role.dto.js +26 -0
  107. package/dist/dto/organization-role.dto.js.map +1 -0
  108. package/dist/dto/organization.dto.d.ts +57 -0
  109. package/dist/dto/organization.dto.d.ts.map +1 -0
  110. package/dist/dto/organization.dto.js +32 -0
  111. package/dist/dto/organization.dto.js.map +1 -0
  112. package/dist/dto/security-audit.dto.d.ts +95 -0
  113. package/dist/dto/security-audit.dto.d.ts.map +1 -0
  114. package/dist/dto/security-audit.dto.js +26 -0
  115. package/dist/dto/security-audit.dto.js.map +1 -0
  116. package/dist/index.d.ts +7 -2
  117. package/dist/index.d.ts.map +1 -1
  118. package/dist/index.js +8 -3
  119. package/dist/index.js.map +1 -1
  120. package/dist/managers/global-role.manager.d.ts +42 -0
  121. package/dist/managers/global-role.manager.d.ts.map +1 -0
  122. package/dist/managers/global-role.manager.js +117 -0
  123. package/dist/managers/global-role.manager.js.map +1 -0
  124. package/dist/managers/index.d.ts +4 -0
  125. package/dist/managers/index.d.ts.map +1 -0
  126. package/dist/managers/index.js +20 -0
  127. package/dist/managers/index.js.map +1 -0
  128. package/dist/managers/organization-role.manager.d.ts +47 -0
  129. package/dist/managers/organization-role.manager.d.ts.map +1 -0
  130. package/dist/managers/organization-role.manager.js +218 -0
  131. package/dist/managers/organization-role.manager.js.map +1 -0
  132. package/dist/managers/organization.manager.d.ts +39 -0
  133. package/dist/managers/organization.manager.d.ts.map +1 -0
  134. package/dist/managers/organization.manager.js +196 -0
  135. package/dist/managers/organization.manager.js.map +1 -0
  136. package/dist/module.d.ts +92 -0
  137. package/dist/module.d.ts.map +1 -0
  138. package/dist/module.js +137 -0
  139. package/dist/module.js.map +1 -0
  140. package/dist/pipes/api-key.pipe.d.ts +8 -0
  141. package/dist/pipes/api-key.pipe.d.ts.map +1 -0
  142. package/dist/pipes/api-key.pipe.js +28 -0
  143. package/dist/pipes/api-key.pipe.js.map +1 -0
  144. package/dist/pipes/organization.pipe.d.ts +8 -0
  145. package/dist/pipes/organization.pipe.d.ts.map +1 -0
  146. package/dist/pipes/organization.pipe.js +28 -0
  147. package/dist/pipes/organization.pipe.js.map +1 -0
  148. package/dist/pipes/role.pipe.d.ts +8 -0
  149. package/dist/pipes/{file.pipe.d.ts.map → role.pipe.d.ts.map} +1 -1
  150. package/dist/pipes/{file.pipe.js → role.pipe.js} +8 -8
  151. package/dist/pipes/{file.pipe.js.map → role.pipe.js.map} +1 -1
  152. package/dist/services/account.service.d.ts +0 -2
  153. package/dist/services/account.service.d.ts.map +1 -1
  154. package/dist/services/account.service.js +1 -37
  155. package/dist/services/account.service.js.map +1 -1
  156. package/dist/services/api-key.service.d.ts +42 -0
  157. package/dist/services/api-key.service.d.ts.map +1 -0
  158. package/dist/services/api-key.service.js +306 -0
  159. package/dist/services/api-key.service.js.map +1 -0
  160. package/dist/services/auth.service.d.ts +50 -0
  161. package/dist/services/auth.service.d.ts.map +1 -0
  162. package/dist/services/auth.service.js +259 -0
  163. package/dist/services/auth.service.js.map +1 -0
  164. package/dist/services/authorization.service.d.ts +44 -9
  165. package/dist/services/authorization.service.d.ts.map +1 -1
  166. package/dist/services/authorization.service.js +107 -41
  167. package/dist/services/authorization.service.js.map +1 -1
  168. package/dist/services/feature.service.d.ts +23 -0
  169. package/dist/services/feature.service.d.ts.map +1 -0
  170. package/dist/services/feature.service.js +49 -0
  171. package/dist/services/feature.service.js.map +1 -0
  172. package/dist/services/global-role.service.d.ts +17 -0
  173. package/dist/services/global-role.service.d.ts.map +1 -0
  174. package/dist/services/global-role.service.js +99 -0
  175. package/dist/services/global-role.service.js.map +1 -0
  176. package/dist/services/index.d.ts +9 -0
  177. package/dist/services/index.d.ts.map +1 -0
  178. package/dist/services/index.js +25 -0
  179. package/dist/services/index.js.map +1 -0
  180. package/dist/services/organization-role.service.d.ts +33 -0
  181. package/dist/services/organization-role.service.d.ts.map +1 -0
  182. package/dist/services/organization-role.service.js +102 -0
  183. package/dist/services/organization-role.service.js.map +1 -0
  184. package/dist/services/organization.service.d.ts +29 -0
  185. package/dist/services/organization.service.d.ts.map +1 -0
  186. package/dist/services/organization.service.js +95 -0
  187. package/dist/services/organization.service.js.map +1 -0
  188. package/dist/types/feature-config.d.ts +9 -0
  189. package/dist/types/feature-config.d.ts.map +1 -0
  190. package/dist/types/feature-config.js +3 -0
  191. package/dist/types/feature-config.js.map +1 -0
  192. package/dist/types/hooks/auth-guard-hook.d.ts.map +1 -0
  193. package/dist/types/hooks/auth-guard-hook.js.map +1 -0
  194. package/dist/types/hooks/index.d.ts +3 -0
  195. package/dist/types/hooks/index.d.ts.map +1 -0
  196. package/dist/types/hooks/index.js +19 -0
  197. package/dist/types/hooks/index.js.map +1 -0
  198. package/dist/types/hooks/organization-delete-hook.d.ts +20 -0
  199. package/dist/types/hooks/organization-delete-hook.d.ts.map +1 -0
  200. package/dist/types/hooks/organization-delete-hook.js +3 -0
  201. package/dist/types/hooks/organization-delete-hook.js.map +1 -0
  202. package/dist/types/index.d.ts +5 -0
  203. package/dist/types/index.d.ts.map +1 -0
  204. package/dist/types/index.js +21 -0
  205. package/dist/types/index.js.map +1 -0
  206. package/dist/types/request-principal.d.ts +9 -0
  207. package/dist/types/request-principal.d.ts.map +1 -0
  208. package/dist/types/request-principal.js +3 -0
  209. package/dist/types/request-principal.js.map +1 -0
  210. package/dist/utils/provider-helpers.d.ts +6 -1
  211. package/dist/utils/provider-helpers.d.ts.map +1 -1
  212. package/dist/utils/provider-helpers.js +11 -1
  213. package/dist/utils/provider-helpers.js.map +1 -1
  214. package/package.json +52 -17
  215. package/test/unit/auth.guard.spec.ts +355 -0
  216. package/test/unit/collections/api-key.collection.spec.ts +416 -0
  217. package/test/unit/managers/global-role.manager.spec.ts +269 -0
  218. package/test/unit/managers/organization-role.manager.spec.ts +632 -0
  219. package/test/unit/managers/organization.manager.spec.ts +395 -0
  220. package/test/unit/module.spec.ts +596 -0
  221. package/test/unit/services/account.service.spec.ts +90 -0
  222. package/test/unit/services/api-key.service.spec.ts +1244 -0
  223. package/test/unit/services/auth.service.spec.ts +1036 -0
  224. package/test/unit/services/authorization.service.spec.ts +636 -0
  225. package/test/unit/services/feature.service.spec.ts +56 -0
  226. package/test/unit/services/global-role.service.spec.ts +289 -0
  227. package/test/unit/services/organization-role.service.spec.ts +300 -0
  228. package/test/unit/services/organization.service.spec.ts +385 -0
  229. package/test/utils/auth-guard.ts +114 -0
  230. package/test/utils/base.ts +16 -0
  231. package/test/utils/entities/account.ts +13 -0
  232. package/test/utils/entities/api-key.ts +15 -0
  233. package/test/utils/entities/audit.ts +18 -0
  234. package/test/utils/entities/index.ts +6 -0
  235. package/test/utils/entities/mapping.ts +20 -0
  236. package/test/utils/entities/organization.ts +13 -0
  237. package/test/utils/entities/role.ts +21 -0
  238. package/test/utils/entities/security-audit.ts +16 -0
  239. package/test/utils/index.ts +4 -0
  240. package/test/utils/models/audit-context.ts +10 -0
  241. package/test/utils/models/authorization.ts +7 -0
  242. package/test/utils/models/global-role.ts +22 -0
  243. package/test/utils/models/index.ts +5 -0
  244. package/test/utils/models/organization-role.ts +23 -0
  245. package/test/utils/models/publishable.ts +7 -0
  246. package/tsconfig.build.json +36 -0
  247. package/tsconfig.build.tsbuildinfo +1 -0
  248. package/dist/auth-guard-hook.d.ts.map +0 -1
  249. package/dist/auth-guard-hook.js.map +0 -1
  250. package/dist/cms.module.d.ts +0 -8
  251. package/dist/cms.module.d.ts.map +0 -1
  252. package/dist/cms.module.js +0 -44
  253. package/dist/cms.module.js.map +0 -1
  254. package/dist/cms.providers.d.ts +0 -120
  255. package/dist/cms.providers.d.ts.map +0 -1
  256. package/dist/cms.providers.js +0 -126
  257. package/dist/cms.providers.js.map +0 -1
  258. package/dist/collections/file.collection.d.ts +0 -21
  259. package/dist/collections/file.collection.d.ts.map +0 -1
  260. package/dist/collections/file.collection.js.map +0 -1
  261. package/dist/collections/text.collection.d.ts +0 -20
  262. package/dist/collections/text.collection.d.ts.map +0 -1
  263. package/dist/collections/text.collection.js +0 -56
  264. package/dist/collections/text.collection.js.map +0 -1
  265. package/dist/pipes/file.pipe.d.ts +0 -8
  266. /package/dist/{auth-guard-hook.d.ts → types/hooks/auth-guard-hook.d.ts} +0 -0
  267. /package/dist/{auth-guard-hook.js → types/hooks/auth-guard-hook.js} +0 -0
@@ -0,0 +1,355 @@
1
+ import { Test, TestingModule } from '@nestjs/testing';
2
+ import { Reflector } from '@nestjs/core';
3
+ import { UnauthorizedException, ForbiddenException, InternalServerErrorException, ExecutionContext } from '@nestjs/common';
4
+ import { FastifyRequest } from 'fastify';
5
+
6
+ import * as trailmixModels from '@trailmix-cms/models';
7
+
8
+ import { AuthGuard } from '@/auth.guard';
9
+ import { AuthService, AuthResult } from '@/services/auth.service';
10
+ import { AUTH_OPTIONS_KEY, AuthOptions } from '@/decorators/auth.decorator';
11
+ import { type RequestPrincipal } from '@/types';
12
+
13
+ import * as TestUtils from '../utils';
14
+
15
+ describe('AuthGuard', () => {
16
+ let guard: AuthGuard;
17
+ let reflector: jest.Mocked<Reflector>;
18
+ let authService: jest.Mocked<AuthService>;
19
+
20
+ const createMockContext = (request: Partial<FastifyRequest> = {}): ExecutionContext => {
21
+ return {
22
+ switchToHttp: () => ({
23
+ getRequest: () => request as FastifyRequest,
24
+ }),
25
+ getHandler: jest.fn(),
26
+ getClass: jest.fn(),
27
+ } as any as ExecutionContext;
28
+ };
29
+
30
+ beforeEach(async () => {
31
+ const mockReflector = {
32
+ getAllAndOverride: jest.fn(),
33
+ };
34
+
35
+ const mockAuthService = {
36
+ getPrincipal: jest.fn(),
37
+ validateAuth: jest.fn(),
38
+ };
39
+
40
+ const module: TestingModule = await Test.createTestingModule({
41
+ providers: [
42
+ AuthGuard,
43
+ {
44
+ provide: Reflector,
45
+ useValue: mockReflector,
46
+ },
47
+ {
48
+ provide: AuthService,
49
+ useValue: mockAuthService,
50
+ },
51
+ ],
52
+ }).compile();
53
+
54
+ guard = module.get<AuthGuard>(AuthGuard);
55
+ reflector = module.get(Reflector);
56
+ authService = module.get(AuthService);
57
+ });
58
+
59
+ afterEach(() => {
60
+ jest.clearAllMocks();
61
+ });
62
+
63
+ describe('canActivate', () => {
64
+ it('returns true and sets principal when authentication is valid (ensuring successful authentication flow)', async () => {
65
+ const accountEntity = TestUtils.Entities.createAccount();
66
+ const principal: RequestPrincipal = {
67
+ entity: accountEntity,
68
+ principal_type: trailmixModels.Principal.Account,
69
+ };
70
+ const request = { url: '/test/endpoint' } as FastifyRequest;
71
+ const context = createMockContext(request);
72
+ const authOptions: AuthOptions = {
73
+ allowAnonymous: false,
74
+ requiredPrincipalTypes: [trailmixModels.Principal.Account],
75
+ requiredGlobalRoles: [],
76
+ requiredApiKeyScopes: [],
77
+ };
78
+
79
+ reflector.getAllAndOverride.mockReturnValue(authOptions);
80
+ authService.getPrincipal.mockResolvedValue(principal);
81
+ authService.validateAuth.mockResolvedValue(AuthResult.IsValid);
82
+
83
+ const result = await guard.canActivate(context);
84
+
85
+ expect(result).toBe(true);
86
+ expect(reflector.getAllAndOverride).toHaveBeenCalledWith(AUTH_OPTIONS_KEY, [
87
+ context.getHandler(),
88
+ context.getClass(),
89
+ ]);
90
+ expect(authService.getPrincipal).toHaveBeenCalledWith(context);
91
+ expect(authService.validateAuth).toHaveBeenCalledWith(
92
+ principal,
93
+ {
94
+ allowAnonymous: false,
95
+ requiredPrincipalTypes: [trailmixModels.Principal.Account],
96
+ requiredGlobalRoles: [],
97
+ requiredApiKeyScopes: [],
98
+ },
99
+ '/test/endpoint'
100
+ );
101
+ expect(request.principal).toEqual(principal);
102
+ });
103
+
104
+ it('uses default auth options when metadata is undefined (ensuring default behavior when no metadata)', async () => {
105
+ const accountEntity = TestUtils.Entities.createAccount();
106
+ const principal: RequestPrincipal = {
107
+ entity: accountEntity,
108
+ principal_type: trailmixModels.Principal.Account,
109
+ };
110
+ const request = { url: '/test/endpoint' } as FastifyRequest;
111
+ const context = createMockContext(request);
112
+
113
+ // When metadata is undefined, destructuring will fail, so return empty object instead
114
+ reflector.getAllAndOverride.mockReturnValue({} as AuthOptions);
115
+ authService.getPrincipal.mockResolvedValue(principal);
116
+ authService.validateAuth.mockResolvedValue(AuthResult.IsValid);
117
+
118
+ const result = await guard.canActivate(context);
119
+
120
+ expect(result).toBe(true);
121
+ expect(authService.validateAuth).toHaveBeenCalledWith(
122
+ principal,
123
+ {
124
+ allowAnonymous: undefined,
125
+ requiredPrincipalTypes: undefined,
126
+ requiredGlobalRoles: undefined,
127
+ requiredApiKeyScopes: undefined,
128
+ },
129
+ '/test/endpoint'
130
+ );
131
+ expect(request.principal).toEqual(principal);
132
+ });
133
+
134
+ it('throws UnauthorizedException when auth result is Unauthorized (ensuring unauthorized requests are rejected)', async () => {
135
+ const request = { url: '/test/endpoint' } as FastifyRequest;
136
+ const context = createMockContext(request);
137
+ const authOptions: AuthOptions = {
138
+ allowAnonymous: false,
139
+ requiredPrincipalTypes: [],
140
+ requiredGlobalRoles: [],
141
+ requiredApiKeyScopes: [],
142
+ };
143
+
144
+ reflector.getAllAndOverride.mockReturnValue(authOptions);
145
+ authService.getPrincipal.mockResolvedValue(null);
146
+ authService.validateAuth.mockResolvedValue(AuthResult.Unauthorized);
147
+
148
+ await expect(guard.canActivate(context)).rejects.toThrow(UnauthorizedException);
149
+ await expect(guard.canActivate(context)).rejects.toThrow('Unauthorized request');
150
+ expect(authService.validateAuth).toHaveBeenCalled();
151
+ expect(request.principal).toBeUndefined();
152
+ });
153
+
154
+ it('throws ForbiddenException when auth result is Forbidden (ensuring forbidden requests are rejected)', async () => {
155
+ const accountEntity = TestUtils.Entities.createAccount();
156
+ const principal: RequestPrincipal = {
157
+ entity: accountEntity,
158
+ principal_type: trailmixModels.Principal.Account,
159
+ };
160
+ const request = { url: '/test/endpoint' } as FastifyRequest;
161
+ const context = createMockContext(request);
162
+ const authOptions: AuthOptions = {
163
+ allowAnonymous: false,
164
+ requiredPrincipalTypes: [],
165
+ requiredGlobalRoles: [trailmixModels.RoleValue.Admin],
166
+ requiredApiKeyScopes: [],
167
+ };
168
+
169
+ reflector.getAllAndOverride.mockReturnValue(authOptions);
170
+ authService.getPrincipal.mockResolvedValue(principal);
171
+ authService.validateAuth.mockResolvedValue(AuthResult.Forbidden);
172
+
173
+ await expect(guard.canActivate(context)).rejects.toThrow(ForbiddenException);
174
+ await expect(guard.canActivate(context)).rejects.toThrow('You are not authorized to access this resource');
175
+ expect(authService.validateAuth).toHaveBeenCalled();
176
+ expect(request.principal).toBeUndefined();
177
+ });
178
+
179
+ it('throws InternalServerErrorException when auth result is unexpected (ensuring unexpected results throw error)', async () => {
180
+ const accountEntity = TestUtils.Entities.createAccount();
181
+ const principal: RequestPrincipal = {
182
+ entity: accountEntity,
183
+ principal_type: trailmixModels.Principal.Account,
184
+ };
185
+ const request = { url: '/test/endpoint' } as FastifyRequest;
186
+ const context = createMockContext(request);
187
+ const authOptions: AuthOptions = {
188
+ allowAnonymous: false,
189
+ requiredPrincipalTypes: [],
190
+ requiredGlobalRoles: [],
191
+ requiredApiKeyScopes: [],
192
+ };
193
+
194
+ reflector.getAllAndOverride.mockReturnValue(authOptions);
195
+ authService.getPrincipal.mockResolvedValue(principal);
196
+ authService.validateAuth.mockResolvedValue('unexpected-result' as AuthResult);
197
+
198
+ await expect(guard.canActivate(context)).rejects.toThrow(InternalServerErrorException);
199
+ await expect(guard.canActivate(context)).rejects.toThrow('Failed to validate authentication');
200
+ expect(authService.validateAuth).toHaveBeenCalled();
201
+ expect(request.principal).toBeUndefined();
202
+ });
203
+
204
+ it('passes correct auth options to validateAuth (ensuring all options are passed correctly)', async () => {
205
+ const accountEntity = TestUtils.Entities.createAccount();
206
+ const principal: RequestPrincipal = {
207
+ entity: accountEntity,
208
+ principal_type: trailmixModels.Principal.Account,
209
+ };
210
+ const request = { url: '/api/users' } as FastifyRequest;
211
+ const context = createMockContext(request);
212
+ const authOptions: AuthOptions = {
213
+ allowAnonymous: true,
214
+ requiredPrincipalTypes: [trailmixModels.Principal.Account, trailmixModels.Principal.ApiKey],
215
+ requiredGlobalRoles: [trailmixModels.RoleValue.User, trailmixModels.RoleValue.Admin],
216
+ requiredApiKeyScopes: [trailmixModels.ApiKeyScope.Account, trailmixModels.ApiKeyScope.Organization],
217
+ };
218
+
219
+ reflector.getAllAndOverride.mockReturnValue(authOptions);
220
+ authService.getPrincipal.mockResolvedValue(principal);
221
+ authService.validateAuth.mockResolvedValue(AuthResult.IsValid);
222
+
223
+ await guard.canActivate(context);
224
+
225
+ expect(authService.validateAuth).toHaveBeenCalledWith(
226
+ principal,
227
+ {
228
+ allowAnonymous: true,
229
+ requiredPrincipalTypes: [trailmixModels.Principal.Account, trailmixModels.Principal.ApiKey],
230
+ requiredGlobalRoles: [trailmixModels.RoleValue.User, trailmixModels.RoleValue.Admin],
231
+ requiredApiKeyScopes: [trailmixModels.ApiKeyScope.Account, trailmixModels.ApiKeyScope.Organization],
232
+ },
233
+ '/api/users'
234
+ );
235
+ });
236
+
237
+ it('handles API key principal correctly (ensuring API key principals work)', async () => {
238
+ const apiKeyEntity = TestUtils.Entities.createApiKey();
239
+ const principal: RequestPrincipal = {
240
+ entity: apiKeyEntity,
241
+ principal_type: trailmixModels.Principal.ApiKey,
242
+ };
243
+ const request = { url: '/api/data' } as FastifyRequest;
244
+ const context = createMockContext(request);
245
+ const authOptions: AuthOptions = {
246
+ allowAnonymous: false,
247
+ requiredPrincipalTypes: [trailmixModels.Principal.ApiKey],
248
+ requiredGlobalRoles: [],
249
+ requiredApiKeyScopes: [trailmixModels.ApiKeyScope.Account],
250
+ };
251
+
252
+ reflector.getAllAndOverride.mockReturnValue(authOptions);
253
+ authService.getPrincipal.mockResolvedValue(principal);
254
+ authService.validateAuth.mockResolvedValue(AuthResult.IsValid);
255
+
256
+ const result = await guard.canActivate(context);
257
+
258
+ expect(result).toBe(true);
259
+ expect(request.principal).toEqual(principal);
260
+ });
261
+
262
+ it('handles null principal when allowAnonymous is true (ensuring anonymous access works)', async () => {
263
+ const request = { url: '/public/endpoint' } as FastifyRequest;
264
+ const context = createMockContext(request);
265
+ const authOptions: AuthOptions = {
266
+ allowAnonymous: true,
267
+ requiredPrincipalTypes: [],
268
+ requiredGlobalRoles: [],
269
+ requiredApiKeyScopes: [],
270
+ };
271
+
272
+ reflector.getAllAndOverride.mockReturnValue(authOptions);
273
+ authService.getPrincipal.mockResolvedValue(null);
274
+ authService.validateAuth.mockResolvedValue(AuthResult.IsValid);
275
+
276
+ const result = await guard.canActivate(context);
277
+
278
+ expect(result).toBe(true);
279
+ expect(authService.validateAuth).toHaveBeenCalledWith(
280
+ null,
281
+ {
282
+ allowAnonymous: true,
283
+ requiredPrincipalTypes: [],
284
+ requiredGlobalRoles: [],
285
+ requiredApiKeyScopes: [],
286
+ },
287
+ '/public/endpoint'
288
+ );
289
+ // Principal is set to null when principal is null (non-null assertion still allows null)
290
+ expect(request.principal).toBeNull();
291
+ });
292
+
293
+ it('extracts request URL correctly (ensuring URL is passed to validateAuth)', async () => {
294
+ const accountEntity = TestUtils.Entities.createAccount();
295
+ const principal: RequestPrincipal = {
296
+ entity: accountEntity,
297
+ principal_type: trailmixModels.Principal.Account,
298
+ };
299
+ const requestUrl = '/api/v1/organizations/123/members';
300
+ const request = { url: requestUrl } as FastifyRequest;
301
+ const context = createMockContext(request);
302
+ const authOptions: AuthOptions = {
303
+ allowAnonymous: false,
304
+ requiredPrincipalTypes: [],
305
+ requiredGlobalRoles: [],
306
+ requiredApiKeyScopes: [],
307
+ };
308
+
309
+ reflector.getAllAndOverride.mockReturnValue(authOptions);
310
+ authService.getPrincipal.mockResolvedValue(principal);
311
+ authService.validateAuth.mockResolvedValue(AuthResult.IsValid);
312
+
313
+ await guard.canActivate(context);
314
+
315
+ expect(authService.validateAuth).toHaveBeenCalledWith(
316
+ principal,
317
+ expect.objectContaining({
318
+ allowAnonymous: false,
319
+ requiredPrincipalTypes: [],
320
+ requiredGlobalRoles: [],
321
+ requiredApiKeyScopes: [],
322
+ }),
323
+ requestUrl
324
+ );
325
+ });
326
+
327
+ it('does not set principal when authentication fails (ensuring principal is not set on failure)', async () => {
328
+ const accountEntity = TestUtils.Entities.createAccount();
329
+ const principal: RequestPrincipal = {
330
+ entity: accountEntity,
331
+ principal_type: trailmixModels.Principal.Account,
332
+ };
333
+ const request = { url: '/test/endpoint' } as FastifyRequest;
334
+ const context = createMockContext(request);
335
+ const authOptions: AuthOptions = {
336
+ allowAnonymous: false,
337
+ requiredPrincipalTypes: [],
338
+ requiredGlobalRoles: [trailmixModels.RoleValue.Admin],
339
+ requiredApiKeyScopes: [],
340
+ };
341
+
342
+ reflector.getAllAndOverride.mockReturnValue(authOptions);
343
+ authService.getPrincipal.mockResolvedValue(principal);
344
+ authService.validateAuth.mockResolvedValue(AuthResult.Forbidden);
345
+
346
+ try {
347
+ await guard.canActivate(context);
348
+ } catch (error) {
349
+ // Expected to throw
350
+ }
351
+
352
+ expect(request.principal).toBeUndefined();
353
+ });
354
+ });
355
+ });