@mondaydotcomorg/monday-authorization 3.2.3-feature-bashanye-navigate-can-action-in-scope-to-graph-af77c6b → 3.2.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/attributions-service.d.ts +3 -2
- package/dist/attributions-service.d.ts.map +1 -1
- package/dist/attributions-service.js +1 -0
- package/dist/authorization-service.d.ts +0 -8
- package/dist/authorization-service.d.ts.map +1 -1
- package/dist/authorization-service.js +20 -114
- package/dist/esm/attributions-service.d.ts +3 -2
- package/dist/esm/attributions-service.d.ts.map +1 -1
- package/dist/esm/attributions-service.mjs +1 -0
- package/dist/esm/authorization-service.d.ts +0 -8
- package/dist/esm/authorization-service.d.ts.map +1 -1
- package/dist/esm/authorization-service.mjs +20 -114
- package/package.json +5 -3
- package/src/attributions-service.ts +93 -0
- package/src/authorization-attributes-service.ts +234 -0
- package/src/authorization-internal-service.ts +129 -0
- package/src/authorization-middleware.ts +51 -0
- package/src/authorization-service.ts +365 -0
- package/src/constants/sns.ts +5 -0
- package/src/constants.ts +22 -0
- package/src/index.ts +46 -0
- package/src/prometheus-service.ts +48 -0
- package/src/roles-service.ts +125 -0
- package/src/testKit/index.ts +66 -0
- package/src/types/authorization-attributes-contracts.ts +33 -0
- package/src/types/express.ts +8 -0
- package/src/types/general.ts +32 -0
- package/src/types/roles.ts +42 -0
- package/src/types/scoped-actions-contracts.ts +48 -0
- package/CHANGELOG.md +0 -46
|
@@ -2,9 +2,10 @@ import { Context, ExecutionContext } from '@mondaydotcomorg/trident-backend-api'
|
|
|
2
2
|
export declare enum PlatformProfile {
|
|
3
3
|
API_INTERNAL = "api-internal",
|
|
4
4
|
SLOW = "slow",
|
|
5
|
-
INTERNAL = "internal"
|
|
5
|
+
INTERNAL = "internal",
|
|
6
|
+
APP = "app"
|
|
6
7
|
}
|
|
7
|
-
export declare function getProfile(): PlatformProfile;
|
|
8
|
+
export declare function getProfile(): PlatformProfile.API_INTERNAL | PlatformProfile.SLOW | PlatformProfile.INTERNAL;
|
|
8
9
|
export declare function getExecutionContext(context: Context): ExecutionContext;
|
|
9
10
|
export declare function getAttributionsFromApi(): {
|
|
10
11
|
[key: string]: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attributions-service.d.ts","sourceRoot":"","sources":["../src/attributions-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,OAAO,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAStF,oBAAY,eAAe;IACzB,YAAY,iBAAiB;IAC7B,IAAI,SAAS;IACb,QAAQ,aAAa;
|
|
1
|
+
{"version":3,"file":"attributions-service.d.ts","sourceRoot":"","sources":["../src/attributions-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,OAAO,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAStF,oBAAY,eAAe;IACzB,YAAY,iBAAiB;IAC7B,IAAI,SAAS;IACb,QAAQ,aAAa;IACrB,GAAG,QAAQ;CACZ;AAED,wBAAgB,UAAU,mFAiBzB;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,gBAAgB,CAEtE;AAED,wBAAgB,sBAAsB,IAAI;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,CAqClE"}
|
|
@@ -12,6 +12,7 @@ exports.PlatformProfile = void 0;
|
|
|
12
12
|
PlatformProfile["API_INTERNAL"] = "api-internal";
|
|
13
13
|
PlatformProfile["SLOW"] = "slow";
|
|
14
14
|
PlatformProfile["INTERNAL"] = "internal";
|
|
15
|
+
PlatformProfile["APP"] = "app";
|
|
15
16
|
})(exports.PlatformProfile || (exports.PlatformProfile = {}));
|
|
16
17
|
function getProfile() {
|
|
17
18
|
const tridentContext = tridentBackendApi.Api.getPart('context');
|
|
@@ -30,14 +30,6 @@ export declare class AuthorizationService {
|
|
|
30
30
|
static canActionInScope(accountId: number, userId: number, action: string, scope: ScopeOptions): Promise<ScopedActionPermit>;
|
|
31
31
|
private static getProfile;
|
|
32
32
|
static canActionInScopeMultiple(accountId: number, userId: number, scopedActions: ScopedAction[]): Promise<ScopedActionResponseObject[]>;
|
|
33
|
-
private static buildScopedActionsPayload;
|
|
34
|
-
private static scopeToResource;
|
|
35
|
-
private static buildGraphRequestBody;
|
|
36
|
-
private static fetchGraphIsAllowed;
|
|
37
|
-
private static mapGraphResponse;
|
|
38
|
-
private static fetchPlatformCanActions;
|
|
39
|
-
private static toCamelCase;
|
|
40
|
-
private static mapPlatformResponse;
|
|
41
33
|
private static isAuthorizedSingular;
|
|
42
34
|
private static isAuthorizedMultiple;
|
|
43
35
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authorization-service.d.ts","sourceRoot":"","sources":["../src/authorization-service.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAGnE,OAAO,EAAmB,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,MAAM,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAE7F,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,0BAA0B,EAC1B,YAAY,EACb,MAAM,kCAAkC,CAAC;
|
|
1
|
+
{"version":3,"file":"authorization-service.d.ts","sourceRoot":"","sources":["../src/authorization-service.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAGnE,OAAO,EAAmB,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,MAAM,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAE7F,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,0BAA0B,EAC1B,YAAY,EACb,MAAM,kCAAkC,CAAC;AAY1C,MAAM,WAAW,iBAAiB;IAChC,YAAY,EAAE,OAAO,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,mBAAmB,CAAC,EAAE,mBAAmB,EAAE,CAAC;CAC7C;AAED,wBAAgB,sBAAsB,CAAC,wBAAwB,EAAE,kBAAkB,QAElF;AAeD,qBAAa,oBAAoB;IAC/B,MAAM,CAAC,WAAW,CAAC,MAAC;IACpB,MAAM,CAAC,sCAAsC,CAAC,EAAE,MAAM,CAAC;IACvD,MAAM,CAAC,YAAY,CAAC,EAAE,YAAY,CAAC;IAEnC;;;OAGG;WACU,YAAY,CACvB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,QAAQ,EAAE,EACrB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,iBAAiB,CAAC;WAEhB,YAAY,CACvB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,2BAA2B,EAAE,mBAAmB,EAAE,GACjD,OAAO,CAAC,iBAAiB,CAAC;IAY7B;;;OAGG;WACU,wBAAwB,CACnC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAO,GAC1C,OAAO,CAAC,OAAO,CAAC;mBAkBE,6BAA6B;IAclD,OAAO,CAAC,MAAM,CAAC,gBAAgB;WAIlB,gBAAgB,CAC3B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,kBAAkB,CAAC;IAM9B,OAAO,CAAC,MAAM,CAAC,UAAU;WAsBZ,wBAAwB,CACnC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,YAAY,EAAE,GAC5B,OAAO,CAAC,0BAA0B,EAAE,CAAC;mBAkEnB,oBAAoB;mBAUpB,oBAAoB;CAmF1C;AAED,wBAAgB,cAAc,CAC5B,MAAM,KAAA,EACN,sCAAsC,GAAE,MAAiD,QAY1F;AAED,wBAAsB,eAAe,kBAMpC;AAED,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,mBAAmB,CAepG"}
|
|
@@ -23,8 +23,6 @@ const PLATFORM_CAN_ACTIONS_IN_SCOPES_PATH = '/internal_ms/authorization/can_acti
|
|
|
23
23
|
const ALLOWED_SDK_PLATFORM_PROFILES_KEY = 'allowed-sdk-platform-profiles';
|
|
24
24
|
const IN_RELEASE_SDK_PLATFORM_PROFILES_KEY = 'in-release-sdk-platform-profile';
|
|
25
25
|
const PLATFORM_PROFILE_RELEASE_FF = 'sdk-platform-profiles';
|
|
26
|
-
const NAVIGATE_CAN_ACTION_IN_SCOPE_TO_GRAPH_FF = 'navigate-can-action-in-scope-to-graph';
|
|
27
|
-
const GRAPH_IS_ALLOWED_PATH = '/permissions/is-allowed';
|
|
28
26
|
function setRequestFetchOptions(customMondayFetchOptions) {
|
|
29
27
|
authorizationInternalService.AuthorizationInternalService.setRequestFetchOptions(customMondayFetchOptions);
|
|
30
28
|
}
|
|
@@ -94,114 +92,19 @@ class AuthorizationService {
|
|
|
94
92
|
this.igniteClient.isReleased(PLATFORM_PROFILE_RELEASE_FF, { accountId, userId })) {
|
|
95
93
|
return attributionsService.getProfile();
|
|
96
94
|
}
|
|
97
|
-
return attributionsService.PlatformProfile.
|
|
95
|
+
return attributionsService.PlatformProfile.APP;
|
|
98
96
|
}
|
|
99
97
|
static async canActionInScopeMultiple(accountId, userId, scopedActions) {
|
|
100
|
-
const shouldNavigateToGraph = Boolean(this.igniteClient?.isReleased(NAVIGATE_CAN_ACTION_IN_SCOPE_TO_GRAPH_FF, { accountId, userId }));
|
|
101
|
-
const internalAuthToken = authorizationInternalService.AuthorizationInternalService.generateInternalAuthToken(accountId, userId);
|
|
102
|
-
if (shouldNavigateToGraph) {
|
|
103
|
-
const response = await this.fetchGraphIsAllowed(internalAuthToken, scopedActions);
|
|
104
|
-
return this.mapGraphResponse(scopedActions, userId, response);
|
|
105
|
-
}
|
|
106
98
|
const profile = this.getProfile(accountId, userId);
|
|
107
|
-
const
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
static buildScopedActionsPayload(scopedActions) {
|
|
112
|
-
return scopedActions.map(scopedAction => {
|
|
113
|
-
return { ...scopedAction, scope: mapKeys__default.default(scopedAction.scope, (_, key) => snakeCase__default.default(key)) };
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
static scopeToResource(scope) {
|
|
117
|
-
if ('workspaceId' in scope) {
|
|
118
|
-
return { resourceType: 'workspace', resourceId: scope.workspaceId };
|
|
119
|
-
}
|
|
120
|
-
if ('boardId' in scope) {
|
|
121
|
-
return { resourceType: 'board', resourceId: scope.boardId };
|
|
122
|
-
}
|
|
123
|
-
if ('pulseId' in scope) {
|
|
124
|
-
return { resourceType: 'pulse', resourceId: scope.pulseId };
|
|
125
|
-
}
|
|
126
|
-
if ('accountProductId' in scope) {
|
|
127
|
-
return { resourceType: 'account_product', resourceId: scope.accountProductId };
|
|
128
|
-
}
|
|
129
|
-
if ('accountId' in scope) {
|
|
130
|
-
return { resourceType: 'account', resourceId: scope.accountId };
|
|
131
|
-
}
|
|
132
|
-
throw new Error('Unsupported scope provided');
|
|
133
|
-
}
|
|
134
|
-
static buildGraphRequestBody(scopedActions) {
|
|
135
|
-
const resourcesAccumulator = {};
|
|
136
|
-
for (const { action, scope } of scopedActions) {
|
|
137
|
-
const { resourceType, resourceId } = this.scopeToResource(scope);
|
|
138
|
-
if (!resourcesAccumulator[resourceType]) {
|
|
139
|
-
resourcesAccumulator[resourceType] = {};
|
|
140
|
-
}
|
|
141
|
-
if (!resourcesAccumulator[resourceType][resourceId]) {
|
|
142
|
-
resourcesAccumulator[resourceType][resourceId] = new Set();
|
|
143
|
-
}
|
|
144
|
-
resourcesAccumulator[resourceType][resourceId].add(action);
|
|
145
|
-
}
|
|
146
|
-
const resourcesPayload = {};
|
|
147
|
-
for (const [resourceType, idMap] of Object.entries(resourcesAccumulator)) {
|
|
148
|
-
resourcesPayload[resourceType] = {};
|
|
149
|
-
for (const [idStr, actionsSet] of Object.entries(idMap)) {
|
|
150
|
-
const idNum = Number(idStr);
|
|
151
|
-
resourcesPayload[resourceType][idNum] = Array.from(actionsSet);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
return resourcesPayload;
|
|
155
|
-
}
|
|
156
|
-
static async fetchGraphIsAllowed(internalAuthToken, scopedActions) {
|
|
157
|
-
const httpClient = tridentBackendApi.Api.getPart('httpClient');
|
|
158
|
-
const attributionHeaders = attributionsService.getAttributionsFromApi();
|
|
159
|
-
const bodyPayload = this.buildGraphRequestBody(scopedActions);
|
|
160
|
-
try {
|
|
161
|
-
const response = await httpClient.fetch({
|
|
162
|
-
url: {
|
|
163
|
-
appName: 'authorization-graph',
|
|
164
|
-
path: GRAPH_IS_ALLOWED_PATH,
|
|
165
|
-
},
|
|
166
|
-
method: 'POST',
|
|
167
|
-
headers: {
|
|
168
|
-
Authorization: internalAuthToken,
|
|
169
|
-
'Content-Type': 'application/json',
|
|
170
|
-
...attributionHeaders,
|
|
171
|
-
},
|
|
172
|
-
body: JSON.stringify(bodyPayload),
|
|
173
|
-
}, {
|
|
174
|
-
timeout: authorizationInternalService.AuthorizationInternalService.getRequestTimeout(),
|
|
175
|
-
retryPolicy: authorizationInternalService.AuthorizationInternalService.getRetriesPolicy(),
|
|
176
|
-
});
|
|
177
|
-
return response;
|
|
178
|
-
}
|
|
179
|
-
catch (err) {
|
|
180
|
-
if (err instanceof mondayFetchApi.HttpFetcherError) {
|
|
181
|
-
authorizationInternalService.AuthorizationInternalService.throwOnHttpError(err.status, 'canActionInScopeMultiple');
|
|
182
|
-
}
|
|
183
|
-
throw err;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
static mapGraphResponse(scopedActions, userId, graphResponse) {
|
|
187
|
-
const resources = graphResponse ?? {};
|
|
188
|
-
return scopedActions.map(scopedAction => {
|
|
189
|
-
const { action, scope } = scopedAction;
|
|
190
|
-
const { resourceType, resourceId } = this.scopeToResource(scope);
|
|
191
|
-
const permissionResult = resources?.[resourceType]?.[String(resourceId)]?.[action];
|
|
192
|
-
const permit = {
|
|
193
|
-
can: permissionResult?.can ?? false,
|
|
194
|
-
reason: { key: permissionResult?.reason ?? 'unknown' },
|
|
195
|
-
technicalReason: 0,
|
|
196
|
-
};
|
|
197
|
-
return { scopedAction, permit };
|
|
99
|
+
const internalAuthToken = authorizationInternalService.AuthorizationInternalService.generateInternalAuthToken(accountId, userId);
|
|
100
|
+
const scopedActionsPayload = scopedActions.map(scopedAction => {
|
|
101
|
+
return { ...scopedAction, scope: mapKeys__default.default(scopedAction.scope, (_, key) => snakeCase__default.default(key)) }; // for example: { workspaceId: 1 } => { workspace_id: 1 }
|
|
198
102
|
});
|
|
199
|
-
}
|
|
200
|
-
static async fetchPlatformCanActions(profile, internalAuthToken, userId, scopedActionsPayload) {
|
|
201
103
|
const attributionHeaders = attributionsService.getAttributionsFromApi();
|
|
202
104
|
const httpClient = tridentBackendApi.Api.getPart('httpClient');
|
|
105
|
+
let response;
|
|
203
106
|
try {
|
|
204
|
-
|
|
107
|
+
response = await httpClient.fetch({
|
|
205
108
|
url: {
|
|
206
109
|
appName: 'platform',
|
|
207
110
|
path: PLATFORM_CAN_ACTIONS_IN_SCOPES_PATH,
|
|
@@ -213,37 +116,40 @@ class AuthorizationService {
|
|
|
213
116
|
'Content-Type': 'application/json',
|
|
214
117
|
...attributionHeaders,
|
|
215
118
|
},
|
|
216
|
-
body: JSON.stringify({
|
|
119
|
+
body: JSON.stringify({
|
|
120
|
+
user_id: userId,
|
|
121
|
+
scoped_actions: scopedActionsPayload,
|
|
122
|
+
}),
|
|
217
123
|
}, {
|
|
218
124
|
timeout: authorizationInternalService.AuthorizationInternalService.getRequestTimeout(),
|
|
219
125
|
retryPolicy: authorizationInternalService.AuthorizationInternalService.getRetriesPolicy(),
|
|
220
126
|
});
|
|
221
|
-
return response;
|
|
222
127
|
}
|
|
223
128
|
catch (err) {
|
|
224
129
|
if (err instanceof mondayFetchApi.HttpFetcherError) {
|
|
225
130
|
authorizationInternalService.AuthorizationInternalService.throwOnHttpError(err.status, 'canActionInScopeMultiple');
|
|
226
131
|
}
|
|
227
|
-
|
|
132
|
+
else {
|
|
133
|
+
throw err;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function toCamelCase(obj) {
|
|
137
|
+
return mapKeys__default.default(obj, (_, key) => camelCase__default.default(key));
|
|
228
138
|
}
|
|
229
|
-
}
|
|
230
|
-
static toCamelCase(obj) {
|
|
231
|
-
return mapKeys__default.default(obj, (_, key) => camelCase__default.default(key));
|
|
232
|
-
}
|
|
233
|
-
static mapPlatformResponse(response) {
|
|
234
139
|
if (!response) {
|
|
235
140
|
authorizationInternalService.logger.error({ tag: 'authorization-service', response }, 'AuthorizationService: missing response');
|
|
236
141
|
throw new Error('AuthorizationService: missing response');
|
|
237
142
|
}
|
|
238
|
-
|
|
143
|
+
const scopedActionsResponseObjects = response.result.map(responseObject => {
|
|
239
144
|
const { scopedAction, permit } = responseObject;
|
|
240
145
|
const { scope } = scopedAction;
|
|
241
146
|
return {
|
|
242
147
|
...responseObject,
|
|
243
|
-
scopedAction: { ...scopedAction, scope:
|
|
244
|
-
permit:
|
|
148
|
+
scopedAction: { ...scopedAction, scope: toCamelCase(scope) },
|
|
149
|
+
permit: toCamelCase(permit),
|
|
245
150
|
};
|
|
246
151
|
});
|
|
152
|
+
return scopedActionsResponseObjects;
|
|
247
153
|
}
|
|
248
154
|
static async isAuthorizedSingular(accountId, userId, resources, action) {
|
|
249
155
|
const { authorizationObjects } = createAuthorizationParams(resources, action);
|
|
@@ -2,9 +2,10 @@ import { Context, ExecutionContext } from '@mondaydotcomorg/trident-backend-api'
|
|
|
2
2
|
export declare enum PlatformProfile {
|
|
3
3
|
API_INTERNAL = "api-internal",
|
|
4
4
|
SLOW = "slow",
|
|
5
|
-
INTERNAL = "internal"
|
|
5
|
+
INTERNAL = "internal",
|
|
6
|
+
APP = "app"
|
|
6
7
|
}
|
|
7
|
-
export declare function getProfile(): PlatformProfile;
|
|
8
|
+
export declare function getProfile(): PlatformProfile.API_INTERNAL | PlatformProfile.SLOW | PlatformProfile.INTERNAL;
|
|
8
9
|
export declare function getExecutionContext(context: Context): ExecutionContext;
|
|
9
10
|
export declare function getAttributionsFromApi(): {
|
|
10
11
|
[key: string]: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attributions-service.d.ts","sourceRoot":"","sources":["../../src/attributions-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,OAAO,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAStF,oBAAY,eAAe;IACzB,YAAY,iBAAiB;IAC7B,IAAI,SAAS;IACb,QAAQ,aAAa;
|
|
1
|
+
{"version":3,"file":"attributions-service.d.ts","sourceRoot":"","sources":["../../src/attributions-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,OAAO,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAStF,oBAAY,eAAe;IACzB,YAAY,iBAAiB;IAC7B,IAAI,SAAS;IACb,QAAQ,aAAa;IACrB,GAAG,QAAQ;CACZ;AAED,wBAAgB,UAAU,mFAiBzB;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,gBAAgB,CAEtE;AAED,wBAAgB,sBAAsB,IAAI;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,CAqClE"}
|
|
@@ -10,6 +10,7 @@ var PlatformProfile;
|
|
|
10
10
|
PlatformProfile["API_INTERNAL"] = "api-internal";
|
|
11
11
|
PlatformProfile["SLOW"] = "slow";
|
|
12
12
|
PlatformProfile["INTERNAL"] = "internal";
|
|
13
|
+
PlatformProfile["APP"] = "app";
|
|
13
14
|
})(PlatformProfile || (PlatformProfile = {}));
|
|
14
15
|
function getProfile() {
|
|
15
16
|
const tridentContext = Api.getPart('context');
|
|
@@ -30,14 +30,6 @@ export declare class AuthorizationService {
|
|
|
30
30
|
static canActionInScope(accountId: number, userId: number, action: string, scope: ScopeOptions): Promise<ScopedActionPermit>;
|
|
31
31
|
private static getProfile;
|
|
32
32
|
static canActionInScopeMultiple(accountId: number, userId: number, scopedActions: ScopedAction[]): Promise<ScopedActionResponseObject[]>;
|
|
33
|
-
private static buildScopedActionsPayload;
|
|
34
|
-
private static scopeToResource;
|
|
35
|
-
private static buildGraphRequestBody;
|
|
36
|
-
private static fetchGraphIsAllowed;
|
|
37
|
-
private static mapGraphResponse;
|
|
38
|
-
private static fetchPlatformCanActions;
|
|
39
|
-
private static toCamelCase;
|
|
40
|
-
private static mapPlatformResponse;
|
|
41
33
|
private static isAuthorizedSingular;
|
|
42
34
|
private static isAuthorizedMultiple;
|
|
43
35
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authorization-service.d.ts","sourceRoot":"","sources":["../../src/authorization-service.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAGnE,OAAO,EAAmB,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,MAAM,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAE7F,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,0BAA0B,EAC1B,YAAY,EACb,MAAM,kCAAkC,CAAC;
|
|
1
|
+
{"version":3,"file":"authorization-service.d.ts","sourceRoot":"","sources":["../../src/authorization-service.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAGnE,OAAO,EAAmB,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,MAAM,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAE7F,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,0BAA0B,EAC1B,YAAY,EACb,MAAM,kCAAkC,CAAC;AAY1C,MAAM,WAAW,iBAAiB;IAChC,YAAY,EAAE,OAAO,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,mBAAmB,CAAC,EAAE,mBAAmB,EAAE,CAAC;CAC7C;AAED,wBAAgB,sBAAsB,CAAC,wBAAwB,EAAE,kBAAkB,QAElF;AAeD,qBAAa,oBAAoB;IAC/B,MAAM,CAAC,WAAW,CAAC,MAAC;IACpB,MAAM,CAAC,sCAAsC,CAAC,EAAE,MAAM,CAAC;IACvD,MAAM,CAAC,YAAY,CAAC,EAAE,YAAY,CAAC;IAEnC;;;OAGG;WACU,YAAY,CACvB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,QAAQ,EAAE,EACrB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,iBAAiB,CAAC;WAEhB,YAAY,CACvB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,2BAA2B,EAAE,mBAAmB,EAAE,GACjD,OAAO,CAAC,iBAAiB,CAAC;IAY7B;;;OAGG;WACU,wBAAwB,CACnC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAO,GAC1C,OAAO,CAAC,OAAO,CAAC;mBAkBE,6BAA6B;IAclD,OAAO,CAAC,MAAM,CAAC,gBAAgB;WAIlB,gBAAgB,CAC3B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,kBAAkB,CAAC;IAM9B,OAAO,CAAC,MAAM,CAAC,UAAU;WAsBZ,wBAAwB,CACnC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,YAAY,EAAE,GAC5B,OAAO,CAAC,0BAA0B,EAAE,CAAC;mBAkEnB,oBAAoB;mBAUpB,oBAAoB;CAmF1C;AAED,wBAAgB,cAAc,CAC5B,MAAM,KAAA,EACN,sCAAsC,GAAE,MAAiD,QAY1F;AAED,wBAAsB,eAAe,kBAMpC;AAED,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,mBAAmB,CAepG"}
|
|
@@ -15,8 +15,6 @@ const PLATFORM_CAN_ACTIONS_IN_SCOPES_PATH = '/internal_ms/authorization/can_acti
|
|
|
15
15
|
const ALLOWED_SDK_PLATFORM_PROFILES_KEY = 'allowed-sdk-platform-profiles';
|
|
16
16
|
const IN_RELEASE_SDK_PLATFORM_PROFILES_KEY = 'in-release-sdk-platform-profile';
|
|
17
17
|
const PLATFORM_PROFILE_RELEASE_FF = 'sdk-platform-profiles';
|
|
18
|
-
const NAVIGATE_CAN_ACTION_IN_SCOPE_TO_GRAPH_FF = 'navigate-can-action-in-scope-to-graph';
|
|
19
|
-
const GRAPH_IS_ALLOWED_PATH = '/permissions/is-allowed';
|
|
20
18
|
function setRequestFetchOptions(customMondayFetchOptions) {
|
|
21
19
|
AuthorizationInternalService.setRequestFetchOptions(customMondayFetchOptions);
|
|
22
20
|
}
|
|
@@ -86,114 +84,19 @@ class AuthorizationService {
|
|
|
86
84
|
this.igniteClient.isReleased(PLATFORM_PROFILE_RELEASE_FF, { accountId, userId })) {
|
|
87
85
|
return getProfile();
|
|
88
86
|
}
|
|
89
|
-
return PlatformProfile.
|
|
87
|
+
return PlatformProfile.APP;
|
|
90
88
|
}
|
|
91
89
|
static async canActionInScopeMultiple(accountId, userId, scopedActions) {
|
|
92
|
-
const shouldNavigateToGraph = Boolean(this.igniteClient?.isReleased(NAVIGATE_CAN_ACTION_IN_SCOPE_TO_GRAPH_FF, { accountId, userId }));
|
|
93
|
-
const internalAuthToken = AuthorizationInternalService.generateInternalAuthToken(accountId, userId);
|
|
94
|
-
if (shouldNavigateToGraph) {
|
|
95
|
-
const response = await this.fetchGraphIsAllowed(internalAuthToken, scopedActions);
|
|
96
|
-
return this.mapGraphResponse(scopedActions, userId, response);
|
|
97
|
-
}
|
|
98
90
|
const profile = this.getProfile(accountId, userId);
|
|
99
|
-
const
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
static buildScopedActionsPayload(scopedActions) {
|
|
104
|
-
return scopedActions.map(scopedAction => {
|
|
105
|
-
return { ...scopedAction, scope: mapKeys(scopedAction.scope, (_, key) => snakeCase(key)) };
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
static scopeToResource(scope) {
|
|
109
|
-
if ('workspaceId' in scope) {
|
|
110
|
-
return { resourceType: 'workspace', resourceId: scope.workspaceId };
|
|
111
|
-
}
|
|
112
|
-
if ('boardId' in scope) {
|
|
113
|
-
return { resourceType: 'board', resourceId: scope.boardId };
|
|
114
|
-
}
|
|
115
|
-
if ('pulseId' in scope) {
|
|
116
|
-
return { resourceType: 'pulse', resourceId: scope.pulseId };
|
|
117
|
-
}
|
|
118
|
-
if ('accountProductId' in scope) {
|
|
119
|
-
return { resourceType: 'account_product', resourceId: scope.accountProductId };
|
|
120
|
-
}
|
|
121
|
-
if ('accountId' in scope) {
|
|
122
|
-
return { resourceType: 'account', resourceId: scope.accountId };
|
|
123
|
-
}
|
|
124
|
-
throw new Error('Unsupported scope provided');
|
|
125
|
-
}
|
|
126
|
-
static buildGraphRequestBody(scopedActions) {
|
|
127
|
-
const resourcesAccumulator = {};
|
|
128
|
-
for (const { action, scope } of scopedActions) {
|
|
129
|
-
const { resourceType, resourceId } = this.scopeToResource(scope);
|
|
130
|
-
if (!resourcesAccumulator[resourceType]) {
|
|
131
|
-
resourcesAccumulator[resourceType] = {};
|
|
132
|
-
}
|
|
133
|
-
if (!resourcesAccumulator[resourceType][resourceId]) {
|
|
134
|
-
resourcesAccumulator[resourceType][resourceId] = new Set();
|
|
135
|
-
}
|
|
136
|
-
resourcesAccumulator[resourceType][resourceId].add(action);
|
|
137
|
-
}
|
|
138
|
-
const resourcesPayload = {};
|
|
139
|
-
for (const [resourceType, idMap] of Object.entries(resourcesAccumulator)) {
|
|
140
|
-
resourcesPayload[resourceType] = {};
|
|
141
|
-
for (const [idStr, actionsSet] of Object.entries(idMap)) {
|
|
142
|
-
const idNum = Number(idStr);
|
|
143
|
-
resourcesPayload[resourceType][idNum] = Array.from(actionsSet);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
return resourcesPayload;
|
|
147
|
-
}
|
|
148
|
-
static async fetchGraphIsAllowed(internalAuthToken, scopedActions) {
|
|
149
|
-
const httpClient = Api.getPart('httpClient');
|
|
150
|
-
const attributionHeaders = getAttributionsFromApi();
|
|
151
|
-
const bodyPayload = this.buildGraphRequestBody(scopedActions);
|
|
152
|
-
try {
|
|
153
|
-
const response = await httpClient.fetch({
|
|
154
|
-
url: {
|
|
155
|
-
appName: 'authorization-graph',
|
|
156
|
-
path: GRAPH_IS_ALLOWED_PATH,
|
|
157
|
-
},
|
|
158
|
-
method: 'POST',
|
|
159
|
-
headers: {
|
|
160
|
-
Authorization: internalAuthToken,
|
|
161
|
-
'Content-Type': 'application/json',
|
|
162
|
-
...attributionHeaders,
|
|
163
|
-
},
|
|
164
|
-
body: JSON.stringify(bodyPayload),
|
|
165
|
-
}, {
|
|
166
|
-
timeout: AuthorizationInternalService.getRequestTimeout(),
|
|
167
|
-
retryPolicy: AuthorizationInternalService.getRetriesPolicy(),
|
|
168
|
-
});
|
|
169
|
-
return response;
|
|
170
|
-
}
|
|
171
|
-
catch (err) {
|
|
172
|
-
if (err instanceof HttpFetcherError) {
|
|
173
|
-
AuthorizationInternalService.throwOnHttpError(err.status, 'canActionInScopeMultiple');
|
|
174
|
-
}
|
|
175
|
-
throw err;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
static mapGraphResponse(scopedActions, userId, graphResponse) {
|
|
179
|
-
const resources = graphResponse ?? {};
|
|
180
|
-
return scopedActions.map(scopedAction => {
|
|
181
|
-
const { action, scope } = scopedAction;
|
|
182
|
-
const { resourceType, resourceId } = this.scopeToResource(scope);
|
|
183
|
-
const permissionResult = resources?.[resourceType]?.[String(resourceId)]?.[action];
|
|
184
|
-
const permit = {
|
|
185
|
-
can: permissionResult?.can ?? false,
|
|
186
|
-
reason: { key: permissionResult?.reason ?? 'unknown' },
|
|
187
|
-
technicalReason: 0,
|
|
188
|
-
};
|
|
189
|
-
return { scopedAction, permit };
|
|
91
|
+
const internalAuthToken = AuthorizationInternalService.generateInternalAuthToken(accountId, userId);
|
|
92
|
+
const scopedActionsPayload = scopedActions.map(scopedAction => {
|
|
93
|
+
return { ...scopedAction, scope: mapKeys(scopedAction.scope, (_, key) => snakeCase(key)) }; // for example: { workspaceId: 1 } => { workspace_id: 1 }
|
|
190
94
|
});
|
|
191
|
-
}
|
|
192
|
-
static async fetchPlatformCanActions(profile, internalAuthToken, userId, scopedActionsPayload) {
|
|
193
95
|
const attributionHeaders = getAttributionsFromApi();
|
|
194
96
|
const httpClient = Api.getPart('httpClient');
|
|
97
|
+
let response;
|
|
195
98
|
try {
|
|
196
|
-
|
|
99
|
+
response = await httpClient.fetch({
|
|
197
100
|
url: {
|
|
198
101
|
appName: 'platform',
|
|
199
102
|
path: PLATFORM_CAN_ACTIONS_IN_SCOPES_PATH,
|
|
@@ -205,37 +108,40 @@ class AuthorizationService {
|
|
|
205
108
|
'Content-Type': 'application/json',
|
|
206
109
|
...attributionHeaders,
|
|
207
110
|
},
|
|
208
|
-
body: JSON.stringify({
|
|
111
|
+
body: JSON.stringify({
|
|
112
|
+
user_id: userId,
|
|
113
|
+
scoped_actions: scopedActionsPayload,
|
|
114
|
+
}),
|
|
209
115
|
}, {
|
|
210
116
|
timeout: AuthorizationInternalService.getRequestTimeout(),
|
|
211
117
|
retryPolicy: AuthorizationInternalService.getRetriesPolicy(),
|
|
212
118
|
});
|
|
213
|
-
return response;
|
|
214
119
|
}
|
|
215
120
|
catch (err) {
|
|
216
121
|
if (err instanceof HttpFetcherError) {
|
|
217
122
|
AuthorizationInternalService.throwOnHttpError(err.status, 'canActionInScopeMultiple');
|
|
218
123
|
}
|
|
219
|
-
|
|
124
|
+
else {
|
|
125
|
+
throw err;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function toCamelCase(obj) {
|
|
129
|
+
return mapKeys(obj, (_, key) => camelCase(key));
|
|
220
130
|
}
|
|
221
|
-
}
|
|
222
|
-
static toCamelCase(obj) {
|
|
223
|
-
return mapKeys(obj, (_, key) => camelCase(key));
|
|
224
|
-
}
|
|
225
|
-
static mapPlatformResponse(response) {
|
|
226
131
|
if (!response) {
|
|
227
132
|
logger.error({ tag: 'authorization-service', response }, 'AuthorizationService: missing response');
|
|
228
133
|
throw new Error('AuthorizationService: missing response');
|
|
229
134
|
}
|
|
230
|
-
|
|
135
|
+
const scopedActionsResponseObjects = response.result.map(responseObject => {
|
|
231
136
|
const { scopedAction, permit } = responseObject;
|
|
232
137
|
const { scope } = scopedAction;
|
|
233
138
|
return {
|
|
234
139
|
...responseObject,
|
|
235
|
-
scopedAction: { ...scopedAction, scope:
|
|
236
|
-
permit:
|
|
140
|
+
scopedAction: { ...scopedAction, scope: toCamelCase(scope) },
|
|
141
|
+
permit: toCamelCase(permit),
|
|
237
142
|
};
|
|
238
143
|
});
|
|
144
|
+
return scopedActionsResponseObjects;
|
|
239
145
|
}
|
|
240
146
|
static async isAuthorizedSingular(accountId, userId, resources, action) {
|
|
241
147
|
const { authorizationObjects } = createAuthorizationParams(resources, action);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mondaydotcomorg/monday-authorization",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.4",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"license": "BSD-3-Clause",
|
|
@@ -46,7 +46,9 @@
|
|
|
46
46
|
"typescript": "^5.2.2"
|
|
47
47
|
},
|
|
48
48
|
"files": [
|
|
49
|
-
"dist/"
|
|
49
|
+
"dist/",
|
|
50
|
+
"src/",
|
|
51
|
+
"dist/node_modules/lodash-cjs/"
|
|
50
52
|
],
|
|
51
53
|
"eslintConfig": {
|
|
52
54
|
"extends": "@mondaydotcomorg/trident-library",
|
|
@@ -62,4 +64,4 @@
|
|
|
62
64
|
"url": "https://github.com/DaPulse/authorization-domain.git",
|
|
63
65
|
"directory": "packages/monday-authorization"
|
|
64
66
|
}
|
|
65
|
-
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { Api, Context, ExecutionContext } from '@mondaydotcomorg/trident-backend-api';
|
|
2
|
+
import { logger } from './authorization-internal-service';
|
|
3
|
+
|
|
4
|
+
const APP_NAME_VARIABLE_KEY = 'APP_NAME';
|
|
5
|
+
const APP_NAME_HEADER_NAME = 'x-caller-app-name-from-sdk';
|
|
6
|
+
const FROM_SDK_HEADER_SUFFIX = `-from-sdk`;
|
|
7
|
+
|
|
8
|
+
let didSendFailureLogOnce = false;
|
|
9
|
+
|
|
10
|
+
export enum PlatformProfile {
|
|
11
|
+
API_INTERNAL = 'api-internal',
|
|
12
|
+
SLOW = 'slow',
|
|
13
|
+
INTERNAL = 'internal',
|
|
14
|
+
APP = 'app',
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getProfile() {
|
|
18
|
+
const tridentContext = Api.getPart('context');
|
|
19
|
+
if (!tridentContext) {
|
|
20
|
+
return PlatformProfile.INTERNAL;
|
|
21
|
+
}
|
|
22
|
+
const { mondayRequestSource } = getExecutionContext(tridentContext);
|
|
23
|
+
|
|
24
|
+
switch (mondayRequestSource) {
|
|
25
|
+
case 'api': {
|
|
26
|
+
return PlatformProfile.API_INTERNAL;
|
|
27
|
+
}
|
|
28
|
+
case 'slow': {
|
|
29
|
+
return PlatformProfile.SLOW;
|
|
30
|
+
}
|
|
31
|
+
default:
|
|
32
|
+
return PlatformProfile.INTERNAL;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function getExecutionContext(context: Context): ExecutionContext {
|
|
37
|
+
return context.execution.get();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function getAttributionsFromApi(): { [key: string]: string } {
|
|
41
|
+
const callerAppNameFromSdk = {
|
|
42
|
+
[APP_NAME_HEADER_NAME]: tryJsonParse(getEnvVariable(APP_NAME_VARIABLE_KEY)),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const tridentContext = Api.getPart('context');
|
|
47
|
+
|
|
48
|
+
if (!tridentContext) {
|
|
49
|
+
return callerAppNameFromSdk;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const { runtimeAttributions } = tridentContext;
|
|
53
|
+
const runtimeAttributionsOutgoingHeaders = runtimeAttributions?.buildOutgoingHeaders('HTTP_INTERNAL');
|
|
54
|
+
|
|
55
|
+
if (!runtimeAttributionsOutgoingHeaders) {
|
|
56
|
+
return callerAppNameFromSdk;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const attributionsHeaders = Object.fromEntries(runtimeAttributionsOutgoingHeaders);
|
|
60
|
+
|
|
61
|
+
const attributionHeadersFromSdk = {};
|
|
62
|
+
Object.keys(attributionsHeaders).forEach(function (key) {
|
|
63
|
+
attributionHeadersFromSdk[`${key}${FROM_SDK_HEADER_SUFFIX}`] = attributionsHeaders[key];
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return attributionHeadersFromSdk;
|
|
67
|
+
} catch (error) {
|
|
68
|
+
if (!didSendFailureLogOnce) {
|
|
69
|
+
logger.warn(
|
|
70
|
+
{ tag: 'authorization-service', error },
|
|
71
|
+
'Failed to generate attributions headers from the API. Unexpected error while extracting headers. It may be caused by out of date Trident version.'
|
|
72
|
+
);
|
|
73
|
+
didSendFailureLogOnce = true;
|
|
74
|
+
}
|
|
75
|
+
return callerAppNameFromSdk;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function getEnvVariable(key: string) {
|
|
80
|
+
const envVar = process.env[key] || process.env[key.toUpperCase()] || process.env[key.toLowerCase()];
|
|
81
|
+
return envVar;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function tryJsonParse(value: string | undefined) {
|
|
85
|
+
if (!value) {
|
|
86
|
+
return value;
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
return JSON.parse(value);
|
|
90
|
+
} catch (_err) {
|
|
91
|
+
return value;
|
|
92
|
+
}
|
|
93
|
+
}
|