@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.
Files changed (86) hide show
  1. package/README.md +590 -473
  2. package/cjs/config/auth.constants.js +8 -0
  3. package/cjs/controllers/authentication.controller.js +218 -39
  4. package/cjs/controllers/branch.controller.js +44 -1
  5. package/cjs/controllers/company-selection.controller.js +27 -20
  6. package/cjs/controllers/company.controller.js +44 -1
  7. package/cjs/controllers/user-permission.controller.js +82 -154
  8. package/cjs/controllers/user.controller.js +68 -15
  9. package/cjs/docs/auth-swagger.config.js +14 -7
  10. package/cjs/dtos/authentication.dto.js +16 -43
  11. package/cjs/dtos/index.js +0 -1
  12. package/cjs/dtos/user-permission.dto.js +28 -0
  13. package/cjs/dtos/user.dto.js +19 -4
  14. package/cjs/entities/user-company-permission.entity.js +0 -6
  15. package/cjs/interceptors/clear-token.interceptor.js +10 -23
  16. package/cjs/interceptors/set-token.interceptor.js +15 -55
  17. package/cjs/interfaces/auth-email-provider.interface.js +1 -1
  18. package/cjs/modules/auth.module.js +63 -147
  19. package/cjs/services/auth-config.service.js +22 -28
  20. package/cjs/services/auth-datasource.provider.js +9 -21
  21. package/cjs/services/auth-email.service.js +89 -85
  22. package/cjs/services/authentication.service.js +225 -396
  23. package/cjs/services/branch.service.js +9 -4
  24. package/cjs/services/company-selection-session.service.js +1 -1
  25. package/cjs/services/user-permission.service.js +43 -73
  26. package/cjs/services/user.service.js +78 -64
  27. package/cjs/strategies/jwt.strategy.js +44 -44
  28. package/cjs/validators/password.validator.js +45 -0
  29. package/config/auth.constants.d.ts +2 -0
  30. package/controllers/authentication.controller.d.ts +5 -3
  31. package/controllers/company-selection.controller.d.ts +6 -2
  32. package/controllers/user-permission.controller.d.ts +5 -4
  33. package/controllers/user.controller.d.ts +6 -0
  34. package/dtos/authentication.dto.d.ts +0 -4
  35. package/dtos/company-branch.dto.d.ts +0 -1
  36. package/dtos/company.dto.d.ts +0 -1
  37. package/dtos/index.d.ts +0 -1
  38. package/dtos/user-permission.dto.d.ts +4 -0
  39. package/dtos/user.dto.d.ts +1 -3
  40. package/entities/index.d.ts +3 -3
  41. package/entities/user-company-permission.entity.d.ts +0 -2
  42. package/fesm/config/auth.constants.js +6 -13
  43. package/fesm/controllers/authentication.controller.js +221 -42
  44. package/fesm/controllers/branch.controller.js +45 -2
  45. package/fesm/controllers/company-selection.controller.js +28 -21
  46. package/fesm/controllers/company.controller.js +45 -2
  47. package/fesm/controllers/user-permission.controller.js +84 -156
  48. package/fesm/controllers/user.controller.js +71 -18
  49. package/fesm/docs/auth-swagger.config.js +14 -7
  50. package/fesm/dtos/authentication.dto.js +17 -44
  51. package/fesm/dtos/index.js +0 -1
  52. package/fesm/dtos/user-permission.dto.js +28 -0
  53. package/fesm/dtos/user.dto.js +20 -5
  54. package/fesm/entities/user-company-permission.entity.js +0 -6
  55. package/fesm/interceptors/clear-token.interceptor.js +9 -22
  56. package/fesm/interceptors/set-token.interceptor.js +14 -54
  57. package/fesm/interfaces/auth-email-provider.interface.js +1 -1
  58. package/fesm/modules/auth.module.js +64 -148
  59. package/fesm/services/auth-config.service.js +23 -29
  60. package/fesm/services/auth-datasource.provider.js +10 -22
  61. package/fesm/services/auth-email.service.js +90 -86
  62. package/fesm/services/authentication.service.js +225 -396
  63. package/fesm/services/branch.service.js +9 -4
  64. package/fesm/services/company-selection-session.service.js +1 -1
  65. package/fesm/services/user-permission.service.js +44 -74
  66. package/fesm/services/user.service.js +79 -65
  67. package/fesm/strategies/jwt.strategy.js +44 -44
  68. package/fesm/validators/password.validator.js +48 -0
  69. package/interceptors/clear-token.interceptor.d.ts +0 -1
  70. package/interceptors/set-token.interceptor.d.ts +1 -1
  71. package/interfaces/authentication.interface.d.ts +0 -4
  72. package/interfaces/user-enricher.interface.d.ts +1 -4
  73. package/modules/auth.module.d.ts +6 -3
  74. package/package.json +4 -4
  75. package/services/auth-config.service.d.ts +6 -8
  76. package/services/auth-datasource.provider.d.ts +2 -4
  77. package/services/auth-email.service.d.ts +8 -4
  78. package/services/authentication.service.d.ts +15 -16
  79. package/services/company-selection-session.service.d.ts +1 -1
  80. package/services/user-permission.service.d.ts +6 -9
  81. package/services/user.service.d.ts +3 -0
  82. package/strategies/jwt.strategy.d.ts +4 -1
  83. package/validators/password.validator.d.ts +10 -0
  84. package/cjs/dtos/user-company-permission.dto.js +0 -110
  85. package/dtos/user-company-permission.dto.d.ts +0 -10
  86. 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, MinLength, ValidateIf } from 'class-validator';
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: '123456',
44
- description: 'User password (min 6 chars)'
44
+ example: 'SecureP@ss1',
45
+ description: 'User password'
45
46
  }),
46
47
  IsString(),
47
48
  IsNotEmpty(),
48
- MinLength(6),
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: 'strongPassword123',
88
- description: 'Password (min 6 chars)'
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
- MinLength(6),
93
- MaxLength(60),
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: 'newPassword456',
188
- description: 'New password (min 6 chars)'
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
- MinLength(6),
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: 'newSecurePassword123',
688
- description: 'New password (min 6 chars)'
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
- MinLength(6),
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 {
@@ -1,6 +1,5 @@
1
1
  export * from './authentication.dto';
2
2
  export * from './company-branch.dto';
3
3
  export * from './company.dto';
4
- export * from './user-company-permission.dto';
5
4
  export * from './user.dto';
6
5
  export * from './user-permission.dto';
@@ -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',
@@ -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: 'password123',
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
- IsString(),
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
- // Clear the refresh token cookie
41
- this.clearRefreshTokenCookie(req, res);
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 { Injectable, Optional, PayloadTooLargeException } from '@nestjs/common';
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
- // Skip if no refreshToken in response
43
- if (!value || !value.refreshToken) {
44
- return value;
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
- * Detect if request is from a browser
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: 7 * 24 * 60 * 60 * 1000
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
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Auth Email Provider Interface
2
+ * Auth Email Provider Interface
3
3
  *
4
4
  * This interface defines the contract for email sending in the auth module.
5
5
  * It allows auth to work independently of the email module.