@flusys/nestjs-auth 1.1.0-beta → 2.0.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +590 -473
  2. package/cjs/config/auth.constants.js +8 -0
  3. package/cjs/controllers/authentication.controller.js +218 -39
  4. package/cjs/controllers/branch.controller.js +44 -1
  5. package/cjs/controllers/company-selection.controller.js +27 -20
  6. package/cjs/controllers/company.controller.js +44 -1
  7. package/cjs/controllers/user-permission.controller.js +82 -154
  8. package/cjs/controllers/user.controller.js +68 -15
  9. package/cjs/docs/auth-swagger.config.js +14 -7
  10. package/cjs/dtos/authentication.dto.js +16 -43
  11. package/cjs/dtos/index.js +0 -1
  12. package/cjs/dtos/user-permission.dto.js +28 -0
  13. package/cjs/dtos/user.dto.js +19 -4
  14. package/cjs/entities/user-company-permission.entity.js +0 -6
  15. package/cjs/interceptors/clear-token.interceptor.js +10 -23
  16. package/cjs/interceptors/set-token.interceptor.js +15 -55
  17. package/cjs/interfaces/auth-email-provider.interface.js +1 -1
  18. package/cjs/modules/auth.module.js +63 -147
  19. package/cjs/services/auth-config.service.js +22 -28
  20. package/cjs/services/auth-datasource.provider.js +9 -21
  21. package/cjs/services/auth-email.service.js +89 -85
  22. package/cjs/services/authentication.service.js +225 -396
  23. package/cjs/services/branch.service.js +9 -4
  24. package/cjs/services/company-selection-session.service.js +1 -1
  25. package/cjs/services/user-permission.service.js +43 -73
  26. package/cjs/services/user.service.js +78 -64
  27. package/cjs/strategies/jwt.strategy.js +44 -44
  28. package/cjs/validators/password.validator.js +45 -0
  29. package/config/auth.constants.d.ts +2 -0
  30. package/controllers/authentication.controller.d.ts +5 -3
  31. package/controllers/company-selection.controller.d.ts +6 -2
  32. package/controllers/user-permission.controller.d.ts +5 -4
  33. package/controllers/user.controller.d.ts +6 -0
  34. package/dtos/authentication.dto.d.ts +0 -4
  35. package/dtos/company-branch.dto.d.ts +0 -1
  36. package/dtos/company.dto.d.ts +0 -1
  37. package/dtos/index.d.ts +0 -1
  38. package/dtos/user-permission.dto.d.ts +4 -0
  39. package/dtos/user.dto.d.ts +1 -3
  40. package/entities/index.d.ts +3 -3
  41. package/entities/user-company-permission.entity.d.ts +0 -2
  42. package/fesm/config/auth.constants.js +6 -13
  43. package/fesm/controllers/authentication.controller.js +221 -42
  44. package/fesm/controllers/branch.controller.js +45 -2
  45. package/fesm/controllers/company-selection.controller.js +28 -21
  46. package/fesm/controllers/company.controller.js +45 -2
  47. package/fesm/controllers/user-permission.controller.js +84 -156
  48. package/fesm/controllers/user.controller.js +71 -18
  49. package/fesm/docs/auth-swagger.config.js +14 -7
  50. package/fesm/dtos/authentication.dto.js +17 -44
  51. package/fesm/dtos/index.js +0 -1
  52. package/fesm/dtos/user-permission.dto.js +28 -0
  53. package/fesm/dtos/user.dto.js +20 -5
  54. package/fesm/entities/user-company-permission.entity.js +0 -6
  55. package/fesm/interceptors/clear-token.interceptor.js +9 -22
  56. package/fesm/interceptors/set-token.interceptor.js +14 -54
  57. package/fesm/interfaces/auth-email-provider.interface.js +1 -1
  58. package/fesm/modules/auth.module.js +64 -148
  59. package/fesm/services/auth-config.service.js +23 -29
  60. package/fesm/services/auth-datasource.provider.js +10 -22
  61. package/fesm/services/auth-email.service.js +90 -86
  62. package/fesm/services/authentication.service.js +225 -396
  63. package/fesm/services/branch.service.js +9 -4
  64. package/fesm/services/company-selection-session.service.js +1 -1
  65. package/fesm/services/user-permission.service.js +44 -74
  66. package/fesm/services/user.service.js +79 -65
  67. package/fesm/strategies/jwt.strategy.js +44 -44
  68. package/fesm/validators/password.validator.js +48 -0
  69. package/interceptors/clear-token.interceptor.d.ts +0 -1
  70. package/interceptors/set-token.interceptor.d.ts +1 -1
  71. package/interfaces/authentication.interface.d.ts +0 -4
  72. package/interfaces/user-enricher.interface.d.ts +1 -4
  73. package/modules/auth.module.d.ts +6 -3
  74. package/package.json +4 -4
  75. package/services/auth-config.service.d.ts +6 -8
  76. package/services/auth-datasource.provider.d.ts +2 -4
  77. package/services/auth-email.service.d.ts +8 -4
  78. package/services/authentication.service.d.ts +15 -16
  79. package/services/company-selection-session.service.d.ts +1 -1
  80. package/services/user-permission.service.d.ts +6 -9
  81. package/services/user.service.d.ts +3 -0
  82. package/strategies/jwt.strategy.d.ts +4 -1
  83. package/validators/password.validator.d.ts +10 -0
  84. package/cjs/dtos/user-company-permission.dto.js +0 -110
  85. package/dtos/user-company-permission.dto.d.ts +0 -10
  86. 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 {
@@ -1,5 +1,4 @@
1
1
  export declare class CreateCompanyBranchDto {
2
- [key: string]: unknown;
3
2
  name: string;
4
3
  slug: string;
5
4
  companyId: string;
@@ -1,5 +1,4 @@
1
1
  export declare class CreateCompanyDto {
2
- [key: string]: unknown;
3
2
  name: string;
4
3
  slug: string;
5
4
  logoId?: string;
package/dtos/index.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  export * from './authentication.dto';
2
2
  export * from './company-branch.dto';
3
3
  export * from './company.dto';
4
- export * from './user-company-permission.dto';
5
4
  export * from './user.dto';
6
5
  export * from './user-permission.dto';
@@ -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
  }
@@ -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;
@@ -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 User | typeof UserToken)[];
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 Company | typeof CompanyBranch | typeof PendingRegistration | typeof User | typeof UserCompanyPermission | typeof UserToken)[];
16
- export declare function getEntitiesByConfig(enableCompanyFeature: boolean): (typeof Company | typeof CompanyBranch | typeof PendingRegistration | typeof User | typeof UserCompanyPermission | typeof UserToken)[];
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)[];
@@ -15,6 +15,4 @@ export declare class UserCompanyPermission {
15
15
  branch?: CompanyBranch;
16
16
  createdAt: Date;
17
17
  updatedAt: Date;
18
- isCompanyPermission(): boolean;
19
- isBranchPermission(): boolean;
20
18
  }
@@ -1,18 +1,11 @@
1
1
  // Injection Tokens
2
2
  export const AUTH_MODULE_OPTIONS = 'AUTH_MODULE_OPTIONS';
3
- /**
4
- * User Enricher Provider Token
5
- *
6
- * Optional provider for extending user list and profile.
7
- * Consuming apps can provide their own implementation.
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, Get, HttpCode, HttpStatus, Inject, Optional, Post, Req, UnauthorizedException, UseGuards, UseInterceptors } from '@nestjs/common';
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 { ChangePasswordDto, CompanySelectionResponseDto, ForgotPasswordDto, LoginDto, LoginResponseDto, MeResponseDto, RefreshTokenDto, RegistrationDto, RegistrationResponseDto, ResendVerificationDto, ResetPasswordDto, VerifyEmailDto } from '../dtos/authentication.dto';
35
- import { MessageResponseDto } from '@flusys/nestjs-shared/dtos';
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
- /** Build auth URL for email links */ buildAuthUrl(req, path) {
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
- // Core Endpoints
47
- /** Login - Returns tokens or company/branch selection requirement */ async login(loginDto) {
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
- /** Refresh access token. Browser uses HTTP-only cookie, API/Mobile uses request body. */ async refreshToken(req, refreshTokenDto) {
63
- // Get refresh token from cookie (browser) or body (API/mobile)
64
- const cookieName = this.authConfig?.getRefreshTokenCookieName() ?? 'fsn_refresh_token';
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
- /** Change password */ async changePassword(userId, dto) {
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
- // Email Auth Endpoints
89
- /** Forgot password - Request password reset email */ async forgotPassword(req, dto) {
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.authEmailService.forgotPassword(dto.email, this.buildAuthUrl(req, '/auth/reset-password'));
109
+ return this.emailService.forgotPassword(dto.email, this.buildAuthUrl(req, '/auth/reset-password'));
97
110
  }
98
- /** Reset password - Reset password using token */ async resetPassword(dto) {
99
- if (!this.authEmailService) {
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.authEmailService.resetPassword(dto.token, dto.newPassword);
118
+ return this.emailService.resetPassword(dto.token, dto.newPassword);
106
119
  }
107
- /** Resend verification email - For unauthenticated users (handles both pending registration and existing users) */ async resendVerification(req, dto) {
108
- if (!this.authEmailService) {
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.authEmailService.resendVerificationEmail(dto.email, this.buildAuthUrl(req, '/auth/verify-email'));
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.authEmailService) {
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.authEmailService.verifyEmail(dto.token);
136
+ const result = await this.emailService.verifyEmail(dto.token);
124
137
  if (result.type === 'pending') {
125
- // Create user from pending registration
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
- type: LoginResponseDto
160
- }),
161
- ApiResponse({
162
- status: 200,
163
- type: CompanySelectionResponseDto
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
- Get('me'),
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: 'jwt'
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;