@flusys/nestjs-auth 1.1.0-beta → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +590 -473
- package/cjs/config/auth.constants.js +8 -0
- package/cjs/controllers/authentication.controller.js +218 -39
- 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 +68 -15
- package/cjs/docs/auth-swagger.config.js +14 -7
- package/cjs/dtos/authentication.dto.js +16 -43
- 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/user-company-permission.entity.js +0 -6
- 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 +1 -1
- package/cjs/modules/auth.module.js +63 -147
- package/cjs/services/auth-config.service.js +22 -28
- package/cjs/services/auth-datasource.provider.js +9 -21
- package/cjs/services/auth-email.service.js +89 -85
- package/cjs/services/authentication.service.js +225 -396
- package/cjs/services/branch.service.js +9 -4
- package/cjs/services/company-selection-session.service.js +1 -1
- package/cjs/services/user-permission.service.js +43 -73
- package/cjs/services/user.service.js +78 -64
- package/cjs/strategies/jwt.strategy.js +44 -44
- package/cjs/validators/password.validator.js +45 -0
- package/config/auth.constants.d.ts +2 -0
- package/controllers/authentication.controller.d.ts +5 -3
- package/controllers/company-selection.controller.d.ts +6 -2
- package/controllers/user-permission.controller.d.ts +5 -4
- package/controllers/user.controller.d.ts +6 -0
- package/dtos/authentication.dto.d.ts +0 -4
- 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 -3
- package/entities/index.d.ts +3 -3
- package/entities/user-company-permission.entity.d.ts +0 -2
- package/fesm/config/auth.constants.js +6 -13
- package/fesm/controllers/authentication.controller.js +221 -42
- 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 +71 -18
- package/fesm/docs/auth-swagger.config.js +14 -7
- package/fesm/dtos/authentication.dto.js +17 -44
- 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/user-company-permission.entity.js +0 -6
- 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 +1 -1
- package/fesm/modules/auth.module.js +64 -148
- package/fesm/services/auth-config.service.js +23 -29
- package/fesm/services/auth-datasource.provider.js +10 -22
- package/fesm/services/auth-email.service.js +90 -86
- package/fesm/services/authentication.service.js +225 -396
- package/fesm/services/branch.service.js +9 -4
- package/fesm/services/company-selection-session.service.js +1 -1
- package/fesm/services/user-permission.service.js +44 -74
- package/fesm/services/user.service.js +79 -65
- 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/authentication.interface.d.ts +0 -4
- package/interfaces/user-enricher.interface.d.ts +1 -4
- package/modules/auth.module.d.ts +6 -3
- package/package.json +4 -4
- package/services/auth-config.service.d.ts +6 -8
- package/services/auth-datasource.provider.d.ts +2 -4
- package/services/auth-email.service.d.ts +8 -4
- package/services/authentication.service.d.ts +15 -16
- package/services/company-selection-session.service.d.ts +1 -1
- package/services/user-permission.service.d.ts +6 -9
- package/services/user.service.d.ts +3 -0
- 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
|
@@ -23,12 +23,15 @@ export declare class UserController extends UserController_base {
|
|
|
23
23
|
};
|
|
24
24
|
}>;
|
|
25
25
|
verifyEmail(id: string): Promise<{
|
|
26
|
+
success: boolean;
|
|
26
27
|
message: string;
|
|
27
28
|
}>;
|
|
28
29
|
verifyPhone(id: string): Promise<{
|
|
30
|
+
success: boolean;
|
|
29
31
|
message: string;
|
|
30
32
|
}>;
|
|
31
33
|
updateStatus(id: string, isActive: boolean, user: ILoggedUserInfo): Promise<{
|
|
34
|
+
success: boolean;
|
|
32
35
|
message: string;
|
|
33
36
|
}>;
|
|
34
37
|
profile(dto: UpdateUserDto, user: ILoggedUserInfo): Promise<{
|
|
@@ -38,10 +41,12 @@ export declare class UserController extends UserController_base {
|
|
|
38
41
|
}>;
|
|
39
42
|
getProfileSections(): Promise<{
|
|
40
43
|
success: boolean;
|
|
44
|
+
message: string;
|
|
41
45
|
data: import("..").IProfileSection[];
|
|
42
46
|
}>;
|
|
43
47
|
getProfileSectionData(id: string, sectionId: string, user: ILoggedUserInfo): Promise<{
|
|
44
48
|
success: boolean;
|
|
49
|
+
message: string;
|
|
45
50
|
data: import("..").IProfileSectionData;
|
|
46
51
|
}>;
|
|
47
52
|
updateProfileSection(id: string, sectionId: string, data: Record<string, any>, user: ILoggedUserInfo): Promise<{
|
|
@@ -50,6 +55,7 @@ export declare class UserController extends UserController_base {
|
|
|
50
55
|
}>;
|
|
51
56
|
getProfileCompletion(id: string, user: ILoggedUserInfo): Promise<{
|
|
52
57
|
success: boolean;
|
|
58
|
+
message: string;
|
|
53
59
|
data: {
|
|
54
60
|
completionPercentage: number;
|
|
55
61
|
};
|
|
@@ -51,14 +51,12 @@ export declare class BranchSelectionInfoDto {
|
|
|
51
51
|
id: string;
|
|
52
52
|
name: string;
|
|
53
53
|
slug: string;
|
|
54
|
-
isActive: boolean;
|
|
55
54
|
logoId?: string | null;
|
|
56
55
|
}
|
|
57
56
|
export declare class CompanyWithBranchesDto {
|
|
58
57
|
id: string;
|
|
59
58
|
name: string;
|
|
60
59
|
slug: string;
|
|
61
|
-
isActive: boolean;
|
|
62
60
|
logoId?: string | null;
|
|
63
61
|
branches: BranchSelectionInfoDto[];
|
|
64
62
|
}
|
|
@@ -68,7 +66,6 @@ export declare class LoginResponseDto {
|
|
|
68
66
|
user: UserInfoDto;
|
|
69
67
|
company?: CompanyInfoDto;
|
|
70
68
|
branch?: BranchInfoDto;
|
|
71
|
-
companyFeatureEnabled?: boolean;
|
|
72
69
|
}
|
|
73
70
|
export declare class RegistrationResponseDto {
|
|
74
71
|
accessToken?: string;
|
|
@@ -76,7 +73,6 @@ export declare class RegistrationResponseDto {
|
|
|
76
73
|
user: UserInfoDto;
|
|
77
74
|
company?: CompanyInfoDto;
|
|
78
75
|
branch?: BranchInfoDto;
|
|
79
|
-
companyFeatureEnabled?: boolean;
|
|
80
76
|
requiresEmailVerification?: boolean;
|
|
81
77
|
}
|
|
82
78
|
export declare class MeResponseDto {
|
package/dtos/company.dto.d.ts
CHANGED
package/dtos/index.d.ts
CHANGED
|
@@ -26,8 +26,12 @@ export declare class UserBranchPermissionDto {
|
|
|
26
26
|
companyId: string;
|
|
27
27
|
}
|
|
28
28
|
export declare class UserCompaniesResponseDto {
|
|
29
|
+
success: boolean;
|
|
30
|
+
message: string;
|
|
29
31
|
data: UserCompanyPermissionDto[];
|
|
30
32
|
}
|
|
31
33
|
export declare class UserBranchesResponseDto {
|
|
34
|
+
success: boolean;
|
|
35
|
+
message: string;
|
|
32
36
|
data: UserBranchPermissionDto[];
|
|
33
37
|
}
|
package/dtos/user.dto.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
export declare class CreateUserDto {
|
|
2
|
-
[key: string]: unknown;
|
|
3
2
|
name: string;
|
|
4
3
|
phone?: string;
|
|
5
4
|
email: string;
|
|
@@ -11,12 +10,12 @@ export declare class CreateUserDto {
|
|
|
11
10
|
additionalFields?: Record<string, any>;
|
|
12
11
|
}
|
|
13
12
|
export declare class UpdateUserDto {
|
|
14
|
-
[key: string]: unknown;
|
|
15
13
|
id: string;
|
|
16
14
|
name?: string;
|
|
17
15
|
phone?: string;
|
|
18
16
|
email?: string;
|
|
19
17
|
password?: string;
|
|
18
|
+
oldPassword?: string;
|
|
20
19
|
profilePictureId?: string;
|
|
21
20
|
isActive?: boolean;
|
|
22
21
|
emailVerified?: boolean;
|
|
@@ -24,7 +23,6 @@ export declare class UpdateUserDto {
|
|
|
24
23
|
additionalFields?: Record<string, any>;
|
|
25
24
|
}
|
|
26
25
|
export declare class UserResponseDto {
|
|
27
|
-
[key: string]: unknown;
|
|
28
26
|
id: string;
|
|
29
27
|
name: string;
|
|
30
28
|
phone?: string | null;
|
package/entities/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { PendingRegistration } from './pending-registration.entity';
|
|
|
10
10
|
import { UserCompanyPermission } from './user-company-permission.entity';
|
|
11
11
|
import { User } from './user.entity';
|
|
12
12
|
import { UserToken } from './user-token.entity';
|
|
13
|
-
export declare const CoreEntities: (typeof PendingRegistration | typeof
|
|
13
|
+
export declare const CoreEntities: (typeof PendingRegistration | typeof UserToken | typeof User)[];
|
|
14
14
|
export declare const CompanyEntities: (typeof Company | typeof CompanyBranch | typeof UserCompanyPermission)[];
|
|
15
|
-
export declare const AllEntities: (typeof
|
|
16
|
-
export declare function getEntitiesByConfig(enableCompanyFeature: boolean): (typeof
|
|
15
|
+
export declare const AllEntities: (typeof PendingRegistration | typeof UserToken | typeof User | typeof Company | typeof CompanyBranch | typeof UserCompanyPermission)[];
|
|
16
|
+
export declare function getEntitiesByConfig(enableCompanyFeature: boolean): (typeof PendingRegistration | typeof UserToken | typeof User | typeof Company | typeof CompanyBranch | typeof UserCompanyPermission)[];
|
|
@@ -1,18 +1,11 @@
|
|
|
1
1
|
// Injection Tokens
|
|
2
2
|
export const AUTH_MODULE_OPTIONS = 'AUTH_MODULE_OPTIONS';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* @example
|
|
10
|
-
* providers: [
|
|
11
|
-
* { provide: USER_ENRICHER, useClass: MyUserEnricher },
|
|
12
|
-
* ]
|
|
13
|
-
*/ export const USER_ENRICHER = 'USER_ENRICHER';
|
|
14
|
-
// Security Configuration
|
|
15
|
-
/** Bcrypt salt rounds (12 = ~250ms hashing time) */ export const BCRYPT_SALT_ROUNDS = 12;
|
|
3
|
+
export const USER_ENRICHER = 'USER_ENRICHER';
|
|
4
|
+
// Security
|
|
5
|
+
export const BCRYPT_SALT_ROUNDS = 12;
|
|
6
|
+
// Cookie
|
|
7
|
+
export const DEFAULT_COOKIE_NAME = 'fsn_refresh_token';
|
|
8
|
+
export const MAX_COOKIE_BYTES = 3500;
|
|
16
9
|
// Cache Key Prefixes
|
|
17
10
|
export const SESSION_CACHE_PREFIX = 'auth:session';
|
|
18
11
|
export const TOKEN_REVOKED_PREFIX = 'auth:revoked';
|
|
@@ -26,26 +26,39 @@ function _ts_param(paramIndex, decorator) {
|
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
28
|
import { CurrentUser } from '@flusys/nestjs-shared/decorators';
|
|
29
|
+
import { MessageResponseDto } from '@flusys/nestjs-shared/dtos';
|
|
29
30
|
import { JwtAuthGuard } from '@flusys/nestjs-shared/guards';
|
|
30
31
|
import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
|
|
31
|
-
import { Body, Controller,
|
|
32
|
-
import { ApiBearerAuth, ApiBody, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
|
32
|
+
import { Body, Controller, HttpCode, HttpStatus, Inject, Optional, Post, Req, UnauthorizedException, UseGuards, UseInterceptors } from '@nestjs/common';
|
|
33
|
+
import { ApiBearerAuth, ApiBody, ApiExtraModels, ApiHeader, ApiOperation, ApiResponse, ApiTags, getSchemaPath } from '@nestjs/swagger';
|
|
34
|
+
import { SkipThrottle, Throttle } from '@nestjs/throttler';
|
|
33
35
|
import { Request } from 'express';
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
36
|
+
import { DEFAULT_COOKIE_NAME } from '../config';
|
|
37
|
+
import { ChangePasswordDto, CompanySelectionResponseDto, ForgotPasswordDto, LoginDto, LoginResponseDto, LoginResponseRequiresEmailVerificationDto, MeResponseDto, RefreshTokenDto, RegistrationDto, RegistrationResponseDto, ResendVerificationDto, ResetPasswordDto, VerifyEmailDto } from '../dtos/authentication.dto';
|
|
36
38
|
import { ClearToken } from '../interceptors/clear-token.interceptor';
|
|
37
39
|
import { SetToken } from '../interceptors/set-token.interceptor';
|
|
38
40
|
import { AuthConfigService } from '../services/auth-config.service';
|
|
39
41
|
import { AuthEmailService } from '../services/auth-email.service';
|
|
40
42
|
import { AuthenticationService } from '../services/authentication.service';
|
|
41
43
|
export class AuthenticationController {
|
|
42
|
-
|
|
44
|
+
buildAuthUrl(req, path) {
|
|
43
45
|
const frontendUrl = this.authConfig?.getFrontendUrl();
|
|
44
46
|
return frontendUrl ? `${frontendUrl}${path}` : `${req.protocol}://${req.get('host')}${path}`;
|
|
45
47
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
get emailService() {
|
|
49
|
+
if (!this.authEmailService) {
|
|
50
|
+
throw new Error('Email service is not configured');
|
|
51
|
+
}
|
|
52
|
+
return this.authEmailService;
|
|
53
|
+
}
|
|
54
|
+
get isEmailEnabled() {
|
|
55
|
+
return !!this.authEmailService;
|
|
56
|
+
}
|
|
57
|
+
async login(loginDto) {
|
|
48
58
|
const result = await this.authService.login(loginDto.email, loginDto.password);
|
|
59
|
+
if ('requiresEmailVerification' in result) {
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
49
62
|
if ('requiresSelection' in result) {
|
|
50
63
|
return result;
|
|
51
64
|
}
|
|
@@ -54,14 +67,15 @@ export class AuthenticationController {
|
|
|
54
67
|
/** Register new user. If company feature enabled, can join/create company. */ async register(req, registrationDto) {
|
|
55
68
|
const verifyUrlBase = this.buildAuthUrl(req, '/auth/verify-email');
|
|
56
69
|
const result = await this.authService.register(registrationDto, verifyUrlBase);
|
|
57
|
-
return
|
|
58
|
-
...result,
|
|
59
|
-
companyFeatureEnabled: this.authConfig?.isCompanyFeatureEnabled() ?? false
|
|
60
|
-
};
|
|
70
|
+
return result;
|
|
61
71
|
}
|
|
62
|
-
/**
|
|
63
|
-
|
|
64
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Refresh access token.
|
|
74
|
+
* - **Browser**: Prefers HTTP-only cookie, falls back to body if cookie missing.
|
|
75
|
+
* - **API/Mobile**: Must send `refreshToken` in request body. Set header `x-client-type: mobile` or `x-client-type: api`.
|
|
76
|
+
*/ async refreshToken(req, refreshTokenDto) {
|
|
77
|
+
const cookieName = this.authConfig?.getRefreshTokenCookieName() ?? DEFAULT_COOKIE_NAME;
|
|
78
|
+
// Always check cookie first, then body - handles Browser, SSR, and Mobile/API
|
|
65
79
|
const refreshToken = req.cookies?.[cookieName] || refreshTokenDto.refreshToken;
|
|
66
80
|
if (!refreshToken) {
|
|
67
81
|
throw new UnauthorizedException('Refresh token is required');
|
|
@@ -78,63 +92,56 @@ export class AuthenticationController {
|
|
|
78
92
|
message: 'Logged out successfully'
|
|
79
93
|
};
|
|
80
94
|
}
|
|
81
|
-
|
|
95
|
+
async changePassword(userId, dto) {
|
|
82
96
|
await this.authService.changePassword(userId, dto.currentPassword, dto.newPassword);
|
|
83
97
|
return {
|
|
84
98
|
success: true,
|
|
85
99
|
message: 'Password changed successfully'
|
|
86
100
|
};
|
|
87
101
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (!this.authEmailService) {
|
|
102
|
+
async forgotPassword(req, dto) {
|
|
103
|
+
if (!this.isEmailEnabled) {
|
|
91
104
|
return {
|
|
92
105
|
success: false,
|
|
93
106
|
message: 'Email service is not configured'
|
|
94
107
|
};
|
|
95
108
|
}
|
|
96
|
-
return this.
|
|
109
|
+
return this.emailService.forgotPassword(dto.email, this.buildAuthUrl(req, '/auth/reset-password'));
|
|
97
110
|
}
|
|
98
|
-
|
|
99
|
-
if (!this.
|
|
111
|
+
async resetPassword(dto) {
|
|
112
|
+
if (!this.isEmailEnabled) {
|
|
100
113
|
return {
|
|
101
114
|
success: false,
|
|
102
115
|
message: 'Email service is not configured'
|
|
103
116
|
};
|
|
104
117
|
}
|
|
105
|
-
return this.
|
|
118
|
+
return this.emailService.resetPassword(dto.token, dto.newPassword);
|
|
106
119
|
}
|
|
107
|
-
|
|
108
|
-
if (!this.
|
|
120
|
+
async resendVerification(req, dto) {
|
|
121
|
+
if (!this.isEmailEnabled) {
|
|
109
122
|
return {
|
|
110
123
|
success: false,
|
|
111
124
|
message: 'Email service is not configured'
|
|
112
125
|
};
|
|
113
126
|
}
|
|
114
|
-
return this.
|
|
127
|
+
return this.emailService.resendVerificationEmail(dto.email, this.buildAuthUrl(req, '/auth/verify-email'));
|
|
115
128
|
}
|
|
116
129
|
/** Verify email - Verify email using token. Handles both pending registration and existing user verification. */ async verifyEmail(dto) {
|
|
117
|
-
if (!this.
|
|
130
|
+
if (!this.isEmailEnabled) {
|
|
118
131
|
return {
|
|
119
132
|
success: false,
|
|
120
133
|
message: 'Email service is not configured'
|
|
121
134
|
};
|
|
122
135
|
}
|
|
123
|
-
const result = await this.
|
|
136
|
+
const result = await this.emailService.verifyEmail(dto.token);
|
|
124
137
|
if (result.type === 'pending') {
|
|
125
|
-
|
|
126
|
-
const registrationResult = await this.authService.createUserFromPendingRegistration(result.pending);
|
|
127
|
-
return {
|
|
128
|
-
...registrationResult,
|
|
129
|
-
companyFeatureEnabled: this.authConfig?.isCompanyFeatureEnabled() ?? false
|
|
130
|
-
};
|
|
138
|
+
return this.authService.createUserFromPendingRegistration(result.pending);
|
|
131
139
|
}
|
|
132
140
|
return {
|
|
133
141
|
success: true,
|
|
134
142
|
message: result.message
|
|
135
143
|
};
|
|
136
144
|
}
|
|
137
|
-
// NOTE: @Inject() required for bundled code - type metadata may be lost during esbuild
|
|
138
145
|
constructor(authService, authConfig, authEmailService){
|
|
139
146
|
_define_property(this, "authService", void 0);
|
|
140
147
|
_define_property(this, "authConfig", void 0);
|
|
@@ -147,6 +154,12 @@ export class AuthenticationController {
|
|
|
147
154
|
_ts_decorate([
|
|
148
155
|
Post('login'),
|
|
149
156
|
HttpCode(HttpStatus.OK),
|
|
157
|
+
Throttle({
|
|
158
|
+
default: {
|
|
159
|
+
limit: 5,
|
|
160
|
+
ttl: 60000
|
|
161
|
+
}
|
|
162
|
+
}),
|
|
150
163
|
UseInterceptors(SetToken),
|
|
151
164
|
ApiOperation({
|
|
152
165
|
summary: 'User login'
|
|
@@ -154,13 +167,102 @@ _ts_decorate([
|
|
|
154
167
|
ApiBody({
|
|
155
168
|
type: LoginDto
|
|
156
169
|
}),
|
|
170
|
+
ApiExtraModels(LoginResponseDto, CompanySelectionResponseDto, LoginResponseRequiresEmailVerificationDto),
|
|
157
171
|
ApiResponse({
|
|
158
172
|
status: 200,
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
173
|
+
description: 'Login successful, requires company selection, or requires email verification',
|
|
174
|
+
content: {
|
|
175
|
+
'application/json': {
|
|
176
|
+
schema: {
|
|
177
|
+
oneOf: [
|
|
178
|
+
{
|
|
179
|
+
$ref: getSchemaPath(LoginResponseDto)
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
$ref: getSchemaPath(CompanySelectionResponseDto)
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
$ref: getSchemaPath(LoginResponseRequiresEmailVerificationDto)
|
|
186
|
+
}
|
|
187
|
+
]
|
|
188
|
+
},
|
|
189
|
+
examples: {
|
|
190
|
+
loginSuccess: {
|
|
191
|
+
summary: 'Login Success',
|
|
192
|
+
description: 'Successful login with tokens (company/branch included when company feature enabled)',
|
|
193
|
+
value: {
|
|
194
|
+
accessToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
|
|
195
|
+
refreshToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
|
|
196
|
+
user: {
|
|
197
|
+
id: '550e8400-e29b-41d4-a716-446655440000',
|
|
198
|
+
email: 'john@example.com',
|
|
199
|
+
name: 'John Doe'
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
loginSuccessWithCompany: {
|
|
204
|
+
summary: 'Login Success (Company Feature)',
|
|
205
|
+
description: 'Login with company/branch - only when company feature is enabled',
|
|
206
|
+
value: {
|
|
207
|
+
accessToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
|
|
208
|
+
refreshToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
|
|
209
|
+
user: {
|
|
210
|
+
id: '550e8400-e29b-41d4-a716-446655440000',
|
|
211
|
+
email: 'john@example.com',
|
|
212
|
+
name: 'John Doe'
|
|
213
|
+
},
|
|
214
|
+
company: {
|
|
215
|
+
id: '550e8400-e29b-41d4-a716-446655440001',
|
|
216
|
+
name: 'Acme Corporation',
|
|
217
|
+
slug: 'acme-corp'
|
|
218
|
+
},
|
|
219
|
+
branch: {
|
|
220
|
+
id: '550e8400-e29b-41d4-a716-446655440002',
|
|
221
|
+
name: 'Main Branch',
|
|
222
|
+
slug: 'main-branch'
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
companySelection: {
|
|
227
|
+
summary: 'Company Selection Required (Company Feature)',
|
|
228
|
+
description: 'User has multiple companies/branches - only when company feature is enabled',
|
|
229
|
+
value: {
|
|
230
|
+
requiresSelection: true,
|
|
231
|
+
sessionId: 'sess_abc123def456',
|
|
232
|
+
expiresAt: '2024-12-18T15:30:00.000Z',
|
|
233
|
+
user: {
|
|
234
|
+
id: '550e8400-e29b-41d4-a716-446655440000',
|
|
235
|
+
email: 'john@example.com',
|
|
236
|
+
name: 'John Doe'
|
|
237
|
+
},
|
|
238
|
+
companies: [
|
|
239
|
+
{
|
|
240
|
+
id: '550e8400-e29b-41d4-a716-446655440001',
|
|
241
|
+
name: 'Company A',
|
|
242
|
+
slug: 'company-a',
|
|
243
|
+
branches: [
|
|
244
|
+
{
|
|
245
|
+
id: '550e8400-e29b-41d4-a716-446655440010',
|
|
246
|
+
name: 'Branch 1',
|
|
247
|
+
slug: 'branch-1'
|
|
248
|
+
}
|
|
249
|
+
]
|
|
250
|
+
}
|
|
251
|
+
]
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
emailVerification: {
|
|
255
|
+
summary: 'Email Verification Required',
|
|
256
|
+
description: 'User must verify email before logging in',
|
|
257
|
+
value: {
|
|
258
|
+
requiresEmailVerification: true,
|
|
259
|
+
email: 'john@example.com',
|
|
260
|
+
message: 'Please verify your email address before logging in.'
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
164
266
|
}),
|
|
165
267
|
_ts_param(0, Body()),
|
|
166
268
|
_ts_metadata("design:type", Function),
|
|
@@ -172,6 +274,12 @@ _ts_decorate([
|
|
|
172
274
|
_ts_decorate([
|
|
173
275
|
Post('register'),
|
|
174
276
|
HttpCode(HttpStatus.CREATED),
|
|
277
|
+
Throttle({
|
|
278
|
+
default: {
|
|
279
|
+
limit: 3,
|
|
280
|
+
ttl: 60000
|
|
281
|
+
}
|
|
282
|
+
}),
|
|
175
283
|
UseInterceptors(SetToken),
|
|
176
284
|
ApiOperation({
|
|
177
285
|
summary: 'Register new user'
|
|
@@ -195,11 +303,50 @@ _ts_decorate([
|
|
|
195
303
|
_ts_decorate([
|
|
196
304
|
Post('refresh'),
|
|
197
305
|
HttpCode(HttpStatus.OK),
|
|
306
|
+
Throttle({
|
|
307
|
+
default: {
|
|
308
|
+
limit: 30,
|
|
309
|
+
ttl: 60000
|
|
310
|
+
}
|
|
311
|
+
}),
|
|
198
312
|
ApiOperation({
|
|
199
|
-
summary: 'Refresh access token'
|
|
313
|
+
summary: 'Refresh access token',
|
|
314
|
+
description: `
|
|
315
|
+
**Browser clients**: Refresh token is read from HTTP-only cookie (automatically set on login/register).
|
|
316
|
+
Falls back to request body if cookie is not available.
|
|
317
|
+
|
|
318
|
+
**API/Mobile clients**: Must send \`refreshToken\` in request body.
|
|
319
|
+
Set header \`x-client-type: mobile\` or \`x-client-type: api\` to identify as non-browser client.
|
|
320
|
+
|
|
321
|
+
**Client detection**: Uses \`x-client-type\` header first, then falls back to User-Agent detection.
|
|
322
|
+
`
|
|
323
|
+
}),
|
|
324
|
+
ApiHeader({
|
|
325
|
+
name: 'x-client-type',
|
|
326
|
+
required: false,
|
|
327
|
+
description: 'Client type: "browser"/"web" for cookie-based auth, "mobile"/"api" for body-based auth',
|
|
328
|
+
example: 'mobile'
|
|
200
329
|
}),
|
|
201
330
|
ApiBody({
|
|
202
|
-
type: RefreshTokenDto
|
|
331
|
+
type: RefreshTokenDto,
|
|
332
|
+
description: 'Required for API/Mobile clients. Browser clients prefer HTTP-only cookie with body fallback.'
|
|
333
|
+
}),
|
|
334
|
+
ApiResponse({
|
|
335
|
+
status: 200,
|
|
336
|
+
description: 'New access token',
|
|
337
|
+
schema: {
|
|
338
|
+
type: 'object',
|
|
339
|
+
properties: {
|
|
340
|
+
accessToken: {
|
|
341
|
+
type: 'string',
|
|
342
|
+
example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}),
|
|
347
|
+
ApiResponse({
|
|
348
|
+
status: 401,
|
|
349
|
+
description: 'Invalid or missing refresh token'
|
|
203
350
|
}),
|
|
204
351
|
_ts_param(0, Req()),
|
|
205
352
|
_ts_param(1, Body()),
|
|
@@ -212,7 +359,8 @@ _ts_decorate([
|
|
|
212
359
|
], AuthenticationController.prototype, "refreshToken", null);
|
|
213
360
|
_ts_decorate([
|
|
214
361
|
UseGuards(JwtAuthGuard),
|
|
215
|
-
|
|
362
|
+
Post('me'),
|
|
363
|
+
SkipThrottle(),
|
|
216
364
|
ApiBearerAuth(),
|
|
217
365
|
ApiOperation({
|
|
218
366
|
summary: 'Get current user info'
|
|
@@ -232,6 +380,7 @@ _ts_decorate([
|
|
|
232
380
|
UseGuards(JwtAuthGuard),
|
|
233
381
|
Post('logout'),
|
|
234
382
|
HttpCode(HttpStatus.OK),
|
|
383
|
+
SkipThrottle(),
|
|
235
384
|
UseInterceptors(ClearToken),
|
|
236
385
|
ApiBearerAuth(),
|
|
237
386
|
ApiOperation({
|
|
@@ -252,6 +401,12 @@ _ts_decorate([
|
|
|
252
401
|
UseGuards(JwtAuthGuard),
|
|
253
402
|
Post('change-password'),
|
|
254
403
|
HttpCode(HttpStatus.OK),
|
|
404
|
+
Throttle({
|
|
405
|
+
default: {
|
|
406
|
+
limit: 5,
|
|
407
|
+
ttl: 60000
|
|
408
|
+
}
|
|
409
|
+
}),
|
|
255
410
|
ApiBearerAuth(),
|
|
256
411
|
ApiOperation({
|
|
257
412
|
summary: 'Change password'
|
|
@@ -275,6 +430,12 @@ _ts_decorate([
|
|
|
275
430
|
_ts_decorate([
|
|
276
431
|
Post('forgot-password'),
|
|
277
432
|
HttpCode(HttpStatus.OK),
|
|
433
|
+
Throttle({
|
|
434
|
+
default: {
|
|
435
|
+
limit: 3,
|
|
436
|
+
ttl: 300000
|
|
437
|
+
}
|
|
438
|
+
}),
|
|
278
439
|
ApiOperation({
|
|
279
440
|
summary: 'Request password reset email'
|
|
280
441
|
}),
|
|
@@ -297,6 +458,12 @@ _ts_decorate([
|
|
|
297
458
|
_ts_decorate([
|
|
298
459
|
Post('reset-password'),
|
|
299
460
|
HttpCode(HttpStatus.OK),
|
|
461
|
+
Throttle({
|
|
462
|
+
default: {
|
|
463
|
+
limit: 5,
|
|
464
|
+
ttl: 900000
|
|
465
|
+
}
|
|
466
|
+
}),
|
|
300
467
|
ApiOperation({
|
|
301
468
|
summary: 'Reset password using token'
|
|
302
469
|
}),
|
|
@@ -317,6 +484,12 @@ _ts_decorate([
|
|
|
317
484
|
_ts_decorate([
|
|
318
485
|
Post('resend-verification'),
|
|
319
486
|
HttpCode(HttpStatus.OK),
|
|
487
|
+
Throttle({
|
|
488
|
+
default: {
|
|
489
|
+
limit: 3,
|
|
490
|
+
ttl: 300000
|
|
491
|
+
}
|
|
492
|
+
}),
|
|
320
493
|
ApiOperation({
|
|
321
494
|
summary: 'Resend email verification link'
|
|
322
495
|
}),
|
|
@@ -339,6 +512,12 @@ _ts_decorate([
|
|
|
339
512
|
_ts_decorate([
|
|
340
513
|
Post('verify-email'),
|
|
341
514
|
HttpCode(HttpStatus.OK),
|
|
515
|
+
Throttle({
|
|
516
|
+
default: {
|
|
517
|
+
limit: 10,
|
|
518
|
+
ttl: 60000
|
|
519
|
+
}
|
|
520
|
+
}),
|
|
342
521
|
UseInterceptors(SetToken),
|
|
343
522
|
ApiOperation({
|
|
344
523
|
summary: 'Verify email using token'
|
|
@@ -25,13 +25,56 @@ function _ts_param(paramIndex, decorator) {
|
|
|
25
25
|
decorator(target, key, paramIndex);
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
-
import { createApiController } from '@flusys/nestjs-shared/classes';
|
|
28
|
+
import { createApiController, BRANCH_PERMISSIONS } from '@flusys/nestjs-shared/classes';
|
|
29
29
|
import { Controller, Inject } from '@nestjs/common';
|
|
30
30
|
import { ApiTags } from '@nestjs/swagger';
|
|
31
31
|
import { CompanyBranchResponseDto, CreateCompanyBranchDto, UpdateCompanyBranchDto } from '../dtos/company-branch.dto';
|
|
32
32
|
import { BranchService } from '../services/branch.service';
|
|
33
33
|
export class BranchController extends createApiController(CreateCompanyBranchDto, UpdateCompanyBranchDto, CompanyBranchResponseDto, {
|
|
34
|
-
security:
|
|
34
|
+
security: {
|
|
35
|
+
insert: {
|
|
36
|
+
level: 'permission',
|
|
37
|
+
permissions: [
|
|
38
|
+
BRANCH_PERMISSIONS.CREATE
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
insertMany: {
|
|
42
|
+
level: 'permission',
|
|
43
|
+
permissions: [
|
|
44
|
+
BRANCH_PERMISSIONS.CREATE
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
getById: {
|
|
48
|
+
level: 'permission',
|
|
49
|
+
permissions: [
|
|
50
|
+
BRANCH_PERMISSIONS.READ
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
getAll: {
|
|
54
|
+
level: 'permission',
|
|
55
|
+
permissions: [
|
|
56
|
+
BRANCH_PERMISSIONS.READ
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
update: {
|
|
60
|
+
level: 'permission',
|
|
61
|
+
permissions: [
|
|
62
|
+
BRANCH_PERMISSIONS.UPDATE
|
|
63
|
+
]
|
|
64
|
+
},
|
|
65
|
+
updateMany: {
|
|
66
|
+
level: 'permission',
|
|
67
|
+
permissions: [
|
|
68
|
+
BRANCH_PERMISSIONS.UPDATE
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
delete: {
|
|
72
|
+
level: 'permission',
|
|
73
|
+
permissions: [
|
|
74
|
+
BRANCH_PERMISSIONS.DELETE
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
}
|
|
35
78
|
}) {
|
|
36
79
|
constructor(branchService){
|
|
37
80
|
super(branchService), _define_property(this, "branchService", void 0), this.branchService = branchService;
|