@autofleet/zehut 2.0.0 → 2.0.1-alpha
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 +0 -4
- package/lib/index.d.ts +1 -0
- package/lib/user/ApiUser.d.ts +3 -2
- package/lib/user/ApiUser.js +13 -24
- package/lib/user/api-user-flows.test.js +10 -21
- package/lib/user/index.d.ts +1 -0
- package/lib/user/index.js +27 -50
- package/package.json +1 -1
- package/lib/app-auth.d.ts +0 -2
- package/lib/app-auth.js +0 -21
- package/lib/exceptions/appDoesNotExist.d.ts +0 -3
- package/lib/exceptions/appDoesNotExist.js +0 -5
package/README.md
CHANGED
package/lib/index.d.ts
CHANGED
package/lib/user/ApiUser.d.ts
CHANGED
|
@@ -21,13 +21,15 @@ export declare type CustomPermissionLoader = (string: any) => Promise<UserPayloa
|
|
|
21
21
|
export default class ApiUser {
|
|
22
22
|
id: string | undefined;
|
|
23
23
|
privatePermissions: UserPayload | undefined;
|
|
24
|
-
|
|
24
|
+
privateElevatedPermissions: PartialUserPayload | undefined;
|
|
25
25
|
privatePermissionsLegacy: any;
|
|
26
26
|
appPermission: {
|
|
27
27
|
[key: string]: any;
|
|
28
28
|
};
|
|
29
29
|
emptyUser: boolean;
|
|
30
30
|
accountType: AccountType | undefined;
|
|
31
|
+
elevationCallsCounter: number;
|
|
32
|
+
elevationInitialState: PartialUserPayload | undefined;
|
|
31
33
|
constructor(id?: string, accountType?: AccountType, elevatedPermissions?: PartialUserPayload);
|
|
32
34
|
getUserPermissions(): Promise<UserPayload>;
|
|
33
35
|
useCustomPermissionLoader(customPermissionLoader: any): Promise<UserPayload>;
|
|
@@ -35,7 +37,6 @@ export default class ApiUser {
|
|
|
35
37
|
get fleets(): string[] | undefined;
|
|
36
38
|
get demandSources(): string[] | undefined;
|
|
37
39
|
getUserProperty(key: any): string[] | undefined;
|
|
38
|
-
get elevatedPermissions(): UserPayload;
|
|
39
40
|
get permissions(): UserPayload | undefined;
|
|
40
41
|
elevatePermissions(addedPermissions: PartialUserPayload): () => void;
|
|
41
42
|
getUserPermissionsLegacy(): Promise<any>;
|
package/lib/user/ApiUser.js
CHANGED
|
@@ -15,8 +15,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
15
15
|
exports.ELEVATED_PERMISSIONS_HEADER = void 0;
|
|
16
16
|
/* eslint-disable consistent-return */
|
|
17
17
|
const node_cache_1 = __importDefault(require("node-cache"));
|
|
18
|
-
const merge_deep_1 = __importDefault(require("merge-deep"));
|
|
19
|
-
const uuid_1 = require("uuid");
|
|
20
18
|
const outbreak_1 = require("@autofleet/outbreak");
|
|
21
19
|
const services_1 = require("../services");
|
|
22
20
|
exports.ELEVATED_PERMISSIONS_HEADER = 'x-af-elevated-permissions';
|
|
@@ -26,10 +24,9 @@ class ApiUser {
|
|
|
26
24
|
this.id = id;
|
|
27
25
|
this.emptyUser = !!id;
|
|
28
26
|
this.appPermission = {};
|
|
29
|
-
this.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
27
|
+
this.elevationCallsCounter = 0;
|
|
28
|
+
this.privateElevatedPermissions = elevatedPermissions;
|
|
29
|
+
this.elevationInitialState = elevatedPermissions;
|
|
33
30
|
if (accountType) {
|
|
34
31
|
this.accountType = accountType;
|
|
35
32
|
}
|
|
@@ -82,37 +79,29 @@ class ApiUser {
|
|
|
82
79
|
}
|
|
83
80
|
return Object.keys(this.privatePermissions[key] || {});
|
|
84
81
|
}
|
|
85
|
-
get elevatedPermissions() {
|
|
86
|
-
let permissions = {
|
|
87
|
-
fleets: {},
|
|
88
|
-
businessModels: {},
|
|
89
|
-
demandSources: {},
|
|
90
|
-
};
|
|
91
|
-
[...this.privateElevatedPermissionsHash.values()].forEach((p) => {
|
|
92
|
-
permissions = merge_deep_1.default(permissions, p);
|
|
93
|
-
});
|
|
94
|
-
return permissions;
|
|
95
|
-
}
|
|
96
82
|
get permissions() {
|
|
97
83
|
if (!this.privatePermissions) {
|
|
98
84
|
throw new Error('Cannot get permissions without calling (async) getUserPermissions before');
|
|
99
85
|
}
|
|
100
|
-
|
|
101
|
-
return permissions;
|
|
86
|
+
return Object.assign(Object.assign({}, this.privatePermissions), this.privateElevatedPermissions);
|
|
102
87
|
}
|
|
103
88
|
elevatePermissions(addedPermissions) {
|
|
104
|
-
|
|
89
|
+
if (this.elevationCallsCounter > 1) {
|
|
90
|
+
throw new Error('Maximum one elvation call in parallel is allowed');
|
|
91
|
+
}
|
|
105
92
|
const currentUserTrace = outbreak_1.getCurrentContext();
|
|
106
93
|
if (!currentUserTrace) {
|
|
107
94
|
throw new Error('Cannot find current user cross services trace');
|
|
108
95
|
}
|
|
109
96
|
const currentElevation = JSON.parse(currentUserTrace.context[exports.ELEVATED_PERMISSIONS_HEADER] || '{}');
|
|
97
|
+
this.elevationInitialState = currentElevation;
|
|
110
98
|
const newElevation = Object.assign(currentElevation, addedPermissions);
|
|
111
|
-
this.
|
|
112
|
-
currentUserTrace.context.set(exports.ELEVATED_PERMISSIONS_HEADER, JSON.stringify(
|
|
99
|
+
this.privateElevatedPermissions = newElevation;
|
|
100
|
+
currentUserTrace.context.set(exports.ELEVATED_PERMISSIONS_HEADER, JSON.stringify(newElevation));
|
|
101
|
+
this.elevationCallsCounter += 1;
|
|
113
102
|
return () => {
|
|
114
|
-
this.
|
|
115
|
-
|
|
103
|
+
this.elevationCallsCounter -= 1;
|
|
104
|
+
this.privateElevatedPermissions = this.elevationInitialState;
|
|
116
105
|
};
|
|
117
106
|
}
|
|
118
107
|
getUserPermissionsLegacy() {
|
|
@@ -31,7 +31,6 @@ const generateApp = (addEndpoints, port) => __awaiter(void 0, void 0, void 0, fu
|
|
|
31
31
|
describe('E2E', () => {
|
|
32
32
|
it('Basic functionality', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
33
33
|
let server2TraceId = null;
|
|
34
|
-
const [uuid1, uuid2] = [uuid_1.v4(), uuid_1.v4()];
|
|
35
34
|
index_1.enableTracing({
|
|
36
35
|
outbreakOptions: {
|
|
37
36
|
headersPrefix: 'x-af-',
|
|
@@ -41,24 +40,17 @@ describe('E2E', () => {
|
|
|
41
40
|
app.use(index_2.middleware());
|
|
42
41
|
app.get('/', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
43
42
|
const user = index_1.getUser();
|
|
44
|
-
const
|
|
43
|
+
const closeElevation = user.elevatePermissions({
|
|
45
44
|
businessModels: {
|
|
46
|
-
[
|
|
47
|
-
},
|
|
48
|
-
});
|
|
49
|
-
const closeElevation2 = user.elevatePermissions({
|
|
50
|
-
businessModels: {
|
|
51
|
-
[uuid2]: ['vehicle:write'],
|
|
45
|
+
[uuid_1.v4()]: ['vehicle:write'],
|
|
52
46
|
},
|
|
53
47
|
});
|
|
54
48
|
const { data: res1 } = yield axios_1.default.post('http://localhost:8082');
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
closeElevation2();
|
|
58
|
-
res.json([res1, res2]);
|
|
49
|
+
closeElevation();
|
|
50
|
+
res.json(res1);
|
|
59
51
|
}));
|
|
60
|
-
},
|
|
61
|
-
|
|
52
|
+
}, 8081);
|
|
53
|
+
let server2NumberOfPermissions = 0;
|
|
62
54
|
const closeServer2 = yield generateApp((app) => {
|
|
63
55
|
app.use(index_2.middleware());
|
|
64
56
|
app.post('/', (req, res) => {
|
|
@@ -68,7 +60,7 @@ describe('E2E', () => {
|
|
|
68
60
|
fleets: {},
|
|
69
61
|
demandSources: {},
|
|
70
62
|
};
|
|
71
|
-
server2NumberOfPermissions
|
|
63
|
+
server2NumberOfPermissions = Object.keys(user.permissions.businessModels).length;
|
|
72
64
|
server2TraceId = req.headers['x-trace-id'];
|
|
73
65
|
res.json({
|
|
74
66
|
value: req.headers['x-af-header'],
|
|
@@ -77,7 +69,7 @@ describe('E2E', () => {
|
|
|
77
69
|
});
|
|
78
70
|
});
|
|
79
71
|
}, 8082);
|
|
80
|
-
const { data:
|
|
72
|
+
const { data: res1, headers } = yield axios_1.default.get('http://localhost:8081', {
|
|
81
73
|
headers: {
|
|
82
74
|
'x-af-header': 'testHeader',
|
|
83
75
|
'x-af-id': 'my-wakanda-id',
|
|
@@ -86,13 +78,10 @@ describe('E2E', () => {
|
|
|
86
78
|
});
|
|
87
79
|
closeServer1();
|
|
88
80
|
closeServer2();
|
|
89
|
-
expect(server2NumberOfPermissions).toEqual(
|
|
81
|
+
expect(server2NumberOfPermissions).toEqual(1);
|
|
90
82
|
expect(headers['x-trace-id']).toEqual(server2TraceId);
|
|
91
83
|
expect(res1.value).toEqual('testHeader');
|
|
92
84
|
expect(res1.wkanda).toEqual('my-wakanda-id');
|
|
93
|
-
expect(JSON.parse(res1.addedPermissions).businessModels
|
|
94
|
-
expect(JSON.parse(res1.addedPermissions).businessModels[uuid2]).toBeDefined();
|
|
95
|
-
expect(JSON.parse(res2.addedPermissions).businessModels[uuid1]).not.toBeDefined();
|
|
96
|
-
expect(JSON.parse(res2.addedPermissions).businessModels[uuid2]).toBeDefined();
|
|
85
|
+
expect(JSON.parse(res1.addedPermissions).businessModels).toBeDefined();
|
|
97
86
|
}));
|
|
98
87
|
});
|
package/lib/user/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export declare const middlewareWithDecode: (options?: {
|
|
|
12
12
|
export declare const appMiddleware: (options: {
|
|
13
13
|
appId: string;
|
|
14
14
|
clientSecret: string;
|
|
15
|
+
appSecret: string;
|
|
15
16
|
}) => (req: any, res: any, next: any) => Promise<void>;
|
|
16
17
|
export declare const eagerLoadPermissionsMiddleware: (req: any, res: any, next: any) => Promise<any>;
|
|
17
18
|
export declare const getDecodedBearer: (req: any) => any;
|
package/lib/user/index.js
CHANGED
|
@@ -27,52 +27,39 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
27
27
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
28
28
|
});
|
|
29
29
|
};
|
|
30
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
31
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
32
|
-
};
|
|
33
30
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
34
31
|
exports.getDecodedBearer = exports.eagerLoadPermissionsMiddleware = exports.appMiddleware = exports.middlewareWithDecode = exports.middleware = void 0;
|
|
35
32
|
const jsonwebtoken_1 = require("jsonwebtoken");
|
|
36
33
|
const ApiUser_1 = __importStar(require("./ApiUser"));
|
|
37
|
-
const tracer_1 = require("../tracer");
|
|
38
|
-
const app_auth_1 = require("../app-auth");
|
|
39
|
-
const appDoesNotExist_1 = __importDefault(require("../exceptions/appDoesNotExist"));
|
|
40
34
|
const utils_1 = require("../utils");
|
|
35
|
+
const tracer_1 = require("../tracer");
|
|
41
36
|
exports.middleware = (options = {}) => (req, res, next) => __awaiter(void 0, void 0, void 0, function* () {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
yield userObject.useCustomPermissionLoader(customPermissionLoader);
|
|
56
|
-
}
|
|
57
|
-
else {
|
|
58
|
-
yield userObject.getUserPermissions();
|
|
59
|
-
}
|
|
37
|
+
const { eagerLoadUserPermissions, eagerLoadUserPermissionsLegacy, customPermissionLoader, } = options;
|
|
38
|
+
const userId = req.headers['x-af-user-id'];
|
|
39
|
+
const trace = tracer_1.newTrace('userPayload');
|
|
40
|
+
if (!userId) {
|
|
41
|
+
return next();
|
|
42
|
+
}
|
|
43
|
+
const elevatedPermissionsFromHeader = req.headers[ApiUser_1.ELEVATED_PERMISSIONS_HEADER] && req.headers[ApiUser_1.ELEVATED_PERMISSIONS_HEADER].length > 0
|
|
44
|
+
? JSON.parse(req.headers[ApiUser_1.ELEVATED_PERMISSIONS_HEADER])
|
|
45
|
+
: {};
|
|
46
|
+
const userObject = new ApiUser_1.default(userId, 'user', elevatedPermissionsFromHeader);
|
|
47
|
+
if (eagerLoadUserPermissions) {
|
|
48
|
+
if (customPermissionLoader) {
|
|
49
|
+
yield userObject.useCustomPermissionLoader(customPermissionLoader);
|
|
60
50
|
}
|
|
61
|
-
|
|
62
|
-
yield userObject.
|
|
51
|
+
else {
|
|
52
|
+
yield userObject.getUserPermissions();
|
|
63
53
|
}
|
|
64
|
-
req.user = userObject;
|
|
65
|
-
trace.context.set('userObject', userObject);
|
|
66
|
-
// Added in order to support outbreak.
|
|
67
|
-
req.headers['x-af-user-permissions'] = userObject;
|
|
68
|
-
return next();
|
|
69
54
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return res.json({
|
|
73
|
-
error: 'cannot authenticate user',
|
|
74
|
-
});
|
|
55
|
+
if (eagerLoadUserPermissionsLegacy) {
|
|
56
|
+
yield userObject.getUserPermissionsLegacy();
|
|
75
57
|
}
|
|
58
|
+
req.user = userObject;
|
|
59
|
+
trace.context.set('userObject', userObject);
|
|
60
|
+
// Added in order to support outbreak.
|
|
61
|
+
req.headers['x-af-user-permissions'] = userObject;
|
|
62
|
+
return next();
|
|
76
63
|
});
|
|
77
64
|
exports.middlewareWithDecode = (options = {}) => (req, res, next) => __awaiter(void 0, void 0, void 0, function* () {
|
|
78
65
|
var _a, _b;
|
|
@@ -81,7 +68,7 @@ exports.middlewareWithDecode = (options = {}) => (req, res, next) => __awaiter(v
|
|
|
81
68
|
let decoded;
|
|
82
69
|
if (req.headers.authorization) {
|
|
83
70
|
try {
|
|
84
|
-
decoded =
|
|
71
|
+
decoded = utils_1.decodeBearer(req.headers.authorization);
|
|
85
72
|
}
|
|
86
73
|
catch (e) {
|
|
87
74
|
if (e instanceof jsonwebtoken_1.TokenExpiredError) {
|
|
@@ -131,7 +118,7 @@ exports.middlewareWithDecode = (options = {}) => (req, res, next) => __awaiter(v
|
|
|
131
118
|
return next();
|
|
132
119
|
});
|
|
133
120
|
exports.appMiddleware = (options) => (req, res, next) => __awaiter(void 0, void 0, void 0, function* () {
|
|
134
|
-
const { appId, clientSecret, } = options;
|
|
121
|
+
const { appId, clientSecret, appSecret, } = options;
|
|
135
122
|
const trace = tracer_1.newTrace('userPayload');
|
|
136
123
|
let decoded;
|
|
137
124
|
if (!req.headers.authorization) {
|
|
@@ -141,10 +128,7 @@ exports.appMiddleware = (options) => (req, res, next) => __awaiter(void 0, void
|
|
|
141
128
|
});
|
|
142
129
|
}
|
|
143
130
|
try {
|
|
144
|
-
decoded =
|
|
145
|
-
if (!decoded) {
|
|
146
|
-
throw new appDoesNotExist_1.default();
|
|
147
|
-
}
|
|
131
|
+
decoded = utils_1.decodeBearer(req.headers.authorization, appSecret);
|
|
148
132
|
}
|
|
149
133
|
catch (e) {
|
|
150
134
|
if (e instanceof jsonwebtoken_1.TokenExpiredError) {
|
|
@@ -159,12 +143,6 @@ exports.appMiddleware = (options) => (req, res, next) => __awaiter(void 0, void
|
|
|
159
143
|
errors: [e.message],
|
|
160
144
|
});
|
|
161
145
|
}
|
|
162
|
-
if (e instanceof appDoesNotExist_1.default) {
|
|
163
|
-
res.status(400);
|
|
164
|
-
return res.json({
|
|
165
|
-
errors: [e.message],
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
146
|
res.status(500);
|
|
169
147
|
return res.json({
|
|
170
148
|
errors: ['Server error while parsing token'],
|
|
@@ -175,14 +153,13 @@ exports.appMiddleware = (options) => (req, res, next) => __awaiter(void 0, void
|
|
|
175
153
|
req.headers['X-AF-USER-ID'] = userId;
|
|
176
154
|
}
|
|
177
155
|
const userObject = new ApiUser_1.default(userId);
|
|
178
|
-
if (appId) {
|
|
156
|
+
if (appId && clientSecret) {
|
|
179
157
|
req.headers['x-autofleet-apps-secret'] = clientSecret;
|
|
180
158
|
// Won't work until we find a better solution for identity ms
|
|
181
159
|
yield userObject.getUserAppPermissions(appId, clientSecret);
|
|
182
160
|
}
|
|
183
161
|
req.user = userObject;
|
|
184
162
|
trace.context.set('userObject', userObject);
|
|
185
|
-
trace.context.set('accessToken', utils_1.getAuthFromBearer(req.headers.authorization));
|
|
186
163
|
// Added in order to support outbreak.
|
|
187
164
|
req.headers['x-af-user-permissions'] = userObject;
|
|
188
165
|
return next();
|
package/package.json
CHANGED
package/lib/app-auth.d.ts
DELETED
package/lib/app-auth.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
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
|
-
exports.getClientSecret = exports.decodeAppBearer = void 0;
|
|
13
|
-
const services_1 = require("./services");
|
|
14
|
-
exports.decodeAppBearer = (bearer, appId) => __awaiter(void 0, void 0, void 0, function* () {
|
|
15
|
-
const { data: decoded } = yield services_1.AutofleetApiNetwork.post('/api/v1/auth', { bearer, appId });
|
|
16
|
-
return decoded;
|
|
17
|
-
});
|
|
18
|
-
exports.getClientSecret = (appId) => __awaiter(void 0, void 0, void 0, function* () {
|
|
19
|
-
const { data: secret } = yield services_1.AutofleetApiNetwork.get(`/api/v1/auth/client-secret/${appId}`);
|
|
20
|
-
return secret;
|
|
21
|
-
});
|