@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
|
@@ -67,11 +67,15 @@ export class BranchService extends RequestScopedApiService {
|
|
|
67
67
|
}
|
|
68
68
|
async getExtraManipulateQuery(query, filterDto, user) {
|
|
69
69
|
const result = await super.getExtraManipulateQuery(query, filterDto, user);
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
70
|
+
// Only apply company filter if companyId is NOT explicitly provided in filter
|
|
71
|
+
// This prevents dual filtering when user selects a specific company
|
|
72
|
+
if (!filterDto?.filter?.companyId) {
|
|
73
|
+
applyCompanyFilter(query, {
|
|
74
|
+
isCompanyFeatureEnabled: this.authConfig.isCompanyFeatureEnabled(),
|
|
75
|
+
entityAlias: 'company_branch',
|
|
76
|
+
columnName: 'company_id'
|
|
77
|
+
}, user);
|
|
78
|
+
}
|
|
75
79
|
return result;
|
|
76
80
|
}
|
|
77
81
|
constructor(cacheManager, utilsService, dataSourceProvider, authConfig){
|
|
@@ -25,124 +25,96 @@ function _ts_param(paramIndex, decorator) {
|
|
|
25
25
|
decorator(target, key, paramIndex);
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
-
import {
|
|
28
|
+
import { ConflictException, Inject, Injectable, NotFoundException, Scope } from '@nestjs/common';
|
|
29
29
|
import { PermissionType, UserCompanyPermission } from '../entities/user-company-permission.entity';
|
|
30
30
|
import { AuthDataSourceProvider } from './auth-datasource.provider';
|
|
31
31
|
export class UserPermissionService {
|
|
32
|
-
|
|
32
|
+
async getRepository() {
|
|
33
33
|
if (!this._repository) {
|
|
34
34
|
this._repository = await this.dataSourceProvider.getRepository(UserCompanyPermission);
|
|
35
35
|
}
|
|
36
36
|
return this._repository;
|
|
37
37
|
}
|
|
38
|
-
//
|
|
39
|
-
/** Find all
|
|
38
|
+
// Generic Permission Operations
|
|
39
|
+
/** Find all permissions of a specific type for a user */ async findUserPermissions(userId, type) {
|
|
40
40
|
const repository = await this.getRepository();
|
|
41
41
|
return repository.find({
|
|
42
42
|
where: {
|
|
43
43
|
userId,
|
|
44
|
-
permissionType:
|
|
44
|
+
permissionType: type
|
|
45
45
|
},
|
|
46
46
|
relations: [
|
|
47
|
-
'company'
|
|
47
|
+
type === PermissionType.COMPANY ? 'company' : 'branch'
|
|
48
48
|
],
|
|
49
49
|
order: {
|
|
50
50
|
createdAt: 'DESC'
|
|
51
51
|
}
|
|
52
52
|
});
|
|
53
53
|
}
|
|
54
|
-
/** Assign
|
|
54
|
+
/** Assign permission to user */ async assignPermission(userId, targetId, type, metadata) {
|
|
55
55
|
const repository = await this.getRepository();
|
|
56
|
-
|
|
56
|
+
const typeName = type === PermissionType.COMPANY ? 'company' : 'branch';
|
|
57
57
|
const existing = await repository.findOne({
|
|
58
58
|
where: {
|
|
59
|
-
userId
|
|
60
|
-
targetId
|
|
61
|
-
permissionType:
|
|
59
|
+
userId,
|
|
60
|
+
targetId,
|
|
61
|
+
permissionType: type
|
|
62
62
|
}
|
|
63
63
|
});
|
|
64
64
|
if (existing) {
|
|
65
|
-
throw new
|
|
65
|
+
throw new ConflictException(`User already assigned to this ${typeName}`);
|
|
66
66
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
permissionType: PermissionType.COMPANY,
|
|
70
|
-
targetId: dto.companyId,
|
|
71
|
-
metadata: dto.metadata
|
|
72
|
-
});
|
|
73
|
-
await repository.save(permission);
|
|
74
|
-
}
|
|
75
|
-
/** Revoke user access to a company */ async revokeCompanyAccess(userId, companyId) {
|
|
76
|
-
const repository = await this.getRepository();
|
|
77
|
-
const permission = await repository.findOne({
|
|
78
|
-
where: {
|
|
67
|
+
try {
|
|
68
|
+
await repository.save(repository.create({
|
|
79
69
|
userId,
|
|
80
|
-
|
|
81
|
-
|
|
70
|
+
permissionType: type,
|
|
71
|
+
targetId,
|
|
72
|
+
metadata
|
|
73
|
+
}));
|
|
74
|
+
} catch (error) {
|
|
75
|
+
if (error?.code === 'ER_DUP_ENTRY' || error?.message?.includes('Duplicate entry')) {
|
|
76
|
+
throw new ConflictException(`User already assigned to this ${typeName}. Please refresh and try again.`);
|
|
82
77
|
}
|
|
83
|
-
|
|
84
|
-
if (!permission) {
|
|
85
|
-
throw new NotFoundException('Company permission not found');
|
|
78
|
+
throw error;
|
|
86
79
|
}
|
|
87
|
-
await repository.remove(permission);
|
|
88
80
|
}
|
|
89
|
-
|
|
90
|
-
/** Find all branches a user has access to */ async findUserBranches(userId) {
|
|
91
|
-
const repository = await this.getRepository();
|
|
92
|
-
return repository.find({
|
|
93
|
-
where: {
|
|
94
|
-
userId,
|
|
95
|
-
permissionType: PermissionType.BRANCH
|
|
96
|
-
},
|
|
97
|
-
relations: [
|
|
98
|
-
'branch'
|
|
99
|
-
],
|
|
100
|
-
order: {
|
|
101
|
-
createdAt: 'DESC'
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
/** Assign user to a branch */ async assignUserToBranch(dto) {
|
|
106
|
-
const repository = await this.getRepository();
|
|
107
|
-
// Check if already exists
|
|
108
|
-
const existing = await repository.findOne({
|
|
109
|
-
where: {
|
|
110
|
-
userId: dto.userId,
|
|
111
|
-
targetId: dto.branchId,
|
|
112
|
-
permissionType: PermissionType.BRANCH
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
if (existing) {
|
|
116
|
-
throw new BadRequestException('User already assigned to this branch');
|
|
117
|
-
}
|
|
118
|
-
const permission = repository.create({
|
|
119
|
-
userId: dto.userId,
|
|
120
|
-
permissionType: PermissionType.BRANCH,
|
|
121
|
-
targetId: dto.branchId,
|
|
122
|
-
metadata: dto.metadata
|
|
123
|
-
});
|
|
124
|
-
await repository.save(permission);
|
|
125
|
-
}
|
|
126
|
-
/** Revoke user access to a branch */ async revokeBranchAccess(userId, branchId) {
|
|
81
|
+
/** Revoke permission from user */ async revokePermission(userId, targetId, type) {
|
|
127
82
|
const repository = await this.getRepository();
|
|
83
|
+
const typeName = type === PermissionType.COMPANY ? 'company' : 'branch';
|
|
128
84
|
const permission = await repository.findOne({
|
|
129
85
|
where: {
|
|
130
86
|
userId,
|
|
131
|
-
targetId
|
|
132
|
-
permissionType:
|
|
87
|
+
targetId,
|
|
88
|
+
permissionType: type
|
|
133
89
|
}
|
|
134
90
|
});
|
|
135
91
|
if (!permission) {
|
|
136
|
-
throw new NotFoundException(
|
|
92
|
+
throw new NotFoundException(`${typeName.charAt(0).toUpperCase() + typeName.slice(1)} permission not found`);
|
|
137
93
|
}
|
|
138
94
|
await repository.remove(permission);
|
|
139
95
|
}
|
|
140
|
-
|
|
96
|
+
/** Find user branch permissions with optional company filter */ async findUserBranches(userId, companyId) {
|
|
97
|
+
const repository = await this.getRepository();
|
|
98
|
+
const queryBuilder = repository.createQueryBuilder('permission').leftJoinAndSelect('permission.branch', 'branch').where('permission.userId = :userId', {
|
|
99
|
+
userId
|
|
100
|
+
}).andWhere('permission.permissionType = :type', {
|
|
101
|
+
type: PermissionType.BRANCH
|
|
102
|
+
}).orderBy('permission.createdAt', 'DESC');
|
|
103
|
+
if (companyId) {
|
|
104
|
+
queryBuilder.andWhere('branch.companyId = :companyId', {
|
|
105
|
+
companyId
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return queryBuilder.getMany();
|
|
109
|
+
}
|
|
141
110
|
constructor(dataSourceProvider){
|
|
142
111
|
_define_property(this, "dataSourceProvider", void 0);
|
|
143
112
|
_define_property(this, "_repository", void 0);
|
|
113
|
+
// Convenience Methods
|
|
114
|
+
_define_property(this, "findUserCompanies", void 0);
|
|
144
115
|
this.dataSourceProvider = dataSourceProvider;
|
|
145
116
|
this._repository = null;
|
|
117
|
+
this.findUserCompanies = (userId)=>this.findUserPermissions(userId, PermissionType.COMPANY);
|
|
146
118
|
}
|
|
147
119
|
}
|
|
148
120
|
UserPermissionService = _ts_decorate([
|
|
@@ -60,12 +60,22 @@ export class UserService extends RequestScopedApiService {
|
|
|
60
60
|
}
|
|
61
61
|
// Custom Methods
|
|
62
62
|
/**
|
|
63
|
-
* Validate
|
|
64
|
-
*
|
|
65
|
-
*/
|
|
66
|
-
if (
|
|
63
|
+
* Validate profile ownership, ensure repository initialized, and fetch user.
|
|
64
|
+
* Throws ForbiddenException if not owner, NotFoundException if user not found.
|
|
65
|
+
*/ async getUserForProfile(userId, user) {
|
|
66
|
+
if (userId !== user.id) {
|
|
67
67
|
throw new ForbiddenException('Cannot access another user\'s profile');
|
|
68
68
|
}
|
|
69
|
+
await this.ensureRepositoryInitialized();
|
|
70
|
+
const userEntity = await this.repository.findOne({
|
|
71
|
+
where: {
|
|
72
|
+
id: userId
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
if (!userEntity) {
|
|
76
|
+
throw new NotFoundException('User not found');
|
|
77
|
+
}
|
|
78
|
+
return userEntity;
|
|
69
79
|
}
|
|
70
80
|
/** Verify email */ async verifyEmail(id) {
|
|
71
81
|
// Ensure repository is initialized for custom method
|
|
@@ -324,16 +334,7 @@ export class UserService extends RequestScopedApiService {
|
|
|
324
334
|
* Get user profile with extras from enricher
|
|
325
335
|
* Returns base user data plus any extra data provided by enricher (roles, permissions, etc.)
|
|
326
336
|
*/ async getProfileWithExtras(userId, user) {
|
|
327
|
-
this.
|
|
328
|
-
await this.ensureRepositoryInitialized();
|
|
329
|
-
const userEntity = await this.repository.findOne({
|
|
330
|
-
where: {
|
|
331
|
-
id: userId
|
|
332
|
-
}
|
|
333
|
-
});
|
|
334
|
-
if (!userEntity) {
|
|
335
|
-
throw new NotFoundException('User not found');
|
|
336
|
-
}
|
|
337
|
+
const userEntity = await this.getUserForProfile(userId, user);
|
|
337
338
|
const userData = this.convertEntityToResponseDto(userEntity, false);
|
|
338
339
|
// Get extras from enricher if available
|
|
339
340
|
let extras;
|
|
@@ -368,17 +369,7 @@ export class UserService extends RequestScopedApiService {
|
|
|
368
369
|
/**
|
|
369
370
|
* Get specific profile section data
|
|
370
371
|
*/ async getProfileSectionData(userId, sectionId, user) {
|
|
371
|
-
this.
|
|
372
|
-
await this.ensureRepositoryInitialized();
|
|
373
|
-
// Verify user exists
|
|
374
|
-
const userEntity = await this.repository.findOne({
|
|
375
|
-
where: {
|
|
376
|
-
id: userId
|
|
377
|
-
}
|
|
378
|
-
});
|
|
379
|
-
if (!userEntity) {
|
|
380
|
-
throw new NotFoundException('User not found');
|
|
381
|
-
}
|
|
372
|
+
await this.getUserForProfile(userId, user);
|
|
382
373
|
if (this.userEnricher?.getProfileSectionData) {
|
|
383
374
|
return this.userEnricher.getProfileSectionData(userId, sectionId, user);
|
|
384
375
|
}
|
|
@@ -387,20 +378,10 @@ export class UserService extends RequestScopedApiService {
|
|
|
387
378
|
/**
|
|
388
379
|
* Update specific profile section
|
|
389
380
|
*/ async updateProfileSection(userId, sectionId, data, user) {
|
|
390
|
-
this.
|
|
391
|
-
await this.ensureRepositoryInitialized();
|
|
381
|
+
const userEntity = await this.getUserForProfile(userId, user);
|
|
392
382
|
if (!this.dataSource) {
|
|
393
383
|
throw new BadRequestException('DataSource not available');
|
|
394
384
|
}
|
|
395
|
-
// Verify user exists
|
|
396
|
-
const userEntity = await this.repository.findOne({
|
|
397
|
-
where: {
|
|
398
|
-
id: userId
|
|
399
|
-
}
|
|
400
|
-
});
|
|
401
|
-
if (!userEntity) {
|
|
402
|
-
throw new NotFoundException('User not found');
|
|
403
|
-
}
|
|
404
385
|
if (!this.userEnricher?.updateProfileSection) {
|
|
405
386
|
throw new BadRequestException('Profile sections not supported');
|
|
406
387
|
}
|
|
@@ -427,17 +408,7 @@ export class UserService extends RequestScopedApiService {
|
|
|
427
408
|
/**
|
|
428
409
|
* Get profile completion percentage
|
|
429
410
|
*/ async getProfileCompletion(userId, user) {
|
|
430
|
-
this.
|
|
431
|
-
await this.ensureRepositoryInitialized();
|
|
432
|
-
// Verify user exists
|
|
433
|
-
const userEntity = await this.repository.findOne({
|
|
434
|
-
where: {
|
|
435
|
-
id: userId
|
|
436
|
-
}
|
|
437
|
-
});
|
|
438
|
-
if (!userEntity) {
|
|
439
|
-
throw new NotFoundException('User not found');
|
|
440
|
-
}
|
|
411
|
+
await this.getUserForProfile(userId, user);
|
|
441
412
|
if (this.userEnricher?.calculateProfileCompletion) {
|
|
442
413
|
return this.userEnricher.calculateProfileCompletion(userId, user);
|
|
443
414
|
}
|
|
@@ -24,75 +24,75 @@ import { Injectable, UnauthorizedException } from '@nestjs/common';
|
|
|
24
24
|
import { ModuleRef } from '@nestjs/core';
|
|
25
25
|
import { PassportStrategy } from '@nestjs/passport';
|
|
26
26
|
import { ExtractJwt, Strategy } from 'passport-jwt';
|
|
27
|
+
import { User } from '../entities/user.entity';
|
|
27
28
|
import { AuthConfigService } from '../services/auth-config.service';
|
|
28
29
|
import { AuthDataSourceProvider } from '../services/auth-datasource.provider';
|
|
29
30
|
import { AuthenticationService } from '../services/authentication.service';
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Type guard to check if payload includes company context
|
|
33
|
-
*/ function isTokenPayloadWithCompany(payload) {
|
|
31
|
+
function hasCompanyContext(payload) {
|
|
34
32
|
return 'companyId' in payload && 'branchId' in payload;
|
|
35
33
|
}
|
|
36
34
|
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
|
|
37
35
|
async validate(_req, payload) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
const userRepo = await this.resolveUserRepository();
|
|
37
|
+
await this.checkTokenRevocation(payload);
|
|
38
|
+
const user = await userRepo.findOne({
|
|
39
|
+
where: {
|
|
40
|
+
id: payload.id,
|
|
41
|
+
isActive: true
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
if (!user) {
|
|
45
|
+
throw new UnauthorizedException('User not found or inactive');
|
|
46
|
+
}
|
|
47
|
+
return this.buildUserInfo(payload);
|
|
48
|
+
}
|
|
49
|
+
async resolveUserRepository() {
|
|
41
50
|
try {
|
|
42
|
-
const
|
|
51
|
+
const provider = await this.moduleRef.resolve(AuthDataSourceProvider, undefined, {
|
|
43
52
|
strict: false
|
|
44
53
|
});
|
|
45
|
-
if (
|
|
46
|
-
|
|
54
|
+
if (provider) {
|
|
55
|
+
return provider.getRepository(User);
|
|
47
56
|
}
|
|
48
|
-
|
|
49
|
-
authService = await this.moduleRef.resolve(AuthenticationService, undefined, {
|
|
50
|
-
strict: false
|
|
51
|
-
});
|
|
52
|
-
} catch (error) {
|
|
57
|
+
} catch {
|
|
53
58
|
throw new UnauthorizedException('Unable to access user data');
|
|
54
59
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
60
|
+
throw new UnauthorizedException('User repository not available');
|
|
61
|
+
}
|
|
62
|
+
async checkTokenRevocation(payload) {
|
|
63
|
+
if (!payload.iat) return;
|
|
64
|
+
try {
|
|
65
|
+
const authService = await this.moduleRef.resolve(AuthenticationService, undefined, {
|
|
66
|
+
strict: false
|
|
67
|
+
});
|
|
68
|
+
if (authService && await authService.isTokenRevoked(payload.id, payload.iat)) {
|
|
62
69
|
throw new UnauthorizedException('Token has been revoked');
|
|
63
70
|
}
|
|
71
|
+
} catch (err) {
|
|
72
|
+
if (err instanceof UnauthorizedException) throw err;
|
|
64
73
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
id: payload.id,
|
|
69
|
-
isActive: true
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
if (!user) {
|
|
73
|
-
throw new UnauthorizedException('User not found or inactive');
|
|
74
|
-
}
|
|
75
|
-
const result = {
|
|
74
|
+
}
|
|
75
|
+
buildUserInfo(payload) {
|
|
76
|
+
const base = {
|
|
76
77
|
id: payload.id,
|
|
77
78
|
email: payload.email
|
|
78
79
|
};
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
result.companyId = payload.companyId;
|
|
82
|
-
// Only set branchId if it's not empty string
|
|
83
|
-
if (payload.branchId) {
|
|
84
|
-
result.branchId = payload.branchId;
|
|
85
|
-
}
|
|
80
|
+
if (!hasCompanyContext(payload)) {
|
|
81
|
+
return base;
|
|
86
82
|
}
|
|
87
|
-
return
|
|
83
|
+
return {
|
|
84
|
+
...base,
|
|
85
|
+
companyId: payload.companyId,
|
|
86
|
+
...payload.branchId && {
|
|
87
|
+
branchId: payload.branchId
|
|
88
|
+
}
|
|
89
|
+
};
|
|
88
90
|
}
|
|
89
91
|
constructor(authConfig, moduleRef){
|
|
90
92
|
super({
|
|
91
93
|
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
|
92
94
|
ignoreExpiration: false,
|
|
93
|
-
|
|
94
|
-
// This ensures AuthConfigService is fully initialized when using forRootAsync
|
|
95
|
-
secretOrKeyProvider: (_request, _rawJwtToken, done)=>{
|
|
95
|
+
secretOrKeyProvider: (_req, _token, done)=>{
|
|
96
96
|
done(null, this.authConfig.getJwtSecret());
|
|
97
97
|
},
|
|
98
98
|
passReqToCallback: true
|
|
@@ -5,8 +5,7 @@ export declare class SetToken implements NestInterceptor {
|
|
|
5
5
|
private readonly authConfig?;
|
|
6
6
|
constructor(authConfig?: AuthConfigService);
|
|
7
7
|
private get cookieName();
|
|
8
|
-
private
|
|
8
|
+
private get maxAgeMs();
|
|
9
9
|
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown>;
|
|
10
|
-
private isBrowserRequest;
|
|
11
10
|
private setRefreshTokenCookie;
|
|
12
11
|
}
|
|
@@ -22,7 +22,6 @@ export interface LoginResponse {
|
|
|
22
22
|
user: IUserMini;
|
|
23
23
|
company?: ICompanyMini;
|
|
24
24
|
branch?: ICompanyBranchMini;
|
|
25
|
-
companyFeatureEnabled?: boolean;
|
|
26
25
|
}
|
|
27
26
|
export interface MeResponse {
|
|
28
27
|
user: IUserMini;
|
|
@@ -33,14 +32,12 @@ export interface BranchForSelection {
|
|
|
33
32
|
id: string;
|
|
34
33
|
name: string;
|
|
35
34
|
slug: string;
|
|
36
|
-
isActive: boolean;
|
|
37
35
|
logoId?: string | null;
|
|
38
36
|
}
|
|
39
37
|
export interface CompanyWithBranches {
|
|
40
38
|
id: string;
|
|
41
39
|
name: string;
|
|
42
40
|
slug: string;
|
|
43
|
-
isActive: boolean;
|
|
44
41
|
logoId?: string | null;
|
|
45
42
|
branches: BranchForSelection[];
|
|
46
43
|
}
|
|
@@ -57,7 +54,6 @@ export interface RegistrationResponse {
|
|
|
57
54
|
user: IUserMini;
|
|
58
55
|
company?: ICompanyMini;
|
|
59
56
|
branch?: ICompanyBranchMini;
|
|
60
|
-
companyFeatureEnabled?: boolean;
|
|
61
57
|
requiresEmailVerification?: boolean;
|
|
62
58
|
}
|
|
63
59
|
export interface LoginResponseRequiresEmailVerification {
|
package/modules/auth.module.d.ts
CHANGED
|
@@ -2,8 +2,12 @@ import { DynamicModule } from '@nestjs/common';
|
|
|
2
2
|
import { AuthModuleAsyncOptions, AuthModuleOptions } from '../interfaces/auth-module-options.interface';
|
|
3
3
|
export declare class AuthModule {
|
|
4
4
|
static forRoot(options: AuthModuleOptions): DynamicModule;
|
|
5
|
-
static forRootAsync(
|
|
6
|
-
private static
|
|
5
|
+
static forRootAsync(options: AuthModuleAsyncOptions): DynamicModule;
|
|
6
|
+
private static buildModule;
|
|
7
|
+
private static buildImports;
|
|
8
|
+
private static buildControllers;
|
|
9
|
+
private static buildProviders;
|
|
10
|
+
private static buildExports;
|
|
7
11
|
private static createAsyncProviders;
|
|
8
12
|
static getEntities(enableCompanyFeature?: boolean): any[];
|
|
9
13
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flusys/nestjs-auth",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Authentication and authorization infrastructure for NestJS applications",
|
|
5
5
|
"main": "cjs/index.js",
|
|
6
6
|
"module": "fesm/index.js",
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
"typeorm": "^0.3.0"
|
|
92
92
|
},
|
|
93
93
|
"dependencies": {
|
|
94
|
-
"@flusys/nestjs-core": "1.0.
|
|
95
|
-
"@flusys/nestjs-shared": "1.0.
|
|
94
|
+
"@flusys/nestjs-core": "1.0.1",
|
|
95
|
+
"@flusys/nestjs-shared": "1.0.1"
|
|
96
96
|
}
|
|
97
97
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { DatabaseMode
|
|
1
|
+
import { DatabaseMode } from '@flusys/nestjs-core';
|
|
2
2
|
import { IModuleConfigService } from '@flusys/nestjs-shared/interfaces';
|
|
3
|
-
import { AuthModuleOptions
|
|
3
|
+
import { AuthModuleOptions } from '../interfaces/auth-module-options.interface';
|
|
4
4
|
export declare class AuthConfigService implements IModuleConfigService {
|
|
5
5
|
private readonly logger;
|
|
6
6
|
private readonly options;
|
|
@@ -16,9 +16,4 @@ export declare class AuthConfigService implements IModuleConfigService {
|
|
|
16
16
|
getRefreshTokenExpiration(): string;
|
|
17
17
|
getRefreshTokenCookieName(): string;
|
|
18
18
|
getFrontendUrl(): string | undefined;
|
|
19
|
-
getDefaultDatabaseConfig(): IDatabaseConfig | undefined;
|
|
20
|
-
getTenantDefaultDatabaseConfig(): IDatabaseConfig | undefined;
|
|
21
|
-
getTenants(): ITenantDatabaseConfig[];
|
|
22
|
-
getOptions(): AuthModuleOptions;
|
|
23
|
-
getConfig(): IAuthModuleConfig;
|
|
24
19
|
}
|
|
@@ -17,9 +17,7 @@ export declare class AuthDataSourceProvider extends MultiTenantDataSourceService
|
|
|
17
17
|
protected getSingleDataSource(): Promise<DataSource>;
|
|
18
18
|
protected getOrCreateTenantConnection(tenant: ITenantDatabaseConfig): Promise<DataSource>;
|
|
19
19
|
private static buildParentOptions;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
getEnableCompanyFeatureForCurrentTenant(): boolean;
|
|
23
|
-
getAuthEntities(enableCompanyFeature?: boolean): Promise<any[]>;
|
|
20
|
+
isCompanyFeatureEnabled(tenant?: ITenantDatabaseConfig): boolean;
|
|
21
|
+
getAuthEntities(): Promise<any[]>;
|
|
24
22
|
protected createDataSourceFromConfig(config: IDatabaseConfig): Promise<DataSource>;
|
|
25
23
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { AuthDataSourceProvider } from './auth-datasource.provider';
|
|
2
1
|
import { PendingRegistration } from '../entities/pending-registration.entity';
|
|
3
2
|
import { IAuthEmailProvider } from '../interfaces';
|
|
3
|
+
import { AuthDataSourceProvider } from './auth-datasource.provider';
|
|
4
4
|
export declare class AuthEmailService {
|
|
5
5
|
private readonly dataSourceProvider?;
|
|
6
6
|
private readonly emailProvider?;
|
|
@@ -8,12 +8,15 @@ export declare class AuthEmailService {
|
|
|
8
8
|
private readonly PASSWORD_RESET_EXPIRY;
|
|
9
9
|
private readonly EMAIL_VERIFICATION_EXPIRY;
|
|
10
10
|
constructor(dataSourceProvider?: AuthDataSourceProvider, emailProvider?: IAuthEmailProvider);
|
|
11
|
-
private getUserRepository;
|
|
12
|
-
private getUserTokenRepository;
|
|
13
|
-
private getPendingRegistrationRepository;
|
|
14
11
|
isEmailEnabled(): boolean;
|
|
12
|
+
private getRepository;
|
|
15
13
|
private generateToken;
|
|
16
14
|
private hashToken;
|
|
15
|
+
private getErrorMessage;
|
|
16
|
+
private assertEmailEnabled;
|
|
17
|
+
private sendVerificationEmailSafe;
|
|
18
|
+
private validateTokenNotExpiredOrUsed;
|
|
19
|
+
hasPendingRegistration(email: string): Promise<boolean>;
|
|
17
20
|
createPendingRegistration(data: {
|
|
18
21
|
email: string;
|
|
19
22
|
name?: string;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
+
import { HybridCache } from '@flusys/nestjs-shared/classes';
|
|
1
2
|
import { JwtService } from '@nestjs/jwt';
|
|
2
|
-
import { AuthConfigService } from './auth-config.service';
|
|
3
|
-
import { AuthDataSourceProvider } from './auth-datasource.provider';
|
|
4
3
|
import { RegistrationDto } from '../dtos/authentication.dto';
|
|
5
|
-
import { HybridCache } from '@flusys/nestjs-shared/classes';
|
|
6
|
-
import { LoginResponse, LoginResponseRequiresEmailVerification, LoginResponseRequiresSelection, MeResponse, RegistrationResponse } from '../interfaces/authentication.interface';
|
|
7
|
-
import { AuthEmailService } from './auth-email.service';
|
|
8
4
|
import { CompanyBranch } from '../entities/company-branch.entity';
|
|
9
5
|
import { Company } from '../entities/company.entity';
|
|
10
6
|
import { PendingRegistration } from '../entities/pending-registration.entity';
|
|
7
|
+
import { LoginResponse, LoginResponseRequiresEmailVerification, LoginResponseRequiresSelection, MeResponse, RegistrationResponse } from '../interfaces/authentication.interface';
|
|
8
|
+
import { AuthConfigService } from './auth-config.service';
|
|
9
|
+
import { AuthDataSourceProvider } from './auth-datasource.provider';
|
|
10
|
+
import { AuthEmailService } from './auth-email.service';
|
|
11
11
|
import { CompanySelectionSessionService } from './company-selection-session.service';
|
|
12
12
|
export declare class AuthenticationService {
|
|
13
13
|
private readonly jwtService;
|
|
@@ -18,6 +18,7 @@ export declare class AuthenticationService {
|
|
|
18
18
|
private readonly authEmailService?;
|
|
19
19
|
private readonly logger;
|
|
20
20
|
constructor(jwtService: JwtService, authConfig: AuthConfigService, cacheManager?: HybridCache, dataSourceProvider?: AuthDataSourceProvider, sessionService?: CompanySelectionSessionService, authEmailService?: AuthEmailService);
|
|
21
|
+
private isCompanyFeatureEnabled;
|
|
21
22
|
private getUserRepository;
|
|
22
23
|
private getPermissionRepository;
|
|
23
24
|
private getCompanyRepository;
|
|
@@ -28,14 +29,15 @@ export declare class AuthenticationService {
|
|
|
28
29
|
requiresEmailVerification: true;
|
|
29
30
|
message: string;
|
|
30
31
|
}>;
|
|
32
|
+
private handleExistingUserCompanyRegistration;
|
|
31
33
|
private createUserDirectly;
|
|
32
34
|
createUserFromPendingRegistration(pending: PendingRegistration): Promise<RegistrationResponse>;
|
|
33
|
-
private handleCompanyRegistrationFromPending;
|
|
34
|
-
private handleCompanyRegistration;
|
|
35
35
|
private processCompanyRegistration;
|
|
36
|
-
private
|
|
36
|
+
private findOrCreateCompanyAndBranch;
|
|
37
|
+
private assignUserPermissions;
|
|
37
38
|
select(sessionId: string, companyId: string, branchId: string): Promise<LoginResponse>;
|
|
38
39
|
switchCompany(userId: string, companyId: string, branchId: string): Promise<LoginResponse>;
|
|
40
|
+
private validateAndLoginWithCompany;
|
|
39
41
|
refreshToken(refreshToken: string): Promise<{
|
|
40
42
|
accessToken: string;
|
|
41
43
|
refreshToken: string;
|
|
@@ -51,6 +53,10 @@ export declare class AuthenticationService {
|
|
|
51
53
|
getUserInfo(userId: string, companyId?: string, branchId?: string): Promise<MeResponse>;
|
|
52
54
|
private ensureCompanyFeatureEnabled;
|
|
53
55
|
private validateUser;
|
|
56
|
+
private toUserInfo;
|
|
57
|
+
private toCompanyInfo;
|
|
58
|
+
private toBranchInfo;
|
|
59
|
+
private generateTokens;
|
|
54
60
|
private generateLoginResponse;
|
|
55
61
|
private generateLoginResponseWithCompany;
|
|
56
62
|
private generateCompanySelectionResponse;
|
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { UserCompanyPermission } from '../entities/user-company-permission.entity';
|
|
1
|
+
import { PermissionType, UserCompanyPermission } from '../entities/user-company-permission.entity';
|
|
3
2
|
import { AuthDataSourceProvider } from './auth-datasource.provider';
|
|
4
3
|
export declare class UserPermissionService {
|
|
5
4
|
private readonly dataSourceProvider;
|
|
6
5
|
private _repository;
|
|
7
6
|
constructor(dataSourceProvider: AuthDataSourceProvider);
|
|
8
7
|
private getRepository;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
revokeBranchAccess(userId: string, branchId: string): Promise<void>;
|
|
8
|
+
findUserPermissions(userId: string, type: PermissionType): Promise<UserCompanyPermission[]>;
|
|
9
|
+
assignPermission(userId: string, targetId: string, type: PermissionType, metadata?: Record<string, any>): Promise<void>;
|
|
10
|
+
revokePermission(userId: string, targetId: string, type: PermissionType): Promise<void>;
|
|
11
|
+
findUserCompanies: (userId: string) => Promise<UserCompanyPermission[]>;
|
|
12
|
+
findUserBranches(userId: string, companyId?: string): Promise<UserCompanyPermission[]>;
|
|
15
13
|
}
|
|
@@ -20,7 +20,7 @@ export declare class UserService extends RequestScopedApiService<CreateUserDto,
|
|
|
20
20
|
protected resolveEntity(): EntityTarget<User>;
|
|
21
21
|
protected getDataSourceProvider(): AuthDataSourceProvider;
|
|
22
22
|
protected ensureRepositoryInitialized(): Promise<void>;
|
|
23
|
-
private
|
|
23
|
+
private getUserForProfile;
|
|
24
24
|
verifyEmail(id: string): Promise<void>;
|
|
25
25
|
verifyPhone(id: string): Promise<void>;
|
|
26
26
|
updateStatus(id: string, isActive: boolean, user?: ILoggedUserInfo | null): Promise<void>;
|