@flusys/nestjs-auth 1.0.0-beta → 1.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 (106) hide show
  1. package/README.md +590 -473
  2. package/cjs/config/auth.constants.js +12 -0
  3. package/cjs/controllers/authentication.controller.js +365 -24
  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 +211 -7
  9. package/cjs/docs/auth-swagger.config.js +14 -7
  10. package/cjs/dtos/authentication.dto.js +114 -38
  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/index.js +5 -1
  15. package/cjs/entities/pending-registration.entity.js +125 -0
  16. package/cjs/entities/user-company-permission.entity.js +3 -9
  17. package/cjs/entities/user-token.entity.js +129 -0
  18. package/cjs/interceptors/clear-token.interceptor.js +10 -23
  19. package/cjs/interceptors/set-token.interceptor.js +15 -55
  20. package/cjs/interfaces/auth-email-provider.interface.js +45 -0
  21. package/cjs/interfaces/index.js +2 -0
  22. package/cjs/interfaces/user-enricher.interface.js +4 -0
  23. package/cjs/modules/auth.module.js +65 -143
  24. package/cjs/services/auth-config.service.js +25 -27
  25. package/cjs/services/auth-datasource.provider.js +13 -23
  26. package/cjs/services/auth-email.service.js +456 -0
  27. package/cjs/services/authentication.service.js +297 -349
  28. package/cjs/services/branch.service.js +9 -4
  29. package/cjs/services/company-selection-session.service.js +1 -1
  30. package/cjs/services/index.js +1 -0
  31. package/cjs/services/user-permission.service.js +43 -73
  32. package/cjs/services/user.service.js +184 -34
  33. package/cjs/strategies/jwt.strategy.js +44 -44
  34. package/cjs/validators/password.validator.js +45 -0
  35. package/config/auth.constants.d.ts +3 -0
  36. package/controllers/authentication.controller.d.ts +16 -10
  37. package/controllers/company-selection.controller.d.ts +6 -2
  38. package/controllers/user-permission.controller.d.ts +5 -4
  39. package/controllers/user.controller.d.ts +33 -0
  40. package/dtos/authentication.dto.d.ts +20 -6
  41. package/dtos/company-branch.dto.d.ts +0 -1
  42. package/dtos/company.dto.d.ts +0 -1
  43. package/dtos/index.d.ts +0 -1
  44. package/dtos/user-permission.dto.d.ts +4 -0
  45. package/dtos/user.dto.d.ts +1 -2
  46. package/entities/index.d.ts +7 -3
  47. package/entities/pending-registration.entity.d.ts +17 -0
  48. package/entities/user-company-permission.entity.d.ts +1 -3
  49. package/entities/user-token.entity.d.ts +15 -0
  50. package/fesm/config/auth.constants.js +6 -2
  51. package/fesm/controllers/authentication.controller.js +368 -27
  52. package/fesm/controllers/branch.controller.js +45 -2
  53. package/fesm/controllers/company-selection.controller.js +28 -21
  54. package/fesm/controllers/company.controller.js +45 -2
  55. package/fesm/controllers/user-permission.controller.js +84 -156
  56. package/fesm/controllers/user.controller.js +214 -10
  57. package/fesm/docs/auth-swagger.config.js +14 -7
  58. package/fesm/dtos/authentication.dto.js +101 -39
  59. package/fesm/dtos/index.js +0 -1
  60. package/fesm/dtos/user-permission.dto.js +28 -0
  61. package/fesm/dtos/user.dto.js +20 -5
  62. package/fesm/entities/index.js +7 -1
  63. package/fesm/entities/pending-registration.entity.js +115 -0
  64. package/fesm/entities/user-company-permission.entity.js +3 -9
  65. package/fesm/entities/user-token.entity.js +113 -0
  66. package/fesm/interceptors/clear-token.interceptor.js +9 -22
  67. package/fesm/interceptors/set-token.interceptor.js +14 -54
  68. package/fesm/interfaces/auth-email-provider.interface.js +38 -0
  69. package/fesm/interfaces/authentication.interface.js +1 -1
  70. package/fesm/interfaces/index.js +2 -0
  71. package/fesm/interfaces/user-enricher.interface.js +26 -0
  72. package/fesm/modules/auth.module.js +67 -145
  73. package/fesm/services/auth-config.service.js +26 -28
  74. package/fesm/services/auth-datasource.provider.js +14 -24
  75. package/fesm/services/auth-email.service.js +405 -0
  76. package/fesm/services/authentication.service.js +297 -349
  77. package/fesm/services/branch.service.js +9 -4
  78. package/fesm/services/company-selection-session.service.js +1 -1
  79. package/fesm/services/index.js +1 -0
  80. package/fesm/services/user-permission.service.js +44 -74
  81. package/fesm/services/user.service.js +186 -36
  82. package/fesm/strategies/jwt.strategy.js +44 -44
  83. package/fesm/validators/password.validator.js +48 -0
  84. package/interceptors/clear-token.interceptor.d.ts +0 -1
  85. package/interceptors/set-token.interceptor.d.ts +1 -1
  86. package/interfaces/auth-email-provider.interface.d.ts +6 -0
  87. package/interfaces/auth-module-options.interface.d.ts +4 -1
  88. package/interfaces/authentication.interface.d.ts +13 -3
  89. package/interfaces/index.d.ts +2 -0
  90. package/interfaces/user-enricher.interface.d.ts +64 -0
  91. package/interfaces/user.interface.d.ts +1 -0
  92. package/modules/auth.module.d.ts +6 -3
  93. package/package.json +4 -4
  94. package/services/auth-config.service.d.ts +7 -8
  95. package/services/auth-datasource.provider.d.ts +2 -4
  96. package/services/auth-email.service.d.ts +54 -0
  97. package/services/authentication.service.d.ts +27 -16
  98. package/services/company-selection-session.service.d.ts +1 -1
  99. package/services/index.d.ts +1 -0
  100. package/services/user-permission.service.d.ts +6 -9
  101. package/services/user.service.d.ts +19 -3
  102. package/strategies/jwt.strategy.d.ts +4 -1
  103. package/validators/password.validator.d.ts +10 -0
  104. package/cjs/dtos/user-company-permission.dto.js +0 -110
  105. package/dtos/user-company-permission.dto.d.ts +0 -10
  106. package/fesm/dtos/user-company-permission.dto.js +0 -98
@@ -0,0 +1,113 @@
1
+ function _define_property(obj, key, value) {
2
+ if (key in obj) {
3
+ Object.defineProperty(obj, key, {
4
+ value: value,
5
+ enumerable: true,
6
+ configurable: true,
7
+ writable: true
8
+ });
9
+ } else {
10
+ obj[key] = value;
11
+ }
12
+ return obj;
13
+ }
14
+ function _ts_decorate(decorators, target, key, desc) {
15
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
16
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
17
+ 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;
18
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
19
+ }
20
+ function _ts_metadata(k, v) {
21
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
22
+ }
23
+ import { Column, CreateDateColumn, Entity, Index, PrimaryGeneratedColumn } from 'typeorm';
24
+ /**
25
+ * Token types for various user actions
26
+ */ export var TokenType = /*#__PURE__*/ function(TokenType) {
27
+ TokenType["PASSWORD_RESET"] = "password_reset";
28
+ TokenType["EMAIL_VERIFICATION"] = "email_verification";
29
+ TokenType["EMAIL_CHANGE"] = "email_change";
30
+ return TokenType;
31
+ }({});
32
+ export class UserToken {
33
+ constructor(){
34
+ _define_property(this, "id", void 0);
35
+ _define_property(this, "userId", void 0);
36
+ _define_property(this, "type", void 0);
37
+ _define_property(this, "token", void 0);
38
+ _define_property(this, "expiresAt", void 0);
39
+ _define_property(this, "usedAt", void 0);
40
+ _define_property(this, "metadata", void 0);
41
+ _define_property(this, "createdAt", void 0);
42
+ }
43
+ }
44
+ _ts_decorate([
45
+ PrimaryGeneratedColumn('uuid'),
46
+ _ts_metadata("design:type", String)
47
+ ], UserToken.prototype, "id", void 0);
48
+ _ts_decorate([
49
+ Column({
50
+ type: 'uuid',
51
+ name: 'user_id'
52
+ }),
53
+ _ts_metadata("design:type", String)
54
+ ], UserToken.prototype, "userId", void 0);
55
+ _ts_decorate([
56
+ Column({
57
+ type: 'varchar',
58
+ length: 32
59
+ }),
60
+ _ts_metadata("design:type", String)
61
+ ], UserToken.prototype, "type", void 0);
62
+ _ts_decorate([
63
+ Column({
64
+ type: 'varchar',
65
+ length: 64
66
+ }),
67
+ _ts_metadata("design:type", String)
68
+ ], UserToken.prototype, "token", void 0);
69
+ _ts_decorate([
70
+ Column({
71
+ type: 'timestamp',
72
+ name: 'expires_at'
73
+ }),
74
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
75
+ ], UserToken.prototype, "expiresAt", void 0);
76
+ _ts_decorate([
77
+ Column({
78
+ type: 'timestamp',
79
+ name: 'used_at',
80
+ nullable: true
81
+ }),
82
+ _ts_metadata("design:type", Object)
83
+ ], UserToken.prototype, "usedAt", void 0);
84
+ _ts_decorate([
85
+ Column({
86
+ type: 'varchar',
87
+ length: 255,
88
+ nullable: true
89
+ }),
90
+ _ts_metadata("design:type", Object)
91
+ ], UserToken.prototype, "metadata", void 0);
92
+ _ts_decorate([
93
+ CreateDateColumn({
94
+ type: 'timestamp',
95
+ name: 'created_at'
96
+ }),
97
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
98
+ ], UserToken.prototype, "createdAt", void 0);
99
+ UserToken = _ts_decorate([
100
+ Entity('user_tokens'),
101
+ Index('IDX_user_token_token', [
102
+ 'token'
103
+ ], {
104
+ unique: true
105
+ }),
106
+ Index('IDX_user_token_user_type', [
107
+ 'userId',
108
+ 'type'
109
+ ]),
110
+ Index('IDX_user_token_expires', [
111
+ 'expiresAt'
112
+ ])
113
+ ], UserToken);
@@ -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
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Auth Email Provider Interface
3
+ *
4
+ * This interface defines the contract for email sending in the auth module.
5
+ * It allows auth to work independently of the email module.
6
+ *
7
+ * To use email features in auth:
8
+ * 1. Provide AUTH_EMAIL_PROVIDER in your app module
9
+ * 2. Implement IAuthEmailProvider using EmailSendService from nestjs-email
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * // In app.module.ts or a provider file
14
+ * {
15
+ * provide: AUTH_EMAIL_PROVIDER,
16
+ * useFactory: (emailSendService: EmailSendService) => ({
17
+ * sendPasswordResetEmail: async (email, token, resetUrl) => {
18
+ * await emailSendService.sendTemplateEmail({
19
+ * templateSlug: 'password-reset',
20
+ * to: email,
21
+ * variables: { resetUrl, token },
22
+ * });
23
+ * },
24
+ * sendVerificationEmail: async (email, token, verifyUrl) => {
25
+ * await emailSendService.sendTemplateEmail({
26
+ * templateSlug: 'email-verification',
27
+ * to: email,
28
+ * variables: { verifyUrl, token },
29
+ * });
30
+ * },
31
+ * }),
32
+ * inject: [EmailSendService],
33
+ * }
34
+ * ```
35
+ */ /**
36
+ * Injection token for auth email provider
37
+ * Use this to provide the email implementation
38
+ */ export const AUTH_EMAIL_PROVIDER = 'AUTH_EMAIL_PROVIDER';
@@ -1 +1 @@
1
- /** Login Response when company/branch selection is required */ export { };
1
+ /** Login Response when email verification is required */ export { };
@@ -1,5 +1,7 @@
1
1
  export * from './authentication.interface';
2
+ export * from './auth-email-provider.interface';
2
3
  export * from './company-branch.interface';
3
4
  export * from './company.interface';
4
5
  export * from './user.interface';
6
+ export * from './user-enricher.interface';
5
7
  export * from './auth-module-options.interface';
@@ -0,0 +1,26 @@
1
+ /**
2
+ * User Enricher Provider Interface
3
+ *
4
+ * Allows consuming applications to extend user list and profile
5
+ * without modifying the base nestjs-auth package.
6
+ *
7
+ * This follows the Provider Interface Pattern:
8
+ * - nestjs-auth defines the interface (this file)
9
+ * - Consuming apps implement the interface
10
+ * - nestjs-auth optionally injects the implementation
11
+ *
12
+ * @example
13
+ * // In your app module
14
+ * providers: [
15
+ * { provide: USER_ENRICHER, useClass: MyUserEnricher },
16
+ * ]
17
+ *
18
+ * // Your enricher implementation
19
+ * @Injectable()
20
+ * export class MyUserEnricher implements IUserEnricher {
21
+ * async enrichListQuery(query, user) {
22
+ * query.leftJoinAndSelect('user.department', 'department');
23
+ * return { extraFields: ['department.name'] };
24
+ * }
25
+ * }
26
+ */ export { };
@@ -5,95 +5,57 @@ function _ts_decorate(decorators, target, key, desc) {
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  }
7
7
  import { CacheModule, UtilsModule } from '@flusys/nestjs-shared/modules';
8
- import { Module, Scope } from '@nestjs/common';
8
+ import { Module } from '@nestjs/common';
9
9
  import { JwtModule } from '@nestjs/jwt';
10
10
  import { PassportModule } from '@nestjs/passport';
11
- import { getRepositoryToken } from '@nestjs/typeorm';
12
- import { AuthConfigService } from '../services/auth-config.service';
13
- import { AuthDataSourceProvider } from '../services/auth-datasource.provider';
11
+ import { JwtAuthGuard } from '@flusys/nestjs-shared/guards';
14
12
  import { AUTH_MODULE_OPTIONS } from '../config/auth.constants';
15
13
  import { AuthenticationController, BranchController, CompanyController, CompanySelectionController, UserController, UserPermissionController } from '../controllers';
16
14
  import { Company, CompanyBranch, User, UserCompanyPermission } from '../entities';
17
- import { JwtAuthGuard } from '@flusys/nestjs-shared/guards';
18
15
  import { ClearToken } from '../interceptors/clear-token.interceptor';
19
16
  import { SetToken } from '../interceptors/set-token.interceptor';
20
- import { AuthenticationService, BranchService, CompanySelectionSessionService, CompanyService, UserPermissionService, UserService } from '../services';
17
+ import { AuthEmailService, AuthenticationService, BranchService, CompanySelectionSessionService, CompanyService, UserPermissionService, UserService } from '../services';
18
+ import { AuthConfigService } from '../services/auth-config.service';
19
+ import { AuthDataSourceProvider } from '../services/auth-datasource.provider';
21
20
  import { JwtStrategy } from '../strategies/jwt.strategy';
22
21
  export class AuthModule {
23
22
  static forRoot(options) {
24
- return this.buildDynamicModule(options);
23
+ const optionsProvider = {
24
+ provide: AUTH_MODULE_OPTIONS,
25
+ useValue: options
26
+ };
27
+ return this.buildModule(options, [
28
+ optionsProvider,
29
+ ...options.providers ?? []
30
+ ]);
25
31
  }
26
- static forRootAsync(asyncOptions) {
27
- const { global = false, includeController = true, bootstrapAppConfig: { enableCompanyFeature } = {}, imports: externalImports = [] } = asyncOptions;
28
- const asyncProviders = this.createAsyncProviders(asyncOptions);
29
- // Build imports
30
- const imports = [
31
- ...externalImports,
32
- PassportModule.register({
33
- defaultStrategy: 'jwt'
34
- }),
35
- JwtModule.register({}),
36
- CacheModule,
37
- UtilsModule
38
- ];
39
- // Build controllers based on configuration
40
- const controllers = [];
41
- if (includeController) {
42
- // Core authentication controller (always included)
43
- controllers.push(AuthenticationController);
44
- // User controller (always included)
45
- controllers.push(UserController);
46
- // Company feature controllers (conditional)
47
- if (enableCompanyFeature) {
48
- controllers.push(CompanySelectionController, CompanyController, BranchController, UserPermissionController);
49
- }
50
- }
51
- // Build providers
52
- const providers = [
32
+ static forRootAsync(options) {
33
+ const asyncProviders = this.createAsyncProviders(options);
34
+ return this.buildModule(options, [
53
35
  ...asyncProviders,
54
- // Config & Infrastructure
55
- AuthConfigService,
56
- AuthDataSourceProvider,
57
- // Auth core
58
- JwtStrategy,
59
- JwtAuthGuard,
60
- AuthenticationService,
61
- // Interceptors
62
- SetToken,
63
- ClearToken,
64
- // Core services (always included)
65
- UserService
66
- ];
67
- // Company feature services (conditional)
68
- if (enableCompanyFeature) {
69
- providers.push(CompanySelectionSessionService, CompanyService, BranchService, UserPermissionService);
70
- }
71
- // Build exports
72
- const exports = [
73
- PassportModule,
74
- AUTH_MODULE_OPTIONS,
75
- AuthConfigService,
76
- AuthDataSourceProvider,
77
- JwtStrategy,
78
- JwtAuthGuard,
79
- AuthenticationService,
80
- UserService
81
- ];
82
- if (enableCompanyFeature) {
83
- exports.push(CompanySelectionSessionService, CompanyService, BranchService, UserPermissionService);
84
- }
36
+ ...options.providers ?? []
37
+ ], options.imports);
38
+ }
39
+ /** Build the dynamic module with shared logic */ static buildModule(options, providers, externalImports = []) {
40
+ const { global = false, includeController = true, bootstrapAppConfig } = options;
41
+ const enableCompanyFeature = bootstrapAppConfig?.enableCompanyFeature ?? false;
85
42
  return {
86
43
  module: AuthModule,
87
44
  global,
88
- imports,
89
- controllers,
90
- providers,
91
- exports
45
+ imports: [
46
+ ...externalImports,
47
+ ...this.buildImports()
48
+ ],
49
+ controllers: this.buildControllers(includeController, enableCompanyFeature),
50
+ providers: [
51
+ ...providers,
52
+ ...this.buildProviders(enableCompanyFeature)
53
+ ],
54
+ exports: this.buildExports(enableCompanyFeature)
92
55
  };
93
56
  }
94
- static buildDynamicModule(options) {
95
- const { bootstrapAppConfig: { enableCompanyFeature } = {}, includeController = true, global = false } = options;
96
- const imports = [
57
+ /** Common module imports */ static buildImports() {
58
+ return [
97
59
  PassportModule.register({
98
60
  defaultStrategy: 'jwt'
99
61
  }),
@@ -101,40 +63,36 @@ export class AuthModule {
101
63
  CacheModule,
102
64
  UtilsModule
103
65
  ];
104
- // Build controllers based on configuration
105
- const controllers = [];
106
- if (includeController) {
107
- // Core authentication controller (always included)
108
- controllers.push(AuthenticationController);
109
- // User controller (always included)
110
- controllers.push(UserController);
111
- // Company feature controllers (conditional)
112
- if (enableCompanyFeature) {
113
- controllers.push(CompanySelectionController, CompanyController, BranchController, UserPermissionController);
114
- }
66
+ }
67
+ /** Build controllers based on feature flags */ static buildControllers(includeController, enableCompanyFeature) {
68
+ if (!includeController) return [];
69
+ const controllers = [
70
+ AuthenticationController,
71
+ UserController
72
+ ];
73
+ if (enableCompanyFeature) {
74
+ controllers.push(CompanySelectionController, CompanyController, BranchController, UserPermissionController);
115
75
  }
76
+ return controllers;
77
+ }
78
+ /** Core providers (without AUTH_MODULE_OPTIONS - that's added separately) */ static buildProviders(enableCompanyFeature) {
116
79
  const providers = [
117
- {
118
- provide: AUTH_MODULE_OPTIONS,
119
- useValue: options
120
- },
121
- // Config & Infrastructure
122
80
  AuthConfigService,
123
81
  AuthDataSourceProvider,
124
- // Auth core
125
82
  JwtStrategy,
126
83
  JwtAuthGuard,
127
84
  AuthenticationService,
128
- // Interceptors
85
+ AuthEmailService,
129
86
  SetToken,
130
87
  ClearToken,
131
- // Core services (always included)
132
88
  UserService
133
89
  ];
134
- // Company feature services (conditional)
135
90
  if (enableCompanyFeature) {
136
91
  providers.push(CompanySelectionSessionService, CompanyService, BranchService, UserPermissionService);
137
92
  }
93
+ return providers;
94
+ }
95
+ /** Build module exports */ static buildExports(enableCompanyFeature) {
138
96
  const exports = [
139
97
  PassportModule,
140
98
  AUTH_MODULE_OPTIONS,
@@ -143,21 +101,15 @@ export class AuthModule {
143
101
  JwtStrategy,
144
102
  JwtAuthGuard,
145
103
  AuthenticationService,
104
+ AuthEmailService,
146
105
  UserService
147
106
  ];
148
107
  if (enableCompanyFeature) {
149
108
  exports.push(CompanySelectionSessionService, CompanyService, BranchService, UserPermissionService);
150
109
  }
151
- return {
152
- module: AuthModule,
153
- global,
154
- imports,
155
- controllers,
156
- providers,
157
- exports
158
- };
110
+ return exports;
159
111
  }
160
- static createAsyncProviders(options) {
112
+ /** Create async providers for forRootAsync */ static createAsyncProviders(options) {
161
113
  const staticOptions = {
162
114
  global: options.global,
163
115
  includeController: options.includeController,
@@ -167,13 +119,10 @@ export class AuthModule {
167
119
  return [
168
120
  {
169
121
  provide: AUTH_MODULE_OPTIONS,
170
- useFactory: async (...args)=>{
171
- const config = await options.useFactory(...args);
172
- return {
122
+ useFactory: async (...args)=>({
173
123
  ...staticOptions,
174
- config
175
- };
176
- },
124
+ config: await options.useFactory(...args)
125
+ }),
177
126
  inject: options.inject || []
178
127
  }
179
128
  ];
@@ -186,13 +135,10 @@ export class AuthModule {
186
135
  },
187
136
  {
188
137
  provide: AUTH_MODULE_OPTIONS,
189
- useFactory: async (factory)=>{
190
- const config = await factory.createAuthOptions();
191
- return {
138
+ useFactory: async (factory)=>({
192
139
  ...staticOptions,
193
- config
194
- };
195
- },
140
+ config: await factory.createAuthOptions()
141
+ }),
196
142
  inject: [
197
143
  options.useClass
198
144
  ]
@@ -203,13 +149,10 @@ export class AuthModule {
203
149
  return [
204
150
  {
205
151
  provide: AUTH_MODULE_OPTIONS,
206
- useFactory: async (factory)=>{
207
- const config = await factory.createAuthOptions();
208
- return {
152
+ useFactory: async (factory)=>({
209
153
  ...staticOptions,
210
- config
211
- };
212
- },
154
+ config: await factory.createAuthOptions()
155
+ }),
213
156
  inject: [
214
157
  options.useExisting
215
158
  ]
@@ -223,35 +166,14 @@ export class AuthModule {
223
166
  }
224
167
  ];
225
168
  }
226
- /**
227
- * Create repository providers that use AuthDataSourceProvider
228
- * This replaces TypeOrmModule.forFeature() functionality
229
- * Note: Must be REQUEST-scoped since AuthDataSourceProvider is REQUEST-scoped
230
- */ static getRepositoryProviders(enableCompanyFeature) {
231
- const entities = this.getEntities(enableCompanyFeature);
232
- return entities.map((entity)=>({
233
- provide: getRepositoryToken(entity),
234
- scope: Scope.REQUEST,
235
- useFactory: async (dataSourceProvider)=>{
236
- return await dataSourceProvider.getRepository(entity);
237
- },
238
- inject: [
239
- AuthDataSourceProvider
240
- ]
241
- }));
242
- }
243
- static getEntities(enableCompanyFeature = false) {
244
- const coreEntities = [
245
- User
246
- ];
247
- const companyEntities = enableCompanyFeature ? [
169
+ /** Get auth entities based on feature flags */ static getEntities(enableCompanyFeature = false) {
170
+ return enableCompanyFeature ? [
171
+ User,
248
172
  Company,
249
173
  CompanyBranch,
250
174
  UserCompanyPermission
251
- ] : [];
252
- return [
253
- ...coreEntities,
254
- ...companyEntities
175
+ ] : [
176
+ User
255
177
  ];
256
178
  }
257
179
  }