@zola_do/authorization 0.2.5 → 0.2.6
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 +491 -90
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
# @zola_do/authorization
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@zola_do/authorization)
|
|
4
|
+
[](https://www.npmjs.com/package/@zola_do/authorization)
|
|
5
|
+
[](https://opensource.org/licenses/ISC)
|
|
6
|
+
|
|
7
|
+
JWT authentication, API key validation, guards, and strategies for NestJS applications.
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
`@zola_do/authorization` provides a complete authentication and authorization solution:
|
|
12
|
+
|
|
13
|
+
- **JWT Guards** — Bearer token authentication
|
|
14
|
+
- **Permission Guards** — Role-based access control (RBAC)
|
|
15
|
+
- **API Key Guard** — External API authentication
|
|
16
|
+
- **Strategies** — Passport JWT strategies (access + refresh tokens)
|
|
17
|
+
- **Decorators** — `@CurrentUser()`, `@AllowAnonymous()`
|
|
18
|
+
- **Auth Helper** — Token generation, password hashing, OTP
|
|
19
|
+
|
|
20
|
+
By default, `JwtGuard` is registered as a global guard, protecting all routes.
|
|
4
21
|
|
|
5
22
|
## Installation
|
|
6
23
|
|
|
@@ -12,31 +29,36 @@ npm install @zola_do/authorization
|
|
|
12
29
|
npm install @zola_do/nestjs-shared
|
|
13
30
|
```
|
|
14
31
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
`@zola_do/authorization` root imports remain fully supported for backward compatibility.
|
|
18
|
-
For new code, prefer focused subpath imports:
|
|
32
|
+
### Dependencies
|
|
19
33
|
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
import { JwtGuard, PermissionsGuard } from '@zola_do/authorization/guards';
|
|
23
|
-
import { AllowAnonymous, CurrentUser } from '@zola_do/authorization/decorators';
|
|
24
|
-
import { AuthHelper } from '@zola_do/authorization/helper';
|
|
34
|
+
```bash
|
|
35
|
+
npm install @nestjs/jwt @nestjs/passport passport passport-jwt bcrypt jsonwebtoken
|
|
25
36
|
```
|
|
26
37
|
|
|
27
|
-
Optional
|
|
38
|
+
### Optional Dependencies
|
|
28
39
|
|
|
29
|
-
```
|
|
30
|
-
|
|
40
|
+
```bash
|
|
41
|
+
# For ThrottlerBehindProxyGuard
|
|
42
|
+
npm install @nestjs/throttler
|
|
31
43
|
```
|
|
32
44
|
|
|
33
|
-
##
|
|
45
|
+
## Quick Start
|
|
34
46
|
|
|
35
|
-
###
|
|
47
|
+
### 1. Configure Environment
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# .env
|
|
51
|
+
JWT_ACCESS_TOKEN_SECRET=your-access-token-secret-min-32-chars
|
|
52
|
+
JWT_ACCESS_TOKEN_EXPIRES=15m
|
|
53
|
+
JWT_REFRESH_TOKEN_SECRET=your-refresh-token-secret-min-32-chars
|
|
54
|
+
JWT_REFRESH_TOKEN_EXPIRES=7d
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 2. Register Module
|
|
36
58
|
|
|
37
59
|
```typescript
|
|
38
|
-
import { Module } from
|
|
39
|
-
import { AuthorizationModule } from
|
|
60
|
+
import { Module } from "@nestjs/common";
|
|
61
|
+
import { AuthorizationModule } from "@zola_do/authorization";
|
|
40
62
|
|
|
41
63
|
@Module({
|
|
42
64
|
imports: [AuthorizationModule],
|
|
@@ -44,147 +66,526 @@ import { AuthorizationModule } from '@zola_do/authorization';
|
|
|
44
66
|
export class AppModule {}
|
|
45
67
|
```
|
|
46
68
|
|
|
47
|
-
|
|
69
|
+
### 3. Access User in Controller
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
import { Controller, Get } from "@nestjs/common";
|
|
73
|
+
import { CurrentUser, JwtGuard, UseGuards } from "@zola_do/authorization";
|
|
74
|
+
|
|
75
|
+
@Controller("profile")
|
|
76
|
+
export class ProfileController {
|
|
77
|
+
@Get()
|
|
78
|
+
@UseGuards(JwtGuard) // Optional - global guard is active by default
|
|
79
|
+
getProfile(@CurrentUser() user: any) {
|
|
80
|
+
return {
|
|
81
|
+
id: user.id,
|
|
82
|
+
email: user.email,
|
|
83
|
+
organization: user.organization,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## JWT Flow
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
93
|
+
│ Login Flow │
|
|
94
|
+
├─────────────────────────────────────────────────────────────────────┤
|
|
95
|
+
│ │
|
|
96
|
+
│ Client Backend JWT Payload │
|
|
97
|
+
│ │ │ │ │
|
|
98
|
+
│ │ POST /login │ │ │
|
|
99
|
+
│ │ {email,pass} │ │ │
|
|
100
|
+
│ │───────────────>│ │ │
|
|
101
|
+
│ │ │ │ │
|
|
102
|
+
│ │ ┌─────▼─────┐ │ │
|
|
103
|
+
│ │ │ Validate │ │ │
|
|
104
|
+
│ │ │ credentials│ │ │
|
|
105
|
+
│ │ └─────┬─────┘ │ │
|
|
106
|
+
│ │ │ │ │
|
|
107
|
+
│ │ ┌─────▼───────────────┐ │ │
|
|
108
|
+
│ │ │ Generate tokens: │ │ │
|
|
109
|
+
│ │ │ - accessToken │─────────────┼─────────────────>│
|
|
110
|
+
│ │ │ - refreshToken │ │ { │
|
|
111
|
+
│ │ └────────────────────┘ │ sub: userId, │
|
|
112
|
+
│ │ │ │ email, │
|
|
113
|
+
│ │ │ │ organization, │
|
|
114
|
+
│ │ │ │ permissions │
|
|
115
|
+
│ │<───────────────│ │ } │
|
|
116
|
+
│ │ { accessToken,│ │
|
|
117
|
+
│ │ refreshToken│ │
|
|
118
|
+
│ │ } │ │
|
|
119
|
+
│ │ │ │
|
|
120
|
+
└────┼────────────────┼───────────────────────────────────────────────┘
|
|
121
|
+
│ │
|
|
122
|
+
│ │
|
|
123
|
+
▼ ▼
|
|
124
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
125
|
+
│ Authenticated Request │
|
|
126
|
+
├─────────────────────────────────────────────────────────────────────┤
|
|
127
|
+
│ │
|
|
128
|
+
│ Client Backend JwtGuard │
|
|
129
|
+
│ │ │ │ │
|
|
130
|
+
│ │ GET /profile │ │ │
|
|
131
|
+
│ │ Authorization:│ │ │
|
|
132
|
+
│ │ Bearer <token>│ │ │
|
|
133
|
+
│ │───────────────>│ │ │
|
|
134
|
+
│ │ │ │ │
|
|
135
|
+
│ │ │ ┌────▼─────┐ │ │
|
|
136
|
+
│ │ │ │ Extract │ │ │
|
|
137
|
+
│ │ │ │ token │ │ │
|
|
138
|
+
│ │ │ └────┬─────┘ │ │
|
|
139
|
+
│ │ │ │ │ │
|
|
140
|
+
│ │ │ ┌────▼────────────┐ │ │
|
|
141
|
+
│ │ │ │ Verify with │ │ │
|
|
142
|
+
│ │ │ │ JWT_ACCESS_ │ │ │
|
|
143
|
+
│ │ │ │ TOKEN_SECRET │ │ │
|
|
144
|
+
│ │ │ └────┬────────────┘ │ │
|
|
145
|
+
│ │ │ │ │ │
|
|
146
|
+
│ │ │ │ set request.user │ │
|
|
147
|
+
│ │ │ ▼ │ │
|
|
148
|
+
│ │ │ req.user = payload │ │
|
|
149
|
+
│ │ │ │ │
|
|
150
|
+
│ │<───────────────│ Controller runs │ │
|
|
151
|
+
│ │ { user data } │ @CurrentUser() = user │ │
|
|
152
|
+
│ │ │ │ │
|
|
153
|
+
└────┴────────────────┴───────────────────────────────────────────────┘
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Guards
|
|
157
|
+
|
|
158
|
+
### JwtGuard
|
|
159
|
+
|
|
160
|
+
Default global guard that validates Bearer tokens:
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
@UseGuards(JwtGuard)
|
|
164
|
+
@Controller("protected")
|
|
165
|
+
export class ProtectedController {}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### OptionalJwtGuard
|
|
169
|
+
|
|
170
|
+
Allows anonymous access but extracts user if token is present:
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
@UseGuards(OptionalJwtGuard)
|
|
174
|
+
@Controller("optional")
|
|
175
|
+
export class OptionalController {
|
|
176
|
+
@Get()
|
|
177
|
+
getData(@CurrentUser() user?: any) {
|
|
178
|
+
if (user) {
|
|
179
|
+
return { message: "Authenticated", user };
|
|
180
|
+
}
|
|
181
|
+
return { message: "Anonymous" };
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### PermissionsGuard
|
|
187
|
+
|
|
188
|
+
Requires specific permissions. Supports pipe-separated OR logic:
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
@UseGuards(JwtGuard, PermissionsGuard("product:create"))
|
|
192
|
+
@Controller("products")
|
|
193
|
+
export class ProductsController {
|
|
194
|
+
@Post()
|
|
195
|
+
createProduct() {
|
|
196
|
+
// Requires 'product:create' permission
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
@Patch(":id")
|
|
200
|
+
@UseGuards(PermissionsGuard("product:update|product:manage"))
|
|
201
|
+
updateProduct() {
|
|
202
|
+
// Requires 'product:update' OR 'product:manage'
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### ApiKeyGuard
|
|
208
|
+
|
|
209
|
+
Validates `X-API-Key` header against `process.env.API_KEY`:
|
|
48
210
|
|
|
49
|
-
|
|
211
|
+
```typescript
|
|
212
|
+
@UseGuards(ApiKeyGuard)
|
|
213
|
+
@Controller("external")
|
|
214
|
+
export class ExternalController {}
|
|
215
|
+
```
|
|
50
216
|
|
|
51
|
-
|
|
52
|
-
|
|
217
|
+
### VendorGuard
|
|
218
|
+
|
|
219
|
+
Checks vendor registration status (custom implementation required):
|
|
53
220
|
|
|
54
221
|
```typescript
|
|
55
|
-
|
|
56
|
-
|
|
222
|
+
@UseGuards(JwtGuard, VendorGuard())
|
|
223
|
+
@Controller("vendor")
|
|
224
|
+
export class VendorController {}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Decorators
|
|
228
|
+
|
|
229
|
+
### @CurrentUser()
|
|
230
|
+
|
|
231
|
+
Extract user from request:
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
@Get('profile')
|
|
235
|
+
getProfile(@CurrentUser() user: UserPayload) {
|
|
236
|
+
return user;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// With property extraction
|
|
240
|
+
@Get('profile')
|
|
241
|
+
getProfile(@CurrentUser('email') email: string) {
|
|
242
|
+
return { email };
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### @AllowAnonymous()
|
|
247
|
+
|
|
248
|
+
Skip authentication for specific routes:
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
@Get('public')
|
|
252
|
+
@AllowAnonymous()
|
|
253
|
+
getPublicData() {
|
|
254
|
+
return { message: 'Public data' };
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## JWT Payload
|
|
259
|
+
|
|
260
|
+
Default payload structure:
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
interface JwtPayload {
|
|
264
|
+
sub: string; // User ID
|
|
265
|
+
email: string; // User email
|
|
266
|
+
organization?: {
|
|
267
|
+
// Organization context
|
|
268
|
+
organizationId: string;
|
|
269
|
+
organizationName: string;
|
|
270
|
+
};
|
|
271
|
+
permissions?: string[]; // User permissions
|
|
272
|
+
iat?: number; // Issued at
|
|
273
|
+
exp?: number; // Expiration
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## AuthHelper
|
|
278
|
+
|
|
279
|
+
Utility service for token operations:
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
import { AuthHelper } from "@zola_do/authorization";
|
|
283
|
+
|
|
284
|
+
@Injectable()
|
|
285
|
+
export class AuthService {
|
|
286
|
+
constructor(private readonly authHelper: AuthHelper) {}
|
|
287
|
+
|
|
288
|
+
async login(user: User) {
|
|
289
|
+
// Generate tokens
|
|
290
|
+
const tokens = this.authHelper.generateTokens(user);
|
|
291
|
+
|
|
292
|
+
// Hash password
|
|
293
|
+
const hashedPassword = await this.authHelper.hashPassword(plainPassword);
|
|
294
|
+
|
|
295
|
+
// Verify password
|
|
296
|
+
const isValid = await this.authHelper.verifyPassword(
|
|
297
|
+
plainPassword,
|
|
298
|
+
hashedPassword,
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
// Generate OTP
|
|
302
|
+
const otp = this.authHelper.generateOtp();
|
|
303
|
+
|
|
304
|
+
return { tokens, hashedPassword, isValid, otp };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async refreshTokens(refreshToken: string) {
|
|
308
|
+
return this.authHelper.refreshTokens(refreshToken);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### AuthHelper Methods
|
|
314
|
+
|
|
315
|
+
| Method | Description | Returns |
|
|
316
|
+
| -------------------------------- | ------------------------------ | ------------------------------- |
|
|
317
|
+
| `generateTokens(user)` | Create access + refresh tokens | `{ accessToken, refreshToken }` |
|
|
318
|
+
| `refreshTokens(refreshToken)` | Refresh using refresh token | `{ accessToken, refreshToken }` |
|
|
319
|
+
| `hashPassword(password)` | bcrypt hash | `string` |
|
|
320
|
+
| `verifyPassword(password, hash)` | Compare password to hash | `boolean` |
|
|
321
|
+
| `generateOtp(length)` | Generate numeric OTP | `string` |
|
|
322
|
+
| `decodeToken(token)` | Decode without verification | `JwtPayload` |
|
|
323
|
+
|
|
324
|
+
## Token Generation Example
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
import { AuthHelper } from "@zola_do/authorization";
|
|
328
|
+
import { JwtPayload } from "@zola_do/authorization/helper";
|
|
329
|
+
|
|
330
|
+
@Controller("auth")
|
|
331
|
+
export class AuthController {
|
|
332
|
+
constructor(private readonly authHelper: AuthHelper) {}
|
|
333
|
+
|
|
334
|
+
@Post("login")
|
|
335
|
+
async login(@Body() dto: LoginDto) {
|
|
336
|
+
const user = await this.userService.validateUser(dto);
|
|
337
|
+
|
|
338
|
+
const payload: JwtPayload = {
|
|
339
|
+
sub: user.id,
|
|
340
|
+
email: user.email,
|
|
341
|
+
organization: {
|
|
342
|
+
organizationId: user.organizationId,
|
|
343
|
+
organizationName: user.organization.name,
|
|
344
|
+
},
|
|
345
|
+
permissions: user.permissions,
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
return this.authHelper.generateTokens(payload);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
@Post("refresh")
|
|
352
|
+
async refresh(@Body("refreshToken") refreshToken: string) {
|
|
353
|
+
return this.authHelper.refreshTokens(refreshToken);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## Custom JwtGuard Override
|
|
359
|
+
|
|
360
|
+
Override the default JWT validation logic:
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
import { Injectable, ExecutionContext } from "@nestjs/common";
|
|
364
|
+
import { JwtGuard } from "@zola_do/authorization";
|
|
57
365
|
|
|
58
366
|
@Injectable()
|
|
59
367
|
export class AppJwtGuard extends JwtGuard {
|
|
60
368
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
|
61
|
-
//
|
|
369
|
+
// Run default validation
|
|
370
|
+
const canActivate = await super.canActivate(context);
|
|
371
|
+
if (!canActivate) return false;
|
|
372
|
+
|
|
373
|
+
// Add custom logic
|
|
374
|
+
const request = context.switchToHttp().getRequest();
|
|
375
|
+
const user = request.user;
|
|
376
|
+
|
|
377
|
+
// Check additional conditions
|
|
378
|
+
if (user.status === "suspended") {
|
|
379
|
+
throw new ForbiddenException("Account suspended");
|
|
380
|
+
}
|
|
381
|
+
|
|
62
382
|
return true;
|
|
63
383
|
}
|
|
64
384
|
}
|
|
65
385
|
```
|
|
66
386
|
|
|
387
|
+
Register in module:
|
|
388
|
+
|
|
67
389
|
```typescript
|
|
68
|
-
import {
|
|
69
|
-
import {
|
|
70
|
-
import { AuthorizationModule, JwtGuard } from '@zola_do/authorization';
|
|
71
|
-
import { AppJwtGuard } from './guards/app-jwt.guard';
|
|
390
|
+
import { APP_GUARD } from "@nestjs/core";
|
|
391
|
+
import { AuthorizationModule, JwtGuard } from "@zola_do/authorization";
|
|
72
392
|
|
|
73
393
|
@Module({
|
|
74
394
|
imports: [AuthorizationModule],
|
|
75
395
|
providers: [
|
|
76
396
|
AppJwtGuard,
|
|
77
|
-
// Used by controllers that reference @UseGuards(JwtGuard)
|
|
78
397
|
{ provide: JwtGuard, useExisting: AppJwtGuard },
|
|
79
|
-
// Used as global guard
|
|
80
398
|
{ provide: APP_GUARD, useExisting: AppJwtGuard },
|
|
81
399
|
],
|
|
82
400
|
})
|
|
83
401
|
export class AppModule {}
|
|
84
402
|
```
|
|
85
403
|
|
|
86
|
-
|
|
404
|
+
## Strategies
|
|
405
|
+
|
|
406
|
+
### JwtStrategy
|
|
407
|
+
|
|
408
|
+
Passport strategy for access token validation:
|
|
87
409
|
|
|
88
410
|
```typescript
|
|
89
|
-
import {
|
|
90
|
-
import { CurrentUser, JwtGuard, UseGuards } from '@zola_do/authorization';
|
|
411
|
+
import { JwtStrategy } from "@zola_do/authorization";
|
|
91
412
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
export class ProfileController {
|
|
95
|
-
@Get()
|
|
96
|
-
getProfile(@CurrentUser() user: any) {
|
|
97
|
-
return user; // Contains JWT payload (id, email, organization, etc.)
|
|
98
|
-
}
|
|
99
|
-
}
|
|
413
|
+
// Already registered by AuthorizationModule
|
|
414
|
+
// Customization via override (see above)
|
|
100
415
|
```
|
|
101
416
|
|
|
102
|
-
###
|
|
417
|
+
### JwtRefreshTokenStrategy
|
|
418
|
+
|
|
419
|
+
Passport strategy for refresh token validation:
|
|
103
420
|
|
|
104
421
|
```typescript
|
|
105
|
-
import {
|
|
422
|
+
import { JwtRefreshTokenStrategy } from "@zola_do/authorization";
|
|
106
423
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
getPublicData() {
|
|
110
|
-
return { message: 'No auth required' };
|
|
111
|
-
}
|
|
424
|
+
// Used for token refresh endpoints
|
|
425
|
+
// Validates against JWT_REFRESH_TOKEN_SECRET
|
|
112
426
|
```
|
|
113
427
|
|
|
114
|
-
|
|
428
|
+
## Environment Variables
|
|
115
429
|
|
|
116
|
-
|
|
430
|
+
| Variable | Description | Default |
|
|
431
|
+
| --------------------------- | ---------------------------- | -------- |
|
|
432
|
+
| `JWT_ACCESS_TOKEN_SECRET` | Access token signing secret | Required |
|
|
433
|
+
| `JWT_ACCESS_TOKEN_EXPIRES` | Access token TTL | `15m` |
|
|
434
|
+
| `JWT_REFRESH_TOKEN_SECRET` | Refresh token signing secret | Required |
|
|
435
|
+
| `JWT_REFRESH_TOKEN_EXPIRES` | Refresh token TTL | `7d` |
|
|
436
|
+
| `API_KEY` | API key for ApiKeyGuard | Optional |
|
|
117
437
|
|
|
118
|
-
|
|
119
|
-
import { PermissionsGuard } from '@zola_do/authorization';
|
|
438
|
+
### Token Expiration Format
|
|
120
439
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
440
|
+
```
|
|
441
|
+
15m - 15 minutes
|
|
442
|
+
1h - 1 hour
|
|
443
|
+
7d - 7 days
|
|
444
|
+
30d - 30 days
|
|
124
445
|
```
|
|
125
446
|
|
|
126
|
-
|
|
447
|
+
## Permission System
|
|
127
448
|
|
|
128
|
-
|
|
449
|
+
Permissions use pipe-separated OR logic:
|
|
129
450
|
|
|
130
451
|
```typescript
|
|
131
|
-
|
|
452
|
+
// Require any of these permissions
|
|
453
|
+
@UseGuards(PermissionsGuard('admin:write|manager:write|product:create'))
|
|
454
|
+
```
|
|
132
455
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
456
|
+
### Common Permission Patterns
|
|
457
|
+
|
|
458
|
+
| Pattern | Description |
|
|
459
|
+
| ----------------- | ----------------------------------------- |
|
|
460
|
+
| `resource:action` | Basic CRUD (product:create, product:read) |
|
|
461
|
+
| `resource:*` | All actions on resource |
|
|
462
|
+
| `*:read` | Read all resources |
|
|
463
|
+
| `admin:*` | Admin access |
|
|
464
|
+
|
|
465
|
+
## Recommended Imports
|
|
466
|
+
|
|
467
|
+
Subpath imports are recommended for tree-shaking:
|
|
468
|
+
|
|
469
|
+
```typescript
|
|
470
|
+
import { AuthorizationModule } from "@zola_do/authorization/module";
|
|
471
|
+
import {
|
|
472
|
+
JwtGuard,
|
|
473
|
+
PermissionsGuard,
|
|
474
|
+
ApiKeyGuard,
|
|
475
|
+
} from "@zola_do/authorization/guards";
|
|
476
|
+
import { AllowAnonymous, CurrentUser } from "@zola_do/authorization/decorators";
|
|
477
|
+
import { AuthHelper } from "@zola_do/authorization/helper";
|
|
478
|
+
import {
|
|
479
|
+
JwtStrategy,
|
|
480
|
+
JwtRefreshTokenStrategy,
|
|
481
|
+
} from "@zola_do/authorization/strategy";
|
|
136
482
|
```
|
|
137
483
|
|
|
138
|
-
|
|
484
|
+
Root import is supported for backward compatibility:
|
|
485
|
+
|
|
486
|
+
```typescript
|
|
487
|
+
import {
|
|
488
|
+
AuthorizationModule,
|
|
489
|
+
JwtGuard,
|
|
490
|
+
AllowAnonymous,
|
|
491
|
+
AuthHelper,
|
|
492
|
+
} from "@zola_do/authorization";
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
## Optional Features
|
|
139
496
|
|
|
140
|
-
|
|
497
|
+
### ThrottlerBehindProxyGuard
|
|
498
|
+
|
|
499
|
+
Rate limiting guard for applications behind a reverse proxy:
|
|
141
500
|
|
|
142
501
|
```typescript
|
|
143
|
-
import {
|
|
502
|
+
import { ThrottlerBehindProxyGuard } from "@zola_do/authorization/optional/throttler";
|
|
144
503
|
|
|
145
|
-
@
|
|
146
|
-
|
|
147
|
-
|
|
504
|
+
@UseGuards(ThrottlerBehindProxyGuard)
|
|
505
|
+
@Controller("api")
|
|
506
|
+
export class ApiController {}
|
|
507
|
+
```
|
|
148
508
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
509
|
+
Requires `@nestjs/throttler` peer dependency.
|
|
510
|
+
|
|
511
|
+
## API Reference
|
|
512
|
+
|
|
513
|
+
### Guards
|
|
514
|
+
|
|
515
|
+
| Guard | Description | Auth Required |
|
|
516
|
+
| ------------------------------- | ----------------------- | -------------------- |
|
|
517
|
+
| `JwtGuard` | Bearer token validation | Yes (global default) |
|
|
518
|
+
| `OptionalJwtGuard` | Bearer token if present | No |
|
|
519
|
+
| `PermissionsGuard(permissions)` | Permission check | Yes |
|
|
520
|
+
| `ApiKeyGuard` | API key validation | Yes |
|
|
521
|
+
| `VendorGuard()` | Vendor status check | Yes |
|
|
522
|
+
| `ThrottlerBehindProxyGuard` | Rate limiting | Configurable |
|
|
523
|
+
|
|
524
|
+
### Decorators
|
|
525
|
+
|
|
526
|
+
| Decorator | Description |
|
|
527
|
+
| ------------------------ | ----------------------------- |
|
|
528
|
+
| `@CurrentUser()` | Get current user from request |
|
|
529
|
+
| `@CurrentUser(property)` | Get specific property |
|
|
530
|
+
| `@AllowAnonymous()` | Skip authentication |
|
|
531
|
+
|
|
532
|
+
### Strategies
|
|
533
|
+
|
|
534
|
+
| Strategy | Token Type | Secret |
|
|
535
|
+
| ------------------------- | ---------- | -------------------------- |
|
|
536
|
+
| `JwtStrategy` | Access | `JWT_ACCESS_TOKEN_SECRET` |
|
|
537
|
+
| `JwtRefreshTokenStrategy` | Refresh | `JWT_REFRESH_TOKEN_SECRET` |
|
|
538
|
+
|
|
539
|
+
### AuthHelper
|
|
540
|
+
|
|
541
|
+
```typescript
|
|
542
|
+
class AuthHelper {
|
|
543
|
+
generateTokens(payload: JwtPayload): {
|
|
544
|
+
accessToken: string;
|
|
545
|
+
refreshToken: string;
|
|
546
|
+
};
|
|
547
|
+
refreshTokens(
|
|
548
|
+
refreshToken: string,
|
|
549
|
+
): Promise<{ accessToken: string; refreshToken: string }>;
|
|
550
|
+
hashPassword(password: string): Promise<string>;
|
|
551
|
+
verifyPassword(password: string, hashedPassword: string): Promise<boolean>;
|
|
552
|
+
generateOtp(length?: number): string;
|
|
553
|
+
decodeToken(token: string): JwtPayload;
|
|
152
554
|
}
|
|
153
555
|
```
|
|
154
556
|
|
|
155
|
-
##
|
|
156
|
-
|
|
157
|
-
| Variable | Description |
|
|
158
|
-
|----------|-------------|
|
|
159
|
-
| `JWT_ACCESS_TOKEN_SECRET` | Secret for access token signing |
|
|
160
|
-
| `JWT_REFRESH_TOKEN_SECRET` | Secret for refresh token signing |
|
|
161
|
-
| `JWT_ACCESS_TOKEN_EXPIRES` | Access token TTL (e.g. `15m`) |
|
|
162
|
-
| `JWT_REFRESH_TOKEN_EXPIRES` | Refresh token TTL (e.g. `7d`) |
|
|
163
|
-
| `API_KEY` | API key for `ApiKeyGuard` validation |
|
|
557
|
+
## Troubleshooting
|
|
164
558
|
|
|
165
|
-
|
|
559
|
+
### Q: Token expired errors?
|
|
166
560
|
|
|
167
|
-
|
|
168
|
-
- **Decorators:** `@CurrentUser()`, `@AllowAnonymous()`
|
|
169
|
-
- **Strategies:** `JwtStrategy`, `JwtRefreshTokenStrategy`
|
|
170
|
-
- **Helpers:** `AuthHelper`
|
|
171
|
-
- **Subpath entrypoints:** `@zola_do/authorization/module`, `@zola_do/authorization/decorators`, `@zola_do/authorization/guards`, `@zola_do/authorization/strategy`, `@zola_do/authorization/helper`, `@zola_do/authorization/models`
|
|
172
|
-
- **Root entrypoint:** `@zola_do/authorization` (supported for backward compatibility)
|
|
561
|
+
Check `JWT_ACCESS_TOKEN_EXPIRES` environment variable and ensure clocks are synchronized.
|
|
173
562
|
|
|
174
|
-
###
|
|
563
|
+
### Q: User is undefined in @CurrentUser()?
|
|
175
564
|
|
|
176
|
-
`
|
|
565
|
+
Ensure `JwtGuard` is active and token is valid:
|
|
177
566
|
|
|
178
567
|
```typescript
|
|
179
|
-
|
|
568
|
+
@UseGuards(JwtGuard) // Add explicit guard if global is not configured
|
|
180
569
|
```
|
|
181
570
|
|
|
182
|
-
|
|
571
|
+
### Q: Custom token payload?
|
|
572
|
+
|
|
573
|
+
Override `JwtStrategy` and `JwtGuard` to implement custom validation.
|
|
574
|
+
|
|
575
|
+
### Q: Skip guard for specific routes?
|
|
576
|
+
|
|
577
|
+
```typescript
|
|
578
|
+
@AllowAnonymous() // Works even with global JwtGuard
|
|
579
|
+
```
|
|
183
580
|
|
|
184
581
|
## Related Packages
|
|
185
582
|
|
|
186
583
|
- [@zola_do/crud](../crud) — Uses JwtGuard and PermissionsGuard
|
|
187
584
|
|
|
585
|
+
## License
|
|
586
|
+
|
|
587
|
+
ISC
|
|
588
|
+
|
|
188
589
|
## Community
|
|
189
590
|
|
|
190
591
|
- [Contributing](../../CONTRIBUTING.md)
|