@flusys/nestjs-auth 0.1.0-beta.3 → 1.0.0-rc
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 +274 -301
- package/cjs/config/auth.constants.js +22 -4
- package/cjs/controllers/authentication.controller.js +222 -38
- package/cjs/controllers/branch.controller.js +46 -4
- package/cjs/controllers/company-selection.controller.js +27 -20
- package/cjs/controllers/company.controller.js +46 -4
- package/cjs/controllers/user-permission.controller.js +40 -37
- package/cjs/controllers/user.controller.js +222 -23
- package/cjs/dtos/authentication.dto.js +119 -13
- package/cjs/dtos/user-permission.dto.js +28 -0
- package/cjs/dtos/user.dto.js +19 -4
- package/cjs/entities/index.js +5 -1
- package/cjs/entities/pending-registration.entity.js +125 -0
- package/cjs/entities/user-company-permission.entity.js +0 -6
- package/cjs/entities/user-token.entity.js +129 -0
- package/cjs/guards/auth-throttler.guard.js +53 -0
- package/cjs/interceptors/clear-token.interceptor.js +3 -6
- package/cjs/interceptors/set-token.interceptor.js +38 -31
- package/cjs/interfaces/auth-email-provider.interface.js +45 -0
- package/cjs/interfaces/authentication.interface.js +0 -3
- package/cjs/interfaces/index.js +2 -0
- package/cjs/interfaces/user-enricher.interface.js +4 -0
- package/cjs/modules/auth.module.js +8 -20
- package/cjs/services/auth-config.service.js +42 -47
- package/cjs/services/auth-datasource.provider.js +18 -34
- package/cjs/services/auth-email.service.js +471 -0
- package/cjs/services/authentication.service.js +240 -153
- package/cjs/services/branch.service.js +15 -48
- package/cjs/services/company-selection-session.service.js +1 -1
- package/cjs/services/company.service.js +7 -36
- package/cjs/services/index.js +1 -0
- package/cjs/services/user-permission.service.js +9 -29
- package/cjs/services/user.service.js +228 -107
- package/cjs/strategies/jwt.strategy.js +14 -1
- package/cjs/validators/password.validator.js +45 -0
- package/config/auth.constants.d.ts +5 -0
- package/controllers/authentication.controller.d.ts +13 -9
- package/controllers/company-selection.controller.d.ts +6 -2
- package/controllers/user.controller.d.ts +33 -0
- package/dtos/authentication.dto.d.ts +21 -3
- package/dtos/company-branch.dto.d.ts +0 -1
- package/dtos/company.dto.d.ts +0 -1
- package/dtos/user-permission.dto.d.ts +4 -0
- package/dtos/user.dto.d.ts +1 -2
- package/entities/index.d.ts +7 -3
- package/entities/pending-registration.entity.d.ts +17 -0
- package/entities/user-company-permission.entity.d.ts +0 -2
- package/entities/user-token.entity.d.ts +15 -0
- package/fesm/config/auth.constants.js +11 -8
- package/fesm/controllers/authentication.controller.js +224 -40
- package/fesm/controllers/branch.controller.js +49 -7
- package/fesm/controllers/company-selection.controller.js +28 -21
- package/fesm/controllers/company.controller.js +49 -7
- package/fesm/controllers/user-permission.controller.js +41 -38
- package/fesm/controllers/user.controller.js +225 -26
- package/fesm/dtos/authentication.dto.js +123 -111
- package/fesm/dtos/user-permission.dto.js +28 -0
- package/fesm/dtos/user.dto.js +20 -5
- package/fesm/entities/index.js +7 -1
- package/fesm/entities/pending-registration.entity.js +115 -0
- package/fesm/entities/user-company-permission.entity.js +0 -6
- package/fesm/entities/user-token.entity.js +113 -0
- package/fesm/guards/auth-throttler.guard.js +43 -0
- package/fesm/interceptors/clear-token.interceptor.js +2 -5
- package/fesm/interceptors/set-token.interceptor.js +37 -30
- package/fesm/interfaces/auth-email-provider.interface.js +38 -0
- package/fesm/interfaces/authentication.interface.js +1 -7
- package/fesm/interfaces/index.js +2 -0
- package/fesm/interfaces/user-enricher.interface.js +26 -0
- package/fesm/modules/auth.module.js +10 -22
- package/fesm/services/auth-config.service.js +42 -47
- package/fesm/services/auth-datasource.provider.js +19 -35
- package/fesm/services/auth-email.service.js +420 -0
- package/fesm/services/authentication.service.js +238 -151
- package/fesm/services/branch.service.js +16 -49
- package/fesm/services/company-selection-session.service.js +1 -1
- package/fesm/services/company.service.js +8 -37
- package/fesm/services/index.js +1 -0
- package/fesm/services/user-permission.service.js +10 -30
- package/fesm/services/user.service.js +229 -108
- package/fesm/strategies/jwt.strategy.js +14 -1
- package/fesm/validators/password.validator.js +48 -0
- package/guards/auth-throttler.guard.d.ts +7 -0
- package/interceptors/set-token.interceptor.d.ts +1 -0
- package/interfaces/auth-email-provider.interface.d.ts +6 -0
- package/interfaces/auth-module-options.interface.d.ts +4 -1
- package/interfaces/authentication.interface.d.ts +14 -0
- package/interfaces/index.d.ts +2 -0
- package/interfaces/user-enricher.interface.d.ts +64 -0
- package/interfaces/user.interface.d.ts +1 -0
- package/modules/auth.module.d.ts +0 -1
- package/package.json +4 -4
- package/services/auth-config.service.d.ts +5 -1
- package/services/auth-email.service.d.ts +51 -0
- package/services/authentication.service.d.ts +21 -12
- package/services/branch.service.d.ts +2 -5
- package/services/company-selection-session.service.d.ts +1 -1
- package/services/company.service.d.ts +1 -4
- package/services/index.d.ts +1 -0
- package/services/user-permission.service.d.ts +0 -1
- package/services/user.service.d.ts +20 -6
- package/validators/password.validator.d.ts +10 -0
|
@@ -10,6 +10,7 @@ Object.defineProperty(exports, "ClearToken", {
|
|
|
10
10
|
});
|
|
11
11
|
const _common = require("@nestjs/common");
|
|
12
12
|
const _rxjs = require("rxjs");
|
|
13
|
+
const _config = require("../config");
|
|
13
14
|
const _authconfigservice = require("../services/auth-config.service");
|
|
14
15
|
function _define_property(obj, key, value) {
|
|
15
16
|
if (key in obj) {
|
|
@@ -38,23 +39,19 @@ function _ts_param(paramIndex, decorator) {
|
|
|
38
39
|
decorator(target, key, paramIndex);
|
|
39
40
|
};
|
|
40
41
|
}
|
|
41
|
-
const DEFAULT_COOKIE_NAME = 'fsn_refresh_token';
|
|
42
42
|
let ClearToken = class ClearToken {
|
|
43
43
|
get cookieName() {
|
|
44
|
-
return this.authConfig?.getRefreshTokenCookieName() ?? DEFAULT_COOKIE_NAME;
|
|
44
|
+
return this.authConfig?.getRefreshTokenCookieName() ?? _config.DEFAULT_COOKIE_NAME;
|
|
45
45
|
}
|
|
46
46
|
intercept(context, next) {
|
|
47
47
|
const req = context.switchToHttp().getRequest();
|
|
48
48
|
const res = context.switchToHttp().getResponse();
|
|
49
49
|
return next.handle().pipe((0, _rxjs.map)((value)=>{
|
|
50
|
-
// Clear the refresh token cookie
|
|
51
50
|
this.clearRefreshTokenCookie(req, res);
|
|
52
51
|
return value;
|
|
53
52
|
}));
|
|
54
53
|
}
|
|
55
|
-
|
|
56
|
-
* Clear refresh token cookie
|
|
57
|
-
*/ clearRefreshTokenCookie(req, res) {
|
|
54
|
+
clearRefreshTokenCookie(req, res) {
|
|
58
55
|
const origin = req.headers.origin || '';
|
|
59
56
|
const hostname = req.hostname || '';
|
|
60
57
|
const isHttps = origin.startsWith('https://') || req.secure;
|
|
@@ -11,6 +11,7 @@ Object.defineProperty(exports, "SetToken", {
|
|
|
11
11
|
const _common = require("@nestjs/common");
|
|
12
12
|
const _buffer = require("buffer");
|
|
13
13
|
const _rxjs = require("rxjs");
|
|
14
|
+
const _config = require("../config");
|
|
14
15
|
const _authconfigservice = require("../services/auth-config.service");
|
|
15
16
|
function _define_property(obj, key, value) {
|
|
16
17
|
if (key in obj) {
|
|
@@ -39,80 +40,85 @@ function _ts_param(paramIndex, decorator) {
|
|
|
39
40
|
decorator(target, key, paramIndex);
|
|
40
41
|
};
|
|
41
42
|
}
|
|
42
|
-
const DEFAULT_COOKIE_NAME = 'fsn_refresh_token';
|
|
43
|
-
const MAX_COOKIE_BYTES = 3500;
|
|
44
43
|
let SetToken = class SetToken {
|
|
45
44
|
get cookieName() {
|
|
46
|
-
return this.authConfig?.getRefreshTokenCookieName() ?? DEFAULT_COOKIE_NAME;
|
|
45
|
+
return this.authConfig?.getRefreshTokenCookieName() ?? _config.DEFAULT_COOKIE_NAME;
|
|
46
|
+
}
|
|
47
|
+
/** Convert expiration string (e.g., '7d', '24h') to milliseconds */ getMaxAgeMs() {
|
|
48
|
+
const expiry = this.authConfig?.getRefreshTokenExpiration() ?? '7d';
|
|
49
|
+
const match = expiry.match(/^(\d+)(s|m|h|d|w)$/);
|
|
50
|
+
if (!match) {
|
|
51
|
+
return 7 * 24 * 60 * 60 * 1000; // Default 7 days
|
|
52
|
+
}
|
|
53
|
+
const value = parseInt(match[1], 10);
|
|
54
|
+
const unit = match[2];
|
|
55
|
+
const multipliers = {
|
|
56
|
+
s: 1000,
|
|
57
|
+
m: 60 * 1000,
|
|
58
|
+
h: 60 * 60 * 1000,
|
|
59
|
+
d: 24 * 60 * 60 * 1000,
|
|
60
|
+
w: 7 * 24 * 60 * 60 * 1000
|
|
61
|
+
};
|
|
62
|
+
return value * (multipliers[unit] || multipliers['d']);
|
|
47
63
|
}
|
|
48
64
|
intercept(context, next) {
|
|
49
65
|
const req = context.switchToHttp().getRequest();
|
|
50
66
|
const res = context.switchToHttp().getResponse();
|
|
51
67
|
return next.handle().pipe((0, _rxjs.map)((value)=>{
|
|
52
|
-
// Skip if no refreshToken in response
|
|
53
68
|
if (!value || !value.refreshToken) {
|
|
54
69
|
return value;
|
|
55
70
|
}
|
|
56
71
|
const isBrowser = this.isBrowserRequest(req);
|
|
57
72
|
const tokenValue = value.refreshToken;
|
|
58
73
|
if (isBrowser) {
|
|
59
|
-
// Browser: Set cookie and remove from response body
|
|
60
74
|
this.setRefreshTokenCookie(req, res, tokenValue);
|
|
61
|
-
// Return response without refreshToken for browsers
|
|
62
75
|
const { refreshToken, ...responseWithoutToken } = value;
|
|
63
76
|
return responseWithoutToken;
|
|
64
77
|
}
|
|
65
|
-
// API/Mobile: Keep refreshToken in response body
|
|
66
78
|
return value;
|
|
67
79
|
}), (0, _rxjs.catchError)((err)=>(0, _rxjs.throwError)(()=>err)));
|
|
68
80
|
}
|
|
69
|
-
|
|
70
|
-
* Detect if request is from a browser
|
|
71
|
-
* Checks Accept header and custom X-Client-Type header
|
|
72
|
-
*/ isBrowserRequest(req) {
|
|
73
|
-
// Check custom header first (allows explicit client identification)
|
|
81
|
+
isBrowserRequest(req) {
|
|
74
82
|
const clientType = req.headers['x-client-type'];
|
|
75
83
|
if (clientType) {
|
|
76
84
|
return clientType.toLowerCase() === 'browser' || clientType.toLowerCase() === 'web';
|
|
77
85
|
}
|
|
78
|
-
// Check Accept header - browsers typically include text/html
|
|
79
86
|
const accept = req.headers['accept'] || '';
|
|
80
87
|
if (accept.includes('text/html')) {
|
|
81
88
|
return true;
|
|
82
89
|
}
|
|
83
|
-
// Check User-Agent for common browsers
|
|
84
90
|
const userAgent = req.headers['user-agent'] || '';
|
|
85
91
|
const browserPatterns = /mozilla|chrome|safari|firefox|edge|opera|msie/i;
|
|
86
|
-
|
|
87
|
-
return true;
|
|
88
|
-
}
|
|
89
|
-
return false;
|
|
92
|
+
return browserPatterns.test(userAgent) && !userAgent.includes('Postman');
|
|
90
93
|
}
|
|
91
|
-
|
|
92
|
-
* Set refresh token in HTTP-only cookie
|
|
93
|
-
*/ setRefreshTokenCookie(req, res, tokenValue) {
|
|
94
|
-
const origin = req.headers.origin || '';
|
|
94
|
+
setRefreshTokenCookie(req, res, tokenValue) {
|
|
95
95
|
const hostname = req.hostname || '';
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
// Determine if we're in production - always use secure cookies in production
|
|
97
|
+
// This handles TLS-terminating proxies correctly (they set x-forwarded-proto)
|
|
98
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
99
|
+
const forwardedProto = req.headers['x-forwarded-proto'];
|
|
100
|
+
const isHttps = isProduction || forwardedProto === 'https' || req.secure;
|
|
101
|
+
// Set cookie domain - only for subdomains with 3+ parts
|
|
102
|
+
// For security, limit to same domain unless explicitly configured
|
|
103
|
+
let cookieDomain;
|
|
98
104
|
const domainParts = hostname.split('.');
|
|
99
|
-
if (domainParts.length > 2) {
|
|
105
|
+
if (domainParts.length > 2 && !hostname.includes('localhost')) {
|
|
100
106
|
cookieDomain = '.' + domainParts.slice(-2).join('.');
|
|
101
107
|
}
|
|
102
|
-
|
|
103
|
-
const rawCookieString = `${this.cookieName}=${tokenValue}; HttpOnly; Domain=${cookieDomain}`;
|
|
108
|
+
const rawCookieString = `${this.cookieName}=${tokenValue}; HttpOnly`;
|
|
104
109
|
const estimatedBytes = _buffer.Buffer.byteLength(rawCookieString, 'utf8');
|
|
105
|
-
if (estimatedBytes > MAX_COOKIE_BYTES) {
|
|
110
|
+
if (estimatedBytes > _config.MAX_COOKIE_BYTES) {
|
|
106
111
|
throw new _common.PayloadTooLargeException(`Refresh token too large (≈${estimatedBytes} bytes)`);
|
|
107
112
|
}
|
|
108
|
-
// Set HTTP-only secure cookie
|
|
109
113
|
res.cookie(this.cookieName, tokenValue, {
|
|
110
114
|
httpOnly: true,
|
|
111
115
|
secure: isHttps,
|
|
112
116
|
sameSite: isHttps ? 'strict' : 'lax',
|
|
113
|
-
|
|
117
|
+
...cookieDomain && {
|
|
118
|
+
domain: cookieDomain
|
|
119
|
+
},
|
|
114
120
|
path: '/',
|
|
115
|
-
maxAge:
|
|
121
|
+
maxAge: this.getMaxAgeMs()
|
|
116
122
|
});
|
|
117
123
|
}
|
|
118
124
|
constructor(authConfig){
|
|
@@ -123,6 +129,7 @@ let SetToken = class SetToken {
|
|
|
123
129
|
SetToken = _ts_decorate([
|
|
124
130
|
(0, _common.Injectable)(),
|
|
125
131
|
_ts_param(0, (0, _common.Optional)()),
|
|
132
|
+
_ts_param(0, (0, _common.Inject)(_authconfigservice.AuthConfigService)),
|
|
126
133
|
_ts_metadata("design:type", Function),
|
|
127
134
|
_ts_metadata("design:paramtypes", [
|
|
128
135
|
typeof _authconfigservice.AuthConfigService === "undefined" ? Object : _authconfigservice.AuthConfigService
|
|
@@ -0,0 +1,45 @@
|
|
|
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
|
+
*/ "use strict";
|
|
36
|
+
Object.defineProperty(exports, "__esModule", {
|
|
37
|
+
value: true
|
|
38
|
+
});
|
|
39
|
+
Object.defineProperty(exports, "AUTH_EMAIL_PROVIDER", {
|
|
40
|
+
enumerable: true,
|
|
41
|
+
get: function() {
|
|
42
|
+
return AUTH_EMAIL_PROVIDER;
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
const AUTH_EMAIL_PROVIDER = 'AUTH_EMAIL_PROVIDER';
|
package/cjs/interfaces/index.js
CHANGED
|
@@ -3,9 +3,11 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
3
3
|
value: true
|
|
4
4
|
});
|
|
5
5
|
_export_star(require("./authentication.interface"), exports);
|
|
6
|
+
_export_star(require("./auth-email-provider.interface"), exports);
|
|
6
7
|
_export_star(require("./company-branch.interface"), exports);
|
|
7
8
|
_export_star(require("./company.interface"), exports);
|
|
8
9
|
_export_star(require("./user.interface"), exports);
|
|
10
|
+
_export_star(require("./user-enricher.interface"), exports);
|
|
9
11
|
_export_star(require("./auth-module-options.interface"), exports);
|
|
10
12
|
function _export_star(from, to) {
|
|
11
13
|
Object.keys(from).forEach(function(k) {
|
|
@@ -12,7 +12,6 @@ const _modules = require("@flusys/nestjs-shared/modules");
|
|
|
12
12
|
const _common = require("@nestjs/common");
|
|
13
13
|
const _jwt = require("@nestjs/jwt");
|
|
14
14
|
const _passport = require("@nestjs/passport");
|
|
15
|
-
const _typeorm = require("@nestjs/typeorm");
|
|
16
15
|
const _authconfigservice = require("../services/auth-config.service");
|
|
17
16
|
const _authdatasourceprovider = require("../services/auth-datasource.provider");
|
|
18
17
|
const _authconstants = require("../config/auth.constants");
|
|
@@ -34,7 +33,7 @@ let AuthModule = class AuthModule {
|
|
|
34
33
|
return this.buildDynamicModule(options);
|
|
35
34
|
}
|
|
36
35
|
static forRootAsync(asyncOptions) {
|
|
37
|
-
const { global = false, includeController = true, bootstrapAppConfig: { enableCompanyFeature } = {}, imports: externalImports = [] } = asyncOptions;
|
|
36
|
+
const { global = false, includeController = true, bootstrapAppConfig: { enableCompanyFeature } = {}, imports: externalImports = [], providers: additionalProviders = [] } = asyncOptions;
|
|
38
37
|
const asyncProviders = this.createAsyncProviders(asyncOptions);
|
|
39
38
|
// Build imports
|
|
40
39
|
const imports = [
|
|
@@ -61,6 +60,7 @@ let AuthModule = class AuthModule {
|
|
|
61
60
|
// Build providers
|
|
62
61
|
const providers = [
|
|
63
62
|
...asyncProviders,
|
|
63
|
+
...additionalProviders,
|
|
64
64
|
// Config & Infrastructure
|
|
65
65
|
_authconfigservice.AuthConfigService,
|
|
66
66
|
_authdatasourceprovider.AuthDataSourceProvider,
|
|
@@ -68,6 +68,7 @@ let AuthModule = class AuthModule {
|
|
|
68
68
|
_jwtstrategy.JwtStrategy,
|
|
69
69
|
_guards.JwtAuthGuard,
|
|
70
70
|
_services.AuthenticationService,
|
|
71
|
+
_services.AuthEmailService,
|
|
71
72
|
// Interceptors
|
|
72
73
|
_settokeninterceptor.SetToken,
|
|
73
74
|
_cleartokeninterceptor.ClearToken,
|
|
@@ -87,6 +88,7 @@ let AuthModule = class AuthModule {
|
|
|
87
88
|
_jwtstrategy.JwtStrategy,
|
|
88
89
|
_guards.JwtAuthGuard,
|
|
89
90
|
_services.AuthenticationService,
|
|
91
|
+
_services.AuthEmailService,
|
|
90
92
|
_services.UserService
|
|
91
93
|
];
|
|
92
94
|
if (enableCompanyFeature) {
|
|
@@ -102,7 +104,7 @@ let AuthModule = class AuthModule {
|
|
|
102
104
|
};
|
|
103
105
|
}
|
|
104
106
|
static buildDynamicModule(options) {
|
|
105
|
-
const { bootstrapAppConfig: { enableCompanyFeature } = {}, includeController = true, global = false } = options;
|
|
107
|
+
const { bootstrapAppConfig: { enableCompanyFeature } = {}, includeController = true, global = false, providers: additionalProviders = [] } = options;
|
|
106
108
|
const imports = [
|
|
107
109
|
_passport.PassportModule.register({
|
|
108
110
|
defaultStrategy: 'jwt'
|
|
@@ -128,6 +130,7 @@ let AuthModule = class AuthModule {
|
|
|
128
130
|
provide: _authconstants.AUTH_MODULE_OPTIONS,
|
|
129
131
|
useValue: options
|
|
130
132
|
},
|
|
133
|
+
...additionalProviders,
|
|
131
134
|
// Config & Infrastructure
|
|
132
135
|
_authconfigservice.AuthConfigService,
|
|
133
136
|
_authdatasourceprovider.AuthDataSourceProvider,
|
|
@@ -135,6 +138,7 @@ let AuthModule = class AuthModule {
|
|
|
135
138
|
_jwtstrategy.JwtStrategy,
|
|
136
139
|
_guards.JwtAuthGuard,
|
|
137
140
|
_services.AuthenticationService,
|
|
141
|
+
_services.AuthEmailService,
|
|
138
142
|
// Interceptors
|
|
139
143
|
_settokeninterceptor.SetToken,
|
|
140
144
|
_cleartokeninterceptor.ClearToken,
|
|
@@ -153,6 +157,7 @@ let AuthModule = class AuthModule {
|
|
|
153
157
|
_jwtstrategy.JwtStrategy,
|
|
154
158
|
_guards.JwtAuthGuard,
|
|
155
159
|
_services.AuthenticationService,
|
|
160
|
+
_services.AuthEmailService,
|
|
156
161
|
_services.UserService
|
|
157
162
|
];
|
|
158
163
|
if (enableCompanyFeature) {
|
|
@@ -233,23 +238,6 @@ let AuthModule = class AuthModule {
|
|
|
233
238
|
}
|
|
234
239
|
];
|
|
235
240
|
}
|
|
236
|
-
/**
|
|
237
|
-
* Create repository providers that use AuthDataSourceProvider
|
|
238
|
-
* This replaces TypeOrmModule.forFeature() functionality
|
|
239
|
-
* Note: Must be REQUEST-scoped since AuthDataSourceProvider is REQUEST-scoped
|
|
240
|
-
*/ static getRepositoryProviders(enableCompanyFeature) {
|
|
241
|
-
const entities = this.getEntities(enableCompanyFeature);
|
|
242
|
-
return entities.map((entity)=>({
|
|
243
|
-
provide: (0, _typeorm.getRepositoryToken)(entity),
|
|
244
|
-
scope: _common.Scope.REQUEST,
|
|
245
|
-
useFactory: async (dataSourceProvider)=>{
|
|
246
|
-
return await dataSourceProvider.getRepository(entity);
|
|
247
|
-
},
|
|
248
|
-
inject: [
|
|
249
|
-
_authdatasourceprovider.AuthDataSourceProvider
|
|
250
|
-
]
|
|
251
|
-
}));
|
|
252
|
-
}
|
|
253
241
|
static getEntities(enableCompanyFeature = false) {
|
|
254
242
|
const coreEntities = [
|
|
255
243
|
_entities.User
|
|
@@ -39,83 +39,78 @@ function _ts_param(paramIndex, decorator) {
|
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
41
|
let AuthConfigService = class AuthConfigService {
|
|
42
|
-
//
|
|
43
|
-
/**
|
|
44
|
-
* Get database mode (single or multi-tenant)
|
|
45
|
-
*/ getDatabaseMode() {
|
|
42
|
+
// Database Mode
|
|
43
|
+
/** Get database mode (single or multi-tenant) */ getDatabaseMode() {
|
|
46
44
|
return this.options.bootstrapAppConfig?.databaseMode ?? 'single';
|
|
47
45
|
}
|
|
48
|
-
/**
|
|
49
|
-
* Check if running in multi-tenant mode
|
|
50
|
-
*/ isMultiTenant() {
|
|
46
|
+
/** Check if running in multi-tenant mode */ isMultiTenant() {
|
|
51
47
|
return this.getDatabaseMode() === 'multi-tenant';
|
|
52
48
|
}
|
|
53
|
-
//
|
|
54
|
-
/**
|
|
55
|
-
* Check if company feature is enabled
|
|
56
|
-
*/ isCompanyFeatureEnabled() {
|
|
49
|
+
// Feature Flags
|
|
50
|
+
/** Check if company feature is enabled */ isCompanyFeatureEnabled() {
|
|
57
51
|
return this.options.bootstrapAppConfig?.enableCompanyFeature ?? false;
|
|
58
52
|
}
|
|
59
|
-
//
|
|
60
|
-
/**
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
53
|
+
// JWT Configuration
|
|
54
|
+
/** Get JWT secret key */ getJwtSecret() {
|
|
55
|
+
if (!this.config.jwtSecret) {
|
|
56
|
+
throw new _common.InternalServerErrorException('[AuthModule] JWT_SECRET is required - set via environment or module config');
|
|
57
|
+
}
|
|
58
|
+
return this.config.jwtSecret;
|
|
64
59
|
}
|
|
65
|
-
/**
|
|
66
|
-
* Get JWT expiration time
|
|
67
|
-
*/ getJwtExpiration() {
|
|
60
|
+
/** Get JWT expiration time */ getJwtExpiration() {
|
|
68
61
|
return this.config.jwtExpiration ?? '1h';
|
|
69
62
|
}
|
|
70
|
-
/**
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
63
|
+
/** Get refresh token secret key */ getRefreshTokenSecret() {
|
|
64
|
+
if (this.config.refreshTokenSecret) {
|
|
65
|
+
return this.config.refreshTokenSecret;
|
|
66
|
+
}
|
|
67
|
+
// Production: require separate refresh token secret for security
|
|
68
|
+
if (process.env.NODE_ENV === 'production') {
|
|
69
|
+
throw new _common.InternalServerErrorException('[AuthModule] REFRESH_TOKEN_SECRET is required in production - ' + 'using the same secret for access and refresh tokens is a security risk');
|
|
70
|
+
}
|
|
71
|
+
// Development: warn once about fallback to JWT secret
|
|
72
|
+
if (!this.refreshTokenSecretWarned) {
|
|
73
|
+
this.refreshTokenSecretWarned = true;
|
|
74
|
+
this.logger.warn('REFRESH_TOKEN_SECRET not configured - using JWT_SECRET as fallback. ' + 'For better security, configure a separate refresh token secret.');
|
|
75
|
+
}
|
|
76
|
+
return this.config.jwtSecret ?? this.getJwtSecret();
|
|
77
|
+
}
|
|
78
|
+
/** Get refresh token expiration time */ getRefreshTokenExpiration() {
|
|
78
79
|
return this.config.refreshTokenExpiration ?? '7d';
|
|
79
80
|
}
|
|
80
|
-
/**
|
|
81
|
-
* Get refresh token cookie name
|
|
82
|
-
*/ getRefreshTokenCookieName() {
|
|
81
|
+
/** Get refresh token cookie name */ getRefreshTokenCookieName() {
|
|
83
82
|
return this.config.refreshTokenCookieName ?? 'fsn_refresh_token';
|
|
84
83
|
}
|
|
85
|
-
//
|
|
86
|
-
/**
|
|
87
|
-
|
|
88
|
-
|
|
84
|
+
// Frontend Configuration
|
|
85
|
+
/** Get frontend URL for email links */ getFrontendUrl() {
|
|
86
|
+
return this.config.frontendUrl;
|
|
87
|
+
}
|
|
88
|
+
// Database Configuration
|
|
89
|
+
/** Get default database config */ getDefaultDatabaseConfig() {
|
|
89
90
|
return this.config.defaultDatabaseConfig ?? this.config.tenantDefaultDatabaseConfig;
|
|
90
91
|
}
|
|
91
|
-
/**
|
|
92
|
-
* Get tenant default database config (for multi-tenant)
|
|
93
|
-
*/ getTenantDefaultDatabaseConfig() {
|
|
92
|
+
/** Get tenant default database config (for multi-tenant) */ getTenantDefaultDatabaseConfig() {
|
|
94
93
|
return this.config.tenantDefaultDatabaseConfig;
|
|
95
94
|
}
|
|
96
|
-
/**
|
|
97
|
-
* Get registered tenants
|
|
98
|
-
*/ getTenants() {
|
|
95
|
+
/** Get registered tenants */ getTenants() {
|
|
99
96
|
return this.config.tenants ?? [];
|
|
100
97
|
}
|
|
101
|
-
//
|
|
102
|
-
/**
|
|
103
|
-
* Get full module options
|
|
104
|
-
*/ getOptions() {
|
|
98
|
+
// Raw Options
|
|
99
|
+
/** Get full module options */ getOptions() {
|
|
105
100
|
return {
|
|
106
101
|
...this.options
|
|
107
102
|
};
|
|
108
103
|
}
|
|
109
|
-
/**
|
|
110
|
-
* Get runtime config
|
|
111
|
-
*/ getConfig() {
|
|
104
|
+
/** Get runtime config */ getConfig() {
|
|
112
105
|
return {
|
|
113
106
|
...this.config
|
|
114
107
|
};
|
|
115
108
|
}
|
|
116
109
|
constructor(injectedOptions){
|
|
110
|
+
_define_property(this, "logger", new _common.Logger(AuthConfigService.name));
|
|
117
111
|
_define_property(this, "options", void 0);
|
|
118
112
|
_define_property(this, "config", void 0);
|
|
113
|
+
_define_property(this, "refreshTokenSecretWarned", false);
|
|
119
114
|
this.options = injectedOptions ?? {
|
|
120
115
|
global: false,
|
|
121
116
|
includeController: true
|
|
@@ -83,11 +83,8 @@ function _ts_param(paramIndex, decorator) {
|
|
|
83
83
|
};
|
|
84
84
|
}
|
|
85
85
|
let AuthDataSourceProvider = class AuthDataSourceProvider extends _modules.MultiTenantDataSourceService {
|
|
86
|
-
//
|
|
87
|
-
/**
|
|
88
|
-
* CRITICAL: Override to access Auth-specific static properties
|
|
89
|
-
* Without this, parent class methods would access parent's static properties
|
|
90
|
-
*/ async getSingleDataSource() {
|
|
86
|
+
// Override Static Cache Access Methods
|
|
87
|
+
/** Override to access Auth-specific static properties */ async getSingleDataSource() {
|
|
91
88
|
if (!AuthDataSourceProvider.singleDataSource) {
|
|
92
89
|
if (AuthDataSourceProvider.singleConnectionLock) {
|
|
93
90
|
return AuthDataSourceProvider.singleConnectionLock;
|
|
@@ -95,7 +92,7 @@ let AuthDataSourceProvider = class AuthDataSourceProvider extends _modules.Multi
|
|
|
95
92
|
const lockPromise = (async ()=>{
|
|
96
93
|
const config = this.getDefaultDatabaseConfig();
|
|
97
94
|
if (!config) {
|
|
98
|
-
throw new
|
|
95
|
+
throw new _common.InternalServerErrorException('Default database config is not available');
|
|
99
96
|
}
|
|
100
97
|
const ds = await this.createDataSourceFromConfig(config);
|
|
101
98
|
AuthDataSourceProvider.singleDataSource = ds;
|
|
@@ -111,9 +108,7 @@ let AuthDataSourceProvider = class AuthDataSourceProvider extends _modules.Multi
|
|
|
111
108
|
}
|
|
112
109
|
return AuthDataSourceProvider.singleDataSource;
|
|
113
110
|
}
|
|
114
|
-
/**
|
|
115
|
-
* CRITICAL: Override to use Auth-specific static cache for tenant connections
|
|
116
|
-
*/ async getOrCreateTenantConnection(tenant) {
|
|
111
|
+
/** Override to use Auth-specific static cache for tenant connections */ async getOrCreateTenantConnection(tenant) {
|
|
117
112
|
// Return existing initialized connection from Auth-specific cache
|
|
118
113
|
const existing = AuthDataSourceProvider.tenantConnections.get(tenant.id);
|
|
119
114
|
if (existing?.isInitialized) {
|
|
@@ -136,10 +131,8 @@ let AuthDataSourceProvider = class AuthDataSourceProvider extends _modules.Multi
|
|
|
136
131
|
AuthDataSourceProvider.connectionLocks.delete(tenant.id);
|
|
137
132
|
}
|
|
138
133
|
}
|
|
139
|
-
//
|
|
140
|
-
/**
|
|
141
|
-
* Build parent options from AuthModuleOptions
|
|
142
|
-
*/ static buildParentOptions(options) {
|
|
134
|
+
// Factory Methods
|
|
135
|
+
/** Build parent options from AuthModuleOptions */ static buildParentOptions(options) {
|
|
143
136
|
return {
|
|
144
137
|
bootstrapAppConfig: options.bootstrapAppConfig,
|
|
145
138
|
defaultDatabaseConfig: options.config?.defaultDatabaseConfig,
|
|
@@ -147,31 +140,24 @@ let AuthDataSourceProvider = class AuthDataSourceProvider extends _modules.Multi
|
|
|
147
140
|
tenants: options.config?.tenants
|
|
148
141
|
};
|
|
149
142
|
}
|
|
150
|
-
//
|
|
151
|
-
/**
|
|
152
|
-
* Get global enable company feature flag
|
|
153
|
-
*/ getEnableCompanyFeature() {
|
|
143
|
+
// Feature Flags
|
|
144
|
+
/** Get global enable company feature flag */ getEnableCompanyFeature() {
|
|
154
145
|
return this.authOptions.bootstrapAppConfig?.enableCompanyFeature ?? false;
|
|
155
146
|
}
|
|
156
|
-
/**
|
|
157
|
-
* Get enable company feature for specific tenant
|
|
158
|
-
* Falls back to global setting if not specified per-tenant
|
|
159
|
-
*/ getEnableCompanyFeatureForTenant(tenant) {
|
|
147
|
+
/** Get enable company feature for specific tenant. Falls back to global setting. */ getEnableCompanyFeatureForTenant(tenant) {
|
|
160
148
|
return tenant?.enableCompanyFeature ?? this.getEnableCompanyFeature();
|
|
161
149
|
}
|
|
162
|
-
/**
|
|
163
|
-
* Get enable company feature for current request context
|
|
164
|
-
*/ getEnableCompanyFeatureForCurrentTenant() {
|
|
150
|
+
/** Get enable company feature for current request context */ getEnableCompanyFeatureForCurrentTenant() {
|
|
165
151
|
return this.getEnableCompanyFeatureForTenant(this.getCurrentTenant() ?? undefined);
|
|
166
152
|
}
|
|
167
|
-
//
|
|
168
|
-
/**
|
|
169
|
-
* Get auth entities based on company feature flag
|
|
170
|
-
*/ async getAuthEntities(enableCompanyFeature) {
|
|
153
|
+
// Entity Management
|
|
154
|
+
/** Get auth entities based on company feature flag */ async getAuthEntities(enableCompanyFeature) {
|
|
171
155
|
const enable = enableCompanyFeature ?? this.getEnableCompanyFeature();
|
|
172
|
-
const { Company, CompanyBranch, User, UserCompanyPermission } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("../entities")));
|
|
156
|
+
const { Company, CompanyBranch, User, UserCompanyPermission, UserToken, PendingRegistration } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("../entities")));
|
|
173
157
|
const coreEntities = [
|
|
174
|
-
User
|
|
158
|
+
User,
|
|
159
|
+
UserToken,
|
|
160
|
+
PendingRegistration
|
|
175
161
|
];
|
|
176
162
|
const companyEntities = enable ? [
|
|
177
163
|
Company,
|
|
@@ -183,10 +169,8 @@ let AuthDataSourceProvider = class AuthDataSourceProvider extends _modules.Multi
|
|
|
183
169
|
...companyEntities
|
|
184
170
|
];
|
|
185
171
|
}
|
|
186
|
-
//
|
|
187
|
-
/**
|
|
188
|
-
* Override to dynamically set entities based on tenant config
|
|
189
|
-
*/ async createDataSourceFromConfig(config) {
|
|
172
|
+
// Overrides
|
|
173
|
+
/** Override to dynamically set entities based on tenant config */ async createDataSourceFromConfig(config) {
|
|
190
174
|
const currentTenant = this.getCurrentTenant();
|
|
191
175
|
const enableCompanyFeature = this.getEnableCompanyFeatureForTenant(currentTenant ?? undefined);
|
|
192
176
|
const entities = await this.getAuthEntities(enableCompanyFeature);
|