@hazeljs/auth 0.2.0-beta.55 → 0.2.0-beta.57

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.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=auth.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.test.d.ts","sourceRoot":"","sources":["../src/auth.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,331 @@
1
+ "use strict";
2
+ /// <reference types="jest" />
3
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
4
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
5
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
6
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
7
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
8
+ };
9
+ var __metadata = (this && this.__metadata) || function (k, v) {
10
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
11
+ };
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ jest.mock('@hazeljs/core', () => ({
14
+ __esModule: true,
15
+ Service: () => () => undefined,
16
+ Injectable: () => () => undefined,
17
+ HazelModule: () => () => undefined,
18
+ RequestContext: class {
19
+ },
20
+ Container: {
21
+ getInstance: jest.fn(),
22
+ },
23
+ Type: class {
24
+ },
25
+ logger: { info: jest.fn(), debug: jest.fn(), warn: jest.fn(), error: jest.fn() },
26
+ default: { info: jest.fn(), debug: jest.fn(), warn: jest.fn(), error: jest.fn() },
27
+ }));
28
+ const core_1 = require("@hazeljs/core");
29
+ const jwt_service_1 = require("./jwt/jwt.service");
30
+ const auth_service_1 = require("./auth.service");
31
+ const auth_guard_1 = require("./auth.guard");
32
+ const auth_guard_2 = require("./auth.guard");
33
+ const jwt_module_1 = require("./jwt/jwt.module");
34
+ const TEST_SECRET = 'test-secret-key-for-unit-tests';
35
+ describe('JwtService', () => {
36
+ beforeEach(() => {
37
+ jwt_service_1.JwtService.configure({ secret: TEST_SECRET, expiresIn: '1h' });
38
+ });
39
+ afterEach(() => {
40
+ jwt_service_1.JwtService.configure({});
41
+ });
42
+ it('throws if no secret is configured', () => {
43
+ jwt_service_1.JwtService.configure({});
44
+ delete process.env.JWT_SECRET;
45
+ expect(() => new jwt_service_1.JwtService()).toThrow('JWT secret is not configured');
46
+ });
47
+ it('uses JWT_SECRET env var when no configure() secret', () => {
48
+ jwt_service_1.JwtService.configure({});
49
+ process.env.JWT_SECRET = 'env-secret';
50
+ expect(() => new jwt_service_1.JwtService()).not.toThrow();
51
+ delete process.env.JWT_SECRET;
52
+ });
53
+ it('signs and verifies a payload', () => {
54
+ const svc = new jwt_service_1.JwtService();
55
+ const token = svc.sign({ sub: 'user-1', role: 'admin' });
56
+ expect(typeof token).toBe('string');
57
+ const payload = svc.verify(token);
58
+ expect(payload.sub).toBe('user-1');
59
+ expect(payload.role).toBe('admin');
60
+ });
61
+ it('sign accepts a custom expiresIn', () => {
62
+ const svc = new jwt_service_1.JwtService();
63
+ const token = svc.sign({ sub: 'user-2' }, { expiresIn: '2h' });
64
+ const payload = svc.verify(token);
65
+ expect(payload.sub).toBe('user-2');
66
+ });
67
+ it('decode returns payload without verification', () => {
68
+ const svc = new jwt_service_1.JwtService();
69
+ const token = svc.sign({ sub: 'user-3', data: 'test' });
70
+ const decoded = svc.decode(token);
71
+ expect(decoded?.sub).toBe('user-3');
72
+ expect(decoded?.data).toBe('test');
73
+ });
74
+ it('decode returns null for invalid token string', () => {
75
+ const svc = new jwt_service_1.JwtService();
76
+ const decoded = svc.decode('not-a-jwt');
77
+ expect(decoded).toBeNull();
78
+ });
79
+ it('verify throws for invalid token', () => {
80
+ const svc = new jwt_service_1.JwtService();
81
+ expect(() => svc.verify('bad-token')).toThrow();
82
+ });
83
+ it('verify throws for token signed with different secret', () => {
84
+ jwt_service_1.JwtService.configure({ secret: 'other-secret' });
85
+ const other = new jwt_service_1.JwtService();
86
+ const token = other.sign({ sub: 'x' });
87
+ jwt_service_1.JwtService.configure({ secret: TEST_SECRET });
88
+ const svc = new jwt_service_1.JwtService();
89
+ expect(() => svc.verify(token)).toThrow();
90
+ });
91
+ it('configure() sets module options used by constructor', () => {
92
+ jwt_service_1.JwtService.configure({
93
+ secret: 'configured-secret',
94
+ expiresIn: '30m',
95
+ issuer: 'hazel',
96
+ audience: 'app',
97
+ });
98
+ const svc = new jwt_service_1.JwtService();
99
+ const token = svc.sign({ sub: 'u' });
100
+ expect(typeof token).toBe('string');
101
+ });
102
+ it('uses JWT_EXPIRES_IN env var', () => {
103
+ jwt_service_1.JwtService.configure({ secret: TEST_SECRET });
104
+ process.env.JWT_EXPIRES_IN = '2h';
105
+ const svc = new jwt_service_1.JwtService();
106
+ const token = svc.sign({ sub: 'u' });
107
+ expect(typeof token).toBe('string');
108
+ delete process.env.JWT_EXPIRES_IN;
109
+ });
110
+ it('uses JWT_ISSUER and JWT_AUDIENCE env vars', () => {
111
+ jwt_service_1.JwtService.configure({ secret: TEST_SECRET });
112
+ process.env.JWT_ISSUER = 'my-issuer';
113
+ process.env.JWT_AUDIENCE = 'my-audience';
114
+ const svc = new jwt_service_1.JwtService();
115
+ const token = svc.sign({ sub: 'u' });
116
+ expect(typeof token).toBe('string');
117
+ delete process.env.JWT_ISSUER;
118
+ delete process.env.JWT_AUDIENCE;
119
+ });
120
+ });
121
+ describe('AuthService', () => {
122
+ let jwtService;
123
+ let authService;
124
+ beforeEach(() => {
125
+ jwt_service_1.JwtService.configure({ secret: TEST_SECRET });
126
+ jwtService = new jwt_service_1.JwtService();
127
+ authService = new auth_service_1.AuthService(jwtService);
128
+ });
129
+ afterEach(() => {
130
+ jwt_service_1.JwtService.configure({});
131
+ });
132
+ it('verifyToken returns user for valid token', async () => {
133
+ const token = jwtService.sign({ sub: 'user-1', role: 'admin', username: 'alice' });
134
+ const user = await authService.verifyToken(token);
135
+ expect(user).not.toBeNull();
136
+ expect(user?.id).toBe('user-1');
137
+ expect(user?.role).toBe('admin');
138
+ expect(user?.username).toBe('alice');
139
+ });
140
+ it('verifyToken returns user with email as username fallback', async () => {
141
+ const token = jwtService.sign({ sub: 'user-2', email: 'bob@example.com' });
142
+ const user = await authService.verifyToken(token);
143
+ expect(user?.username).toBe('bob@example.com');
144
+ });
145
+ it('verifyToken defaults role to "user" when not in payload', async () => {
146
+ const token = jwtService.sign({ sub: 'user-3' });
147
+ const user = await authService.verifyToken(token);
148
+ expect(user?.role).toBe('user');
149
+ });
150
+ it('verifyToken returns null for invalid token', async () => {
151
+ const user = await authService.verifyToken('invalid-token');
152
+ expect(user).toBeNull();
153
+ });
154
+ it('verifyToken returns null when jwtService.verify throws', async () => {
155
+ const mockJwt = {
156
+ verify: jest.fn().mockImplementation(() => {
157
+ throw new Error('jwt expired');
158
+ }),
159
+ };
160
+ const svc = new auth_service_1.AuthService(mockJwt);
161
+ const user = await svc.verifyToken('any-token');
162
+ expect(user).toBeNull();
163
+ });
164
+ });
165
+ describe('AuthGuard', () => {
166
+ let jwtService;
167
+ let authService;
168
+ let guard;
169
+ function makeContext(overrides = {}) {
170
+ return {
171
+ headers: {},
172
+ method: 'GET',
173
+ url: '/test',
174
+ ...overrides,
175
+ };
176
+ }
177
+ beforeEach(() => {
178
+ jwt_service_1.JwtService.configure({ secret: TEST_SECRET });
179
+ jwtService = new jwt_service_1.JwtService();
180
+ authService = new auth_service_1.AuthService(jwtService);
181
+ guard = new auth_guard_1.AuthGuard(authService);
182
+ });
183
+ afterEach(() => {
184
+ jwt_service_1.JwtService.configure({});
185
+ });
186
+ it('throws 400 when no authorization header', async () => {
187
+ const ctx = makeContext();
188
+ await expect(guard.canActivate(ctx)).rejects.toMatchObject({
189
+ message: 'No authorization header',
190
+ status: 400,
191
+ });
192
+ });
193
+ it('throws 400 when authorization header has no token part', async () => {
194
+ const ctx = makeContext({ headers: { authorization: 'Bearer' } });
195
+ await expect(guard.canActivate(ctx)).rejects.toMatchObject({
196
+ message: 'Invalid authorization header format',
197
+ status: 400,
198
+ });
199
+ });
200
+ it('throws 401 when token is invalid', async () => {
201
+ const ctx = makeContext({ headers: { authorization: 'Bearer badtoken' } });
202
+ await expect(guard.canActivate(ctx)).rejects.toMatchObject({ status: 401 });
203
+ });
204
+ it('returns true and attaches user for valid token', async () => {
205
+ const token = jwtService.sign({ sub: 'u1', role: 'user' });
206
+ const ctx = makeContext({ headers: { authorization: `Bearer ${token}` } });
207
+ const result = await guard.canActivate(ctx);
208
+ expect(result).toBe(true);
209
+ expect(ctx.user).toBeDefined();
210
+ });
211
+ it('throws 403 when user role is not in required roles', async () => {
212
+ const token = jwtService.sign({ sub: 'u2', role: 'user' });
213
+ const ctx = makeContext({ headers: { authorization: `Bearer ${token}` } });
214
+ await expect(guard.canActivate(ctx, { roles: ['admin'] })).rejects.toMatchObject({
215
+ message: 'Insufficient permissions',
216
+ status: 403,
217
+ });
218
+ });
219
+ it('returns true when user role matches required roles', async () => {
220
+ const token = jwtService.sign({ sub: 'u3', role: 'admin' });
221
+ const ctx = makeContext({ headers: { authorization: `Bearer ${token}` } });
222
+ const result = await guard.canActivate(ctx, { roles: ['admin', 'superadmin'] });
223
+ expect(result).toBe(true);
224
+ });
225
+ it('logs stack trace in development mode', async () => {
226
+ const originalEnv = process.env.NODE_ENV;
227
+ process.env.NODE_ENV = 'development';
228
+ const ctx = makeContext({ headers: { authorization: 'Bearer bad' } });
229
+ await expect(guard.canActivate(ctx)).rejects.toBeDefined();
230
+ process.env.NODE_ENV = originalEnv;
231
+ });
232
+ });
233
+ describe('Auth decorator', () => {
234
+ let jwtService;
235
+ let authService;
236
+ let guard;
237
+ beforeEach(() => {
238
+ jwt_service_1.JwtService.configure({ secret: TEST_SECRET });
239
+ jwtService = new jwt_service_1.JwtService();
240
+ authService = new auth_service_1.AuthService(jwtService);
241
+ guard = new auth_guard_1.AuthGuard(authService);
242
+ });
243
+ afterEach(() => {
244
+ jwt_service_1.JwtService.configure({});
245
+ });
246
+ it('calls the original method when auth passes', async () => {
247
+ core_1.Container.getInstance.mockReturnValue({
248
+ resolve: jest.fn().mockReturnValue(guard),
249
+ });
250
+ const token = jwtService.sign({ sub: 'u1', role: 'user' });
251
+ class TestController {
252
+ async getResource(_context) {
253
+ return 'ok';
254
+ }
255
+ }
256
+ __decorate([
257
+ (0, auth_guard_2.Auth)(),
258
+ __metadata("design:type", Function),
259
+ __metadata("design:paramtypes", [Object]),
260
+ __metadata("design:returntype", Promise)
261
+ ], TestController.prototype, "getResource", null);
262
+ const ctrl = new TestController();
263
+ const ctx = {
264
+ headers: { authorization: `Bearer ${token}` },
265
+ method: 'GET',
266
+ url: '/test',
267
+ };
268
+ const result = await ctrl.getResource(ctx);
269
+ expect(result).toBe('ok');
270
+ });
271
+ it('throws when guard is not found in container', async () => {
272
+ core_1.Container.getInstance.mockReturnValue({
273
+ resolve: jest.fn().mockReturnValue(null),
274
+ });
275
+ class TestController {
276
+ async getResource(_context) {
277
+ return 'ok';
278
+ }
279
+ }
280
+ __decorate([
281
+ (0, auth_guard_2.Auth)(),
282
+ __metadata("design:type", Function),
283
+ __metadata("design:paramtypes", [Object]),
284
+ __metadata("design:returntype", Promise)
285
+ ], TestController.prototype, "getResource", null);
286
+ const ctrl = new TestController();
287
+ const ctx = {
288
+ headers: { authorization: 'Bearer sometoken' },
289
+ method: 'GET',
290
+ url: '/test',
291
+ };
292
+ await expect(ctrl.getResource(ctx)).rejects.toThrow('AuthGuard not found');
293
+ });
294
+ it('propagates auth errors from guard', async () => {
295
+ core_1.Container.getInstance.mockReturnValue({
296
+ resolve: jest.fn().mockReturnValue(guard),
297
+ });
298
+ class TestController {
299
+ async adminOnly(_context) {
300
+ return 'admin';
301
+ }
302
+ }
303
+ __decorate([
304
+ (0, auth_guard_2.Auth)({ roles: ['admin'] }),
305
+ __metadata("design:type", Function),
306
+ __metadata("design:paramtypes", [Object]),
307
+ __metadata("design:returntype", Promise)
308
+ ], TestController.prototype, "adminOnly", null);
309
+ const ctrl = new TestController();
310
+ const token = jwtService.sign({ sub: 'u2', role: 'user' });
311
+ const ctx = {
312
+ headers: { authorization: `Bearer ${token}` },
313
+ method: 'GET',
314
+ url: '/admin',
315
+ };
316
+ await expect(ctrl.adminOnly(ctx)).rejects.toMatchObject({
317
+ message: 'Insufficient permissions',
318
+ });
319
+ });
320
+ });
321
+ describe('JwtModule', () => {
322
+ it('forRoot returns JwtModule', () => {
323
+ const result = jwt_module_1.JwtModule.forRoot({ secret: TEST_SECRET });
324
+ expect(result).toBe(jwt_module_1.JwtModule);
325
+ });
326
+ it('forRoot without options returns JwtModule', () => {
327
+ jwt_service_1.JwtService.configure({ secret: TEST_SECRET });
328
+ const result = jwt_module_1.JwtModule.forRoot();
329
+ expect(result).toBe(jwt_module_1.JwtModule);
330
+ });
331
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hazeljs/auth",
3
- "version": "0.2.0-beta.55",
3
+ "version": "0.2.0-beta.57",
4
4
  "description": "Authentication and JWT module for HazelJS framework",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -51,5 +51,5 @@
51
51
  "peerDependencies": {
52
52
  "@hazeljs/core": ">=0.2.0-beta.0"
53
53
  },
54
- "gitHead": "f2e54f346eea552595a44607999454a9e388cb9e"
54
+ "gitHead": "98c84683e8f487c660e344c2beda3a8dcaaff07f"
55
55
  }