@flusys/nestjs-auth 1.0.0-rc → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +530 -396
- package/cjs/controllers/authentication.controller.js +160 -28
- package/cjs/controllers/user-permission.controller.js +49 -136
- package/cjs/docs/auth-swagger.config.js +14 -7
- package/cjs/dtos/authentication.dto.js +0 -30
- package/cjs/dtos/index.js +0 -1
- package/cjs/entities/user-company-permission.entity.js +3 -3
- package/cjs/interceptors/clear-token.interceptor.js +8 -18
- package/cjs/interceptors/set-token.interceptor.js +8 -55
- package/cjs/interfaces/auth-email-provider.interface.js +1 -1
- package/cjs/modules/auth.module.js +63 -129
- package/cjs/services/auth-config.service.js +6 -29
- package/cjs/services/auth-datasource.provider.js +8 -20
- package/cjs/services/auth-email.service.js +72 -87
- package/cjs/services/authentication.service.js +211 -355
- package/cjs/services/branch.service.js +9 -5
- package/cjs/services/user-permission.service.js +43 -71
- package/cjs/services/user.service.js +18 -47
- package/cjs/strategies/jwt.strategy.js +44 -44
- package/controllers/authentication.controller.d.ts +5 -3
- package/controllers/user-permission.controller.d.ts +5 -4
- package/dtos/authentication.dto.d.ts +0 -4
- package/dtos/index.d.ts +0 -1
- package/entities/index.d.ts +3 -3
- package/entities/user-company-permission.entity.d.ts +1 -1
- package/fesm/controllers/authentication.controller.js +162 -30
- package/fesm/controllers/user-permission.controller.js +50 -137
- package/fesm/docs/auth-swagger.config.js +14 -7
- package/fesm/dtos/authentication.dto.js +0 -30
- package/fesm/dtos/index.js +0 -1
- package/fesm/entities/user-company-permission.entity.js +3 -3
- package/fesm/interceptors/clear-token.interceptor.js +8 -18
- package/fesm/interceptors/set-token.interceptor.js +8 -55
- package/fesm/interfaces/auth-email-provider.interface.js +1 -1
- package/fesm/modules/auth.module.js +63 -129
- package/fesm/services/auth-config.service.js +6 -29
- package/fesm/services/auth-datasource.provider.js +8 -20
- package/fesm/services/auth-email.service.js +72 -87
- package/fesm/services/authentication.service.js +211 -355
- package/fesm/services/branch.service.js +9 -5
- package/fesm/services/user-permission.service.js +44 -72
- package/fesm/services/user.service.js +18 -47
- package/fesm/strategies/jwt.strategy.js +44 -44
- package/interceptors/clear-token.interceptor.d.ts +0 -1
- package/interceptors/set-token.interceptor.d.ts +1 -2
- package/interfaces/authentication.interface.d.ts +0 -4
- package/modules/auth.module.d.ts +6 -2
- package/package.json +3 -3
- package/services/auth-config.service.d.ts +2 -7
- package/services/auth-datasource.provider.d.ts +2 -4
- package/services/auth-email.service.d.ts +7 -4
- package/services/authentication.service.d.ts +14 -8
- package/services/user-permission.service.d.ts +6 -8
- package/services/user.service.d.ts +1 -1
- package/strategies/jwt.strategy.d.ts +4 -1
- package/cjs/dtos/user-company-permission.dto.js +0 -110
- package/cjs/guards/auth-throttler.guard.js +0 -53
- package/dtos/user-company-permission.dto.d.ts +0 -10
- package/fesm/dtos/user-company-permission.dto.js +0 -98
- package/fesm/guards/auth-throttler.guard.js +0 -43
- package/guards/auth-throttler.guard.d.ts +0 -7
|
@@ -26,14 +26,15 @@ 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
32
|
import { Body, Controller, HttpCode, HttpStatus, Inject, Optional, Post, Req, UnauthorizedException, UseGuards, UseInterceptors } from '@nestjs/common';
|
|
32
|
-
import { ApiBearerAuth, ApiBody, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
|
33
|
+
import { ApiBearerAuth, ApiBody, ApiExtraModels, ApiHeader, ApiOperation, ApiResponse, ApiTags, getSchemaPath } from '@nestjs/swagger';
|
|
33
34
|
import { SkipThrottle, Throttle } from '@nestjs/throttler';
|
|
34
35
|
import { Request } from 'express';
|
|
35
|
-
import {
|
|
36
|
-
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';
|
|
37
38
|
import { ClearToken } from '../interceptors/clear-token.interceptor';
|
|
38
39
|
import { SetToken } from '../interceptors/set-token.interceptor';
|
|
39
40
|
import { AuthConfigService } from '../services/auth-config.service';
|
|
@@ -44,8 +45,20 @@ export class AuthenticationController {
|
|
|
44
45
|
const frontendUrl = this.authConfig?.getFrontendUrl();
|
|
45
46
|
return frontendUrl ? `${frontendUrl}${path}` : `${req.protocol}://${req.get('host')}${path}`;
|
|
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
|
+
}
|
|
47
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');
|
|
@@ -86,46 +100,42 @@ export class AuthenticationController {
|
|
|
86
100
|
};
|
|
87
101
|
}
|
|
88
102
|
async forgotPassword(req, dto) {
|
|
89
|
-
if (!this.
|
|
103
|
+
if (!this.isEmailEnabled) {
|
|
90
104
|
return {
|
|
91
105
|
success: false,
|
|
92
106
|
message: 'Email service is not configured'
|
|
93
107
|
};
|
|
94
108
|
}
|
|
95
|
-
return this.
|
|
109
|
+
return this.emailService.forgotPassword(dto.email, this.buildAuthUrl(req, '/auth/reset-password'));
|
|
96
110
|
}
|
|
97
111
|
async resetPassword(dto) {
|
|
98
|
-
if (!this.
|
|
112
|
+
if (!this.isEmailEnabled) {
|
|
99
113
|
return {
|
|
100
114
|
success: false,
|
|
101
115
|
message: 'Email service is not configured'
|
|
102
116
|
};
|
|
103
117
|
}
|
|
104
|
-
return this.
|
|
118
|
+
return this.emailService.resetPassword(dto.token, dto.newPassword);
|
|
105
119
|
}
|
|
106
120
|
async resendVerification(req, dto) {
|
|
107
|
-
if (!this.
|
|
121
|
+
if (!this.isEmailEnabled) {
|
|
108
122
|
return {
|
|
109
123
|
success: false,
|
|
110
124
|
message: 'Email service is not configured'
|
|
111
125
|
};
|
|
112
126
|
}
|
|
113
|
-
return this.
|
|
127
|
+
return this.emailService.resendVerificationEmail(dto.email, this.buildAuthUrl(req, '/auth/verify-email'));
|
|
114
128
|
}
|
|
115
129
|
/** Verify email - Verify email using token. Handles both pending registration and existing user verification. */ async verifyEmail(dto) {
|
|
116
|
-
if (!this.
|
|
130
|
+
if (!this.isEmailEnabled) {
|
|
117
131
|
return {
|
|
118
132
|
success: false,
|
|
119
133
|
message: 'Email service is not configured'
|
|
120
134
|
};
|
|
121
135
|
}
|
|
122
|
-
const result = await this.
|
|
136
|
+
const result = await this.emailService.verifyEmail(dto.token);
|
|
123
137
|
if (result.type === 'pending') {
|
|
124
|
-
|
|
125
|
-
return {
|
|
126
|
-
...registrationResult,
|
|
127
|
-
companyFeatureEnabled: this.authConfig?.isCompanyFeatureEnabled() ?? false
|
|
128
|
-
};
|
|
138
|
+
return this.authService.createUserFromPendingRegistration(result.pending);
|
|
129
139
|
}
|
|
130
140
|
return {
|
|
131
141
|
success: true,
|
|
@@ -157,13 +167,102 @@ _ts_decorate([
|
|
|
157
167
|
ApiBody({
|
|
158
168
|
type: LoginDto
|
|
159
169
|
}),
|
|
170
|
+
ApiExtraModels(LoginResponseDto, CompanySelectionResponseDto, LoginResponseRequiresEmailVerificationDto),
|
|
160
171
|
ApiResponse({
|
|
161
172
|
status: 200,
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
+
}
|
|
167
266
|
}),
|
|
168
267
|
_ts_param(0, Body()),
|
|
169
268
|
_ts_metadata("design:type", Function),
|
|
@@ -211,10 +310,43 @@ _ts_decorate([
|
|
|
211
310
|
}
|
|
212
311
|
}),
|
|
213
312
|
ApiOperation({
|
|
214
|
-
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'
|
|
215
329
|
}),
|
|
216
330
|
ApiBody({
|
|
217
|
-
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'
|
|
218
350
|
}),
|
|
219
351
|
_ts_param(0, Req()),
|
|
220
352
|
_ts_param(1, Body()),
|
|
@@ -25,129 +25,74 @@ function _ts_param(paramIndex, decorator) {
|
|
|
25
25
|
decorator(target, key, paramIndex);
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
+
import { JwtAuthGuard, RequirePermission, USER_PERMISSIONS } from '@flusys/nestjs-shared';
|
|
28
29
|
import { Body, Controller, Inject, Post, UseGuards } from '@nestjs/common';
|
|
29
30
|
import { ApiBearerAuth, ApiBody, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
31
|
+
import { AssignUserBranchDto, AssignUserCompanyDto, UserBranchesResponseDto, UserCompaniesResponseDto } from '../dtos/user-permission.dto';
|
|
32
|
+
import { PermissionType } from '../entities/user-company-permission.entity';
|
|
32
33
|
import { UserPermissionService } from '../services/user-permission.service';
|
|
33
34
|
export class UserPermissionController {
|
|
34
35
|
async assignUserCompany(dto) {
|
|
35
|
-
|
|
36
|
-
let removedCount = 0;
|
|
37
|
-
for (const item of dto.items){
|
|
38
|
-
if (item.isAdd) {
|
|
39
|
-
await this.userPermissionService.assignUserToCompany({
|
|
40
|
-
userId: dto.userId,
|
|
41
|
-
companyId: item.targetId
|
|
42
|
-
});
|
|
43
|
-
addedCount++;
|
|
44
|
-
} else {
|
|
45
|
-
await this.userPermissionService.revokeCompanyAccess(dto.userId, item.targetId);
|
|
46
|
-
removedCount++;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
return {
|
|
50
|
-
success: true,
|
|
51
|
-
message: `Processed ${addedCount} additions and ${removedCount} removals`
|
|
52
|
-
};
|
|
36
|
+
return this.processBatchPermissions(dto.userId, dto.items, PermissionType.COMPANY);
|
|
53
37
|
}
|
|
54
38
|
async getUserCompanies(userId) {
|
|
55
|
-
const permissions = await this.
|
|
56
|
-
const data = permissions.map((permission)=>({
|
|
57
|
-
id: permission.id,
|
|
58
|
-
userId: permission.userId,
|
|
59
|
-
companyId: permission.company?.id || permission.targetId,
|
|
60
|
-
companyName: permission.company?.name || '',
|
|
61
|
-
companySlug: permission.company?.slug || ''
|
|
62
|
-
}));
|
|
39
|
+
const permissions = await this.permissionService.findUserCompanies(userId);
|
|
63
40
|
return {
|
|
64
41
|
success: true,
|
|
65
42
|
message: 'User companies retrieved successfully',
|
|
66
|
-
data
|
|
43
|
+
data: permissions.map((p)=>({
|
|
44
|
+
id: p.id,
|
|
45
|
+
userId: p.userId,
|
|
46
|
+
companyId: p.company?.id || p.targetId,
|
|
47
|
+
companyName: p.company?.name || '',
|
|
48
|
+
companySlug: p.company?.slug || ''
|
|
49
|
+
}))
|
|
67
50
|
};
|
|
68
51
|
}
|
|
69
52
|
async assignUserBranch(dto) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
await this.userPermissionService.assignUserToBranch({
|
|
75
|
-
userId: dto.userId,
|
|
76
|
-
branchId: item.targetId
|
|
77
|
-
});
|
|
78
|
-
addedCount++;
|
|
79
|
-
} else {
|
|
80
|
-
await this.userPermissionService.revokeBranchAccess(dto.userId, item.targetId);
|
|
81
|
-
removedCount++;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
53
|
+
return this.processBatchPermissions(dto.userId, dto.items, PermissionType.BRANCH);
|
|
54
|
+
}
|
|
55
|
+
async getUserBranches(userId, companyId) {
|
|
56
|
+
const permissions = await this.permissionService.findUserBranches(userId, companyId);
|
|
84
57
|
return {
|
|
85
58
|
success: true,
|
|
86
|
-
message:
|
|
59
|
+
message: 'User branches retrieved successfully',
|
|
60
|
+
data: permissions.map((p)=>({
|
|
61
|
+
id: p.id,
|
|
62
|
+
userId: p.userId,
|
|
63
|
+
branchId: p.branch?.id || p.targetId,
|
|
64
|
+
branchName: p.branch?.name || '',
|
|
65
|
+
branchSlug: p.branch?.slug || '',
|
|
66
|
+
companyId: p.branch?.companyId || ''
|
|
67
|
+
}))
|
|
87
68
|
};
|
|
88
69
|
}
|
|
89
|
-
async
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
70
|
+
/** Process batch permission assignments/revocations */ async processBatchPermissions(userId, items, type) {
|
|
71
|
+
let added = 0, removed = 0;
|
|
72
|
+
for (const item of items){
|
|
73
|
+
if (item.isAdd) {
|
|
74
|
+
await this.permissionService.assignPermission(userId, item.targetId, type);
|
|
75
|
+
added++;
|
|
76
|
+
} else {
|
|
77
|
+
await this.permissionService.revokePermission(userId, item.targetId, type);
|
|
78
|
+
removed++;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
99
81
|
return {
|
|
100
82
|
success: true,
|
|
101
|
-
message:
|
|
102
|
-
data
|
|
83
|
+
message: `Processed ${added} additions and ${removed} removals`
|
|
103
84
|
};
|
|
104
85
|
}
|
|
105
|
-
constructor(
|
|
106
|
-
_define_property(this, "
|
|
107
|
-
this.
|
|
86
|
+
constructor(permissionService){
|
|
87
|
+
_define_property(this, "permissionService", void 0);
|
|
88
|
+
this.permissionService = permissionService;
|
|
108
89
|
}
|
|
109
90
|
}
|
|
110
91
|
_ts_decorate([
|
|
111
92
|
Post('user-company/assign'),
|
|
112
93
|
RequirePermission(USER_PERMISSIONS.UPDATE),
|
|
113
94
|
ApiOperation({
|
|
114
|
-
summary: 'Batch assign/revoke user-company permissions'
|
|
115
|
-
description: `
|
|
116
|
-
Batch operation to assign or revoke user permissions to companies.
|
|
117
|
-
|
|
118
|
-
**Example: Add company permissions**
|
|
119
|
-
\`\`\`json
|
|
120
|
-
{
|
|
121
|
-
"userId": "user-id",
|
|
122
|
-
"items": [
|
|
123
|
-
{ "targetId": "company-id-1", "isAdd": true },
|
|
124
|
-
{ "targetId": "company-id-2", "isAdd": true }
|
|
125
|
-
]
|
|
126
|
-
}
|
|
127
|
-
\`\`\`
|
|
128
|
-
|
|
129
|
-
**Example: Remove company permissions**
|
|
130
|
-
\`\`\`json
|
|
131
|
-
{
|
|
132
|
-
"userId": "user-id",
|
|
133
|
-
"items": [
|
|
134
|
-
{ "targetId": "company-id-1", "isAdd": false },
|
|
135
|
-
{ "targetId": "company-id-2", "isAdd": false }
|
|
136
|
-
]
|
|
137
|
-
}
|
|
138
|
-
\`\`\`
|
|
139
|
-
|
|
140
|
-
**Example: Mix add and remove**
|
|
141
|
-
\`\`\`json
|
|
142
|
-
{
|
|
143
|
-
"userId": "user-id",
|
|
144
|
-
"items": [
|
|
145
|
-
{ "targetId": "company-id-1", "isAdd": true },
|
|
146
|
-
{ "targetId": "company-id-2", "isAdd": false }
|
|
147
|
-
]
|
|
148
|
-
}
|
|
149
|
-
\`\`\`
|
|
150
|
-
`
|
|
95
|
+
summary: 'Batch assign/revoke user-company permissions'
|
|
151
96
|
}),
|
|
152
97
|
ApiResponse({
|
|
153
98
|
status: 200,
|
|
@@ -167,8 +112,7 @@ _ts_decorate([
|
|
|
167
112
|
Post('get-user-company'),
|
|
168
113
|
RequirePermission(USER_PERMISSIONS.READ),
|
|
169
114
|
ApiOperation({
|
|
170
|
-
summary: 'Get user companies'
|
|
171
|
-
description: 'Returns all companies the user has access to.'
|
|
115
|
+
summary: 'Get user companies'
|
|
172
116
|
}),
|
|
173
117
|
ApiBody({
|
|
174
118
|
schema: {
|
|
@@ -197,43 +141,7 @@ _ts_decorate([
|
|
|
197
141
|
Post('user-branch/assign'),
|
|
198
142
|
RequirePermission(USER_PERMISSIONS.UPDATE),
|
|
199
143
|
ApiOperation({
|
|
200
|
-
summary: 'Batch assign/revoke user-branch permissions'
|
|
201
|
-
description: `
|
|
202
|
-
Batch operation to assign or revoke user permissions to branches.
|
|
203
|
-
|
|
204
|
-
**Example: Add branch permissions**
|
|
205
|
-
\`\`\`json
|
|
206
|
-
{
|
|
207
|
-
"userId": "user-id",
|
|
208
|
-
"items": [
|
|
209
|
-
{ "targetId": "branch-id-1", "isAdd": true },
|
|
210
|
-
{ "targetId": "branch-id-2", "isAdd": true }
|
|
211
|
-
]
|
|
212
|
-
}
|
|
213
|
-
\`\`\`
|
|
214
|
-
|
|
215
|
-
**Example: Remove branch permissions**
|
|
216
|
-
\`\`\`json
|
|
217
|
-
{
|
|
218
|
-
"userId": "user-id",
|
|
219
|
-
"items": [
|
|
220
|
-
{ "targetId": "branch-id-1", "isAdd": false },
|
|
221
|
-
{ "targetId": "branch-id-2", "isAdd": false }
|
|
222
|
-
]
|
|
223
|
-
}
|
|
224
|
-
\`\`\`
|
|
225
|
-
|
|
226
|
-
**Example: Mix add and remove**
|
|
227
|
-
\`\`\`json
|
|
228
|
-
{
|
|
229
|
-
"userId": "user-id",
|
|
230
|
-
"items": [
|
|
231
|
-
{ "targetId": "branch-id-1", "isAdd": true },
|
|
232
|
-
{ "targetId": "branch-id-2", "isAdd": false }
|
|
233
|
-
]
|
|
234
|
-
}
|
|
235
|
-
\`\`\`
|
|
236
|
-
`
|
|
144
|
+
summary: 'Batch assign/revoke user-branch permissions'
|
|
237
145
|
}),
|
|
238
146
|
ApiResponse({
|
|
239
147
|
status: 200,
|
|
@@ -253,14 +161,17 @@ _ts_decorate([
|
|
|
253
161
|
Post('get-user-branch'),
|
|
254
162
|
RequirePermission(USER_PERMISSIONS.READ),
|
|
255
163
|
ApiOperation({
|
|
256
|
-
summary: 'Get user branches'
|
|
257
|
-
description: 'Returns all branches the user has access to.'
|
|
164
|
+
summary: 'Get user branches'
|
|
258
165
|
}),
|
|
259
166
|
ApiBody({
|
|
260
167
|
schema: {
|
|
261
168
|
properties: {
|
|
262
169
|
userId: {
|
|
263
170
|
type: 'string'
|
|
171
|
+
},
|
|
172
|
+
companyId: {
|
|
173
|
+
type: 'string',
|
|
174
|
+
description: 'Optional company ID to filter branches'
|
|
264
175
|
}
|
|
265
176
|
},
|
|
266
177
|
required: [
|
|
@@ -273,8 +184,10 @@ _ts_decorate([
|
|
|
273
184
|
type: UserBranchesResponseDto
|
|
274
185
|
}),
|
|
275
186
|
_ts_param(0, Body('userId')),
|
|
187
|
+
_ts_param(1, Body('companyId')),
|
|
276
188
|
_ts_metadata("design:type", Function),
|
|
277
189
|
_ts_metadata("design:paramtypes", [
|
|
190
|
+
String,
|
|
278
191
|
String
|
|
279
192
|
]),
|
|
280
193
|
_ts_metadata("design:returntype", Promise)
|
|
@@ -4,6 +4,16 @@
|
|
|
4
4
|
'User Permissions',
|
|
5
5
|
'Company Selection'
|
|
6
6
|
];
|
|
7
|
+
/** Examples to exclude when company feature is disabled */ const COMPANY_EXAMPLE_EXCLUSIONS = [
|
|
8
|
+
{
|
|
9
|
+
pathPattern: '/auth/login',
|
|
10
|
+
method: 'post',
|
|
11
|
+
examples: [
|
|
12
|
+
'loginSuccessWithCompany',
|
|
13
|
+
'companySelection'
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
];
|
|
7
17
|
/**
|
|
8
18
|
* Schema properties to exclude when company feature is disabled
|
|
9
19
|
*
|
|
@@ -48,16 +58,14 @@
|
|
|
48
58
|
schemaName: 'RegistrationResponseDto',
|
|
49
59
|
properties: [
|
|
50
60
|
'company',
|
|
51
|
-
'branch'
|
|
52
|
-
'companyFeatureEnabled'
|
|
61
|
+
'branch'
|
|
53
62
|
]
|
|
54
63
|
},
|
|
55
64
|
{
|
|
56
65
|
schemaName: 'LoginResponseDto',
|
|
57
66
|
properties: [
|
|
58
67
|
'company',
|
|
59
|
-
'branch'
|
|
60
|
-
'companyFeatureEnabled'
|
|
68
|
+
'branch'
|
|
61
69
|
]
|
|
62
70
|
},
|
|
63
71
|
{
|
|
@@ -92,7 +100,6 @@
|
|
|
92
100
|
'id',
|
|
93
101
|
'name',
|
|
94
102
|
'slug',
|
|
95
|
-
'isActive',
|
|
96
103
|
'logoId'
|
|
97
104
|
]
|
|
98
105
|
},
|
|
@@ -102,7 +109,6 @@
|
|
|
102
109
|
'id',
|
|
103
110
|
'name',
|
|
104
111
|
'slug',
|
|
105
|
-
'isActive',
|
|
106
112
|
'logoId',
|
|
107
113
|
'branches'
|
|
108
114
|
]
|
|
@@ -401,6 +407,7 @@ POST /auth/login → { accessToken, refreshToken, user }
|
|
|
401
407
|
path: 'api/docs/auth',
|
|
402
408
|
bearerAuth: true,
|
|
403
409
|
excludeTags,
|
|
404
|
-
excludeSchemaProperties: enableCompanyFeature ? undefined : COMPANY_SCHEMA_EXCLUSIONS
|
|
410
|
+
excludeSchemaProperties: enableCompanyFeature ? undefined : COMPANY_SCHEMA_EXCLUSIONS,
|
|
411
|
+
excludeExamples: enableCompanyFeature ? undefined : COMPANY_EXAMPLE_EXCLUSIONS
|
|
405
412
|
};
|
|
406
413
|
}
|
|
@@ -361,7 +361,6 @@ _ts_decorate([
|
|
|
361
361
|
_define_property(this, "id", void 0);
|
|
362
362
|
_define_property(this, "name", void 0);
|
|
363
363
|
_define_property(this, "slug", void 0);
|
|
364
|
-
_define_property(this, "isActive", void 0);
|
|
365
364
|
_define_property(this, "logoId", void 0);
|
|
366
365
|
}
|
|
367
366
|
}
|
|
@@ -383,12 +382,6 @@ _ts_decorate([
|
|
|
383
382
|
}),
|
|
384
383
|
_ts_metadata("design:type", String)
|
|
385
384
|
], BranchSelectionInfoDto.prototype, "slug", void 0);
|
|
386
|
-
_ts_decorate([
|
|
387
|
-
ApiProperty({
|
|
388
|
-
example: true
|
|
389
|
-
}),
|
|
390
|
-
_ts_metadata("design:type", Boolean)
|
|
391
|
-
], BranchSelectionInfoDto.prototype, "isActive", void 0);
|
|
392
385
|
_ts_decorate([
|
|
393
386
|
ApiPropertyOptional({
|
|
394
387
|
example: '550e8400-e29b-41d4-a716-446655440077',
|
|
@@ -402,7 +395,6 @@ _ts_decorate([
|
|
|
402
395
|
_define_property(this, "id", void 0);
|
|
403
396
|
_define_property(this, "name", void 0);
|
|
404
397
|
_define_property(this, "slug", void 0);
|
|
405
|
-
_define_property(this, "isActive", void 0);
|
|
406
398
|
_define_property(this, "logoId", void 0);
|
|
407
399
|
_define_property(this, "branches", void 0);
|
|
408
400
|
}
|
|
@@ -425,12 +417,6 @@ _ts_decorate([
|
|
|
425
417
|
}),
|
|
426
418
|
_ts_metadata("design:type", String)
|
|
427
419
|
], CompanyWithBranchesDto.prototype, "slug", void 0);
|
|
428
|
-
_ts_decorate([
|
|
429
|
-
ApiProperty({
|
|
430
|
-
example: true
|
|
431
|
-
}),
|
|
432
|
-
_ts_metadata("design:type", Boolean)
|
|
433
|
-
], CompanyWithBranchesDto.prototype, "isActive", void 0);
|
|
434
420
|
_ts_decorate([
|
|
435
421
|
ApiPropertyOptional({
|
|
436
422
|
example: '550e8400-e29b-41d4-a716-446655440088',
|
|
@@ -455,7 +441,6 @@ _ts_decorate([
|
|
|
455
441
|
_define_property(this, "user", void 0);
|
|
456
442
|
_define_property(this, "company", void 0);
|
|
457
443
|
_define_property(this, "branch", void 0);
|
|
458
|
-
_define_property(this, "companyFeatureEnabled", void 0);
|
|
459
444
|
}
|
|
460
445
|
}
|
|
461
446
|
_ts_decorate([
|
|
@@ -491,13 +476,6 @@ _ts_decorate([
|
|
|
491
476
|
}),
|
|
492
477
|
_ts_metadata("design:type", typeof BranchInfoDto === "undefined" ? Object : BranchInfoDto)
|
|
493
478
|
], LoginResponseDto.prototype, "branch", void 0);
|
|
494
|
-
_ts_decorate([
|
|
495
|
-
ApiPropertyOptional({
|
|
496
|
-
example: true,
|
|
497
|
-
description: 'Whether company feature is enabled'
|
|
498
|
-
}),
|
|
499
|
-
_ts_metadata("design:type", Boolean)
|
|
500
|
-
], LoginResponseDto.prototype, "companyFeatureEnabled", void 0);
|
|
501
479
|
/** Registration Response DTO - Returned on successful registration */ export class RegistrationResponseDto {
|
|
502
480
|
constructor(){
|
|
503
481
|
_define_property(this, "accessToken", void 0);
|
|
@@ -505,7 +483,6 @@ _ts_decorate([
|
|
|
505
483
|
_define_property(this, "user", void 0);
|
|
506
484
|
_define_property(this, "company", void 0);
|
|
507
485
|
_define_property(this, "branch", void 0);
|
|
508
|
-
_define_property(this, "companyFeatureEnabled", void 0);
|
|
509
486
|
_define_property(this, "requiresEmailVerification", void 0);
|
|
510
487
|
}
|
|
511
488
|
}
|
|
@@ -542,13 +519,6 @@ _ts_decorate([
|
|
|
542
519
|
}),
|
|
543
520
|
_ts_metadata("design:type", typeof BranchInfoDto === "undefined" ? Object : BranchInfoDto)
|
|
544
521
|
], RegistrationResponseDto.prototype, "branch", void 0);
|
|
545
|
-
_ts_decorate([
|
|
546
|
-
ApiPropertyOptional({
|
|
547
|
-
example: true,
|
|
548
|
-
description: 'Whether company feature is enabled'
|
|
549
|
-
}),
|
|
550
|
-
_ts_metadata("design:type", Boolean)
|
|
551
|
-
], RegistrationResponseDto.prototype, "companyFeatureEnabled", void 0);
|
|
552
522
|
_ts_decorate([
|
|
553
523
|
ApiPropertyOptional({
|
|
554
524
|
example: true,
|