@uniorganization/uni-lib 5.0.3 → 5.0.4
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/dist/modules/common/jwt.strategy.js +13 -4
- package/dist/modules/tenant/impersonation-audit.service.d.ts +27 -0
- package/dist/modules/tenant/impersonation-audit.service.js +85 -0
- package/dist/modules/tenant/index.d.ts +1 -0
- package/dist/modules/tenant/index.js +1 -0
- package/dist/modules/tenant/master-tenant.guard.js +3 -1
- package/dist/modules/tenant/tenant-audit.types.d.ts +1 -0
- package/dist/modules/tenant/tenant-authorization.service.d.ts +1 -1
- package/dist/modules/tenant/tenant-authorization.service.js +7 -38
- package/dist/modules/tenant/tenant-context.interceptor.d.ts +7 -2
- package/dist/modules/tenant/tenant-context.interceptor.js +86 -20
- package/dist/modules/tenant/tenant-context.service.d.ts +1 -0
- package/dist/modules/tenant/tenant-context.service.js +3 -0
- package/dist/modules/tenant/tenant-credential.guard.js +1 -0
- package/dist/modules/tenant/tenant-runner.service.js +3 -2
- package/dist/modules/tenant/tenant.constants.d.ts +0 -1
- package/dist/modules/tenant/tenant.constants.js +1 -2
- package/dist/modules/tenant/tenant.module.js +3 -0
- package/dist/modules/tenant/tenant.types.d.ts +3 -0
- package/package.json +2 -2
|
@@ -39,6 +39,8 @@ let JwtStrategy = class JwtStrategy extends (0, passport_1.PassportStrategy)(pas
|
|
|
39
39
|
const signedUser = payload === null || payload === void 0 ? void 0 : payload.user;
|
|
40
40
|
const effectiveClientId = Number(signedUser === null || signedUser === void 0 ? void 0 : signedUser.clientId);
|
|
41
41
|
const originalClientId = (_a = signedUser === null || signedUser === void 0 ? void 0 : signedUser.originalClientId) !== null && _a !== void 0 ? _a : null;
|
|
42
|
+
const originalUserId = (_b = signedUser === null || signedUser === void 0 ? void 0 : signedUser.originalUserId) !== null && _b !== void 0 ? _b : null;
|
|
43
|
+
const impersonationType = (_c = signedUser === null || signedUser === void 0 ? void 0 : signedUser.impersonationType) !== null && _c !== void 0 ? _c : null;
|
|
42
44
|
const now = Math.floor(Date.now() / 1000);
|
|
43
45
|
if ((payload === null || payload === void 0 ? void 0 : payload.type) !== 'access' ||
|
|
44
46
|
!signedUser ||
|
|
@@ -56,18 +58,23 @@ let JwtStrategy = class JwtStrategy extends (0, passport_1.PassportStrategy)(pas
|
|
|
56
58
|
if (yield this.securityState.isAccessTokenRevoked(payload.jti)) {
|
|
57
59
|
throw new common_1.UnauthorizedException();
|
|
58
60
|
}
|
|
59
|
-
const impersonating =
|
|
61
|
+
const impersonating = originalUserId !== null;
|
|
60
62
|
if ((impersonating &&
|
|
61
63
|
(originalClientId !== tenant_1.NEWTECH_CLIENT_ID ||
|
|
62
64
|
effectiveClientId === tenant_1.NEWTECH_CLIENT_ID ||
|
|
63
|
-
!
|
|
65
|
+
!Number.isInteger(originalUserId) ||
|
|
66
|
+
originalUserId <= 0 ||
|
|
67
|
+
originalUserId === signedUser.id ||
|
|
68
|
+
impersonationType !== 'user' ||
|
|
64
69
|
!signedUser.impersonatedAt ||
|
|
65
70
|
!signedUser.impersonationReason)) ||
|
|
66
|
-
(!impersonating &&
|
|
71
|
+
(!impersonating &&
|
|
72
|
+
(originalClientId !== null || impersonationType !== null)) ||
|
|
73
|
+
(impersonating && originalClientId === null)) {
|
|
67
74
|
throw new common_1.UnauthorizedException();
|
|
68
75
|
}
|
|
69
76
|
const resolved = impersonating
|
|
70
|
-
? yield this.authorization.
|
|
77
|
+
? yield this.authorization.resolveUserImpersonation(originalUserId, originalClientId, signedUser.id, effectiveClientId)
|
|
71
78
|
: yield this.authorization.resolve(signedUser.id, effectiveClientId);
|
|
72
79
|
const { identity, snapshot } = resolved;
|
|
73
80
|
const isMasterTenantAdmin = !impersonating &&
|
|
@@ -93,6 +100,8 @@ let JwtStrategy = class JwtStrategy extends (0, passport_1.PassportStrategy)(pas
|
|
|
93
100
|
jti: payload.jti,
|
|
94
101
|
tokenExpiresAt: payload.exp,
|
|
95
102
|
originalClientId,
|
|
103
|
+
originalUserId,
|
|
104
|
+
impersonationType: impersonating ? 'user' : null,
|
|
96
105
|
impersonatedAt: impersonating ? signedUser.impersonatedAt : null,
|
|
97
106
|
impersonationReason: impersonating
|
|
98
107
|
? signedUser.impersonationReason
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { DataSource } from 'typeorm';
|
|
2
|
+
type MutationAuditInput = {
|
|
3
|
+
actorUserId: number | null;
|
|
4
|
+
effectiveUserId: number | null;
|
|
5
|
+
clientId: number;
|
|
6
|
+
reason: string | null;
|
|
7
|
+
correlationId: string;
|
|
8
|
+
method: string;
|
|
9
|
+
route: string;
|
|
10
|
+
};
|
|
11
|
+
type LifecycleAuditInput = {
|
|
12
|
+
event: 'USER_IMPERSONATION_START' | 'USER_IMPERSONATION_END';
|
|
13
|
+
actorUserId: number;
|
|
14
|
+
targetUserId: number;
|
|
15
|
+
clientId: number;
|
|
16
|
+
reason: string | null;
|
|
17
|
+
correlationId: string;
|
|
18
|
+
result: 'SUCCESS' | 'DENIED';
|
|
19
|
+
};
|
|
20
|
+
export declare class ImpersonationAuditService {
|
|
21
|
+
private readonly dataSource;
|
|
22
|
+
constructor(dataSource: DataSource);
|
|
23
|
+
beginMutation(input: MutationAuditInput): Promise<number>;
|
|
24
|
+
completeMutation(auditId: number, result: 'SUCCESS' | 'FAILURE', statusCode?: number | null): Promise<void>;
|
|
25
|
+
recordLifecycle(input: LifecycleAuditInput): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
12
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
13
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
14
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
15
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
16
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
17
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.ImpersonationAuditService = void 0;
|
|
22
|
+
const common_1 = require("@nestjs/common");
|
|
23
|
+
const typeorm_1 = require("typeorm");
|
|
24
|
+
let ImpersonationAuditService = class ImpersonationAuditService {
|
|
25
|
+
constructor(dataSource) {
|
|
26
|
+
this.dataSource = dataSource;
|
|
27
|
+
}
|
|
28
|
+
beginMutation(input) {
|
|
29
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
30
|
+
var _a, _b, _c;
|
|
31
|
+
const rows = yield this.dataSource.query(`INSERT INTO stm."ACCESS_AUDIT_LOG" (
|
|
32
|
+
client_id, actor_user_id, target_user_id, event,
|
|
33
|
+
reason, correlation_id, details
|
|
34
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb)
|
|
35
|
+
RETURNING id`, [
|
|
36
|
+
input.clientId,
|
|
37
|
+
input.actorUserId,
|
|
38
|
+
input.effectiveUserId,
|
|
39
|
+
'USER_IMPERSONATED_MUTATION',
|
|
40
|
+
input.reason,
|
|
41
|
+
input.correlationId,
|
|
42
|
+
JSON.stringify({
|
|
43
|
+
service: (_b = (_a = process.env.SERVICE_NAME) !== null && _a !== void 0 ? _a : process.env.npm_package_name) !== null && _b !== void 0 ? _b : 'unknown',
|
|
44
|
+
method: input.method,
|
|
45
|
+
route: input.route.split('?')[0],
|
|
46
|
+
effectiveUserId: input.effectiveUserId,
|
|
47
|
+
result: 'STARTED',
|
|
48
|
+
}),
|
|
49
|
+
]);
|
|
50
|
+
const auditId = Number((_c = rows[0]) === null || _c === void 0 ? void 0 : _c.id);
|
|
51
|
+
if (!Number.isInteger(auditId) || auditId <= 0) {
|
|
52
|
+
throw new Error('The impersonation audit record was not created');
|
|
53
|
+
}
|
|
54
|
+
return auditId;
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
completeMutation(auditId_1, result_1) {
|
|
58
|
+
return __awaiter(this, arguments, void 0, function* (auditId, result, statusCode = null) {
|
|
59
|
+
yield this.dataSource.query(`UPDATE stm."ACCESS_AUDIT_LOG"
|
|
60
|
+
SET details = details || $2::jsonb
|
|
61
|
+
WHERE id = $1`, [auditId, JSON.stringify({ result, statusCode })]);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
recordLifecycle(input) {
|
|
65
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
66
|
+
yield this.dataSource.query(`INSERT INTO stm."ACCESS_AUDIT_LOG" (
|
|
67
|
+
client_id, actor_user_id, target_user_id, event,
|
|
68
|
+
reason, correlation_id, details
|
|
69
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb)`, [
|
|
70
|
+
input.clientId,
|
|
71
|
+
input.actorUserId,
|
|
72
|
+
input.targetUserId,
|
|
73
|
+
input.event,
|
|
74
|
+
input.reason,
|
|
75
|
+
input.correlationId,
|
|
76
|
+
JSON.stringify({ result: input.result }),
|
|
77
|
+
]);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
exports.ImpersonationAuditService = ImpersonationAuditService;
|
|
82
|
+
exports.ImpersonationAuditService = ImpersonationAuditService = __decorate([
|
|
83
|
+
(0, common_1.Injectable)(),
|
|
84
|
+
__metadata("design:paramtypes", [typeorm_1.DataSource])
|
|
85
|
+
], ImpersonationAuditService);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from './current-tenant.decorator';
|
|
2
2
|
export * from './master-tenant.decorator';
|
|
3
3
|
export * from './master-tenant.guard';
|
|
4
|
+
export * from './impersonation-audit.service';
|
|
4
5
|
export * from './redis-tenant-state.store';
|
|
5
6
|
export * from './tenant-audit.types';
|
|
6
7
|
export * from './tenant-authorization.keys';
|
|
@@ -17,6 +17,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
17
17
|
__exportStar(require("./current-tenant.decorator"), exports);
|
|
18
18
|
__exportStar(require("./master-tenant.decorator"), exports);
|
|
19
19
|
__exportStar(require("./master-tenant.guard"), exports);
|
|
20
|
+
__exportStar(require("./impersonation-audit.service"), exports);
|
|
20
21
|
__exportStar(require("./redis-tenant-state.store"), exports);
|
|
21
22
|
__exportStar(require("./tenant-audit.types"), exports);
|
|
22
23
|
__exportStar(require("./tenant-authorization.keys"), exports);
|
|
@@ -18,7 +18,7 @@ let MasterTenantGuard = class MasterTenantGuard {
|
|
|
18
18
|
this.reflector = reflector;
|
|
19
19
|
}
|
|
20
20
|
canActivate(context) {
|
|
21
|
-
var _a, _b, _c, _d;
|
|
21
|
+
var _a, _b, _c, _d, _e;
|
|
22
22
|
const required = this.reflector.getAllAndOverride(tenant_constants_1.MASTER_TENANT_METADATA_KEY, [context.getHandler(), context.getClass()]);
|
|
23
23
|
if (!required) {
|
|
24
24
|
return true;
|
|
@@ -29,8 +29,10 @@ let MasterTenantGuard = class MasterTenantGuard {
|
|
|
29
29
|
}
|
|
30
30
|
const effectiveClientId = ((_a = request.tenantContext) === null || _a === void 0 ? void 0 : _a.clientId) || request.user.clientId;
|
|
31
31
|
const originalClientId = (_d = (_c = (_b = request.tenantContext) === null || _b === void 0 ? void 0 : _b.originalClientId) !== null && _c !== void 0 ? _c : request.user.originalClientId) !== null && _d !== void 0 ? _d : null;
|
|
32
|
+
const originalUserId = (_e = request.user.originalUserId) !== null && _e !== void 0 ? _e : null;
|
|
32
33
|
if (effectiveClientId !== tenant_constants_1.NEWTECH_CLIENT_ID ||
|
|
33
34
|
originalClientId !== null ||
|
|
35
|
+
originalUserId !== null ||
|
|
34
36
|
!request.user.isMasterTenantAdmin) {
|
|
35
37
|
throw new common_1.ForbiddenException();
|
|
36
38
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { TenantAuthenticationType } from './tenant.types';
|
|
2
2
|
export interface TenantAuditMetadata {
|
|
3
3
|
actorUserId: number | null;
|
|
4
|
+
effectiveUserId: number | null;
|
|
4
5
|
originalClientId: number | null;
|
|
5
6
|
effectiveClientId: number;
|
|
6
7
|
authenticationType: TenantAuthenticationType;
|
|
@@ -13,7 +13,7 @@ export declare class TenantAuthorizationService {
|
|
|
13
13
|
private readonly logger;
|
|
14
14
|
constructor(store: TenantAuthorizationStore, userRepository: Repository<Users>, clientRepository: Repository<Client>, userRoleRepository: Repository<UserRoles>);
|
|
15
15
|
resolve(userId: number, clientId: number, _legacyOriginClientId?: number): Promise<ResolvedTenantAuthorization>;
|
|
16
|
-
|
|
16
|
+
resolveUserImpersonation(actorUserId: number, originClientId: number, targetUserId: number, targetClientId: number): Promise<ResolvedTenantAuthorization>;
|
|
17
17
|
invalidate(userId: number, clientId: number): Promise<void>;
|
|
18
18
|
invalidateMany(clientId: number, userIds: readonly number[]): Promise<void>;
|
|
19
19
|
private loadFromDatabase;
|
|
@@ -63,50 +63,19 @@ let TenantAuthorizationService = TenantAuthorizationService_1 = class TenantAuth
|
|
|
63
63
|
return resolved;
|
|
64
64
|
});
|
|
65
65
|
}
|
|
66
|
-
|
|
66
|
+
resolveUserImpersonation(actorUserId, originClientId, targetUserId, targetClientId) {
|
|
67
67
|
return __awaiter(this, void 0, void 0, function* () {
|
|
68
|
-
const targetClientId = legacyTargetClientId !== null && legacyTargetClientId !== void 0 ? legacyTargetClientId : targetClientIdOrLegacyOriginClientId;
|
|
69
68
|
if (originClientId !== tenant_constants_1.NEWTECH_CLIENT_ID ||
|
|
70
|
-
targetClientId === tenant_constants_1.NEWTECH_CLIENT_ID
|
|
69
|
+
targetClientId === tenant_constants_1.NEWTECH_CLIENT_ID ||
|
|
70
|
+
actorUserId === targetUserId) {
|
|
71
71
|
throw new common_1.UnauthorizedException();
|
|
72
72
|
}
|
|
73
|
-
const origin = yield this.resolve(
|
|
74
|
-
|
|
75
|
-
origin.snapshot.
|
|
76
|
-
role.roleName.toLowerCase() === 'sysadmin');
|
|
77
|
-
const targetClient = yield this.clientRepository.findOne({
|
|
78
|
-
where: { id: targetClientId, deletedAt: (0, typeorm_2.IsNull)() },
|
|
79
|
-
});
|
|
80
|
-
if (!isNewtechMaster || !targetClient) {
|
|
73
|
+
const origin = yield this.resolve(actorUserId, originClientId);
|
|
74
|
+
if (!origin.snapshot.permissions.includes('master.users.read') ||
|
|
75
|
+
!origin.snapshot.permissions.includes('master.users.impersonate')) {
|
|
81
76
|
throw new common_1.UnauthorizedException();
|
|
82
77
|
}
|
|
83
|
-
|
|
84
|
-
userId,
|
|
85
|
-
clientId: targetClientId,
|
|
86
|
-
clientName: targetClient.name,
|
|
87
|
-
clientAlias: targetClient.alias,
|
|
88
|
-
roles: [
|
|
89
|
-
{
|
|
90
|
-
roleId: 0,
|
|
91
|
-
roleName: tenant_constants_1.TENANT_IMPERSONATOR_ROLE,
|
|
92
|
-
moduleId: 0,
|
|
93
|
-
moduleName: tenant_constants_1.TENANT_IMPERSONATOR_ROLE,
|
|
94
|
-
},
|
|
95
|
-
],
|
|
96
|
-
privileges: [],
|
|
97
|
-
accessRole: {
|
|
98
|
-
id: 0,
|
|
99
|
-
code: tenant_constants_1.TENANT_IMPERSONATOR_ROLE,
|
|
100
|
-
name: 'Tenant impersonator',
|
|
101
|
-
scope: 'GLOBAL',
|
|
102
|
-
clientId: null,
|
|
103
|
-
},
|
|
104
|
-
permissions: [],
|
|
105
|
-
authzVersion: 1,
|
|
106
|
-
identity: origin.identity,
|
|
107
|
-
cachedAt: new Date().toISOString(),
|
|
108
|
-
};
|
|
109
|
-
return { identity: origin.identity, snapshot };
|
|
78
|
+
return this.resolve(targetUserId, targetClientId);
|
|
110
79
|
});
|
|
111
80
|
}
|
|
112
81
|
invalidate(userId, clientId) {
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { CallHandler, ExecutionContext, NestInterceptor } from '@nestjs/common';
|
|
2
2
|
import { Observable } from 'rxjs';
|
|
3
3
|
import { TenantContextStorage } from './tenant-context.storage';
|
|
4
|
+
import { ImpersonationAuditService } from './impersonation-audit.service';
|
|
4
5
|
export declare class TenantContextInterceptor implements NestInterceptor {
|
|
5
6
|
private readonly tenantContextStorage;
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
private readonly impersonationAudit;
|
|
8
|
+
private readonly logger;
|
|
9
|
+
constructor(tenantContextStorage: TenantContextStorage, impersonationAudit: ImpersonationAuditService);
|
|
10
|
+
intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<unknown>>;
|
|
11
|
+
private beginImpersonatedMutation;
|
|
12
|
+
private errorStatus;
|
|
8
13
|
private fromAuthenticatedUser;
|
|
9
14
|
}
|
|
@@ -8,33 +8,97 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
8
8
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
9
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
10
|
};
|
|
11
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
12
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
13
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
14
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
15
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
16
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
17
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
var TenantContextInterceptor_1;
|
|
11
21
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
22
|
exports.TenantContextInterceptor = void 0;
|
|
13
23
|
const crypto_1 = require("crypto");
|
|
14
24
|
const common_1 = require("@nestjs/common");
|
|
15
25
|
const rxjs_1 = require("rxjs");
|
|
16
26
|
const tenant_context_storage_1 = require("./tenant-context.storage");
|
|
17
|
-
|
|
18
|
-
|
|
27
|
+
const impersonation_audit_service_1 = require("./impersonation-audit.service");
|
|
28
|
+
let TenantContextInterceptor = TenantContextInterceptor_1 = class TenantContextInterceptor {
|
|
29
|
+
constructor(tenantContextStorage, impersonationAudit) {
|
|
19
30
|
this.tenantContextStorage = tenantContextStorage;
|
|
31
|
+
this.impersonationAudit = impersonationAudit;
|
|
32
|
+
this.logger = new common_1.Logger(TenantContextInterceptor_1.name);
|
|
20
33
|
}
|
|
21
34
|
intercept(context, next) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
return () =>
|
|
35
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
36
|
+
if (context.getType() !== 'http') {
|
|
37
|
+
return next.handle();
|
|
38
|
+
}
|
|
39
|
+
const request = context.switchToHttp().getRequest();
|
|
40
|
+
const state = request.tenantContext || this.fromAuthenticatedUser(request);
|
|
41
|
+
if (!state) {
|
|
42
|
+
return next.handle();
|
|
43
|
+
}
|
|
44
|
+
request.tenantContext = Object.freeze(state);
|
|
45
|
+
const auditId = yield this.beginImpersonatedMutation(request, state);
|
|
46
|
+
return new rxjs_1.Observable((subscriber) => {
|
|
47
|
+
const subscription = this.tenantContextStorage.run(state, () => next
|
|
48
|
+
.handle()
|
|
49
|
+
.pipe((0, rxjs_1.concatMap)((value) => auditId === null
|
|
50
|
+
? (0, rxjs_1.of)(value)
|
|
51
|
+
: (0, rxjs_1.from)(this.impersonationAudit.completeMutation(auditId, 'SUCCESS')).pipe((0, rxjs_1.map)(() => value), (0, rxjs_1.catchError)((error) => {
|
|
52
|
+
this.logger.error(`Could not complete impersonation audit ${auditId}`, error instanceof Error ? error.stack : String(error));
|
|
53
|
+
return (0, rxjs_1.of)(value);
|
|
54
|
+
}))), (0, rxjs_1.catchError)((error) => {
|
|
55
|
+
if (auditId === null)
|
|
56
|
+
return (0, rxjs_1.throwError)(() => error);
|
|
57
|
+
return (0, rxjs_1.from)(this.impersonationAudit.completeMutation(auditId, 'FAILURE', this.errorStatus(error))).pipe((0, rxjs_1.catchError)((auditError) => {
|
|
58
|
+
this.logger.error(`Could not complete failed impersonation audit ${auditId}`, auditError instanceof Error
|
|
59
|
+
? auditError.stack
|
|
60
|
+
: String(auditError));
|
|
61
|
+
return (0, rxjs_1.of)(undefined);
|
|
62
|
+
}), (0, rxjs_1.concatMap)(() => (0, rxjs_1.throwError)(() => error)));
|
|
63
|
+
}))
|
|
64
|
+
.subscribe(subscriber));
|
|
65
|
+
return () => subscription.unsubscribe();
|
|
66
|
+
});
|
|
34
67
|
});
|
|
35
68
|
}
|
|
69
|
+
beginImpersonatedMutation(request, state) {
|
|
70
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
71
|
+
var _a;
|
|
72
|
+
const method = String(request.method || '').toUpperCase();
|
|
73
|
+
if (!['POST', 'PUT', 'PATCH', 'DELETE'].includes(method))
|
|
74
|
+
return null;
|
|
75
|
+
if (!state.impersonatedAt || state.actorUserId === state.effectiveUserId) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
const route = request.originalUrl || ((_a = request.route) === null || _a === void 0 ? void 0 : _a.path) || 'unknown';
|
|
79
|
+
if (route.split('?')[0].endsWith('/auth/end-impersonation'))
|
|
80
|
+
return null;
|
|
81
|
+
return this.impersonationAudit.beginMutation({
|
|
82
|
+
actorUserId: state.actorUserId,
|
|
83
|
+
effectiveUserId: state.effectiveUserId,
|
|
84
|
+
clientId: state.clientId,
|
|
85
|
+
reason: state.impersonationReason,
|
|
86
|
+
correlationId: state.correlationId,
|
|
87
|
+
method,
|
|
88
|
+
route,
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
errorStatus(error) {
|
|
93
|
+
var _a;
|
|
94
|
+
if (!error || typeof error !== 'object')
|
|
95
|
+
return null;
|
|
96
|
+
const candidate = error;
|
|
97
|
+
const status = Number((_a = candidate.status) !== null && _a !== void 0 ? _a : candidate.statusCode);
|
|
98
|
+
return Number.isInteger(status) ? status : null;
|
|
99
|
+
}
|
|
36
100
|
fromAuthenticatedUser(request) {
|
|
37
|
-
var _a, _b, _c, _d;
|
|
101
|
+
var _a, _b, _c, _d, _e;
|
|
38
102
|
if (!request.user || request.user === true) {
|
|
39
103
|
return undefined;
|
|
40
104
|
}
|
|
@@ -53,17 +117,19 @@ let TenantContextInterceptor = class TenantContextInterceptor {
|
|
|
53
117
|
return {
|
|
54
118
|
clientId: user.clientId,
|
|
55
119
|
originalClientId: (_b = user.originalClientId) !== null && _b !== void 0 ? _b : null,
|
|
56
|
-
actorUserId: user.id,
|
|
120
|
+
actorUserId: (_c = user.originalUserId) !== null && _c !== void 0 ? _c : user.id,
|
|
121
|
+
effectiveUserId: user.id,
|
|
57
122
|
authenticationType: 'jwt',
|
|
58
|
-
impersonatedAt: (
|
|
59
|
-
impersonationReason: (
|
|
123
|
+
impersonatedAt: (_d = user.impersonatedAt) !== null && _d !== void 0 ? _d : null,
|
|
124
|
+
impersonationReason: (_e = user.impersonationReason) !== null && _e !== void 0 ? _e : null,
|
|
60
125
|
correlationId,
|
|
61
126
|
roles: user.roles || [],
|
|
62
127
|
};
|
|
63
128
|
}
|
|
64
129
|
};
|
|
65
130
|
exports.TenantContextInterceptor = TenantContextInterceptor;
|
|
66
|
-
exports.TenantContextInterceptor = TenantContextInterceptor = __decorate([
|
|
131
|
+
exports.TenantContextInterceptor = TenantContextInterceptor = TenantContextInterceptor_1 = __decorate([
|
|
67
132
|
(0, common_1.Injectable)(),
|
|
68
|
-
__metadata("design:paramtypes", [tenant_context_storage_1.TenantContextStorage
|
|
133
|
+
__metadata("design:paramtypes", [tenant_context_storage_1.TenantContextStorage,
|
|
134
|
+
impersonation_audit_service_1.ImpersonationAuditService])
|
|
69
135
|
], TenantContextInterceptor);
|
|
@@ -8,6 +8,7 @@ export declare class TenantContext {
|
|
|
8
8
|
getClientId(): number;
|
|
9
9
|
getOriginalClientId(): number | null;
|
|
10
10
|
getActorUserId(): number | null;
|
|
11
|
+
getEffectiveUserId(): number | null;
|
|
11
12
|
getAuthenticationType(): TenantAuthenticationType;
|
|
12
13
|
getImpersonationReason(): string | null;
|
|
13
14
|
getCorrelationId(): string;
|
|
@@ -36,6 +36,9 @@ let TenantContext = class TenantContext {
|
|
|
36
36
|
getActorUserId() {
|
|
37
37
|
return this.getState().actorUserId;
|
|
38
38
|
}
|
|
39
|
+
getEffectiveUserId() {
|
|
40
|
+
return this.getState().effectiveUserId;
|
|
41
|
+
}
|
|
39
42
|
getAuthenticationType() {
|
|
40
43
|
return this.getState().authenticationType;
|
|
41
44
|
}
|
|
@@ -65,6 +65,7 @@ let TenantCredentialGuard = class TenantCredentialGuard {
|
|
|
65
65
|
clientId: credential.clientId,
|
|
66
66
|
originalClientId: null,
|
|
67
67
|
actorUserId: null,
|
|
68
|
+
effectiveUserId: null,
|
|
68
69
|
authenticationType: 'client-credential',
|
|
69
70
|
impersonatedAt: null,
|
|
70
71
|
impersonationReason: null,
|
|
@@ -18,7 +18,7 @@ let TenantRunner = class TenantRunner {
|
|
|
18
18
|
this.tenantContextStorage = tenantContextStorage;
|
|
19
19
|
}
|
|
20
20
|
run(clientId, metadata, callback) {
|
|
21
|
-
var _a, _b;
|
|
21
|
+
var _a, _b, _c;
|
|
22
22
|
if (!Number.isInteger(clientId) || clientId <= 0) {
|
|
23
23
|
throw new Error('TenantRunner requires a valid client ID');
|
|
24
24
|
}
|
|
@@ -26,11 +26,12 @@ let TenantRunner = class TenantRunner {
|
|
|
26
26
|
clientId,
|
|
27
27
|
originalClientId: null,
|
|
28
28
|
actorUserId: (_a = metadata.actorUserId) !== null && _a !== void 0 ? _a : null,
|
|
29
|
+
effectiveUserId: (_b = metadata.actorUserId) !== null && _b !== void 0 ? _b : null,
|
|
29
30
|
authenticationType: metadata.authenticationType,
|
|
30
31
|
impersonatedAt: null,
|
|
31
32
|
impersonationReason: null,
|
|
32
33
|
correlationId: metadata.correlationId || (0, crypto_1.randomUUID)(),
|
|
33
|
-
credentialId: (
|
|
34
|
+
credentialId: (_c = metadata.credentialId) !== null && _c !== void 0 ? _c : null,
|
|
34
35
|
}, callback);
|
|
35
36
|
}
|
|
36
37
|
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export declare const NEWTECH_CLIENT_ID = 1;
|
|
2
2
|
export declare const ALTERA_CLIENT_ID = 2;
|
|
3
|
-
export declare const TENANT_IMPERSONATOR_ROLE = "TENANT_IMPERSONATOR";
|
|
4
3
|
export declare const MASTER_TENANT_METADATA_KEY = "masterTenant";
|
|
5
4
|
export declare const PUBLIC_ROUTE_METADATA_KEY = "publicRoute";
|
|
6
5
|
export declare const TENANT_CONTEXT_REQUEST_KEY = "tenantContext";
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.TENANT_CREDENTIAL_HEADER = exports.TENANT_CONTEXT_REQUEST_KEY = exports.PUBLIC_ROUTE_METADATA_KEY = exports.MASTER_TENANT_METADATA_KEY = exports.
|
|
3
|
+
exports.TENANT_CREDENTIAL_HEADER = exports.TENANT_CONTEXT_REQUEST_KEY = exports.PUBLIC_ROUTE_METADATA_KEY = exports.MASTER_TENANT_METADATA_KEY = exports.ALTERA_CLIENT_ID = exports.NEWTECH_CLIENT_ID = void 0;
|
|
4
4
|
exports.NEWTECH_CLIENT_ID = 1;
|
|
5
5
|
exports.ALTERA_CLIENT_ID = 2;
|
|
6
|
-
exports.TENANT_IMPERSONATOR_ROLE = 'TENANT_IMPERSONATOR';
|
|
7
6
|
exports.MASTER_TENANT_METADATA_KEY = 'masterTenant';
|
|
8
7
|
exports.PUBLIC_ROUTE_METADATA_KEY = 'publicRoute';
|
|
9
8
|
exports.TENANT_CONTEXT_REQUEST_KEY = 'tenantContext';
|
|
@@ -25,6 +25,7 @@ const tenant_credential_guard_1 = require("./tenant-credential.guard");
|
|
|
25
25
|
const master_tenant_guard_1 = require("./master-tenant.guard");
|
|
26
26
|
const tenant_ownership_service_1 = require("./tenant-ownership.service");
|
|
27
27
|
const tenant_runner_service_1 = require("./tenant-runner.service");
|
|
28
|
+
const impersonation_audit_service_1 = require("./impersonation-audit.service");
|
|
28
29
|
let TenantModule = class TenantModule {
|
|
29
30
|
};
|
|
30
31
|
exports.TenantModule = TenantModule;
|
|
@@ -49,6 +50,7 @@ exports.TenantModule = TenantModule = __decorate([
|
|
|
49
50
|
tenant_context_storage_1.TenantContextStorage,
|
|
50
51
|
tenant_context_service_1.TenantContext,
|
|
51
52
|
tenant_runner_service_1.TenantRunner,
|
|
53
|
+
impersonation_audit_service_1.ImpersonationAuditService,
|
|
52
54
|
tenant_context_interceptor_1.TenantContextInterceptor,
|
|
53
55
|
tenant_credential_guard_1.TenantCredentialGuard,
|
|
54
56
|
master_tenant_guard_1.MasterTenantGuard,
|
|
@@ -61,6 +63,7 @@ exports.TenantModule = TenantModule = __decorate([
|
|
|
61
63
|
tenant_security_state_service_1.TenantSecurityStateService,
|
|
62
64
|
tenant_context_service_1.TenantContext,
|
|
63
65
|
tenant_runner_service_1.TenantRunner,
|
|
66
|
+
impersonation_audit_service_1.ImpersonationAuditService,
|
|
64
67
|
tenant_context_interceptor_1.TenantContextInterceptor,
|
|
65
68
|
tenant_credential_guard_1.TenantCredentialGuard,
|
|
66
69
|
master_tenant_guard_1.MasterTenantGuard,
|
|
@@ -3,6 +3,7 @@ export interface TenantContextState {
|
|
|
3
3
|
readonly clientId: number;
|
|
4
4
|
readonly originalClientId: number | null;
|
|
5
5
|
readonly actorUserId: number | null;
|
|
6
|
+
readonly effectiveUserId: number | null;
|
|
6
7
|
readonly authenticationType: TenantAuthenticationType;
|
|
7
8
|
readonly impersonatedAt: string | null;
|
|
8
9
|
readonly impersonationReason: string | null;
|
|
@@ -18,6 +19,8 @@ export interface AuthenticatedTenantUser {
|
|
|
18
19
|
jti: string;
|
|
19
20
|
tokenExpiresAt: number;
|
|
20
21
|
originalClientId: number | null;
|
|
22
|
+
originalUserId: number | null;
|
|
23
|
+
impersonationType: 'user' | null;
|
|
21
24
|
impersonatedAt: string | null;
|
|
22
25
|
impersonationReason: string | null;
|
|
23
26
|
roles: string[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniorganization/uni-lib",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.4",
|
|
4
4
|
"description": "UNI Library",
|
|
5
5
|
"author": "Jhomiguel",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
},
|
|
75
75
|
"scripts": {
|
|
76
76
|
"start:dev": "tsc -w",
|
|
77
|
-
"build": "tsc",
|
|
77
|
+
"build": "tsc -p tsconfig.build.json",
|
|
78
78
|
"format": "prettier --write \"src/**/*.ts\"",
|
|
79
79
|
"lint": "tslint -p tsconfig.json -c tslint.json",
|
|
80
80
|
"test": "jest",
|