@flusys/nestjs-auth 1.0.0-rc → 2.0.0-rc.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 +530 -396
- package/cjs/controllers/authentication.controller.js +160 -28
- package/cjs/controllers/user-permission.controller.js +49 -136
- package/cjs/docs/auth-swagger.config.js +14 -7
- package/cjs/dtos/authentication.dto.js +0 -30
- package/cjs/dtos/index.js +0 -1
- package/cjs/interceptors/clear-token.interceptor.js +8 -18
- package/cjs/interceptors/set-token.interceptor.js +8 -55
- package/cjs/interfaces/auth-email-provider.interface.js +1 -1
- package/cjs/modules/auth.module.js +63 -129
- package/cjs/services/auth-config.service.js +6 -29
- package/cjs/services/auth-datasource.provider.js +8 -20
- package/cjs/services/auth-email.service.js +72 -87
- package/cjs/services/authentication.service.js +211 -355
- package/cjs/services/branch.service.js +9 -5
- package/cjs/services/user-permission.service.js +43 -71
- package/cjs/services/user.service.js +18 -47
- package/cjs/strategies/jwt.strategy.js +44 -44
- package/controllers/authentication.controller.d.ts +5 -3
- package/controllers/user-permission.controller.d.ts +5 -4
- package/dtos/authentication.dto.d.ts +0 -4
- package/dtos/index.d.ts +0 -1
- package/entities/index.d.ts +3 -3
- package/fesm/controllers/authentication.controller.js +162 -30
- package/fesm/controllers/user-permission.controller.js +50 -137
- package/fesm/docs/auth-swagger.config.js +14 -7
- package/fesm/dtos/authentication.dto.js +0 -30
- package/fesm/dtos/index.js +0 -1
- package/fesm/interceptors/clear-token.interceptor.js +8 -18
- package/fesm/interceptors/set-token.interceptor.js +8 -55
- package/fesm/interfaces/auth-email-provider.interface.js +1 -1
- package/fesm/modules/auth.module.js +63 -129
- package/fesm/services/auth-config.service.js +6 -29
- package/fesm/services/auth-datasource.provider.js +8 -20
- package/fesm/services/auth-email.service.js +72 -87
- package/fesm/services/authentication.service.js +211 -355
- package/fesm/services/branch.service.js +9 -5
- package/fesm/services/user-permission.service.js +44 -72
- package/fesm/services/user.service.js +18 -47
- package/fesm/strategies/jwt.strategy.js +44 -44
- package/interceptors/clear-token.interceptor.d.ts +0 -1
- package/interceptors/set-token.interceptor.d.ts +1 -2
- package/interfaces/authentication.interface.d.ts +0 -4
- package/modules/auth.module.d.ts +6 -2
- package/package.json +3 -3
- package/services/auth-config.service.d.ts +2 -7
- package/services/auth-datasource.provider.d.ts +2 -4
- package/services/auth-email.service.d.ts +7 -4
- package/services/authentication.service.d.ts +14 -8
- package/services/user-permission.service.d.ts +6 -8
- package/services/user.service.d.ts +1 -1
- package/strategies/jwt.strategy.d.ts +4 -1
- package/cjs/dtos/user-company-permission.dto.js +0 -110
- package/cjs/guards/auth-throttler.guard.js +0 -53
- package/dtos/user-company-permission.dto.d.ts +0 -10
- package/fesm/dtos/user-company-permission.dto.js +0 -98
- package/fesm/guards/auth-throttler.guard.js +0 -43
- package/guards/auth-throttler.guard.d.ts +0 -7
|
@@ -25,6 +25,7 @@ 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';
|
|
30
31
|
import { DEFAULT_COOKIE_NAME } from '../config';
|
|
@@ -37,27 +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
|
+
if (isBrowserRequest(req)) {
|
|
42
|
+
res.clearCookie(this.cookieName, {
|
|
43
|
+
httpOnly: true,
|
|
44
|
+
path: '/',
|
|
45
|
+
...buildCookieOptions(req)
|
|
46
|
+
});
|
|
47
|
+
}
|
|
41
48
|
return value;
|
|
42
49
|
}));
|
|
43
50
|
}
|
|
44
|
-
clearRefreshTokenCookie(req, res) {
|
|
45
|
-
const origin = req.headers.origin || '';
|
|
46
|
-
const hostname = req.hostname || '';
|
|
47
|
-
const isHttps = origin.startsWith('https://') || req.secure;
|
|
48
|
-
let cookieDomain = hostname;
|
|
49
|
-
const domainParts = hostname.split('.');
|
|
50
|
-
if (domainParts.length > 2) {
|
|
51
|
-
cookieDomain = '.' + domainParts.slice(-2).join('.');
|
|
52
|
-
}
|
|
53
|
-
res.clearCookie(this.cookieName, {
|
|
54
|
-
httpOnly: true,
|
|
55
|
-
secure: isHttps,
|
|
56
|
-
sameSite: isHttps ? 'strict' : 'lax',
|
|
57
|
-
domain: cookieDomain,
|
|
58
|
-
path: '/'
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
51
|
constructor(authConfig){
|
|
62
52
|
_define_property(this, "authConfig", void 0);
|
|
63
53
|
this.authConfig = authConfig;
|
|
@@ -25,6 +25,7 @@ function _ts_param(paramIndex, decorator) {
|
|
|
25
25
|
decorator(target, key, paramIndex);
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
+
import { buildCookieOptions, isBrowserRequest, parseDurationToMs } from '@flusys/nestjs-shared/utils';
|
|
28
29
|
import { Inject, Injectable, Optional, PayloadTooLargeException } from '@nestjs/common';
|
|
29
30
|
import { Buffer } from 'buffer';
|
|
30
31
|
import { catchError, map, throwError } from 'rxjs';
|
|
@@ -34,67 +35,23 @@ export class SetToken {
|
|
|
34
35
|
get cookieName() {
|
|
35
36
|
return this.authConfig?.getRefreshTokenCookieName() ?? DEFAULT_COOKIE_NAME;
|
|
36
37
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const match = expiry.match(/^(\d+)(s|m|h|d|w)$/);
|
|
40
|
-
if (!match) {
|
|
41
|
-
return 7 * 24 * 60 * 60 * 1000; // Default 7 days
|
|
42
|
-
}
|
|
43
|
-
const value = parseInt(match[1], 10);
|
|
44
|
-
const unit = match[2];
|
|
45
|
-
const multipliers = {
|
|
46
|
-
s: 1000,
|
|
47
|
-
m: 60 * 1000,
|
|
48
|
-
h: 60 * 60 * 1000,
|
|
49
|
-
d: 24 * 60 * 60 * 1000,
|
|
50
|
-
w: 7 * 24 * 60 * 60 * 1000
|
|
51
|
-
};
|
|
52
|
-
return value * (multipliers[unit] || multipliers['d']);
|
|
38
|
+
get maxAgeMs() {
|
|
39
|
+
return parseDurationToMs(this.authConfig?.getRefreshTokenExpiration() ?? '7d');
|
|
53
40
|
}
|
|
54
41
|
intercept(context, next) {
|
|
55
42
|
const req = context.switchToHttp().getRequest();
|
|
56
43
|
const res = context.switchToHttp().getResponse();
|
|
57
44
|
return next.handle().pipe(map((value)=>{
|
|
58
|
-
if (!value
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const isBrowser = this.isBrowserRequest(req);
|
|
62
|
-
const tokenValue = value.refreshToken;
|
|
63
|
-
if (isBrowser) {
|
|
64
|
-
this.setRefreshTokenCookie(req, res, tokenValue);
|
|
45
|
+
if (!value?.refreshToken) return value;
|
|
46
|
+
if (isBrowserRequest(req)) {
|
|
47
|
+
this.setRefreshTokenCookie(req, res, value.refreshToken);
|
|
65
48
|
const { refreshToken, ...responseWithoutToken } = value;
|
|
66
49
|
return responseWithoutToken;
|
|
67
50
|
}
|
|
68
51
|
return value;
|
|
69
52
|
}), catchError((err)=>throwError(()=>err)));
|
|
70
53
|
}
|
|
71
|
-
isBrowserRequest(req) {
|
|
72
|
-
const clientType = req.headers['x-client-type'];
|
|
73
|
-
if (clientType) {
|
|
74
|
-
return clientType.toLowerCase() === 'browser' || clientType.toLowerCase() === 'web';
|
|
75
|
-
}
|
|
76
|
-
const accept = req.headers['accept'] || '';
|
|
77
|
-
if (accept.includes('text/html')) {
|
|
78
|
-
return true;
|
|
79
|
-
}
|
|
80
|
-
const userAgent = req.headers['user-agent'] || '';
|
|
81
|
-
const browserPatterns = /mozilla|chrome|safari|firefox|edge|opera|msie/i;
|
|
82
|
-
return browserPatterns.test(userAgent) && !userAgent.includes('Postman');
|
|
83
|
-
}
|
|
84
54
|
setRefreshTokenCookie(req, res, tokenValue) {
|
|
85
|
-
const hostname = req.hostname || '';
|
|
86
|
-
// Determine if we're in production - always use secure cookies in production
|
|
87
|
-
// This handles TLS-terminating proxies correctly (they set x-forwarded-proto)
|
|
88
|
-
const isProduction = process.env.NODE_ENV === 'production';
|
|
89
|
-
const forwardedProto = req.headers['x-forwarded-proto'];
|
|
90
|
-
const isHttps = isProduction || forwardedProto === 'https' || req.secure;
|
|
91
|
-
// Set cookie domain - only for subdomains with 3+ parts
|
|
92
|
-
// For security, limit to same domain unless explicitly configured
|
|
93
|
-
let cookieDomain;
|
|
94
|
-
const domainParts = hostname.split('.');
|
|
95
|
-
if (domainParts.length > 2 && !hostname.includes('localhost')) {
|
|
96
|
-
cookieDomain = '.' + domainParts.slice(-2).join('.');
|
|
97
|
-
}
|
|
98
55
|
const rawCookieString = `${this.cookieName}=${tokenValue}; HttpOnly`;
|
|
99
56
|
const estimatedBytes = Buffer.byteLength(rawCookieString, 'utf8');
|
|
100
57
|
if (estimatedBytes > MAX_COOKIE_BYTES) {
|
|
@@ -102,13 +59,9 @@ export class SetToken {
|
|
|
102
59
|
}
|
|
103
60
|
res.cookie(this.cookieName, tokenValue, {
|
|
104
61
|
httpOnly: true,
|
|
105
|
-
secure: isHttps,
|
|
106
|
-
sameSite: isHttps ? 'strict' : 'lax',
|
|
107
|
-
...cookieDomain && {
|
|
108
|
-
domain: cookieDomain
|
|
109
|
-
},
|
|
110
62
|
path: '/',
|
|
111
|
-
maxAge: this.
|
|
63
|
+
maxAge: this.maxAgeMs,
|
|
64
|
+
...buildCookieOptions(req)
|
|
112
65
|
});
|
|
113
66
|
}
|
|
114
67
|
constructor(authConfig){
|
|
@@ -8,94 +8,54 @@ import { CacheModule, UtilsModule } from '@flusys/nestjs-shared/modules';
|
|
|
8
8
|
import { Module } from '@nestjs/common';
|
|
9
9
|
import { JwtModule } from '@nestjs/jwt';
|
|
10
10
|
import { PassportModule } from '@nestjs/passport';
|
|
11
|
-
import {
|
|
12
|
-
import { AuthDataSourceProvider } from '../services/auth-datasource.provider';
|
|
11
|
+
import { JwtAuthGuard } from '@flusys/nestjs-shared/guards';
|
|
13
12
|
import { AUTH_MODULE_OPTIONS } from '../config/auth.constants';
|
|
14
13
|
import { AuthenticationController, BranchController, CompanyController, CompanySelectionController, UserController, UserPermissionController } from '../controllers';
|
|
15
14
|
import { Company, CompanyBranch, User, UserCompanyPermission } from '../entities';
|
|
16
|
-
import { JwtAuthGuard } from '@flusys/nestjs-shared/guards';
|
|
17
15
|
import { ClearToken } from '../interceptors/clear-token.interceptor';
|
|
18
16
|
import { SetToken } from '../interceptors/set-token.interceptor';
|
|
19
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';
|
|
20
20
|
import { JwtStrategy } from '../strategies/jwt.strategy';
|
|
21
21
|
export class AuthModule {
|
|
22
22
|
static forRoot(options) {
|
|
23
|
-
|
|
23
|
+
const optionsProvider = {
|
|
24
|
+
provide: AUTH_MODULE_OPTIONS,
|
|
25
|
+
useValue: options
|
|
26
|
+
};
|
|
27
|
+
return this.buildModule(options, [
|
|
28
|
+
optionsProvider,
|
|
29
|
+
...options.providers ?? []
|
|
30
|
+
]);
|
|
24
31
|
}
|
|
25
|
-
static forRootAsync(
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
// Build imports
|
|
29
|
-
const imports = [
|
|
30
|
-
...externalImports,
|
|
31
|
-
PassportModule.register({
|
|
32
|
-
defaultStrategy: 'jwt'
|
|
33
|
-
}),
|
|
34
|
-
JwtModule.register({}),
|
|
35
|
-
CacheModule,
|
|
36
|
-
UtilsModule
|
|
37
|
-
];
|
|
38
|
-
// Build controllers based on configuration
|
|
39
|
-
const controllers = [];
|
|
40
|
-
if (includeController) {
|
|
41
|
-
// Core authentication controller (always included)
|
|
42
|
-
controllers.push(AuthenticationController);
|
|
43
|
-
// User controller (always included)
|
|
44
|
-
controllers.push(UserController);
|
|
45
|
-
// Company feature controllers (conditional)
|
|
46
|
-
if (enableCompanyFeature) {
|
|
47
|
-
controllers.push(CompanySelectionController, CompanyController, BranchController, UserPermissionController);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
// Build providers
|
|
51
|
-
const providers = [
|
|
32
|
+
static forRootAsync(options) {
|
|
33
|
+
const asyncProviders = this.createAsyncProviders(options);
|
|
34
|
+
return this.buildModule(options, [
|
|
52
35
|
...asyncProviders,
|
|
53
|
-
...
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
JwtAuthGuard,
|
|
60
|
-
AuthenticationService,
|
|
61
|
-
AuthEmailService,
|
|
62
|
-
// Interceptors
|
|
63
|
-
SetToken,
|
|
64
|
-
ClearToken,
|
|
65
|
-
// Core services (always included)
|
|
66
|
-
UserService
|
|
67
|
-
];
|
|
68
|
-
// Company feature services (conditional)
|
|
69
|
-
if (enableCompanyFeature) {
|
|
70
|
-
providers.push(CompanySelectionSessionService, CompanyService, BranchService, UserPermissionService);
|
|
71
|
-
}
|
|
72
|
-
// Build exports
|
|
73
|
-
const exports = [
|
|
74
|
-
PassportModule,
|
|
75
|
-
AUTH_MODULE_OPTIONS,
|
|
76
|
-
AuthConfigService,
|
|
77
|
-
AuthDataSourceProvider,
|
|
78
|
-
JwtStrategy,
|
|
79
|
-
JwtAuthGuard,
|
|
80
|
-
AuthenticationService,
|
|
81
|
-
AuthEmailService,
|
|
82
|
-
UserService
|
|
83
|
-
];
|
|
84
|
-
if (enableCompanyFeature) {
|
|
85
|
-
exports.push(CompanySelectionSessionService, CompanyService, BranchService, UserPermissionService);
|
|
86
|
-
}
|
|
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;
|
|
87
42
|
return {
|
|
88
43
|
module: AuthModule,
|
|
89
44
|
global,
|
|
90
|
-
imports
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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)
|
|
94
55
|
};
|
|
95
56
|
}
|
|
96
|
-
static
|
|
97
|
-
|
|
98
|
-
const imports = [
|
|
57
|
+
/** Common module imports */ static buildImports() {
|
|
58
|
+
return [
|
|
99
59
|
PassportModule.register({
|
|
100
60
|
defaultStrategy: 'jwt'
|
|
101
61
|
}),
|
|
@@ -103,42 +63,36 @@ export class AuthModule {
|
|
|
103
63
|
CacheModule,
|
|
104
64
|
UtilsModule
|
|
105
65
|
];
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if (includeController)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
controllers.push(CompanySelectionController, CompanyController, BranchController, UserPermissionController);
|
|
116
|
-
}
|
|
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);
|
|
117
75
|
}
|
|
76
|
+
return controllers;
|
|
77
|
+
}
|
|
78
|
+
/** Core providers (without AUTH_MODULE_OPTIONS - that's added separately) */ static buildProviders(enableCompanyFeature) {
|
|
118
79
|
const providers = [
|
|
119
|
-
{
|
|
120
|
-
provide: AUTH_MODULE_OPTIONS,
|
|
121
|
-
useValue: options
|
|
122
|
-
},
|
|
123
|
-
...additionalProviders,
|
|
124
|
-
// Config & Infrastructure
|
|
125
80
|
AuthConfigService,
|
|
126
81
|
AuthDataSourceProvider,
|
|
127
|
-
// Auth core
|
|
128
82
|
JwtStrategy,
|
|
129
83
|
JwtAuthGuard,
|
|
130
84
|
AuthenticationService,
|
|
131
85
|
AuthEmailService,
|
|
132
|
-
// Interceptors
|
|
133
86
|
SetToken,
|
|
134
87
|
ClearToken,
|
|
135
|
-
// Core services (always included)
|
|
136
88
|
UserService
|
|
137
89
|
];
|
|
138
|
-
// Company feature services (conditional)
|
|
139
90
|
if (enableCompanyFeature) {
|
|
140
91
|
providers.push(CompanySelectionSessionService, CompanyService, BranchService, UserPermissionService);
|
|
141
92
|
}
|
|
93
|
+
return providers;
|
|
94
|
+
}
|
|
95
|
+
/** Build module exports */ static buildExports(enableCompanyFeature) {
|
|
142
96
|
const exports = [
|
|
143
97
|
PassportModule,
|
|
144
98
|
AUTH_MODULE_OPTIONS,
|
|
@@ -153,16 +107,9 @@ export class AuthModule {
|
|
|
153
107
|
if (enableCompanyFeature) {
|
|
154
108
|
exports.push(CompanySelectionSessionService, CompanyService, BranchService, UserPermissionService);
|
|
155
109
|
}
|
|
156
|
-
return
|
|
157
|
-
module: AuthModule,
|
|
158
|
-
global,
|
|
159
|
-
imports,
|
|
160
|
-
controllers,
|
|
161
|
-
providers,
|
|
162
|
-
exports
|
|
163
|
-
};
|
|
110
|
+
return exports;
|
|
164
111
|
}
|
|
165
|
-
static createAsyncProviders(options) {
|
|
112
|
+
/** Create async providers for forRootAsync */ static createAsyncProviders(options) {
|
|
166
113
|
const staticOptions = {
|
|
167
114
|
global: options.global,
|
|
168
115
|
includeController: options.includeController,
|
|
@@ -172,13 +119,10 @@ export class AuthModule {
|
|
|
172
119
|
return [
|
|
173
120
|
{
|
|
174
121
|
provide: AUTH_MODULE_OPTIONS,
|
|
175
|
-
useFactory: async (...args)=>{
|
|
176
|
-
const config = await options.useFactory(...args);
|
|
177
|
-
return {
|
|
122
|
+
useFactory: async (...args)=>({
|
|
178
123
|
...staticOptions,
|
|
179
|
-
config
|
|
180
|
-
}
|
|
181
|
-
},
|
|
124
|
+
config: await options.useFactory(...args)
|
|
125
|
+
}),
|
|
182
126
|
inject: options.inject || []
|
|
183
127
|
}
|
|
184
128
|
];
|
|
@@ -191,13 +135,10 @@ export class AuthModule {
|
|
|
191
135
|
},
|
|
192
136
|
{
|
|
193
137
|
provide: AUTH_MODULE_OPTIONS,
|
|
194
|
-
useFactory: async (factory)=>{
|
|
195
|
-
const config = await factory.createAuthOptions();
|
|
196
|
-
return {
|
|
138
|
+
useFactory: async (factory)=>({
|
|
197
139
|
...staticOptions,
|
|
198
|
-
config
|
|
199
|
-
}
|
|
200
|
-
},
|
|
140
|
+
config: await factory.createAuthOptions()
|
|
141
|
+
}),
|
|
201
142
|
inject: [
|
|
202
143
|
options.useClass
|
|
203
144
|
]
|
|
@@ -208,13 +149,10 @@ export class AuthModule {
|
|
|
208
149
|
return [
|
|
209
150
|
{
|
|
210
151
|
provide: AUTH_MODULE_OPTIONS,
|
|
211
|
-
useFactory: async (factory)=>{
|
|
212
|
-
const config = await factory.createAuthOptions();
|
|
213
|
-
return {
|
|
152
|
+
useFactory: async (factory)=>({
|
|
214
153
|
...staticOptions,
|
|
215
|
-
config
|
|
216
|
-
}
|
|
217
|
-
},
|
|
154
|
+
config: await factory.createAuthOptions()
|
|
155
|
+
}),
|
|
218
156
|
inject: [
|
|
219
157
|
options.useExisting
|
|
220
158
|
]
|
|
@@ -228,18 +166,14 @@ export class AuthModule {
|
|
|
228
166
|
}
|
|
229
167
|
];
|
|
230
168
|
}
|
|
231
|
-
static getEntities(enableCompanyFeature = false) {
|
|
232
|
-
|
|
233
|
-
User
|
|
234
|
-
];
|
|
235
|
-
const companyEntities = enableCompanyFeature ? [
|
|
169
|
+
/** Get auth entities based on feature flags */ static getEntities(enableCompanyFeature = false) {
|
|
170
|
+
return enableCompanyFeature ? [
|
|
171
|
+
User,
|
|
236
172
|
Company,
|
|
237
173
|
CompanyBranch,
|
|
238
174
|
UserCompanyPermission
|
|
239
|
-
] : [
|
|
240
|
-
|
|
241
|
-
...coreEntities,
|
|
242
|
-
...companyEntities
|
|
175
|
+
] : [
|
|
176
|
+
User
|
|
243
177
|
];
|
|
244
178
|
}
|
|
245
179
|
}
|
|
@@ -26,18 +26,18 @@ function _ts_param(paramIndex, decorator) {
|
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
28
|
import { Inject, Injectable, InternalServerErrorException, Logger, Optional } from '@nestjs/common';
|
|
29
|
-
import { AuthModuleOptions } from '../interfaces/auth-module-options.interface';
|
|
30
29
|
import { AUTH_MODULE_OPTIONS } from '../config/auth.constants';
|
|
30
|
+
import { AuthModuleOptions } from '../interfaces/auth-module-options.interface';
|
|
31
31
|
export class AuthConfigService {
|
|
32
|
-
// Database Mode
|
|
33
|
-
|
|
32
|
+
// Database Mode (required by IModuleConfigService)
|
|
33
|
+
getDatabaseMode() {
|
|
34
34
|
return this.options.bootstrapAppConfig?.databaseMode ?? 'single';
|
|
35
35
|
}
|
|
36
|
-
|
|
36
|
+
isMultiTenant() {
|
|
37
37
|
return this.getDatabaseMode() === 'multi-tenant';
|
|
38
38
|
}
|
|
39
39
|
// Feature Flags
|
|
40
|
-
|
|
40
|
+
isCompanyFeatureEnabled() {
|
|
41
41
|
return this.options.bootstrapAppConfig?.enableCompanyFeature ?? false;
|
|
42
42
|
}
|
|
43
43
|
// JWT Configuration
|
|
@@ -54,16 +54,14 @@ export class AuthConfigService {
|
|
|
54
54
|
if (this.config.refreshTokenSecret) {
|
|
55
55
|
return this.config.refreshTokenSecret;
|
|
56
56
|
}
|
|
57
|
-
// Production: require separate refresh token secret for security
|
|
58
57
|
if (process.env.NODE_ENV === 'production') {
|
|
59
58
|
throw new InternalServerErrorException('[AuthModule] REFRESH_TOKEN_SECRET is required in production - ' + 'using the same secret for access and refresh tokens is a security risk');
|
|
60
59
|
}
|
|
61
|
-
// Development: warn once about fallback to JWT secret
|
|
62
60
|
if (!this.refreshTokenSecretWarned) {
|
|
63
61
|
this.refreshTokenSecretWarned = true;
|
|
64
62
|
this.logger.warn('REFRESH_TOKEN_SECRET not configured - using JWT_SECRET as fallback. ' + 'For better security, configure a separate refresh token secret.');
|
|
65
63
|
}
|
|
66
|
-
return this.
|
|
64
|
+
return this.getJwtSecret();
|
|
67
65
|
}
|
|
68
66
|
/** Get refresh token expiration time */ getRefreshTokenExpiration() {
|
|
69
67
|
return this.config.refreshTokenExpiration ?? '7d';
|
|
@@ -75,27 +73,6 @@ export class AuthConfigService {
|
|
|
75
73
|
/** Get frontend URL for email links */ getFrontendUrl() {
|
|
76
74
|
return this.config.frontendUrl;
|
|
77
75
|
}
|
|
78
|
-
// Database Configuration
|
|
79
|
-
/** Get default database config */ getDefaultDatabaseConfig() {
|
|
80
|
-
return this.config.defaultDatabaseConfig ?? this.config.tenantDefaultDatabaseConfig;
|
|
81
|
-
}
|
|
82
|
-
/** Get tenant default database config (for multi-tenant) */ getTenantDefaultDatabaseConfig() {
|
|
83
|
-
return this.config.tenantDefaultDatabaseConfig;
|
|
84
|
-
}
|
|
85
|
-
/** Get registered tenants */ getTenants() {
|
|
86
|
-
return this.config.tenants ?? [];
|
|
87
|
-
}
|
|
88
|
-
// Raw Options
|
|
89
|
-
/** Get full module options */ getOptions() {
|
|
90
|
-
return {
|
|
91
|
-
...this.options
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
/** Get runtime config */ getConfig() {
|
|
95
|
-
return {
|
|
96
|
-
...this.config
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
76
|
constructor(injectedOptions){
|
|
100
77
|
_define_property(this, "logger", new Logger(AuthConfigService.name));
|
|
101
78
|
_define_property(this, "options", void 0);
|
|
@@ -90,40 +90,28 @@ export class AuthDataSourceProvider extends MultiTenantDataSourceService {
|
|
|
90
90
|
};
|
|
91
91
|
}
|
|
92
92
|
// Feature Flags
|
|
93
|
-
/**
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
/** Get enable company feature for specific tenant. Falls back to global setting. */ getEnableCompanyFeatureForTenant(tenant) {
|
|
97
|
-
return tenant?.enableCompanyFeature ?? this.getEnableCompanyFeature();
|
|
98
|
-
}
|
|
99
|
-
/** Get enable company feature for current request context */ getEnableCompanyFeatureForCurrentTenant() {
|
|
100
|
-
return this.getEnableCompanyFeatureForTenant(this.getCurrentTenant() ?? undefined);
|
|
93
|
+
/** Check if company feature is enabled (tenant-specific or global fallback) */ isCompanyFeatureEnabled(tenant) {
|
|
94
|
+
const effectiveTenant = tenant ?? this.getCurrentTenant();
|
|
95
|
+
return effectiveTenant?.enableCompanyFeature ?? this.authOptions.bootstrapAppConfig?.enableCompanyFeature ?? false;
|
|
101
96
|
}
|
|
102
97
|
// Entity Management
|
|
103
|
-
/** Get auth entities based on company feature flag */ async getAuthEntities(
|
|
104
|
-
const enable = enableCompanyFeature ?? this.getEnableCompanyFeature();
|
|
98
|
+
/** Get auth entities based on company feature flag */ async getAuthEntities() {
|
|
105
99
|
const { Company, CompanyBranch, User, UserCompanyPermission, UserToken, PendingRegistration } = await import('../entities');
|
|
106
100
|
const coreEntities = [
|
|
107
101
|
User,
|
|
108
102
|
UserToken,
|
|
109
103
|
PendingRegistration
|
|
110
104
|
];
|
|
111
|
-
|
|
105
|
+
return this.isCompanyFeatureEnabled() ? [
|
|
106
|
+
...coreEntities,
|
|
112
107
|
Company,
|
|
113
108
|
CompanyBranch,
|
|
114
109
|
UserCompanyPermission
|
|
115
|
-
] :
|
|
116
|
-
return [
|
|
117
|
-
...coreEntities,
|
|
118
|
-
...companyEntities
|
|
119
|
-
];
|
|
110
|
+
] : coreEntities;
|
|
120
111
|
}
|
|
121
112
|
// Overrides
|
|
122
113
|
/** Override to dynamically set entities based on tenant config */ async createDataSourceFromConfig(config) {
|
|
123
|
-
|
|
124
|
-
const enableCompanyFeature = this.getEnableCompanyFeatureForTenant(currentTenant ?? undefined);
|
|
125
|
-
const entities = await this.getAuthEntities(enableCompanyFeature);
|
|
126
|
-
return super.createDataSourceFromConfig(config, entities);
|
|
114
|
+
return super.createDataSourceFromConfig(config, await this.getAuthEntities());
|
|
127
115
|
}
|
|
128
116
|
constructor(authOptions, request){
|
|
129
117
|
super(AuthDataSourceProvider.buildParentOptions(authOptions), request), _define_property(this, "authOptions", void 0), _define_property(this, "logger", void 0), this.authOptions = authOptions, this.logger = new Logger(AuthDataSourceProvider.name);
|