@hazeljs/auth 0.2.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.
- package/README.md +483 -0
- package/dist/auth.guard.d.ts +15 -0
- package/dist/auth.guard.d.ts.map +1 -0
- package/dist/auth.guard.js +96 -0
- package/dist/auth.service.d.ts +13 -0
- package/dist/auth.service.d.ts.map +1 -0
- package/dist/auth.service.js +38 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/jwt/jwt.module.d.ts +7 -0
- package/dist/jwt/jwt.module.d.ts.map +1 -0
- package/dist/jwt/jwt.module.js +27 -0
- package/dist/jwt/jwt.service.d.ts +25 -0
- package/dist/jwt/jwt.service.d.ts.map +1 -0
- package/dist/jwt/jwt.service.js +61 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
# @hazeljs/auth
|
|
2
|
+
|
|
3
|
+
**Authentication and JWT Module for HazelJS**
|
|
4
|
+
|
|
5
|
+
Secure your HazelJS applications with JWT-based authentication, guards, and decorators.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@hazeljs/auth)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- 🔐 **JWT Authentication** - Secure token-based authentication
|
|
13
|
+
- 🛡️ **Auth Guards** - Protect routes with decorators
|
|
14
|
+
- 👤 **User Extraction** - Get current user from request
|
|
15
|
+
- 🔑 **Token Management** - Generate, verify, and refresh tokens
|
|
16
|
+
- ⏰ **Token Expiration** - Configurable expiration times
|
|
17
|
+
- 🎯 **Role-Based Access** - Role and permission guards
|
|
18
|
+
- 🔄 **Refresh Tokens** - Long-lived refresh token support
|
|
19
|
+
- 📊 **Token Blacklisting** - Revoke tokens when needed
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install @hazeljs/auth
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
### 1. Configure Auth Module
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { HazelModule } from '@hazeljs/core';
|
|
33
|
+
import { AuthModule } from '@hazeljs/auth';
|
|
34
|
+
|
|
35
|
+
@HazelModule({
|
|
36
|
+
imports: [
|
|
37
|
+
AuthModule.forRoot({
|
|
38
|
+
secret: process.env.JWT_SECRET || 'your-secret-key',
|
|
39
|
+
expiresIn: '1h',
|
|
40
|
+
refreshExpiresIn: '7d',
|
|
41
|
+
}),
|
|
42
|
+
],
|
|
43
|
+
})
|
|
44
|
+
export class AppModule {}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 2. Create Auth Service
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { Injectable } from '@hazeljs/core';
|
|
51
|
+
import { AuthService } from '@hazeljs/auth';
|
|
52
|
+
|
|
53
|
+
@Injectable()
|
|
54
|
+
export class UserAuthService {
|
|
55
|
+
constructor(private authService: AuthService) {}
|
|
56
|
+
|
|
57
|
+
async login(email: string, password: string) {
|
|
58
|
+
// Validate credentials
|
|
59
|
+
const user = await this.validateUser(email, password);
|
|
60
|
+
|
|
61
|
+
if (!user) {
|
|
62
|
+
throw new Error('Invalid credentials');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Generate tokens
|
|
66
|
+
const accessToken = await this.authService.generateToken({
|
|
67
|
+
sub: user.id,
|
|
68
|
+
email: user.email,
|
|
69
|
+
roles: user.roles,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const refreshToken = await this.authService.generateRefreshToken({
|
|
73
|
+
sub: user.id,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
accessToken,
|
|
78
|
+
refreshToken,
|
|
79
|
+
user,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async validateUser(email: string, password: string) {
|
|
84
|
+
// Your user validation logic
|
|
85
|
+
const user = await this.userService.findByEmail(email);
|
|
86
|
+
|
|
87
|
+
if (user && await this.comparePasswords(password, user.password)) {
|
|
88
|
+
return user;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### 3. Protect Routes with Guards
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import { Controller, Get, Post, Body } from '@hazeljs/core';
|
|
100
|
+
import { UseGuard, AuthGuard, CurrentUser } from '@hazeljs/auth';
|
|
101
|
+
|
|
102
|
+
@Controller('/api')
|
|
103
|
+
export class ApiController {
|
|
104
|
+
@Post('/login')
|
|
105
|
+
async login(@Body() credentials: LoginDto) {
|
|
106
|
+
return this.authService.login(credentials.email, credentials.password);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@Get('/profile')
|
|
110
|
+
@UseGuard(AuthGuard)
|
|
111
|
+
getProfile(@CurrentUser() user: any) {
|
|
112
|
+
return { user };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
@Get('/admin')
|
|
116
|
+
@UseGuard(AuthGuard)
|
|
117
|
+
@UseGuard(RoleGuard(['admin']))
|
|
118
|
+
getAdminData(@CurrentUser() user: any) {
|
|
119
|
+
return { message: 'Admin only data', user };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Authentication Flow
|
|
125
|
+
|
|
126
|
+
### Login
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
@Post('/auth/login')
|
|
130
|
+
async login(@Body() loginDto: LoginDto) {
|
|
131
|
+
const user = await this.authService.validateUser(
|
|
132
|
+
loginDto.email,
|
|
133
|
+
loginDto.password
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
if (!user) {
|
|
137
|
+
throw new UnauthorizedException('Invalid credentials');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const payload = {
|
|
141
|
+
sub: user.id,
|
|
142
|
+
email: user.email,
|
|
143
|
+
roles: user.roles,
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
accessToken: await this.authService.generateToken(payload),
|
|
148
|
+
refreshToken: await this.authService.generateRefreshToken(payload),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Refresh Token
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
@Post('/auth/refresh')
|
|
157
|
+
async refresh(@Body() refreshDto: RefreshDto) {
|
|
158
|
+
const payload = await this.authService.verifyRefreshToken(
|
|
159
|
+
refreshDto.refreshToken
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
accessToken: await this.authService.generateToken({
|
|
164
|
+
sub: payload.sub,
|
|
165
|
+
email: payload.email,
|
|
166
|
+
roles: payload.roles,
|
|
167
|
+
}),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Logout
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
@Post('/auth/logout')
|
|
176
|
+
@UseGuard(AuthGuard)
|
|
177
|
+
async logout(@CurrentUser() user: any, @Headers('authorization') token: string) {
|
|
178
|
+
// Extract token from "Bearer <token>"
|
|
179
|
+
const jwt = token.split(' ')[1];
|
|
180
|
+
|
|
181
|
+
// Blacklist the token
|
|
182
|
+
await this.authService.blacklistToken(jwt);
|
|
183
|
+
|
|
184
|
+
return { message: 'Logged out successfully' };
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Guards
|
|
189
|
+
|
|
190
|
+
### Auth Guard
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
import { AuthGuard } from '@hazeljs/auth';
|
|
194
|
+
|
|
195
|
+
@Controller('/protected')
|
|
196
|
+
export class ProtectedController {
|
|
197
|
+
@Get()
|
|
198
|
+
@UseGuard(AuthGuard)
|
|
199
|
+
getData(@CurrentUser() user: any) {
|
|
200
|
+
return { data: 'protected', user };
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Role Guard
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
import { RoleGuard } from '@hazeljs/auth';
|
|
209
|
+
|
|
210
|
+
@Controller('/admin')
|
|
211
|
+
export class AdminController {
|
|
212
|
+
@Get('/users')
|
|
213
|
+
@UseGuard(AuthGuard)
|
|
214
|
+
@UseGuard(RoleGuard(['admin']))
|
|
215
|
+
getAllUsers() {
|
|
216
|
+
return { users: [] };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
@Get('/settings')
|
|
220
|
+
@UseGuard(AuthGuard)
|
|
221
|
+
@UseGuard(RoleGuard(['admin', 'superadmin']))
|
|
222
|
+
getSettings() {
|
|
223
|
+
return { settings: {} };
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Permission Guard
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
import { PermissionGuard } from '@hazeljs/auth';
|
|
232
|
+
|
|
233
|
+
@Controller('/posts')
|
|
234
|
+
export class PostController {
|
|
235
|
+
@Post()
|
|
236
|
+
@UseGuard(AuthGuard)
|
|
237
|
+
@UseGuard(PermissionGuard(['posts:create']))
|
|
238
|
+
createPost(@Body() createPostDto: CreatePostDto) {
|
|
239
|
+
return this.postService.create(createPostDto);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
@Delete('/:id')
|
|
243
|
+
@UseGuard(AuthGuard)
|
|
244
|
+
@UseGuard(PermissionGuard(['posts:delete']))
|
|
245
|
+
deletePost(@Param('id') id: string) {
|
|
246
|
+
return this.postService.delete(id);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Decorators
|
|
252
|
+
|
|
253
|
+
### @CurrentUser()
|
|
254
|
+
|
|
255
|
+
Extract the authenticated user from the request:
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
@Get('/me')
|
|
259
|
+
@UseGuard(AuthGuard)
|
|
260
|
+
getMe(@CurrentUser() user: any) {
|
|
261
|
+
return user;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// With specific property
|
|
265
|
+
@Get('/email')
|
|
266
|
+
@UseGuard(AuthGuard)
|
|
267
|
+
getEmail(@CurrentUser('email') email: string) {
|
|
268
|
+
return { email };
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### @Public()
|
|
273
|
+
|
|
274
|
+
Mark routes as public (skip authentication):
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
import { Public } from '@hazeljs/auth';
|
|
278
|
+
|
|
279
|
+
@Controller('/api')
|
|
280
|
+
@UseGuard(AuthGuard) // Applied to all routes
|
|
281
|
+
export class ApiController {
|
|
282
|
+
@Get('/public')
|
|
283
|
+
@Public() // This route skips authentication
|
|
284
|
+
getPublicData() {
|
|
285
|
+
return { data: 'public' };
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
@Get('/private')
|
|
289
|
+
getPrivateData(@CurrentUser() user: any) {
|
|
290
|
+
return { data: 'private', user };
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### @Roles()
|
|
296
|
+
|
|
297
|
+
Shorthand for role-based access:
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
import { Roles } from '@hazeljs/auth';
|
|
301
|
+
|
|
302
|
+
@Controller('/admin')
|
|
303
|
+
export class AdminController {
|
|
304
|
+
@Get('/dashboard')
|
|
305
|
+
@Roles('admin', 'superadmin')
|
|
306
|
+
getDashboard() {
|
|
307
|
+
return { dashboard: 'data' };
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## Token Management
|
|
313
|
+
|
|
314
|
+
### Generate Token
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
const token = await authService.generateToken({
|
|
318
|
+
sub: user.id,
|
|
319
|
+
email: user.email,
|
|
320
|
+
roles: ['user'],
|
|
321
|
+
customClaim: 'value',
|
|
322
|
+
});
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### Verify Token
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
try {
|
|
329
|
+
const payload = await authService.verifyToken(token);
|
|
330
|
+
console.log(payload.sub); // user.id
|
|
331
|
+
console.log(payload.email);
|
|
332
|
+
} catch (error) {
|
|
333
|
+
console.error('Invalid token:', error.message);
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Decode Token (without verification)
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
const payload = authService.decodeToken(token);
|
|
341
|
+
console.log(payload);
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### Blacklist Token
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
await authService.blacklistToken(token);
|
|
348
|
+
|
|
349
|
+
// Check if blacklisted
|
|
350
|
+
const isBlacklisted = await authService.isTokenBlacklisted(token);
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
## Configuration
|
|
354
|
+
|
|
355
|
+
### Module Configuration
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
AuthModule.forRoot({
|
|
359
|
+
// JWT secret key
|
|
360
|
+
secret: process.env.JWT_SECRET,
|
|
361
|
+
|
|
362
|
+
// Access token expiration
|
|
363
|
+
expiresIn: '15m',
|
|
364
|
+
|
|
365
|
+
// Refresh token expiration
|
|
366
|
+
refreshExpiresIn: '7d',
|
|
367
|
+
|
|
368
|
+
// Token issuer
|
|
369
|
+
issuer: 'hazeljs-app',
|
|
370
|
+
|
|
371
|
+
// Token audience
|
|
372
|
+
audience: 'hazeljs-users',
|
|
373
|
+
|
|
374
|
+
// Algorithm
|
|
375
|
+
algorithm: 'HS256',
|
|
376
|
+
|
|
377
|
+
// Token blacklist (requires Redis)
|
|
378
|
+
blacklist: {
|
|
379
|
+
enabled: true,
|
|
380
|
+
redis: redisClient,
|
|
381
|
+
},
|
|
382
|
+
})
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### Environment Variables
|
|
386
|
+
|
|
387
|
+
```env
|
|
388
|
+
JWT_SECRET=your-super-secret-key-change-in-production
|
|
389
|
+
JWT_EXPIRES_IN=1h
|
|
390
|
+
JWT_REFRESH_EXPIRES_IN=7d
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
## Password Hashing
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
import { hash, compare } from '@hazeljs/auth';
|
|
397
|
+
|
|
398
|
+
// Hash password
|
|
399
|
+
const hashedPassword = await hash('user-password', 10);
|
|
400
|
+
|
|
401
|
+
// Compare password
|
|
402
|
+
const isValid = await compare('user-password', hashedPassword);
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
## Custom Guards
|
|
406
|
+
|
|
407
|
+
```typescript
|
|
408
|
+
import { Guard, GuardContext } from '@hazeljs/core';
|
|
409
|
+
import { Injectable } from '@hazeljs/core';
|
|
410
|
+
|
|
411
|
+
@Injectable()
|
|
412
|
+
export class CustomAuthGuard implements Guard {
|
|
413
|
+
async canActivate(context: GuardContext): Promise<boolean> {
|
|
414
|
+
const request = context.request;
|
|
415
|
+
const token = request.headers.authorization?.split(' ')[1];
|
|
416
|
+
|
|
417
|
+
if (!token) {
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
try {
|
|
422
|
+
const payload = await this.authService.verifyToken(token);
|
|
423
|
+
request.user = payload;
|
|
424
|
+
return true;
|
|
425
|
+
} catch {
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
## API Reference
|
|
433
|
+
|
|
434
|
+
### AuthService
|
|
435
|
+
|
|
436
|
+
```typescript
|
|
437
|
+
class AuthService {
|
|
438
|
+
generateToken(payload: any, options?: SignOptions): Promise<string>;
|
|
439
|
+
generateRefreshToken(payload: any): Promise<string>;
|
|
440
|
+
verifyToken(token: string): Promise<any>;
|
|
441
|
+
verifyRefreshToken(token: string): Promise<any>;
|
|
442
|
+
decodeToken(token: string): any;
|
|
443
|
+
blacklistToken(token: string): Promise<void>;
|
|
444
|
+
isTokenBlacklisted(token: string): Promise<boolean>;
|
|
445
|
+
}
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### Guards
|
|
449
|
+
|
|
450
|
+
- `AuthGuard` - Validates JWT token
|
|
451
|
+
- `RoleGuard(roles: string[])` - Checks user roles
|
|
452
|
+
- `PermissionGuard(permissions: string[])` - Checks user permissions
|
|
453
|
+
|
|
454
|
+
### Decorators
|
|
455
|
+
|
|
456
|
+
- `@CurrentUser(property?: string)` - Extract user from request
|
|
457
|
+
- `@Public()` - Skip authentication
|
|
458
|
+
- `@Roles(...roles: string[])` - Require specific roles
|
|
459
|
+
|
|
460
|
+
## Examples
|
|
461
|
+
|
|
462
|
+
See the [examples](../../example/src/auth) directory for complete working examples.
|
|
463
|
+
|
|
464
|
+
## Testing
|
|
465
|
+
|
|
466
|
+
```bash
|
|
467
|
+
npm test
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
## Contributing
|
|
471
|
+
|
|
472
|
+
Contributions are welcome! Please read our [Contributing Guide](../../CONTRIBUTING.md) for details.
|
|
473
|
+
|
|
474
|
+
## License
|
|
475
|
+
|
|
476
|
+
MIT © [HazelJS](https://hazeljs.com)
|
|
477
|
+
|
|
478
|
+
## Links
|
|
479
|
+
|
|
480
|
+
- [Documentation](https://hazeljs.com/docs/packages/auth)
|
|
481
|
+
- [GitHub](https://github.com/hazel-js/hazeljs)
|
|
482
|
+
- [Issues](https://github.com/hazeljs/hazel-js/issues)
|
|
483
|
+
- [Discord](https://discord.gg/hazeljs)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { RequestContext } from '@hazeljs/core';
|
|
2
|
+
import { AuthService } from './auth.service';
|
|
3
|
+
export interface AuthGuardOptions {
|
|
4
|
+
roles?: string[];
|
|
5
|
+
}
|
|
6
|
+
export interface IAuthGuard {
|
|
7
|
+
canActivate(context: RequestContext, options?: AuthGuardOptions): Promise<boolean>;
|
|
8
|
+
}
|
|
9
|
+
export declare class AuthGuard implements IAuthGuard {
|
|
10
|
+
private authService;
|
|
11
|
+
constructor(authService: AuthService);
|
|
12
|
+
canActivate(context: RequestContext, options?: AuthGuardOptions): Promise<boolean>;
|
|
13
|
+
}
|
|
14
|
+
export declare function Auth(options?: AuthGuardOptions): (target: unknown, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
|
15
|
+
//# sourceMappingURL=auth.guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.guard.d.ts","sourceRoot":"","sources":["../src/auth.guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAK/C,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,WAAW,CAAC,OAAO,EAAE,cAAc,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACpF;AAMD,qBACa,SAAU,YAAW,UAAU;IAC9B,OAAO,CAAC,WAAW;gBAAX,WAAW,EAAE,WAAW;IAEtC,WAAW,CAAC,OAAO,EAAE,cAAc,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;CA4CzF;AAGD,wBAAgB,IAAI,CAAC,OAAO,CAAC,EAAE,gBAAgB,IAE3C,QAAQ,OAAO,EACf,aAAa,MAAM,EACnB,YAAY,kBAAkB,KAC7B,kBAAkB,CA2BtB"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
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;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.AuthGuard = void 0;
|
|
16
|
+
exports.Auth = Auth;
|
|
17
|
+
const core_1 = require("@hazeljs/core");
|
|
18
|
+
const core_2 = __importDefault(require("@hazeljs/core"));
|
|
19
|
+
const core_3 = require("@hazeljs/core");
|
|
20
|
+
const auth_service_1 = require("./auth.service");
|
|
21
|
+
let AuthGuard = class AuthGuard {
|
|
22
|
+
constructor(authService) {
|
|
23
|
+
this.authService = authService;
|
|
24
|
+
}
|
|
25
|
+
async canActivate(context, options) {
|
|
26
|
+
const authHeader = context.headers['authorization'];
|
|
27
|
+
if (!authHeader) {
|
|
28
|
+
const error = new Error('No authorization header');
|
|
29
|
+
error.status = 400;
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
const token = authHeader.split(' ')[1];
|
|
33
|
+
if (!token) {
|
|
34
|
+
const error = new Error('Invalid authorization header format');
|
|
35
|
+
error.status = 400;
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const user = await this.authService.verifyToken(token);
|
|
40
|
+
if (!user) {
|
|
41
|
+
const error = new Error('Invalid token');
|
|
42
|
+
error.status = 401;
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
// Check roles if specified
|
|
46
|
+
if (options?.roles && !options.roles.includes(user.role)) {
|
|
47
|
+
const error = new Error('Insufficient permissions');
|
|
48
|
+
error.status = 403;
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
// Attach user to context
|
|
52
|
+
context.user = user;
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
const authError = error;
|
|
57
|
+
core_2.default.error(`[${context.method}] ${context.url} - ${authError.message} (status: ${authError.status || 500})`);
|
|
58
|
+
if (process.env.NODE_ENV === 'development' && authError.stack) {
|
|
59
|
+
core_2.default.debug(authError.stack);
|
|
60
|
+
}
|
|
61
|
+
throw authError;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
exports.AuthGuard = AuthGuard;
|
|
66
|
+
exports.AuthGuard = AuthGuard = __decorate([
|
|
67
|
+
(0, core_1.Injectable)(),
|
|
68
|
+
__metadata("design:paramtypes", [auth_service_1.AuthService])
|
|
69
|
+
], AuthGuard);
|
|
70
|
+
// Decorator factory for protecting routes
|
|
71
|
+
function Auth(options) {
|
|
72
|
+
return function (target, propertyKey, descriptor) {
|
|
73
|
+
const originalMethod = descriptor.value;
|
|
74
|
+
descriptor.value = async function (context) {
|
|
75
|
+
try {
|
|
76
|
+
// Get the auth guard instance from the container
|
|
77
|
+
const container = core_3.Container.getInstance();
|
|
78
|
+
const guard = container.resolve(AuthGuard);
|
|
79
|
+
if (!guard) {
|
|
80
|
+
throw new Error('AuthGuard not found. Make sure to provide an AuthGuard implementation.');
|
|
81
|
+
}
|
|
82
|
+
await guard.canActivate(context, options);
|
|
83
|
+
return originalMethod.call(this, context);
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
const authError = error;
|
|
87
|
+
core_2.default.error(`[${context.method}] ${context.url} - ${authError.message} (status: ${authError.status || 500})`);
|
|
88
|
+
if (process.env.NODE_ENV === 'development' && authError.stack) {
|
|
89
|
+
core_2.default.debug(authError.stack);
|
|
90
|
+
}
|
|
91
|
+
throw authError;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
return descriptor;
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { JwtService } from './jwt/jwt.service';
|
|
2
|
+
export interface AuthUser {
|
|
3
|
+
id: string;
|
|
4
|
+
username?: string;
|
|
5
|
+
role: string;
|
|
6
|
+
[key: string]: unknown;
|
|
7
|
+
}
|
|
8
|
+
export declare class AuthService {
|
|
9
|
+
private readonly jwtService;
|
|
10
|
+
constructor(jwtService: JwtService);
|
|
11
|
+
verifyToken(token: string): Promise<AuthUser | null>;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=auth.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.service.d.ts","sourceRoot":"","sources":["../src/auth.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAc,MAAM,mBAAmB,CAAC;AAE3D,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,qBACa,WAAW;IACV,OAAO,CAAC,QAAQ,CAAC,UAAU;gBAAV,UAAU,EAAE,UAAU;IAE7C,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;CAa3D"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
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;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.AuthService = void 0;
|
|
13
|
+
const core_1 = require("@hazeljs/core");
|
|
14
|
+
const jwt_service_1 = require("./jwt/jwt.service");
|
|
15
|
+
let AuthService = class AuthService {
|
|
16
|
+
constructor(jwtService) {
|
|
17
|
+
this.jwtService = jwtService;
|
|
18
|
+
}
|
|
19
|
+
async verifyToken(token) {
|
|
20
|
+
try {
|
|
21
|
+
const payload = this.jwtService.verify(token);
|
|
22
|
+
return {
|
|
23
|
+
id: payload.sub,
|
|
24
|
+
username: payload.username || payload.email,
|
|
25
|
+
role: payload.role || 'user',
|
|
26
|
+
...payload,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
exports.AuthService = AuthService;
|
|
35
|
+
exports.AuthService = AuthService = __decorate([
|
|
36
|
+
(0, core_1.Injectable)(),
|
|
37
|
+
__metadata("design:paramtypes", [jwt_service_1.JwtService])
|
|
38
|
+
], AuthService);
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hazeljs/auth - Authentication module for HazelJS
|
|
3
|
+
*/
|
|
4
|
+
export { AuthGuard } from './auth.guard';
|
|
5
|
+
export { AuthService } from './auth.service';
|
|
6
|
+
export { JwtModule } from './jwt/jwt.module';
|
|
7
|
+
export { JwtService } from './jwt/jwt.service';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @hazeljs/auth - Authentication module for HazelJS
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.JwtService = exports.JwtModule = exports.AuthService = exports.AuthGuard = void 0;
|
|
7
|
+
var auth_guard_1 = require("./auth.guard");
|
|
8
|
+
Object.defineProperty(exports, "AuthGuard", { enumerable: true, get: function () { return auth_guard_1.AuthGuard; } });
|
|
9
|
+
var auth_service_1 = require("./auth.service");
|
|
10
|
+
Object.defineProperty(exports, "AuthService", { enumerable: true, get: function () { return auth_service_1.AuthService; } });
|
|
11
|
+
var jwt_module_1 = require("./jwt/jwt.module");
|
|
12
|
+
Object.defineProperty(exports, "JwtModule", { enumerable: true, get: function () { return jwt_module_1.JwtModule; } });
|
|
13
|
+
var jwt_service_1 = require("./jwt/jwt.service");
|
|
14
|
+
Object.defineProperty(exports, "JwtService", { enumerable: true, get: function () { return jwt_service_1.JwtService; } });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwt.module.d.ts","sourceRoot":"","sources":["../../src/jwt/jwt.module.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAE9D,MAAM,WAAW,gBAAiB,SAAQ,iBAAiB;CAAG;AAE9D,qBAIa,SAAS;IACpB,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,SAAS;CAM7D"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
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;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var JwtModule_1;
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.JwtModule = void 0;
|
|
11
|
+
const core_1 = require("@hazeljs/core");
|
|
12
|
+
const jwt_service_1 = require("./jwt.service");
|
|
13
|
+
let JwtModule = JwtModule_1 = class JwtModule {
|
|
14
|
+
static forRoot(options) {
|
|
15
|
+
if (options) {
|
|
16
|
+
jwt_service_1.JwtService.configure(options);
|
|
17
|
+
}
|
|
18
|
+
return JwtModule_1;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
exports.JwtModule = JwtModule;
|
|
22
|
+
exports.JwtModule = JwtModule = JwtModule_1 = __decorate([
|
|
23
|
+
(0, core_1.HazelModule)({
|
|
24
|
+
providers: [jwt_service_1.JwtService],
|
|
25
|
+
exports: [jwt_service_1.JwtService],
|
|
26
|
+
})
|
|
27
|
+
], JwtModule);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface JwtPayload {
|
|
2
|
+
sub: string;
|
|
3
|
+
[key: string]: unknown;
|
|
4
|
+
}
|
|
5
|
+
export interface JwtServiceOptions {
|
|
6
|
+
secret?: string;
|
|
7
|
+
expiresIn?: string | number;
|
|
8
|
+
issuer?: string;
|
|
9
|
+
audience?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare class JwtService {
|
|
12
|
+
private readonly secret;
|
|
13
|
+
private readonly defaultExpiresIn;
|
|
14
|
+
private readonly issuer?;
|
|
15
|
+
private readonly audience?;
|
|
16
|
+
constructor();
|
|
17
|
+
private static moduleOptions;
|
|
18
|
+
static configure(options: JwtServiceOptions): void;
|
|
19
|
+
sign(payload: JwtPayload, options?: {
|
|
20
|
+
expiresIn?: string | number;
|
|
21
|
+
}): string;
|
|
22
|
+
verify(token: string): JwtPayload;
|
|
23
|
+
decode(token: string): JwtPayload | null;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=jwt.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwt.service.d.ts","sourceRoot":"","sources":["../../src/jwt/jwt.service.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,qBACa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAkB;IACnD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAS;;IAgBnC,OAAO,CAAC,MAAM,CAAC,aAAa,CAAyB;IAErD,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI;IAIlD,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,GAAG,MAAM;IAU5E,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU;IAQjC,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI;CAIzC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
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;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
var JwtService_1;
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.JwtService = void 0;
|
|
17
|
+
const core_1 = require("@hazeljs/core");
|
|
18
|
+
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
19
|
+
let JwtService = JwtService_1 = class JwtService {
|
|
20
|
+
constructor() {
|
|
21
|
+
const options = JwtService_1.moduleOptions;
|
|
22
|
+
this.secret = options.secret || process.env.JWT_SECRET || '';
|
|
23
|
+
this.defaultExpiresIn = options.expiresIn || process.env.JWT_EXPIRES_IN || '1h';
|
|
24
|
+
this.issuer = options.issuer || process.env.JWT_ISSUER;
|
|
25
|
+
this.audience = options.audience || process.env.JWT_AUDIENCE;
|
|
26
|
+
if (!this.secret) {
|
|
27
|
+
throw new Error('JWT secret is not configured. Set JWT_SECRET environment variable or pass secret via JwtModule.forRoot({ secret: "..." })');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
static configure(options) {
|
|
31
|
+
JwtService_1.moduleOptions = options;
|
|
32
|
+
}
|
|
33
|
+
sign(payload, options) {
|
|
34
|
+
const signOptions = {
|
|
35
|
+
expiresIn: (options?.expiresIn || this.defaultExpiresIn),
|
|
36
|
+
};
|
|
37
|
+
if (this.issuer)
|
|
38
|
+
signOptions.issuer = this.issuer;
|
|
39
|
+
if (this.audience)
|
|
40
|
+
signOptions.audience = this.audience;
|
|
41
|
+
return jsonwebtoken_1.default.sign(payload, this.secret, signOptions);
|
|
42
|
+
}
|
|
43
|
+
verify(token) {
|
|
44
|
+
const verifyOptions = {};
|
|
45
|
+
if (this.issuer)
|
|
46
|
+
verifyOptions.issuer = this.issuer;
|
|
47
|
+
if (this.audience)
|
|
48
|
+
verifyOptions.audience = this.audience;
|
|
49
|
+
return jsonwebtoken_1.default.verify(token, this.secret, verifyOptions);
|
|
50
|
+
}
|
|
51
|
+
decode(token) {
|
|
52
|
+
const decoded = jsonwebtoken_1.default.decode(token);
|
|
53
|
+
return decoded;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
exports.JwtService = JwtService;
|
|
57
|
+
JwtService.moduleOptions = {};
|
|
58
|
+
exports.JwtService = JwtService = JwtService_1 = __decorate([
|
|
59
|
+
(0, core_1.Injectable)(),
|
|
60
|
+
__metadata("design:paramtypes", [])
|
|
61
|
+
], JwtService);
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hazeljs/auth",
|
|
3
|
+
"version": "0.2.0-beta.1",
|
|
4
|
+
"description": "Authentication and JWT module for HazelJS framework",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"test": "jest --coverage --passWithNoTests",
|
|
13
|
+
"lint": "eslint \"src/**/*.ts\"",
|
|
14
|
+
"lint:fix": "eslint \"src/**/*.ts\" --fix",
|
|
15
|
+
"clean": "rm -rf dist"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@hazeljs/core": "file:../core",
|
|
19
|
+
"jsonwebtoken": "^9.0.2"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/jsonwebtoken": "^9.0.9",
|
|
23
|
+
"@types/node": "^20.17.50",
|
|
24
|
+
"@typescript-eslint/eslint-plugin": "^8.18.2",
|
|
25
|
+
"@typescript-eslint/parser": "^8.18.2",
|
|
26
|
+
"eslint": "^8.56.0",
|
|
27
|
+
"jest": "^29.7.0",
|
|
28
|
+
"ts-jest": "^29.1.2",
|
|
29
|
+
"typescript": "^5.3.3"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/hazel-js/hazeljs.git",
|
|
37
|
+
"directory": "packages/auth"
|
|
38
|
+
},
|
|
39
|
+
"keywords": [
|
|
40
|
+
"hazeljs",
|
|
41
|
+
"auth",
|
|
42
|
+
"authentication",
|
|
43
|
+
"jwt",
|
|
44
|
+
"security"
|
|
45
|
+
],
|
|
46
|
+
"author": "Muhammad Arslan <marslan@hazeljs.com>",
|
|
47
|
+
"license": "MIT"
|
|
48
|
+
}
|