@flusys/nestjs-auth 1.1.0-beta → 2.0.0
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 +590 -473
- package/cjs/config/auth.constants.js +8 -0
- package/cjs/controllers/authentication.controller.js +218 -39
- package/cjs/controllers/branch.controller.js +44 -1
- package/cjs/controllers/company-selection.controller.js +27 -20
- package/cjs/controllers/company.controller.js +44 -1
- package/cjs/controllers/user-permission.controller.js +82 -154
- package/cjs/controllers/user.controller.js +68 -15
- package/cjs/docs/auth-swagger.config.js +14 -7
- package/cjs/dtos/authentication.dto.js +16 -43
- package/cjs/dtos/index.js +0 -1
- package/cjs/dtos/user-permission.dto.js +28 -0
- package/cjs/dtos/user.dto.js +19 -4
- package/cjs/entities/user-company-permission.entity.js +0 -6
- package/cjs/interceptors/clear-token.interceptor.js +10 -23
- package/cjs/interceptors/set-token.interceptor.js +15 -55
- package/cjs/interfaces/auth-email-provider.interface.js +1 -1
- package/cjs/modules/auth.module.js +63 -147
- package/cjs/services/auth-config.service.js +22 -28
- package/cjs/services/auth-datasource.provider.js +9 -21
- package/cjs/services/auth-email.service.js +89 -85
- package/cjs/services/authentication.service.js +225 -396
- package/cjs/services/branch.service.js +9 -4
- package/cjs/services/company-selection-session.service.js +1 -1
- package/cjs/services/user-permission.service.js +43 -73
- package/cjs/services/user.service.js +78 -64
- package/cjs/strategies/jwt.strategy.js +44 -44
- package/cjs/validators/password.validator.js +45 -0
- package/config/auth.constants.d.ts +2 -0
- package/controllers/authentication.controller.d.ts +5 -3
- package/controllers/company-selection.controller.d.ts +6 -2
- package/controllers/user-permission.controller.d.ts +5 -4
- package/controllers/user.controller.d.ts +6 -0
- package/dtos/authentication.dto.d.ts +0 -4
- package/dtos/company-branch.dto.d.ts +0 -1
- package/dtos/company.dto.d.ts +0 -1
- package/dtos/index.d.ts +0 -1
- package/dtos/user-permission.dto.d.ts +4 -0
- package/dtos/user.dto.d.ts +1 -3
- package/entities/index.d.ts +3 -3
- package/entities/user-company-permission.entity.d.ts +0 -2
- package/fesm/config/auth.constants.js +6 -13
- package/fesm/controllers/authentication.controller.js +221 -42
- package/fesm/controllers/branch.controller.js +45 -2
- package/fesm/controllers/company-selection.controller.js +28 -21
- package/fesm/controllers/company.controller.js +45 -2
- package/fesm/controllers/user-permission.controller.js +84 -156
- package/fesm/controllers/user.controller.js +71 -18
- package/fesm/docs/auth-swagger.config.js +14 -7
- package/fesm/dtos/authentication.dto.js +17 -44
- package/fesm/dtos/index.js +0 -1
- package/fesm/dtos/user-permission.dto.js +28 -0
- package/fesm/dtos/user.dto.js +20 -5
- package/fesm/entities/user-company-permission.entity.js +0 -6
- package/fesm/interceptors/clear-token.interceptor.js +9 -22
- package/fesm/interceptors/set-token.interceptor.js +14 -54
- package/fesm/interfaces/auth-email-provider.interface.js +1 -1
- package/fesm/modules/auth.module.js +64 -148
- package/fesm/services/auth-config.service.js +23 -29
- package/fesm/services/auth-datasource.provider.js +10 -22
- package/fesm/services/auth-email.service.js +90 -86
- package/fesm/services/authentication.service.js +225 -396
- package/fesm/services/branch.service.js +9 -4
- package/fesm/services/company-selection-session.service.js +1 -1
- package/fesm/services/user-permission.service.js +44 -74
- package/fesm/services/user.service.js +79 -65
- package/fesm/strategies/jwt.strategy.js +44 -44
- package/fesm/validators/password.validator.js +48 -0
- package/interceptors/clear-token.interceptor.d.ts +0 -1
- package/interceptors/set-token.interceptor.d.ts +1 -1
- package/interfaces/authentication.interface.d.ts +0 -4
- package/interfaces/user-enricher.interface.d.ts +1 -4
- package/modules/auth.module.d.ts +6 -3
- package/package.json +4 -4
- package/services/auth-config.service.d.ts +6 -8
- package/services/auth-datasource.provider.d.ts +2 -4
- package/services/auth-email.service.d.ts +8 -4
- package/services/authentication.service.d.ts +15 -16
- package/services/company-selection-session.service.d.ts +1 -1
- package/services/user-permission.service.d.ts +6 -9
- package/services/user.service.d.ts +3 -0
- package/strategies/jwt.strategy.d.ts +4 -1
- package/validators/password.validator.d.ts +10 -0
- package/cjs/dtos/user-company-permission.dto.js +0 -110
- package/dtos/user-company-permission.dto.d.ts +0 -10
- package/fesm/dtos/user-company-permission.dto.js +0 -98
|
@@ -4,6 +4,16 @@
|
|
|
4
4
|
'User Permissions',
|
|
5
5
|
'Company Selection'
|
|
6
6
|
];
|
|
7
|
+
/** Examples to exclude when company feature is disabled */ const COMPANY_EXAMPLE_EXCLUSIONS = [
|
|
8
|
+
{
|
|
9
|
+
pathPattern: '/auth/login',
|
|
10
|
+
method: 'post',
|
|
11
|
+
examples: [
|
|
12
|
+
'loginSuccessWithCompany',
|
|
13
|
+
'companySelection'
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
];
|
|
7
17
|
/**
|
|
8
18
|
* Schema properties to exclude when company feature is disabled
|
|
9
19
|
*
|
|
@@ -48,16 +58,14 @@
|
|
|
48
58
|
schemaName: 'RegistrationResponseDto',
|
|
49
59
|
properties: [
|
|
50
60
|
'company',
|
|
51
|
-
'branch'
|
|
52
|
-
'companyFeatureEnabled'
|
|
61
|
+
'branch'
|
|
53
62
|
]
|
|
54
63
|
},
|
|
55
64
|
{
|
|
56
65
|
schemaName: 'LoginResponseDto',
|
|
57
66
|
properties: [
|
|
58
67
|
'company',
|
|
59
|
-
'branch'
|
|
60
|
-
'companyFeatureEnabled'
|
|
68
|
+
'branch'
|
|
61
69
|
]
|
|
62
70
|
},
|
|
63
71
|
{
|
|
@@ -92,7 +100,6 @@
|
|
|
92
100
|
'id',
|
|
93
101
|
'name',
|
|
94
102
|
'slug',
|
|
95
|
-
'isActive',
|
|
96
103
|
'logoId'
|
|
97
104
|
]
|
|
98
105
|
},
|
|
@@ -102,7 +109,6 @@
|
|
|
102
109
|
'id',
|
|
103
110
|
'name',
|
|
104
111
|
'slug',
|
|
105
|
-
'isActive',
|
|
106
112
|
'logoId',
|
|
107
113
|
'branches'
|
|
108
114
|
]
|
|
@@ -401,6 +407,7 @@ POST /auth/login → { accessToken, refreshToken, user }
|
|
|
401
407
|
path: 'api/docs/auth',
|
|
402
408
|
bearerAuth: true,
|
|
403
409
|
excludeTags,
|
|
404
|
-
excludeSchemaProperties: enableCompanyFeature ? undefined : COMPANY_SCHEMA_EXCLUSIONS
|
|
410
|
+
excludeSchemaProperties: enableCompanyFeature ? undefined : COMPANY_SCHEMA_EXCLUSIONS,
|
|
411
|
+
excludeExamples: enableCompanyFeature ? undefined : COMPANY_EXAMPLE_EXCLUSIONS
|
|
405
412
|
};
|
|
406
413
|
}
|
|
@@ -21,7 +21,8 @@ function _ts_metadata(k, v) {
|
|
|
21
21
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
22
22
|
}
|
|
23
23
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
24
|
-
import { IsEmail, IsNotEmpty, IsOptional, IsString, IsUUID, MaxLength,
|
|
24
|
+
import { IsEmail, IsNotEmpty, IsOptional, IsString, IsUUID, MaxLength, ValidateIf } from 'class-validator';
|
|
25
|
+
import { IsStrongPassword } from '../validators/password.validator';
|
|
25
26
|
// Request DTOs
|
|
26
27
|
/** Login Request DTO */ export class LoginDto {
|
|
27
28
|
constructor(){
|
|
@@ -40,12 +41,12 @@ _ts_decorate([
|
|
|
40
41
|
], LoginDto.prototype, "email", void 0);
|
|
41
42
|
_ts_decorate([
|
|
42
43
|
ApiProperty({
|
|
43
|
-
example: '
|
|
44
|
-
description: 'User password
|
|
44
|
+
example: 'SecureP@ss1',
|
|
45
|
+
description: 'User password'
|
|
45
46
|
}),
|
|
46
47
|
IsString(),
|
|
47
48
|
IsNotEmpty(),
|
|
48
|
-
|
|
49
|
+
MaxLength(128),
|
|
49
50
|
_ts_metadata("design:type", String)
|
|
50
51
|
], LoginDto.prototype, "password", void 0);
|
|
51
52
|
/** Registration Request DTO - Supports simple and company-based registration */ export class RegistrationDto {
|
|
@@ -84,13 +85,13 @@ _ts_decorate([
|
|
|
84
85
|
], RegistrationDto.prototype, "email", void 0);
|
|
85
86
|
_ts_decorate([
|
|
86
87
|
ApiProperty({
|
|
87
|
-
example: '
|
|
88
|
-
description: 'Password (min
|
|
88
|
+
example: 'StrongP@ss123',
|
|
89
|
+
description: 'Password (min 8 chars, requires uppercase, lowercase, number, special char)'
|
|
89
90
|
}),
|
|
90
91
|
IsNotEmpty(),
|
|
91
92
|
IsString(),
|
|
92
|
-
|
|
93
|
-
MaxLength(
|
|
93
|
+
IsStrongPassword(),
|
|
94
|
+
MaxLength(128),
|
|
94
95
|
_ts_metadata("design:type", String)
|
|
95
96
|
], RegistrationDto.prototype, "password", void 0);
|
|
96
97
|
_ts_decorate([
|
|
@@ -151,6 +152,7 @@ _ts_decorate([
|
|
|
151
152
|
}),
|
|
152
153
|
IsOptional(),
|
|
153
154
|
IsString(),
|
|
155
|
+
MaxLength(500),
|
|
154
156
|
_ts_metadata("design:type", String)
|
|
155
157
|
], RegistrationDto.prototype, "newCompanyAddress", void 0);
|
|
156
158
|
/** Refresh Token Request DTO - refreshToken optional in body (browsers use HTTP-only cookie) */ export class RefreshTokenDto {
|
|
@@ -180,16 +182,17 @@ _ts_decorate([
|
|
|
180
182
|
}),
|
|
181
183
|
IsString(),
|
|
182
184
|
IsNotEmpty(),
|
|
185
|
+
MaxLength(128),
|
|
183
186
|
_ts_metadata("design:type", String)
|
|
184
187
|
], ChangePasswordDto.prototype, "currentPassword", void 0);
|
|
185
188
|
_ts_decorate([
|
|
186
189
|
ApiProperty({
|
|
187
|
-
example: '
|
|
188
|
-
description: 'New password (min
|
|
190
|
+
example: 'NewP@ssword456',
|
|
191
|
+
description: 'New password (min 8 chars, requires uppercase, lowercase, number, special char)'
|
|
189
192
|
}),
|
|
190
193
|
IsString(),
|
|
191
194
|
IsNotEmpty(),
|
|
192
|
-
|
|
195
|
+
IsStrongPassword(),
|
|
193
196
|
_ts_metadata("design:type", String)
|
|
194
197
|
], ChangePasswordDto.prototype, "newPassword", void 0);
|
|
195
198
|
/** Select Company & Branch Request DTO - Used after login when user needs to select company and branch */ export class SelectDto {
|
|
@@ -358,7 +361,6 @@ _ts_decorate([
|
|
|
358
361
|
_define_property(this, "id", void 0);
|
|
359
362
|
_define_property(this, "name", void 0);
|
|
360
363
|
_define_property(this, "slug", void 0);
|
|
361
|
-
_define_property(this, "isActive", void 0);
|
|
362
364
|
_define_property(this, "logoId", void 0);
|
|
363
365
|
}
|
|
364
366
|
}
|
|
@@ -380,12 +382,6 @@ _ts_decorate([
|
|
|
380
382
|
}),
|
|
381
383
|
_ts_metadata("design:type", String)
|
|
382
384
|
], BranchSelectionInfoDto.prototype, "slug", void 0);
|
|
383
|
-
_ts_decorate([
|
|
384
|
-
ApiProperty({
|
|
385
|
-
example: true
|
|
386
|
-
}),
|
|
387
|
-
_ts_metadata("design:type", Boolean)
|
|
388
|
-
], BranchSelectionInfoDto.prototype, "isActive", void 0);
|
|
389
385
|
_ts_decorate([
|
|
390
386
|
ApiPropertyOptional({
|
|
391
387
|
example: '550e8400-e29b-41d4-a716-446655440077',
|
|
@@ -399,7 +395,6 @@ _ts_decorate([
|
|
|
399
395
|
_define_property(this, "id", void 0);
|
|
400
396
|
_define_property(this, "name", void 0);
|
|
401
397
|
_define_property(this, "slug", void 0);
|
|
402
|
-
_define_property(this, "isActive", void 0);
|
|
403
398
|
_define_property(this, "logoId", void 0);
|
|
404
399
|
_define_property(this, "branches", void 0);
|
|
405
400
|
}
|
|
@@ -422,12 +417,6 @@ _ts_decorate([
|
|
|
422
417
|
}),
|
|
423
418
|
_ts_metadata("design:type", String)
|
|
424
419
|
], CompanyWithBranchesDto.prototype, "slug", void 0);
|
|
425
|
-
_ts_decorate([
|
|
426
|
-
ApiProperty({
|
|
427
|
-
example: true
|
|
428
|
-
}),
|
|
429
|
-
_ts_metadata("design:type", Boolean)
|
|
430
|
-
], CompanyWithBranchesDto.prototype, "isActive", void 0);
|
|
431
420
|
_ts_decorate([
|
|
432
421
|
ApiPropertyOptional({
|
|
433
422
|
example: '550e8400-e29b-41d4-a716-446655440088',
|
|
@@ -452,7 +441,6 @@ _ts_decorate([
|
|
|
452
441
|
_define_property(this, "user", void 0);
|
|
453
442
|
_define_property(this, "company", void 0);
|
|
454
443
|
_define_property(this, "branch", void 0);
|
|
455
|
-
_define_property(this, "companyFeatureEnabled", void 0);
|
|
456
444
|
}
|
|
457
445
|
}
|
|
458
446
|
_ts_decorate([
|
|
@@ -488,13 +476,6 @@ _ts_decorate([
|
|
|
488
476
|
}),
|
|
489
477
|
_ts_metadata("design:type", typeof BranchInfoDto === "undefined" ? Object : BranchInfoDto)
|
|
490
478
|
], LoginResponseDto.prototype, "branch", void 0);
|
|
491
|
-
_ts_decorate([
|
|
492
|
-
ApiPropertyOptional({
|
|
493
|
-
example: true,
|
|
494
|
-
description: 'Whether company feature is enabled'
|
|
495
|
-
}),
|
|
496
|
-
_ts_metadata("design:type", Boolean)
|
|
497
|
-
], LoginResponseDto.prototype, "companyFeatureEnabled", void 0);
|
|
498
479
|
/** Registration Response DTO - Returned on successful registration */ export class RegistrationResponseDto {
|
|
499
480
|
constructor(){
|
|
500
481
|
_define_property(this, "accessToken", void 0);
|
|
@@ -502,7 +483,6 @@ _ts_decorate([
|
|
|
502
483
|
_define_property(this, "user", void 0);
|
|
503
484
|
_define_property(this, "company", void 0);
|
|
504
485
|
_define_property(this, "branch", void 0);
|
|
505
|
-
_define_property(this, "companyFeatureEnabled", void 0);
|
|
506
486
|
_define_property(this, "requiresEmailVerification", void 0);
|
|
507
487
|
}
|
|
508
488
|
}
|
|
@@ -539,13 +519,6 @@ _ts_decorate([
|
|
|
539
519
|
}),
|
|
540
520
|
_ts_metadata("design:type", typeof BranchInfoDto === "undefined" ? Object : BranchInfoDto)
|
|
541
521
|
], RegistrationResponseDto.prototype, "branch", void 0);
|
|
542
|
-
_ts_decorate([
|
|
543
|
-
ApiPropertyOptional({
|
|
544
|
-
example: true,
|
|
545
|
-
description: 'Whether company feature is enabled'
|
|
546
|
-
}),
|
|
547
|
-
_ts_metadata("design:type", Boolean)
|
|
548
|
-
], RegistrationResponseDto.prototype, "companyFeatureEnabled", void 0);
|
|
549
522
|
_ts_decorate([
|
|
550
523
|
ApiPropertyOptional({
|
|
551
524
|
example: true,
|
|
@@ -684,12 +657,12 @@ _ts_decorate([
|
|
|
684
657
|
], ResetPasswordDto.prototype, "token", void 0);
|
|
685
658
|
_ts_decorate([
|
|
686
659
|
ApiProperty({
|
|
687
|
-
example: '
|
|
688
|
-
description: 'New password (min
|
|
660
|
+
example: 'NewSecureP@ss123',
|
|
661
|
+
description: 'New password (min 8 chars, requires uppercase, lowercase, number, special char)'
|
|
689
662
|
}),
|
|
690
663
|
IsString(),
|
|
691
664
|
IsNotEmpty(),
|
|
692
|
-
|
|
665
|
+
IsStrongPassword(),
|
|
693
666
|
_ts_metadata("design:type", String)
|
|
694
667
|
], ResetPasswordDto.prototype, "newPassword", void 0);
|
|
695
668
|
/** Verify Email Request DTO - Verifies email using token */ export class VerifyEmailDto {
|
package/fesm/dtos/index.js
CHANGED
|
@@ -233,9 +233,23 @@ _ts_decorate([
|
|
|
233
233
|
* User companies response
|
|
234
234
|
*/ export class UserCompaniesResponseDto {
|
|
235
235
|
constructor(){
|
|
236
|
+
_define_property(this, "success", void 0);
|
|
237
|
+
_define_property(this, "message", void 0);
|
|
236
238
|
_define_property(this, "data", void 0);
|
|
237
239
|
}
|
|
238
240
|
}
|
|
241
|
+
_ts_decorate([
|
|
242
|
+
ApiProperty({
|
|
243
|
+
example: true
|
|
244
|
+
}),
|
|
245
|
+
_ts_metadata("design:type", Boolean)
|
|
246
|
+
], UserCompaniesResponseDto.prototype, "success", void 0);
|
|
247
|
+
_ts_decorate([
|
|
248
|
+
ApiProperty({
|
|
249
|
+
example: 'User companies retrieved successfully'
|
|
250
|
+
}),
|
|
251
|
+
_ts_metadata("design:type", String)
|
|
252
|
+
], UserCompaniesResponseDto.prototype, "message", void 0);
|
|
239
253
|
_ts_decorate([
|
|
240
254
|
ApiProperty({
|
|
241
255
|
description: 'List of companies the user has access to',
|
|
@@ -265,9 +279,23 @@ _ts_decorate([
|
|
|
265
279
|
* User branches response
|
|
266
280
|
*/ export class UserBranchesResponseDto {
|
|
267
281
|
constructor(){
|
|
282
|
+
_define_property(this, "success", void 0);
|
|
283
|
+
_define_property(this, "message", void 0);
|
|
268
284
|
_define_property(this, "data", void 0);
|
|
269
285
|
}
|
|
270
286
|
}
|
|
287
|
+
_ts_decorate([
|
|
288
|
+
ApiProperty({
|
|
289
|
+
example: true
|
|
290
|
+
}),
|
|
291
|
+
_ts_metadata("design:type", Boolean)
|
|
292
|
+
], UserBranchesResponseDto.prototype, "success", void 0);
|
|
293
|
+
_ts_decorate([
|
|
294
|
+
ApiProperty({
|
|
295
|
+
example: 'User branches retrieved successfully'
|
|
296
|
+
}),
|
|
297
|
+
_ts_metadata("design:type", String)
|
|
298
|
+
], UserBranchesResponseDto.prototype, "message", void 0);
|
|
271
299
|
_ts_decorate([
|
|
272
300
|
ApiProperty({
|
|
273
301
|
description: 'List of branches the user has access to',
|
package/fesm/dtos/user.dto.js
CHANGED
|
@@ -21,7 +21,8 @@ function _ts_metadata(k, v) {
|
|
|
21
21
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
22
22
|
}
|
|
23
23
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
24
|
-
import { IsBoolean, IsEmail, IsObject, IsOptional, IsString } from 'class-validator';
|
|
24
|
+
import { IsBoolean, IsEmail, IsNotEmpty, IsObject, IsOptional, IsString, IsUUID, MaxLength } from 'class-validator';
|
|
25
|
+
import { IsStrongPassword } from '../validators/password.validator';
|
|
25
26
|
export class CreateUserDto {
|
|
26
27
|
constructor(){
|
|
27
28
|
_define_property(this, "name", void 0);
|
|
@@ -62,10 +63,13 @@ _ts_decorate([
|
|
|
62
63
|
], CreateUserDto.prototype, "email", void 0);
|
|
63
64
|
_ts_decorate([
|
|
64
65
|
ApiProperty({
|
|
65
|
-
example: '
|
|
66
|
-
description: 'Password'
|
|
66
|
+
example: 'SecureP@ss123',
|
|
67
|
+
description: 'Password (must meet complexity requirements)'
|
|
67
68
|
}),
|
|
68
69
|
IsString(),
|
|
70
|
+
IsNotEmpty(),
|
|
71
|
+
IsStrongPassword(),
|
|
72
|
+
MaxLength(128),
|
|
69
73
|
_ts_metadata("design:type", String)
|
|
70
74
|
], CreateUserDto.prototype, "password", void 0);
|
|
71
75
|
_ts_decorate([
|
|
@@ -118,6 +122,7 @@ export class UpdateUserDto {
|
|
|
118
122
|
_define_property(this, "phone", void 0);
|
|
119
123
|
_define_property(this, "email", void 0);
|
|
120
124
|
_define_property(this, "password", void 0);
|
|
125
|
+
_define_property(this, "oldPassword", void 0);
|
|
121
126
|
_define_property(this, "profilePictureId", void 0);
|
|
122
127
|
_define_property(this, "isActive", void 0);
|
|
123
128
|
_define_property(this, "emailVerified", void 0);
|
|
@@ -129,7 +134,7 @@ _ts_decorate([
|
|
|
129
134
|
ApiProperty({
|
|
130
135
|
description: 'User ID'
|
|
131
136
|
}),
|
|
132
|
-
|
|
137
|
+
IsUUID(),
|
|
133
138
|
_ts_metadata("design:type", String)
|
|
134
139
|
], UpdateUserDto.prototype, "id", void 0);
|
|
135
140
|
_ts_decorate([
|
|
@@ -161,12 +166,22 @@ _ts_decorate([
|
|
|
161
166
|
], UpdateUserDto.prototype, "email", void 0);
|
|
162
167
|
_ts_decorate([
|
|
163
168
|
ApiPropertyOptional({
|
|
164
|
-
description: 'New password'
|
|
169
|
+
description: 'New password (must meet complexity requirements)'
|
|
165
170
|
}),
|
|
166
171
|
IsOptional(),
|
|
167
172
|
IsString(),
|
|
173
|
+
IsStrongPassword(),
|
|
174
|
+
MaxLength(128),
|
|
168
175
|
_ts_metadata("design:type", String)
|
|
169
176
|
], UpdateUserDto.prototype, "password", void 0);
|
|
177
|
+
_ts_decorate([
|
|
178
|
+
ApiPropertyOptional({
|
|
179
|
+
description: 'Current password (required when changing password on own profile)'
|
|
180
|
+
}),
|
|
181
|
+
IsOptional(),
|
|
182
|
+
IsString(),
|
|
183
|
+
_ts_metadata("design:type", String)
|
|
184
|
+
], UpdateUserDto.prototype, "oldPassword", void 0);
|
|
170
185
|
_ts_decorate([
|
|
171
186
|
ApiPropertyOptional({
|
|
172
187
|
description: 'Profile picture file ID'
|
|
@@ -27,12 +27,6 @@ import { Company } from './company.entity';
|
|
|
27
27
|
import { User } from './user.entity';
|
|
28
28
|
export { PermissionType };
|
|
29
29
|
export class UserCompanyPermission {
|
|
30
|
-
/* ---------- Helpers ---------- */ isCompanyPermission() {
|
|
31
|
-
return this.permissionType === PermissionType.COMPANY;
|
|
32
|
-
}
|
|
33
|
-
isBranchPermission() {
|
|
34
|
-
return this.permissionType === PermissionType.BRANCH;
|
|
35
|
-
}
|
|
36
30
|
constructor(){
|
|
37
31
|
_define_property(this, "id", void 0);
|
|
38
32
|
/* ---------- Ownership ---------- */ _define_property(this, "userId", void 0);
|
|
@@ -25,10 +25,11 @@ function _ts_param(paramIndex, decorator) {
|
|
|
25
25
|
decorator(target, key, paramIndex);
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
+
import { buildCookieOptions, isBrowserRequest } from '@flusys/nestjs-shared/utils';
|
|
28
29
|
import { Injectable, Optional } from '@nestjs/common';
|
|
29
30
|
import { map } from 'rxjs';
|
|
31
|
+
import { DEFAULT_COOKIE_NAME } from '../config';
|
|
30
32
|
import { AuthConfigService } from '../services/auth-config.service';
|
|
31
|
-
const DEFAULT_COOKIE_NAME = 'fsn_refresh_token';
|
|
32
33
|
export class ClearToken {
|
|
33
34
|
get cookieName() {
|
|
34
35
|
return this.authConfig?.getRefreshTokenCookieName() ?? DEFAULT_COOKIE_NAME;
|
|
@@ -37,30 +38,16 @@ export class ClearToken {
|
|
|
37
38
|
const req = context.switchToHttp().getRequest();
|
|
38
39
|
const res = context.switchToHttp().getResponse();
|
|
39
40
|
return next.handle().pipe(map((value)=>{
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
if (isBrowserRequest(req)) {
|
|
42
|
+
res.clearCookie(this.cookieName, {
|
|
43
|
+
httpOnly: true,
|
|
44
|
+
path: '/',
|
|
45
|
+
...buildCookieOptions(req)
|
|
46
|
+
});
|
|
47
|
+
}
|
|
42
48
|
return value;
|
|
43
49
|
}));
|
|
44
50
|
}
|
|
45
|
-
/**
|
|
46
|
-
* Clear refresh token cookie
|
|
47
|
-
*/ clearRefreshTokenCookie(req, res) {
|
|
48
|
-
const origin = req.headers.origin || '';
|
|
49
|
-
const hostname = req.hostname || '';
|
|
50
|
-
const isHttps = origin.startsWith('https://') || req.secure;
|
|
51
|
-
let cookieDomain = hostname;
|
|
52
|
-
const domainParts = hostname.split('.');
|
|
53
|
-
if (domainParts.length > 2) {
|
|
54
|
-
cookieDomain = '.' + domainParts.slice(-2).join('.');
|
|
55
|
-
}
|
|
56
|
-
res.clearCookie(this.cookieName, {
|
|
57
|
-
httpOnly: true,
|
|
58
|
-
secure: isHttps,
|
|
59
|
-
sameSite: isHttps ? 'strict' : 'lax',
|
|
60
|
-
domain: cookieDomain,
|
|
61
|
-
path: '/'
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
51
|
constructor(authConfig){
|
|
65
52
|
_define_property(this, "authConfig", void 0);
|
|
66
53
|
this.authConfig = authConfig;
|
|
@@ -25,84 +25,43 @@ function _ts_param(paramIndex, decorator) {
|
|
|
25
25
|
decorator(target, key, paramIndex);
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
-
import {
|
|
28
|
+
import { buildCookieOptions, isBrowserRequest, parseDurationToMs } from '@flusys/nestjs-shared/utils';
|
|
29
|
+
import { Inject, Injectable, Optional, PayloadTooLargeException } from '@nestjs/common';
|
|
29
30
|
import { Buffer } from 'buffer';
|
|
30
31
|
import { catchError, map, throwError } from 'rxjs';
|
|
32
|
+
import { DEFAULT_COOKIE_NAME, MAX_COOKIE_BYTES } from '../config';
|
|
31
33
|
import { AuthConfigService } from '../services/auth-config.service';
|
|
32
|
-
const DEFAULT_COOKIE_NAME = 'fsn_refresh_token';
|
|
33
|
-
const MAX_COOKIE_BYTES = 3500;
|
|
34
34
|
export class SetToken {
|
|
35
35
|
get cookieName() {
|
|
36
36
|
return this.authConfig?.getRefreshTokenCookieName() ?? DEFAULT_COOKIE_NAME;
|
|
37
37
|
}
|
|
38
|
+
get maxAgeMs() {
|
|
39
|
+
return parseDurationToMs(this.authConfig?.getRefreshTokenExpiration() ?? '7d');
|
|
40
|
+
}
|
|
38
41
|
intercept(context, next) {
|
|
39
42
|
const req = context.switchToHttp().getRequest();
|
|
40
43
|
const res = context.switchToHttp().getResponse();
|
|
41
44
|
return next.handle().pipe(map((value)=>{
|
|
42
|
-
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
const isBrowser = this.isBrowserRequest(req);
|
|
47
|
-
const tokenValue = value.refreshToken;
|
|
48
|
-
if (isBrowser) {
|
|
49
|
-
// Browser: Set cookie and remove from response body
|
|
50
|
-
this.setRefreshTokenCookie(req, res, tokenValue);
|
|
51
|
-
// Return response without refreshToken for browsers
|
|
45
|
+
if (!value?.refreshToken) return value;
|
|
46
|
+
if (isBrowserRequest(req)) {
|
|
47
|
+
this.setRefreshTokenCookie(req, res, value.refreshToken);
|
|
52
48
|
const { refreshToken, ...responseWithoutToken } = value;
|
|
53
49
|
return responseWithoutToken;
|
|
54
50
|
}
|
|
55
|
-
// API/Mobile: Keep refreshToken in response body
|
|
56
51
|
return value;
|
|
57
52
|
}), catchError((err)=>throwError(()=>err)));
|
|
58
53
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
* Checks Accept header and custom X-Client-Type header
|
|
62
|
-
*/ isBrowserRequest(req) {
|
|
63
|
-
// Check custom header first (allows explicit client identification)
|
|
64
|
-
const clientType = req.headers['x-client-type'];
|
|
65
|
-
if (clientType) {
|
|
66
|
-
return clientType.toLowerCase() === 'browser' || clientType.toLowerCase() === 'web';
|
|
67
|
-
}
|
|
68
|
-
// Check Accept header - browsers typically include text/html
|
|
69
|
-
const accept = req.headers['accept'] || '';
|
|
70
|
-
if (accept.includes('text/html')) {
|
|
71
|
-
return true;
|
|
72
|
-
}
|
|
73
|
-
// Check User-Agent for common browsers
|
|
74
|
-
const userAgent = req.headers['user-agent'] || '';
|
|
75
|
-
const browserPatterns = /mozilla|chrome|safari|firefox|edge|opera|msie/i;
|
|
76
|
-
if (browserPatterns.test(userAgent) && !userAgent.includes('Postman')) {
|
|
77
|
-
return true;
|
|
78
|
-
}
|
|
79
|
-
return false;
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Set refresh token in HTTP-only cookie
|
|
83
|
-
*/ setRefreshTokenCookie(req, res, tokenValue) {
|
|
84
|
-
const origin = req.headers.origin || '';
|
|
85
|
-
const hostname = req.hostname || '';
|
|
86
|
-
const isHttps = origin.startsWith('https://') || req.secure;
|
|
87
|
-
let cookieDomain = hostname;
|
|
88
|
-
const domainParts = hostname.split('.');
|
|
89
|
-
if (domainParts.length > 2) {
|
|
90
|
-
cookieDomain = '.' + domainParts.slice(-2).join('.');
|
|
91
|
-
}
|
|
92
|
-
// Check cookie size
|
|
93
|
-
const rawCookieString = `${this.cookieName}=${tokenValue}; HttpOnly; Domain=${cookieDomain}`;
|
|
54
|
+
setRefreshTokenCookie(req, res, tokenValue) {
|
|
55
|
+
const rawCookieString = `${this.cookieName}=${tokenValue}; HttpOnly`;
|
|
94
56
|
const estimatedBytes = Buffer.byteLength(rawCookieString, 'utf8');
|
|
95
57
|
if (estimatedBytes > MAX_COOKIE_BYTES) {
|
|
96
58
|
throw new PayloadTooLargeException(`Refresh token too large (≈${estimatedBytes} bytes)`);
|
|
97
59
|
}
|
|
98
|
-
// Set HTTP-only secure cookie
|
|
99
60
|
res.cookie(this.cookieName, tokenValue, {
|
|
100
61
|
httpOnly: true,
|
|
101
|
-
secure: isHttps,
|
|
102
|
-
sameSite: isHttps ? 'strict' : 'lax',
|
|
103
|
-
domain: cookieDomain,
|
|
104
62
|
path: '/',
|
|
105
|
-
maxAge:
|
|
63
|
+
maxAge: this.maxAgeMs,
|
|
64
|
+
...buildCookieOptions(req)
|
|
106
65
|
});
|
|
107
66
|
}
|
|
108
67
|
constructor(authConfig){
|
|
@@ -113,6 +72,7 @@ export class SetToken {
|
|
|
113
72
|
SetToken = _ts_decorate([
|
|
114
73
|
Injectable(),
|
|
115
74
|
_ts_param(0, Optional()),
|
|
75
|
+
_ts_param(0, Inject(AuthConfigService)),
|
|
116
76
|
_ts_metadata("design:type", Function),
|
|
117
77
|
_ts_metadata("design:paramtypes", [
|
|
118
78
|
typeof AuthConfigService === "undefined" ? Object : AuthConfigService
|