@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.
- package/README.md +590 -473
- package/cjs/config/auth.constants.js +12 -0
- package/cjs/controllers/authentication.controller.js +365 -24
- package/cjs/controllers/branch.controller.js +44 -1
- package/cjs/controllers/company-selection.controller.js +27 -20
- package/cjs/controllers/company.controller.js +44 -1
- package/cjs/controllers/user-permission.controller.js +82 -154
- package/cjs/controllers/user.controller.js +211 -7
- package/cjs/docs/auth-swagger.config.js +14 -7
- package/cjs/dtos/authentication.dto.js +114 -38
- package/cjs/dtos/index.js +0 -1
- package/cjs/dtos/user-permission.dto.js +28 -0
- package/cjs/dtos/user.dto.js +19 -4
- package/cjs/entities/index.js +5 -1
- package/cjs/entities/pending-registration.entity.js +125 -0
- package/cjs/entities/user-company-permission.entity.js +3 -9
- package/cjs/entities/user-token.entity.js +129 -0
- package/cjs/interceptors/clear-token.interceptor.js +10 -23
- package/cjs/interceptors/set-token.interceptor.js +15 -55
- package/cjs/interfaces/auth-email-provider.interface.js +45 -0
- package/cjs/interfaces/index.js +2 -0
- package/cjs/interfaces/user-enricher.interface.js +4 -0
- package/cjs/modules/auth.module.js +65 -143
- package/cjs/services/auth-config.service.js +25 -27
- package/cjs/services/auth-datasource.provider.js +13 -23
- package/cjs/services/auth-email.service.js +456 -0
- package/cjs/services/authentication.service.js +297 -349
- package/cjs/services/branch.service.js +9 -4
- package/cjs/services/company-selection-session.service.js +1 -1
- package/cjs/services/index.js +1 -0
- package/cjs/services/user-permission.service.js +43 -73
- package/cjs/services/user.service.js +184 -34
- package/cjs/strategies/jwt.strategy.js +44 -44
- package/cjs/validators/password.validator.js +45 -0
- package/config/auth.constants.d.ts +3 -0
- package/controllers/authentication.controller.d.ts +16 -10
- package/controllers/company-selection.controller.d.ts +6 -2
- package/controllers/user-permission.controller.d.ts +5 -4
- package/controllers/user.controller.d.ts +33 -0
- package/dtos/authentication.dto.d.ts +20 -6
- package/dtos/company-branch.dto.d.ts +0 -1
- package/dtos/company.dto.d.ts +0 -1
- package/dtos/index.d.ts +0 -1
- package/dtos/user-permission.dto.d.ts +4 -0
- package/dtos/user.dto.d.ts +1 -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 +1 -3
- package/entities/user-token.entity.d.ts +15 -0
- package/fesm/config/auth.constants.js +6 -2
- package/fesm/controllers/authentication.controller.js +368 -27
- package/fesm/controllers/branch.controller.js +45 -2
- package/fesm/controllers/company-selection.controller.js +28 -21
- package/fesm/controllers/company.controller.js +45 -2
- package/fesm/controllers/user-permission.controller.js +84 -156
- package/fesm/controllers/user.controller.js +214 -10
- package/fesm/docs/auth-swagger.config.js +14 -7
- package/fesm/dtos/authentication.dto.js +101 -39
- package/fesm/dtos/index.js +0 -1
- package/fesm/dtos/user-permission.dto.js +28 -0
- package/fesm/dtos/user.dto.js +20 -5
- package/fesm/entities/index.js +7 -1
- package/fesm/entities/pending-registration.entity.js +115 -0
- package/fesm/entities/user-company-permission.entity.js +3 -9
- package/fesm/entities/user-token.entity.js +113 -0
- package/fesm/interceptors/clear-token.interceptor.js +9 -22
- package/fesm/interceptors/set-token.interceptor.js +14 -54
- package/fesm/interfaces/auth-email-provider.interface.js +38 -0
- package/fesm/interfaces/authentication.interface.js +1 -1
- package/fesm/interfaces/index.js +2 -0
- package/fesm/interfaces/user-enricher.interface.js +26 -0
- package/fesm/modules/auth.module.js +67 -145
- package/fesm/services/auth-config.service.js +26 -28
- package/fesm/services/auth-datasource.provider.js +14 -24
- package/fesm/services/auth-email.service.js +405 -0
- package/fesm/services/authentication.service.js +297 -349
- package/fesm/services/branch.service.js +9 -4
- package/fesm/services/company-selection-session.service.js +1 -1
- package/fesm/services/index.js +1 -0
- package/fesm/services/user-permission.service.js +44 -74
- package/fesm/services/user.service.js +186 -36
- package/fesm/strategies/jwt.strategy.js +44 -44
- package/fesm/validators/password.validator.js +48 -0
- package/interceptors/clear-token.interceptor.d.ts +0 -1
- package/interceptors/set-token.interceptor.d.ts +1 -1
- package/interfaces/auth-email-provider.interface.d.ts +6 -0
- package/interfaces/auth-module-options.interface.d.ts +4 -1
- package/interfaces/authentication.interface.d.ts +13 -3
- 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 +6 -3
- package/package.json +4 -4
- package/services/auth-config.service.d.ts +7 -8
- package/services/auth-datasource.provider.d.ts +2 -4
- package/services/auth-email.service.d.ts +54 -0
- package/services/authentication.service.d.ts +27 -16
- package/services/company-selection-session.service.d.ts +1 -1
- package/services/index.d.ts +1 -0
- package/services/user-permission.service.d.ts +6 -9
- package/services/user.service.d.ts +19 -3
- package/strategies/jwt.strategy.d.ts +4 -1
- package/validators/password.validator.d.ts +10 -0
- package/cjs/dtos/user-company-permission.dto.js +0 -110
- package/dtos/user-company-permission.dto.d.ts +0 -10
- package/fesm/dtos/user-company-permission.dto.js +0 -98
|
@@ -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
|
-
|
|
41
|
-
|
|
41
|
+
if (isBrowserRequest(req)) {
|
|
42
|
+
res.clearCookie(this.cookieName, {
|
|
43
|
+
httpOnly: true,
|
|
44
|
+
path: '/',
|
|
45
|
+
...buildCookieOptions(req)
|
|
46
|
+
});
|
|
47
|
+
}
|
|
42
48
|
return value;
|
|
43
49
|
}));
|
|
44
50
|
}
|
|
45
|
-
/**
|
|
46
|
-
* Clear refresh token cookie
|
|
47
|
-
*/ clearRefreshTokenCookie(req, res) {
|
|
48
|
-
const origin = req.headers.origin || '';
|
|
49
|
-
const hostname = req.hostname || '';
|
|
50
|
-
const isHttps = origin.startsWith('https://') || req.secure;
|
|
51
|
-
let cookieDomain = hostname;
|
|
52
|
-
const domainParts = hostname.split('.');
|
|
53
|
-
if (domainParts.length > 2) {
|
|
54
|
-
cookieDomain = '.' + domainParts.slice(-2).join('.');
|
|
55
|
-
}
|
|
56
|
-
res.clearCookie(this.cookieName, {
|
|
57
|
-
httpOnly: true,
|
|
58
|
-
secure: isHttps,
|
|
59
|
-
sameSite: isHttps ? 'strict' : 'lax',
|
|
60
|
-
domain: cookieDomain,
|
|
61
|
-
path: '/'
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
51
|
constructor(authConfig){
|
|
65
52
|
_define_property(this, "authConfig", void 0);
|
|
66
53
|
this.authConfig = authConfig;
|
|
@@ -25,84 +25,43 @@ function _ts_param(paramIndex, decorator) {
|
|
|
25
25
|
decorator(target, key, paramIndex);
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
-
import {
|
|
28
|
+
import { buildCookieOptions, isBrowserRequest, parseDurationToMs } from '@flusys/nestjs-shared/utils';
|
|
29
|
+
import { Inject, Injectable, Optional, PayloadTooLargeException } from '@nestjs/common';
|
|
29
30
|
import { Buffer } from 'buffer';
|
|
30
31
|
import { catchError, map, throwError } from 'rxjs';
|
|
32
|
+
import { DEFAULT_COOKIE_NAME, MAX_COOKIE_BYTES } from '../config';
|
|
31
33
|
import { AuthConfigService } from '../services/auth-config.service';
|
|
32
|
-
const DEFAULT_COOKIE_NAME = 'fsn_refresh_token';
|
|
33
|
-
const MAX_COOKIE_BYTES = 3500;
|
|
34
34
|
export class SetToken {
|
|
35
35
|
get cookieName() {
|
|
36
36
|
return this.authConfig?.getRefreshTokenCookieName() ?? DEFAULT_COOKIE_NAME;
|
|
37
37
|
}
|
|
38
|
+
get maxAgeMs() {
|
|
39
|
+
return parseDurationToMs(this.authConfig?.getRefreshTokenExpiration() ?? '7d');
|
|
40
|
+
}
|
|
38
41
|
intercept(context, next) {
|
|
39
42
|
const req = context.switchToHttp().getRequest();
|
|
40
43
|
const res = context.switchToHttp().getResponse();
|
|
41
44
|
return next.handle().pipe(map((value)=>{
|
|
42
|
-
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
const isBrowser = this.isBrowserRequest(req);
|
|
47
|
-
const tokenValue = value.refreshToken;
|
|
48
|
-
if (isBrowser) {
|
|
49
|
-
// Browser: Set cookie and remove from response body
|
|
50
|
-
this.setRefreshTokenCookie(req, res, tokenValue);
|
|
51
|
-
// Return response without refreshToken for browsers
|
|
45
|
+
if (!value?.refreshToken) return value;
|
|
46
|
+
if (isBrowserRequest(req)) {
|
|
47
|
+
this.setRefreshTokenCookie(req, res, value.refreshToken);
|
|
52
48
|
const { refreshToken, ...responseWithoutToken } = value;
|
|
53
49
|
return responseWithoutToken;
|
|
54
50
|
}
|
|
55
|
-
// API/Mobile: Keep refreshToken in response body
|
|
56
51
|
return value;
|
|
57
52
|
}), catchError((err)=>throwError(()=>err)));
|
|
58
53
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
* Checks Accept header and custom X-Client-Type header
|
|
62
|
-
*/ isBrowserRequest(req) {
|
|
63
|
-
// Check custom header first (allows explicit client identification)
|
|
64
|
-
const clientType = req.headers['x-client-type'];
|
|
65
|
-
if (clientType) {
|
|
66
|
-
return clientType.toLowerCase() === 'browser' || clientType.toLowerCase() === 'web';
|
|
67
|
-
}
|
|
68
|
-
// Check Accept header - browsers typically include text/html
|
|
69
|
-
const accept = req.headers['accept'] || '';
|
|
70
|
-
if (accept.includes('text/html')) {
|
|
71
|
-
return true;
|
|
72
|
-
}
|
|
73
|
-
// Check User-Agent for common browsers
|
|
74
|
-
const userAgent = req.headers['user-agent'] || '';
|
|
75
|
-
const browserPatterns = /mozilla|chrome|safari|firefox|edge|opera|msie/i;
|
|
76
|
-
if (browserPatterns.test(userAgent) && !userAgent.includes('Postman')) {
|
|
77
|
-
return true;
|
|
78
|
-
}
|
|
79
|
-
return false;
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Set refresh token in HTTP-only cookie
|
|
83
|
-
*/ setRefreshTokenCookie(req, res, tokenValue) {
|
|
84
|
-
const origin = req.headers.origin || '';
|
|
85
|
-
const hostname = req.hostname || '';
|
|
86
|
-
const isHttps = origin.startsWith('https://') || req.secure;
|
|
87
|
-
let cookieDomain = hostname;
|
|
88
|
-
const domainParts = hostname.split('.');
|
|
89
|
-
if (domainParts.length > 2) {
|
|
90
|
-
cookieDomain = '.' + domainParts.slice(-2).join('.');
|
|
91
|
-
}
|
|
92
|
-
// Check cookie size
|
|
93
|
-
const rawCookieString = `${this.cookieName}=${tokenValue}; HttpOnly; Domain=${cookieDomain}`;
|
|
54
|
+
setRefreshTokenCookie(req, res, tokenValue) {
|
|
55
|
+
const rawCookieString = `${this.cookieName}=${tokenValue}; HttpOnly`;
|
|
94
56
|
const estimatedBytes = Buffer.byteLength(rawCookieString, 'utf8');
|
|
95
57
|
if (estimatedBytes > MAX_COOKIE_BYTES) {
|
|
96
58
|
throw new PayloadTooLargeException(`Refresh token too large (≈${estimatedBytes} bytes)`);
|
|
97
59
|
}
|
|
98
|
-
// Set HTTP-only secure cookie
|
|
99
60
|
res.cookie(this.cookieName, tokenValue, {
|
|
100
61
|
httpOnly: true,
|
|
101
|
-
secure: isHttps,
|
|
102
|
-
sameSite: isHttps ? 'strict' : 'lax',
|
|
103
|
-
domain: cookieDomain,
|
|
104
62
|
path: '/',
|
|
105
|
-
maxAge:
|
|
63
|
+
maxAge: this.maxAgeMs,
|
|
64
|
+
...buildCookieOptions(req)
|
|
106
65
|
});
|
|
107
66
|
}
|
|
108
67
|
constructor(authConfig){
|
|
@@ -113,6 +72,7 @@ export class SetToken {
|
|
|
113
72
|
SetToken = _ts_decorate([
|
|
114
73
|
Injectable(),
|
|
115
74
|
_ts_param(0, Optional()),
|
|
75
|
+
_ts_param(0, Inject(AuthConfigService)),
|
|
116
76
|
_ts_metadata("design:type", Function),
|
|
117
77
|
_ts_metadata("design:paramtypes", [
|
|
118
78
|
typeof AuthConfigService === "undefined" ? Object : AuthConfigService
|
|
@@ -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
|
|
1
|
+
/** Login Response when email verification is required */ export { };
|
package/fesm/interfaces/index.js
CHANGED
|
@@ -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
|
|
8
|
+
import { Module } from '@nestjs/common';
|
|
9
9
|
import { JwtModule } from '@nestjs/jwt';
|
|
10
10
|
import { PassportModule } from '@nestjs/passport';
|
|
11
|
-
import {
|
|
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
|
-
|
|
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(
|
|
27
|
-
const
|
|
28
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
|
95
|
-
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
if (includeController)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
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
|
-
|
|
228
|
-
|
|
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
|
-
|
|
253
|
-
...coreEntities,
|
|
254
|
-
...companyEntities
|
|
175
|
+
] : [
|
|
176
|
+
User
|
|
255
177
|
];
|
|
256
178
|
}
|
|
257
179
|
}
|