@uniorganization/uni-lib 4.1.4 → 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/modules/common/jwt.strategy.js +7 -4
- package/dist/modules/common/jwt.strategy.spec.d.ts +1 -0
- package/dist/modules/common/jwt.strategy.spec.js +89 -0
- package/dist/modules/tenant/redis-tenant-state.store.js +10 -0
- package/dist/modules/tenant/tenant-authorization.service.d.ts +1 -1
- package/dist/modules/tenant/tenant-authorization.service.js +80 -22
- package/dist/modules/tenant/tenant-authorization.service.spec.d.ts +1 -0
- package/dist/modules/tenant/tenant-authorization.service.spec.js +101 -0
- package/dist/modules/tenant/tenant-authorization.types.d.ts +10 -0
- package/dist/modules/tenant/tenant.types.d.ts +9 -0
- package/package.json +1 -1
|
@@ -62,7 +62,6 @@ let JwtStrategy = class JwtStrategy extends (0, passport_1.PassportStrategy)(pas
|
|
|
62
62
|
const impersonating = originalClientId !== null;
|
|
63
63
|
if ((impersonating &&
|
|
64
64
|
(originalClientId !== tenant_1.NEWTECH_CLIENT_ID ||
|
|
65
|
-
homeClientId !== tenant_1.NEWTECH_CLIENT_ID ||
|
|
66
65
|
effectiveClientId === tenant_1.NEWTECH_CLIENT_ID ||
|
|
67
66
|
!((_b = signedUser.roles) === null || _b === void 0 ? void 0 : _b.includes(tenant_1.TENANT_IMPERSONATOR_ROLE)) ||
|
|
68
67
|
!signedUser.impersonatedAt ||
|
|
@@ -71,13 +70,14 @@ let JwtStrategy = class JwtStrategy extends (0, passport_1.PassportStrategy)(pas
|
|
|
71
70
|
throw new common_1.UnauthorizedException();
|
|
72
71
|
}
|
|
73
72
|
const resolved = impersonating
|
|
74
|
-
? yield this.authorization.resolveImpersonation(signedUser.id, homeClientId, effectiveClientId)
|
|
73
|
+
? yield this.authorization.resolveImpersonation(signedUser.id, originalClientId, homeClientId, effectiveClientId)
|
|
75
74
|
: yield this.authorization.resolve(signedUser.id, effectiveClientId, homeClientId);
|
|
76
75
|
const { identity, snapshot } = resolved;
|
|
77
76
|
const isMasterTenantAdmin = !impersonating &&
|
|
78
77
|
snapshot.clientId === tenant_1.NEWTECH_CLIENT_ID &&
|
|
79
|
-
snapshot.
|
|
80
|
-
role.
|
|
78
|
+
(snapshot.permissions.includes('master.users.read') ||
|
|
79
|
+
snapshot.roles.some((role) => role.moduleName.toLowerCase() === 'root' &&
|
|
80
|
+
role.roleName.toLowerCase() === 'sysadmin'));
|
|
81
81
|
return {
|
|
82
82
|
id: identity.id,
|
|
83
83
|
first_name: identity.firstName,
|
|
@@ -104,6 +104,9 @@ let JwtStrategy = class JwtStrategy extends (0, passport_1.PassportStrategy)(pas
|
|
|
104
104
|
: null,
|
|
105
105
|
roles: Array.from(new Set(snapshot.roles.map((role) => role.roleName))),
|
|
106
106
|
privileges: snapshot.privileges,
|
|
107
|
+
role: snapshot.accessRole,
|
|
108
|
+
permissions: snapshot.permissions,
|
|
109
|
+
authzVersion: snapshot.authzVersion,
|
|
107
110
|
isMasterTenantAdmin,
|
|
108
111
|
};
|
|
109
112
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const common_1 = require("@nestjs/common");
|
|
13
|
+
const tenant_1 = require("../tenant");
|
|
14
|
+
const jwt_strategy_1 = require("./jwt.strategy");
|
|
15
|
+
describe('JwtStrategy impersonated sessions', () => {
|
|
16
|
+
process.env.JWT_SECRET = 'test-secret';
|
|
17
|
+
const authorization = { resolveImpersonation: jest.fn() };
|
|
18
|
+
const securityState = { isAccessTokenRevoked: jest.fn() };
|
|
19
|
+
const strategy = new jwt_strategy_1.JwtStrategy(authorization, securityState);
|
|
20
|
+
const payload = (overrides = {}) => {
|
|
21
|
+
const now = Math.floor(Date.now() / 1000);
|
|
22
|
+
const expiresAt = now + 900;
|
|
23
|
+
const user = Object.assign({ id: 1, homeClientId: 2, clientId: 2, clientName: 'Altera', clientAlias: 'altera', jti: 'impersonated-jti', tokenExpiresAt: expiresAt, originalClientId: tenant_1.NEWTECH_CLIENT_ID, impersonatedAt: new Date().toISOString(), impersonationReason: 'Investigate issue', roles: [tenant_1.TENANT_IMPERSONATOR_ROLE], privileges: [], role: {
|
|
24
|
+
id: 0,
|
|
25
|
+
code: tenant_1.TENANT_IMPERSONATOR_ROLE,
|
|
26
|
+
name: 'Tenant impersonator',
|
|
27
|
+
scope: 'GLOBAL',
|
|
28
|
+
clientId: null,
|
|
29
|
+
}, permissions: [], authzVersion: 1, isMasterTenantAdmin: false }, overrides);
|
|
30
|
+
return {
|
|
31
|
+
type: 'access',
|
|
32
|
+
user,
|
|
33
|
+
sub: '1',
|
|
34
|
+
iat: now - 10,
|
|
35
|
+
exp: expiresAt,
|
|
36
|
+
jti: 'impersonated-jti',
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
jest.clearAllMocks();
|
|
41
|
+
securityState.isAccessTokenRevoked.mockResolvedValue(false);
|
|
42
|
+
authorization.resolveImpersonation.mockResolvedValue({
|
|
43
|
+
identity: { id: 1, homeClientId: 2 },
|
|
44
|
+
snapshot: {
|
|
45
|
+
userId: 1,
|
|
46
|
+
homeClientId: 2,
|
|
47
|
+
clientId: 2,
|
|
48
|
+
clientName: 'Altera',
|
|
49
|
+
clientAlias: 'altera',
|
|
50
|
+
roles: [
|
|
51
|
+
{
|
|
52
|
+
roleId: 0,
|
|
53
|
+
roleName: tenant_1.TENANT_IMPERSONATOR_ROLE,
|
|
54
|
+
moduleId: 0,
|
|
55
|
+
moduleName: tenant_1.TENANT_IMPERSONATOR_ROLE,
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
privileges: [],
|
|
59
|
+
accessRole: {
|
|
60
|
+
id: 0,
|
|
61
|
+
code: tenant_1.TENANT_IMPERSONATOR_ROLE,
|
|
62
|
+
name: 'Tenant impersonator',
|
|
63
|
+
scope: 'GLOBAL',
|
|
64
|
+
clientId: null,
|
|
65
|
+
},
|
|
66
|
+
permissions: [],
|
|
67
|
+
authzVersion: 1,
|
|
68
|
+
identity: { id: 1, homeClientId: 2 },
|
|
69
|
+
cachedAt: new Date().toISOString(),
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
it('revalidates an impersonated token with an Altera home client through Newtech', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
74
|
+
const validated = yield strategy.validate(payload());
|
|
75
|
+
expect(authorization.resolveImpersonation).toHaveBeenCalledWith(1, 1, 2, 2);
|
|
76
|
+
expect(validated.homeClientId).toBe(2);
|
|
77
|
+
expect(validated.clientId).toBe(2);
|
|
78
|
+
expect(validated.originalClientId).toBe(tenant_1.NEWTECH_CLIENT_ID);
|
|
79
|
+
}));
|
|
80
|
+
const invalidTokenCases = [
|
|
81
|
+
[{ originalClientId: 2 }, 'a non-Newtech origin'],
|
|
82
|
+
[{ roles: [] }, 'a token without the impersonator role'],
|
|
83
|
+
[{ impersonationReason: null }, 'a token without a support reason'],
|
|
84
|
+
];
|
|
85
|
+
it.each(invalidTokenCases)('rejects %s', (overrides) => __awaiter(void 0, void 0, void 0, function* () {
|
|
86
|
+
yield expect(strategy.validate(payload(overrides))).rejects.toBeInstanceOf(common_1.UnauthorizedException);
|
|
87
|
+
expect(authorization.resolveImpersonation).not.toHaveBeenCalled();
|
|
88
|
+
}));
|
|
89
|
+
});
|
|
@@ -154,6 +154,16 @@ let RedisTenantStateStore = RedisTenantStateStore_1 = class RedisTenantStateStor
|
|
|
154
154
|
typeof role.moduleName === 'string') &&
|
|
155
155
|
Array.isArray(value.privileges) &&
|
|
156
156
|
value.privileges.every((privilege) => typeof privilege === 'string') &&
|
|
157
|
+
value.accessRole &&
|
|
158
|
+
Number.isInteger(value.accessRole.id) &&
|
|
159
|
+
value.accessRole.id >= 0 &&
|
|
160
|
+
typeof value.accessRole.code === 'string' &&
|
|
161
|
+
typeof value.accessRole.name === 'string' &&
|
|
162
|
+
['GLOBAL', 'CLIENT'].includes(value.accessRole.scope) &&
|
|
163
|
+
Array.isArray(value.permissions) &&
|
|
164
|
+
value.permissions.every((permission) => typeof permission === 'string') &&
|
|
165
|
+
Number.isInteger(value.authzVersion) &&
|
|
166
|
+
value.authzVersion > 0 &&
|
|
157
167
|
((_a = value.identity) === null || _a === void 0 ? void 0 : _a.id) === value.userId &&
|
|
158
168
|
value.identity.homeClientId === value.homeClientId &&
|
|
159
169
|
typeof value.cachedAt === 'string');
|
|
@@ -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, expectedHomeClientId: number): Promise<ResolvedTenantAuthorization>;
|
|
16
|
-
resolveImpersonation(userId: number, homeClientId: number, targetClientId: number): Promise<ResolvedTenantAuthorization>;
|
|
16
|
+
resolveImpersonation(userId: number, originClientId: number, homeClientId: 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;
|
|
@@ -64,15 +64,16 @@ let TenantAuthorizationService = TenantAuthorizationService_1 = class TenantAuth
|
|
|
64
64
|
return resolved;
|
|
65
65
|
});
|
|
66
66
|
}
|
|
67
|
-
resolveImpersonation(userId, homeClientId, targetClientId) {
|
|
67
|
+
resolveImpersonation(userId, originClientId, homeClientId, targetClientId) {
|
|
68
68
|
return __awaiter(this, void 0, void 0, function* () {
|
|
69
|
-
if (
|
|
69
|
+
if (originClientId !== tenant_constants_1.NEWTECH_CLIENT_ID ||
|
|
70
70
|
targetClientId === tenant_constants_1.NEWTECH_CLIENT_ID) {
|
|
71
71
|
throw new common_1.UnauthorizedException();
|
|
72
72
|
}
|
|
73
|
-
const origin = yield this.resolve(userId,
|
|
74
|
-
const isNewtechMaster = origin.snapshot.
|
|
75
|
-
role.
|
|
73
|
+
const origin = yield this.resolve(userId, originClientId, homeClientId);
|
|
74
|
+
const isNewtechMaster = origin.snapshot.permissions.includes('master.users.read') ||
|
|
75
|
+
origin.snapshot.roles.some((role) => role.moduleName.toLowerCase() === 'root' &&
|
|
76
|
+
role.roleName.toLowerCase() === 'sysadmin');
|
|
76
77
|
const targetClient = yield this.clientRepository.findOne({
|
|
77
78
|
where: { id: targetClientId, deletedAt: (0, typeorm_2.IsNull)() },
|
|
78
79
|
});
|
|
@@ -94,6 +95,15 @@ let TenantAuthorizationService = TenantAuthorizationService_1 = class TenantAuth
|
|
|
94
95
|
},
|
|
95
96
|
],
|
|
96
97
|
privileges: [],
|
|
98
|
+
accessRole: {
|
|
99
|
+
id: 0,
|
|
100
|
+
code: tenant_constants_1.TENANT_IMPERSONATOR_ROLE,
|
|
101
|
+
name: 'Tenant impersonator',
|
|
102
|
+
scope: 'GLOBAL',
|
|
103
|
+
clientId: null,
|
|
104
|
+
},
|
|
105
|
+
permissions: [],
|
|
106
|
+
authzVersion: 1,
|
|
97
107
|
identity: origin.identity,
|
|
98
108
|
cachedAt: new Date().toISOString(),
|
|
99
109
|
};
|
|
@@ -115,7 +125,7 @@ let TenantAuthorizationService = TenantAuthorizationService_1 = class TenantAuth
|
|
|
115
125
|
}
|
|
116
126
|
loadFromDatabase(userId, clientId, expectedHomeClientId) {
|
|
117
127
|
return __awaiter(this, void 0, void 0, function* () {
|
|
118
|
-
const [user, client, assignments] = yield Promise.all([
|
|
128
|
+
const [user, client, assignments, accessRows] = yield Promise.all([
|
|
119
129
|
this.userRepository.findOne({ where: { id: userId } }),
|
|
120
130
|
this.clientRepository.findOne({
|
|
121
131
|
where: { id: clientId, deletedAt: (0, typeorm_2.IsNull)() },
|
|
@@ -129,23 +139,64 @@ let TenantAuthorizationService = TenantAuthorizationService_1 = class TenantAuth
|
|
|
129
139
|
.where('assignment."USER_ID" = :userId', { userId })
|
|
130
140
|
.andWhere('assignment.client_id = :clientId', { clientId })
|
|
131
141
|
.getMany(),
|
|
142
|
+
this.userRoleRepository.manager.query(`SELECT
|
|
143
|
+
role.id,
|
|
144
|
+
role.code,
|
|
145
|
+
role.name,
|
|
146
|
+
role.scope,
|
|
147
|
+
role.client_id AS "roleClientId",
|
|
148
|
+
role.authz_version AS "authzVersion",
|
|
149
|
+
COALESCE(
|
|
150
|
+
jsonb_agg(permission.code ORDER BY permission.code)
|
|
151
|
+
FILTER (WHERE permission.id IS NOT NULL AND permission.is_active),
|
|
152
|
+
'[]'::jsonb
|
|
153
|
+
) AS permissions
|
|
154
|
+
FROM stm."USER_ACCESS_ROLES" membership
|
|
155
|
+
JOIN stm."ACCESS_ROLES" role
|
|
156
|
+
ON role.id = membership.role_id
|
|
157
|
+
AND role.is_active
|
|
158
|
+
LEFT JOIN stm."ACCESS_ROLE_PERMISSIONS" definition
|
|
159
|
+
ON definition.role_id = role.id
|
|
160
|
+
LEFT JOIN stm."ACCESS_PERMISSIONS" permission
|
|
161
|
+
ON permission.id = definition.permission_id
|
|
162
|
+
WHERE membership.user_id = $1
|
|
163
|
+
AND membership.client_id = $2
|
|
164
|
+
GROUP BY role.id`, [userId, clientId]),
|
|
132
165
|
]);
|
|
133
166
|
if (!user ||
|
|
134
167
|
!Number(user.is_active) ||
|
|
135
168
|
user.clientId !== expectedHomeClientId ||
|
|
136
169
|
!client ||
|
|
137
|
-
|
|
170
|
+
accessRows.length !== 1) {
|
|
138
171
|
throw new common_1.UnauthorizedException();
|
|
139
172
|
}
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
173
|
+
const accessRow = accessRows[0];
|
|
174
|
+
const accessRole = {
|
|
175
|
+
id: Number(accessRow.id),
|
|
176
|
+
code: accessRow.code,
|
|
177
|
+
name: accessRow.name,
|
|
178
|
+
scope: accessRow.scope,
|
|
179
|
+
clientId: accessRow.roleClientId == null ? null : Number(accessRow.roleClientId),
|
|
180
|
+
};
|
|
181
|
+
const usingLegacyCompatibility = accessRole.code.startsWith('migrated-');
|
|
182
|
+
const roles = usingLegacyCompatibility && assignments.length
|
|
183
|
+
? assignments.map((assignment) => {
|
|
184
|
+
var _a, _b, _c, _d, _e, _f;
|
|
185
|
+
return ({
|
|
186
|
+
roleId: Number(assignment.roleId || ((_a = assignment.role) === null || _a === void 0 ? void 0 : _a.id)),
|
|
187
|
+
roleName: (_b = assignment.role) === null || _b === void 0 ? void 0 : _b.roleName,
|
|
188
|
+
moduleId: Number((_d = (_c = assignment.role) === null || _c === void 0 ? void 0 : _c.module) === null || _d === void 0 ? void 0 : _d.id),
|
|
189
|
+
moduleName: (_f = (_e = assignment.role) === null || _e === void 0 ? void 0 : _e.module) === null || _f === void 0 ? void 0 : _f.moduleName,
|
|
190
|
+
});
|
|
191
|
+
})
|
|
192
|
+
: [
|
|
193
|
+
{
|
|
194
|
+
roleId: accessRole.id,
|
|
195
|
+
roleName: accessRole.name,
|
|
196
|
+
moduleId: accessRole.id,
|
|
197
|
+
moduleName: 'access-control',
|
|
198
|
+
},
|
|
199
|
+
];
|
|
149
200
|
if (roles.some((role) => !Number.isInteger(role.roleId) ||
|
|
150
201
|
role.roleId <= 0 ||
|
|
151
202
|
!role.roleName ||
|
|
@@ -154,12 +205,14 @@ let TenantAuthorizationService = TenantAuthorizationService_1 = class TenantAuth
|
|
|
154
205
|
!role.moduleName)) {
|
|
155
206
|
throw new common_1.UnauthorizedException();
|
|
156
207
|
}
|
|
157
|
-
const privileges =
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
208
|
+
const privileges = usingLegacyCompatibility
|
|
209
|
+
? Array.from(new Set(assignments.flatMap((assignment) => {
|
|
210
|
+
var _a;
|
|
211
|
+
return (((_a = assignment.role) === null || _a === void 0 ? void 0 : _a.privileges) || [])
|
|
212
|
+
.map((definition) => { var _a; return (_a = definition.privilege) === null || _a === void 0 ? void 0 : _a.privilege; })
|
|
213
|
+
.filter((privilege) => Boolean(privilege));
|
|
214
|
+
}))).sort()
|
|
215
|
+
: [];
|
|
163
216
|
const identity = {
|
|
164
217
|
id: user.id,
|
|
165
218
|
homeClientId: user.clientId,
|
|
@@ -183,6 +236,11 @@ let TenantAuthorizationService = TenantAuthorizationService_1 = class TenantAuth
|
|
|
183
236
|
clientAlias: client.alias,
|
|
184
237
|
roles,
|
|
185
238
|
privileges,
|
|
239
|
+
accessRole,
|
|
240
|
+
permissions: Array.isArray(accessRow.permissions)
|
|
241
|
+
? accessRow.permissions
|
|
242
|
+
: [],
|
|
243
|
+
authzVersion: Number(accessRow.authzVersion),
|
|
186
244
|
identity,
|
|
187
245
|
cachedAt: new Date().toISOString(),
|
|
188
246
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const common_1 = require("@nestjs/common");
|
|
13
|
+
const tenant_constants_1 = require("./tenant.constants");
|
|
14
|
+
const tenant_authorization_service_1 = require("./tenant-authorization.service");
|
|
15
|
+
describe('TenantAuthorizationService impersonation', () => {
|
|
16
|
+
const store = {
|
|
17
|
+
get: jest.fn(),
|
|
18
|
+
set: jest.fn(),
|
|
19
|
+
delete: jest.fn(),
|
|
20
|
+
deleteMany: jest.fn(),
|
|
21
|
+
};
|
|
22
|
+
const userRepository = {};
|
|
23
|
+
const clientRepository = { findOne: jest.fn() };
|
|
24
|
+
const userRoleRepository = {};
|
|
25
|
+
const service = new tenant_authorization_service_1.TenantAuthorizationService(store, userRepository, clientRepository, userRoleRepository);
|
|
26
|
+
const origin = (permissions) => ({
|
|
27
|
+
identity: {
|
|
28
|
+
id: 1,
|
|
29
|
+
homeClientId: 2,
|
|
30
|
+
firstName: 'Root',
|
|
31
|
+
lastName: 'User',
|
|
32
|
+
},
|
|
33
|
+
snapshot: {
|
|
34
|
+
userId: 1,
|
|
35
|
+
homeClientId: 2,
|
|
36
|
+
clientId: tenant_constants_1.NEWTECH_CLIENT_ID,
|
|
37
|
+
clientName: 'Newtech',
|
|
38
|
+
clientAlias: 'newtech',
|
|
39
|
+
roles: [
|
|
40
|
+
{
|
|
41
|
+
roleId: 8,
|
|
42
|
+
roleName: 'Master Root',
|
|
43
|
+
moduleId: 8,
|
|
44
|
+
moduleName: 'access-control',
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
privileges: [],
|
|
48
|
+
accessRole: {
|
|
49
|
+
id: 8,
|
|
50
|
+
code: 'master-root',
|
|
51
|
+
name: 'Master Root',
|
|
52
|
+
scope: 'CLIENT',
|
|
53
|
+
clientId: tenant_constants_1.NEWTECH_CLIENT_ID,
|
|
54
|
+
},
|
|
55
|
+
permissions,
|
|
56
|
+
authzVersion: 3,
|
|
57
|
+
identity: {
|
|
58
|
+
id: 1,
|
|
59
|
+
homeClientId: 2,
|
|
60
|
+
firstName: 'Root',
|
|
61
|
+
lastName: 'User',
|
|
62
|
+
},
|
|
63
|
+
cachedAt: new Date().toISOString(),
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
beforeEach(() => {
|
|
67
|
+
jest.clearAllMocks();
|
|
68
|
+
clientRepository.findOne.mockResolvedValue({
|
|
69
|
+
id: 2,
|
|
70
|
+
name: 'Altera',
|
|
71
|
+
alias: 'altera',
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
it('resolves support from active Newtech when the permanent home is Altera', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
75
|
+
jest
|
|
76
|
+
.spyOn(service, 'resolve')
|
|
77
|
+
.mockResolvedValue(origin(['master.users.read']));
|
|
78
|
+
const resolved = yield service.resolveImpersonation(1, tenant_constants_1.NEWTECH_CLIENT_ID, 2, 2);
|
|
79
|
+
expect(service.resolve).toHaveBeenCalledWith(1, tenant_constants_1.NEWTECH_CLIENT_ID, 2);
|
|
80
|
+
expect(resolved.snapshot.clientId).toBe(2);
|
|
81
|
+
expect(resolved.snapshot.homeClientId).toBe(2);
|
|
82
|
+
expect(resolved.snapshot.clientName).toBe('Altera');
|
|
83
|
+
}));
|
|
84
|
+
it('rejects an origin outside Newtech', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
85
|
+
yield expect(service.resolveImpersonation(1, 2, 2, 2)).rejects.toBeInstanceOf(common_1.UnauthorizedException);
|
|
86
|
+
}));
|
|
87
|
+
it('rejects Newtech as the target', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
88
|
+
yield expect(service.resolveImpersonation(1, tenant_constants_1.NEWTECH_CLIENT_ID, 2, tenant_constants_1.NEWTECH_CLIENT_ID)).rejects.toBeInstanceOf(common_1.UnauthorizedException);
|
|
89
|
+
}));
|
|
90
|
+
it('rejects an origin without master access', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
91
|
+
jest.spyOn(service, 'resolve').mockResolvedValue(origin([]));
|
|
92
|
+
yield expect(service.resolveImpersonation(1, tenant_constants_1.NEWTECH_CLIENT_ID, 2, 2)).rejects.toBeInstanceOf(common_1.UnauthorizedException);
|
|
93
|
+
}));
|
|
94
|
+
it('rejects an inactive or missing target client', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
95
|
+
jest
|
|
96
|
+
.spyOn(service, 'resolve')
|
|
97
|
+
.mockResolvedValue(origin(['master.users.read']));
|
|
98
|
+
clientRepository.findOne.mockResolvedValue(null);
|
|
99
|
+
yield expect(service.resolveImpersonation(1, tenant_constants_1.NEWTECH_CLIENT_ID, 2, 2)).rejects.toBeInstanceOf(common_1.UnauthorizedException);
|
|
100
|
+
}));
|
|
101
|
+
});
|
|
@@ -12,9 +12,19 @@ export interface TenantAuthorizationSnapshot {
|
|
|
12
12
|
clientAlias: string;
|
|
13
13
|
roles: TenantRoleAuthorization[];
|
|
14
14
|
privileges: string[];
|
|
15
|
+
accessRole: TenantAccessRoleAuthorization;
|
|
16
|
+
permissions: string[];
|
|
17
|
+
authzVersion: number;
|
|
15
18
|
identity: TenantAuthorizationIdentity;
|
|
16
19
|
cachedAt: string;
|
|
17
20
|
}
|
|
21
|
+
export interface TenantAccessRoleAuthorization {
|
|
22
|
+
id: number;
|
|
23
|
+
code: string;
|
|
24
|
+
name: string;
|
|
25
|
+
scope: 'GLOBAL' | 'CLIENT';
|
|
26
|
+
clientId: number | null;
|
|
27
|
+
}
|
|
18
28
|
export interface TenantAuthorizationIdentity {
|
|
19
29
|
id: number;
|
|
20
30
|
homeClientId: number;
|
|
@@ -23,6 +23,15 @@ export interface AuthenticatedTenantUser {
|
|
|
23
23
|
impersonationReason: string | null;
|
|
24
24
|
roles: string[];
|
|
25
25
|
privileges: string[];
|
|
26
|
+
role: {
|
|
27
|
+
id: number;
|
|
28
|
+
code: string;
|
|
29
|
+
name: string;
|
|
30
|
+
scope: 'GLOBAL' | 'CLIENT';
|
|
31
|
+
clientId: number | null;
|
|
32
|
+
};
|
|
33
|
+
permissions: string[];
|
|
34
|
+
authzVersion: number;
|
|
26
35
|
isMasterTenantAdmin: boolean;
|
|
27
36
|
first_name?: string;
|
|
28
37
|
username?: string;
|