@autofleet/zehut 3.1.2-beta.1 → 3.1.2-beta.11

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.
@@ -7,7 +7,6 @@ exports.CONTEXTS_IDS_HEADER = exports.ELEVATED_PERMISSIONS_HEADER = void 0;
7
7
  /* eslint-disable consistent-return */
8
8
  const node_cache_1 = __importDefault(require("node-cache"));
9
9
  const object_hash_1 = __importDefault(require("object-hash"));
10
- const merge_deep_1 = __importDefault(require("merge-deep"));
11
10
  const uuid_1 = require("uuid");
12
11
  const outbreak_1 = require("@autofleet/outbreak");
13
12
  const services_1 = require("../services");
@@ -82,13 +81,21 @@ class ApiUser {
82
81
  return Object.keys(this.privatePermissions[key] || {});
83
82
  }
84
83
  get elevatedPermissions() {
85
- let permissions = {
84
+ const permissions = {
86
85
  fleets: {},
87
86
  businessModels: {},
88
87
  demandSources: {},
89
88
  };
90
89
  [...this.privateElevatedPermissionsHash.values()].forEach((p) => {
91
- permissions = (0, merge_deep_1.default)(permissions, p);
90
+ Object.keys(p).forEach((topLevelKey) => {
91
+ if (!permissions[topLevelKey]) {
92
+ permissions[topLevelKey] = {};
93
+ }
94
+ // Merge each [entityId => string[]]
95
+ Object.entries(p[topLevelKey]).forEach(([entityId, perms]) => {
96
+ permissions[topLevelKey][entityId] = (permissions[topLevelKey][entityId] || []).concat(perms);
97
+ });
98
+ });
92
99
  });
93
100
  return permissions;
94
101
  }
@@ -96,8 +103,17 @@ class ApiUser {
96
103
  if (!this.privatePermissions) {
97
104
  throw new Error('Cannot get permissions without calling (async) getUserPermissions before');
98
105
  }
99
- const permissions = (0, merge_deep_1.default)(this.privatePermissions, this.elevatedPermissions);
100
- return permissions;
106
+ const base = JSON.parse(JSON.stringify(this.privatePermissions));
107
+ Object.keys(this.elevatedPermissions).forEach((topLevelKey) => {
108
+ if (!base[topLevelKey]) {
109
+ base[topLevelKey] = {};
110
+ }
111
+ // Merge each [entityId => string[]]
112
+ Object.entries(this.elevatedPermissions[topLevelKey]).forEach(([entityId, perms]) => {
113
+ base[topLevelKey][entityId] = (base[topLevelKey][entityId] || []).concat(perms);
114
+ });
115
+ });
116
+ return base;
101
117
  }
102
118
  elevatePermissions(addedPermissions) {
103
119
  const elevationId = (0, uuid_1.v4)();
@@ -8,6 +8,20 @@ const express_1 = __importDefault(require("express"));
8
8
  const axios_1 = __importDefault(require("axios"));
9
9
  const index_1 = require("../index");
10
10
  const index_2 = require("./index");
11
+ const services_1 = require("../services");
12
+ jest.spyOn(services_1.IdentityNetwork, 'get').mockImplementation(async (url) => {
13
+ if (url.includes('/api/v1/users/')) {
14
+ return {
15
+ data: {
16
+ accountType: 'user',
17
+ fleets: {},
18
+ businessModels: {},
19
+ demandSources: {},
20
+ },
21
+ };
22
+ }
23
+ return { data: {} };
24
+ });
11
25
  const generateApp = async (addEndpoints, port) => {
12
26
  const app = (0, express_1.default)();
13
27
  addEndpoints(app);
@@ -122,4 +136,169 @@ describe('E2E', () => {
122
136
  closeServer1();
123
137
  expect(error.message).toEqual('Entity id on elevatePermissions is not a valid UUID, provided: nnn');
124
138
  });
139
+ it('should correctly handle elevation of permissions and their reversion', async () => {
140
+ let capturedError = null;
141
+ // Snapshots to capture state after each step
142
+ let afterFirstElevationElevated = {
143
+ fleets: {},
144
+ businessModels: {},
145
+ demandSources: {},
146
+ };
147
+ let afterFirstElevationCombined = {
148
+ fleets: {},
149
+ businessModels: {},
150
+ demandSources: {},
151
+ };
152
+ let afterSecondElevationElevated = {
153
+ fleets: {},
154
+ businessModels: {},
155
+ demandSources: {},
156
+ };
157
+ let afterSecondElevationCombined = {
158
+ fleets: {},
159
+ businessModels: {},
160
+ demandSources: {},
161
+ };
162
+ let afterCloseSecondElevated = {
163
+ fleets: {},
164
+ businessModels: {},
165
+ demandSources: {},
166
+ };
167
+ let afterCloseSecondCombined = {
168
+ fleets: {},
169
+ businessModels: {},
170
+ demandSources: {},
171
+ };
172
+ let afterCloseFirstElevated = {
173
+ fleets: {},
174
+ businessModels: {},
175
+ demandSources: {},
176
+ };
177
+ let afterCloseFirstCombined = {
178
+ fleets: {},
179
+ businessModels: {},
180
+ demandSources: {},
181
+ };
182
+ // Enable tracing if necessary (adjust based on your implementation)
183
+ (0, index_1.enableTracing)({
184
+ outbreakOptions: {
185
+ headersPrefix: 'x-af-',
186
+ },
187
+ });
188
+ const userId = (0, uuid_1.v4)();
189
+ // Generate UUIDs for test entities
190
+ const fleetUUID1 = (0, uuid_1.v4)();
191
+ const fleetUUID2 = (0, uuid_1.v4)();
192
+ const bmUUID1 = (0, uuid_1.v4)();
193
+ const dsUUID1 = (0, uuid_1.v4)();
194
+ const dsUUID2 = (0, uuid_1.v4)();
195
+ // Spin up a test server
196
+ const closeServer = await generateApp((app) => {
197
+ app.use((0, index_2.middleware)());
198
+ app.get('/', async (req, res) => {
199
+ try {
200
+ const user = (0, index_1.getUser)();
201
+ // Load base permissions (mocked)
202
+ await user.getUserPermissions();
203
+ // Now user.privatePermissions is set
204
+ // 1. First Elevation
205
+ const closeElevation1 = user.elevatePermissions({
206
+ fleets: {
207
+ [fleetUUID1]: ['readF1'],
208
+ },
209
+ businessModels: {
210
+ [bmUUID1]: ['writeBM1'],
211
+ },
212
+ demandSources: {
213
+ [dsUUID1]: ['readDS1'],
214
+ },
215
+ });
216
+ // Capture elevated and combined permissions after first elevation
217
+ afterFirstElevationElevated = user.elevatedPermissions;
218
+ afterFirstElevationCombined = user.permissions; // Non-null assertion since privatePermissions is set
219
+ // 2. Second Elevation
220
+ const closeElevation2 = user.elevatePermissions({
221
+ fleets: {
222
+ [fleetUUID1]: ['manageF1'],
223
+ [fleetUUID2]: ['createF2'], // New fleet
224
+ },
225
+ businessModels: {
226
+ [bmUUID1]: ['writeBM2'], // Additional permission to existing business model
227
+ },
228
+ demandSources: {
229
+ [dsUUID2]: ['readDS2', 'readDS2'], // New demand source with duplicate permissions
230
+ },
231
+ });
232
+ // Capture elevated and combined permissions after second elevation
233
+ afterSecondElevationElevated = user.elevatedPermissions;
234
+ afterSecondElevationCombined = user.permissions;
235
+ // 3. Close Second Elevation
236
+ closeElevation2();
237
+ afterCloseSecondElevated = user.elevatedPermissions;
238
+ afterCloseSecondCombined = user.permissions;
239
+ // 4. Close First Elevation
240
+ closeElevation1();
241
+ afterCloseFirstElevated = user.elevatedPermissions;
242
+ afterCloseFirstCombined = user.permissions;
243
+ }
244
+ catch (e) {
245
+ capturedError = e;
246
+ }
247
+ res.json({ status: 'ok' });
248
+ });
249
+ }, 8089);
250
+ // Trigger the test route
251
+ const response = await axios_1.default.get('http://localhost:8089', {
252
+ headers: {
253
+ 'x-af-user-id': userId,
254
+ },
255
+ });
256
+ closeServer();
257
+ // Basic assertions
258
+ expect(response.status).toEqual(200);
259
+ expect(capturedError).toBeNull();
260
+ console.log('afterFirstElevationElevated', afterFirstElevationElevated);
261
+ // ---------------------
262
+ // Assertions After First Elevation
263
+ // ---------------------
264
+ expect(afterFirstElevationElevated.fleets?.[fleetUUID1]).toEqual(expect.arrayContaining(['readF1']));
265
+ expect(afterFirstElevationElevated.businessModels?.[bmUUID1]).toEqual(expect.arrayContaining(['writeBM1']));
266
+ expect(afterFirstElevationElevated.demandSources?.[dsUUID1]).toEqual(expect.arrayContaining(['readDS1']));
267
+ expect(afterFirstElevationCombined.fleets?.[fleetUUID1]).toEqual(expect.arrayContaining(['readF1']));
268
+ expect(afterFirstElevationCombined.businessModels?.[bmUUID1]).toEqual(expect.arrayContaining(['writeBM1']));
269
+ expect(afterFirstElevationCombined.demandSources?.[dsUUID1]).toEqual(expect.arrayContaining(['readDS1']));
270
+ // ---------------------
271
+ // Assertions After Second Elevation
272
+ // ---------------------
273
+ expect(afterSecondElevationElevated.fleets?.[fleetUUID1]).toEqual(expect.arrayContaining(['readF1', 'manageF1']));
274
+ expect(afterSecondElevationElevated.fleets?.[fleetUUID2]).toEqual(expect.arrayContaining(['createF2']));
275
+ expect(afterSecondElevationElevated.businessModels?.[bmUUID1]).toEqual(expect.arrayContaining(['writeBM1', 'writeBM2']));
276
+ expect(afterSecondElevationElevated.demandSources?.[dsUUID2]).toEqual(expect.arrayContaining(['readDS2', 'readDS2']));
277
+ expect(afterSecondElevationCombined.fleets?.[fleetUUID1]).toEqual(expect.arrayContaining(['readF1', 'manageF1']));
278
+ expect(afterSecondElevationCombined.fleets?.[fleetUUID2]).toEqual(expect.arrayContaining(['createF2']));
279
+ expect(afterSecondElevationCombined.businessModels?.[bmUUID1]).toEqual(expect.arrayContaining(['writeBM1', 'writeBM2']));
280
+ expect(afterSecondElevationCombined.demandSources?.[dsUUID2]).toEqual(expect.arrayContaining(['readDS2', 'readDS2']));
281
+ // ---------------------
282
+ // Assertions After Closing Second Elevation
283
+ // ---------------------
284
+ expect(afterCloseSecondElevated.fleets?.[fleetUUID1]).toEqual(expect.arrayContaining(['readF1']));
285
+ expect(afterCloseSecondElevated.fleets?.[fleetUUID2]).toBeUndefined();
286
+ expect(afterCloseSecondElevated.businessModels?.[bmUUID1]).toEqual(expect.arrayContaining(['writeBM1']));
287
+ expect(afterCloseSecondElevated.demandSources?.[dsUUID1]).toEqual(expect.arrayContaining(['readDS1']));
288
+ expect(afterCloseSecondElevated.demandSources?.[dsUUID2]).toBeUndefined();
289
+ expect(afterCloseSecondCombined.fleets?.[fleetUUID1]).toEqual(expect.arrayContaining(['readF1']));
290
+ expect(afterCloseSecondCombined.fleets?.[fleetUUID2]).toBeUndefined();
291
+ expect(afterCloseSecondCombined.businessModels?.[bmUUID1]).toEqual(expect.arrayContaining(['writeBM1']));
292
+ expect(afterCloseSecondCombined.demandSources?.[dsUUID1]).toEqual(expect.arrayContaining(['readDS1']));
293
+ expect(afterCloseSecondCombined.demandSources?.[dsUUID2]).toBeUndefined();
294
+ // ---------------------
295
+ // Assertions After Closing First Elevation
296
+ // ---------------------
297
+ expect(afterCloseFirstElevated.fleets).toEqual({});
298
+ expect(afterCloseFirstElevated.businessModels).toEqual({});
299
+ expect(afterCloseFirstElevated.demandSources).toEqual({});
300
+ expect(afterCloseFirstCombined.fleets).toEqual({});
301
+ expect(afterCloseFirstCombined.businessModels).toEqual({});
302
+ expect(afterCloseFirstCombined.demandSources).toEqual({});
303
+ });
125
304
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autofleet/zehut",
3
- "version": "3.1.2-beta.1",
3
+ "version": "3.1.2-beta.11",
4
4
  "description": "manage user's identity",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -28,7 +28,6 @@
28
28
  "axios": "^0.27.2",
29
29
  "express": "^4.18.1",
30
30
  "jsonwebtoken": "^8.5.1",
31
- "merge-deep": "^3.0.3",
32
31
  "methods": "^1.1.2",
33
32
  "moment": "^2.29.1",
34
33
  "nock": "^13.2.9",
@@ -50,7 +49,7 @@
50
49
  "typescript": "^4.9.5"
51
50
  },
52
51
  "peerDependencies": {
53
- "@autofleet/shtinker": "1.2.3-beta"
52
+ "@autofleet/shtinker": "^1.2.0"
54
53
  },
55
54
  "peerDependenciesMeta": {
56
55
  "@autofleet/shtinker": {