@mondaydotcomorg/monday-authorization 3.2.3 → 3.3.0-feat-add-graph-api-routing-support-2d70b30
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 +29 -0
- package/dist/authorization-service.d.ts.map +1 -1
- package/dist/authorization-service.js +43 -61
- package/dist/clients/graph-api.client.d.ts +24 -0
- package/dist/clients/graph-api.client.d.ts.map +1 -0
- package/dist/clients/graph-api.client.js +123 -0
- package/dist/clients/platform-api.client.d.ts +31 -0
- package/dist/clients/platform-api.client.d.ts.map +1 -0
- package/dist/clients/platform-api.client.js +89 -0
- package/dist/esm/authorization-service.d.ts.map +1 -1
- package/dist/esm/authorization-service.mjs +44 -56
- package/dist/esm/clients/graph-api.client.d.ts +24 -0
- package/dist/esm/clients/graph-api.client.d.ts.map +1 -0
- package/dist/esm/clients/graph-api.client.mjs +121 -0
- package/dist/esm/clients/platform-api.client.d.ts +31 -0
- package/dist/esm/clients/platform-api.client.d.ts.map +1 -0
- package/dist/esm/clients/platform-api.client.mjs +87 -0
- package/dist/esm/prometheus-service.d.ts +3 -1
- package/dist/esm/prometheus-service.d.ts.map +1 -1
- package/dist/esm/prometheus-service.mjs +61 -5
- package/dist/esm/testKit/index.d.ts.map +1 -1
- package/dist/esm/testKit/index.mjs +9 -7
- package/dist/esm/types/graph-api.types.d.ts +15 -0
- package/dist/esm/types/graph-api.types.d.ts.map +1 -0
- package/dist/esm/types/graph-api.types.mjs +1 -0
- package/dist/esm/utils/authorization.utils.d.ts +22 -0
- package/dist/esm/utils/authorization.utils.d.ts.map +1 -0
- package/dist/esm/utils/authorization.utils.mjs +39 -0
- package/dist/prometheus-service.d.ts +3 -1
- package/dist/prometheus-service.d.ts.map +1 -1
- package/dist/prometheus-service.js +62 -4
- package/dist/testKit/index.d.ts.map +1 -1
- package/dist/testKit/index.js +9 -7
- package/dist/types/graph-api.types.d.ts +15 -0
- package/dist/types/graph-api.types.d.ts.map +1 -0
- package/dist/types/graph-api.types.js +1 -0
- package/dist/utils/authorization.utils.d.ts +22 -0
- package/dist/utils/authorization.utils.d.ts.map +1 -0
- package/dist/utils/authorization.utils.js +49 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -137,6 +137,18 @@ const canActionInScopeMultipleResponse: ScopedActionResponseObject[] =
|
|
|
137
137
|
* /
|
|
138
138
|
```
|
|
139
139
|
|
|
140
|
+
**Graph API Routing (v3.3.0+):**
|
|
141
|
+
|
|
142
|
+
Starting from version 3.3.0, `canActionInScope` and `canActionInScopeMultiple` can route authorization checks to the Graph API (`authorization-graph` service) instead of the Platform API. This routing is controlled by the Ignite feature flag `navigate-can-action-in-scope-to-graph`.
|
|
143
|
+
|
|
144
|
+
- **Feature Flag**: `navigate-can-action-in-scope-to-graph`
|
|
145
|
+
- **Default Behavior**: When the feature flag is disabled (default), the SDK routes to Platform API (backward compatible)
|
|
146
|
+
- **Graph Routing**: When enabled, authorization checks are routed to the Graph API endpoint `/permissions/is-allowed`
|
|
147
|
+
- **Automatic Fallback**: The SDK automatically falls back to Platform API if the feature flag is disabled
|
|
148
|
+
- **No Code Changes Required**: The routing is transparent - your code doesn't need to change. Simply upgrade to v3.3.0+ and the feature flag controls the routing behavior
|
|
149
|
+
|
|
150
|
+
The Graph API provides the same authorization results with improved performance and scalability. The feature flag allows for gradual rollout and easy rollback if needed.
|
|
151
|
+
|
|
140
152
|
### Authorization Attributes API
|
|
141
153
|
|
|
142
154
|
Authorization attributes have 2 options to get called: sync (http request) and async (send to SNS and consumed asynchronously).
|
|
@@ -244,6 +256,7 @@ const rolesResponse = await rolesService.getRoles(accountId, resourceTypes, styl
|
|
|
244
256
|
```
|
|
245
257
|
|
|
246
258
|
**Parameters:**
|
|
259
|
+
|
|
247
260
|
- `accountId` - The account ID
|
|
248
261
|
- `resourceTypes` - Array of resource types to filter roles by (e.g., ['account', 'workspace'])
|
|
249
262
|
- `style` - Deprecated, don't use it. the style of the roles to return, either 'A' or 'B' (default is 'A'). Note that basic role IDs are returned in A style and not B style.
|
|
@@ -273,6 +286,7 @@ const rolesResponse = await rolesService.createCustomRole(accountId, customRoles
|
|
|
273
286
|
```
|
|
274
287
|
|
|
275
288
|
**Parameters:**
|
|
289
|
+
|
|
276
290
|
- `accountId` - The account ID
|
|
277
291
|
- `roles` - Array of `RoleCreateRequest` objects (cannot be empty)
|
|
278
292
|
|
|
@@ -298,6 +312,7 @@ const rolesResponse = await rolesService.updateCustomRole(accountId, updateReque
|
|
|
298
312
|
```
|
|
299
313
|
|
|
300
314
|
**Parameters:**
|
|
315
|
+
|
|
301
316
|
- `accountId` - The account ID
|
|
302
317
|
- `updateRequests` - Array of `RoleUpdateRequest` objects
|
|
303
318
|
|
|
@@ -316,6 +331,7 @@ const rolesResponse = await rolesService.deleteCustomRole(accountId, roleIds);
|
|
|
316
331
|
```
|
|
317
332
|
|
|
318
333
|
**Parameters:**
|
|
334
|
+
|
|
319
335
|
- `accountId` - The account ID
|
|
320
336
|
- `roleIds` - Array of custom role IDs to delete
|
|
321
337
|
|
|
@@ -376,3 +392,16 @@ interface RolesResponse {
|
|
|
376
392
|
basicRoles?: BasicRole[];
|
|
377
393
|
}
|
|
378
394
|
```
|
|
395
|
+
|
|
396
|
+
## Development
|
|
397
|
+
|
|
398
|
+
### Local Development and Testing
|
|
399
|
+
|
|
400
|
+
This package includes an `ignite-local-overrides.json` file for local development and testing only. It does **not** affect consumers of this package - they use their own Ignite configuration.
|
|
401
|
+
|
|
402
|
+
The file enables feature flags for testing:
|
|
403
|
+
|
|
404
|
+
- `sdk-platform-profiles`: Platform profile routing
|
|
405
|
+
- `navigate-can-action-in-scope-to-graph`: Graph API routing for `canActionInScope` methods
|
|
406
|
+
|
|
407
|
+
Modify this file for different local test scenarios, but remember changes only affect this package's development/testing.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authorization-service.d.ts","sourceRoot":"","sources":["../src/authorization-service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"authorization-service.d.ts","sourceRoot":"","sources":["../src/authorization-service.ts"],"names":[],"mappings":"AACA,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;AAe1C,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;AAMD,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;mBA6DnB,oBAAoB;mBAUpB,oBAAoB;CAoF1C;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"}
|
|
@@ -1,28 +1,22 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
2
|
|
|
3
3
|
const perf_hooks = require('perf_hooks');
|
|
4
|
-
const snakeCase = require('lodash/snakeCase.js');
|
|
5
|
-
const camelCase = require('lodash/camelCase.js');
|
|
6
|
-
const mapKeys = require('lodash/mapKeys.js');
|
|
7
4
|
const tridentBackendApi = require('@mondaydotcomorg/trident-backend-api');
|
|
8
5
|
const mondayFetchApi = require('@mondaydotcomorg/monday-fetch-api');
|
|
9
6
|
const igniteSdk = require('@mondaydotcomorg/ignite-sdk');
|
|
10
7
|
const prometheusService = require('./prometheus-service.js');
|
|
11
8
|
const authorizationInternalService = require('./authorization-internal-service.js');
|
|
12
9
|
const attributionsService = require('./attributions-service.js');
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
const snakeCase__default = /*#__PURE__*/_interopDefault(snakeCase);
|
|
17
|
-
const camelCase__default = /*#__PURE__*/_interopDefault(camelCase);
|
|
18
|
-
const mapKeys__default = /*#__PURE__*/_interopDefault(mapKeys);
|
|
10
|
+
const clients_graphApi_client = require('./clients/graph-api.client.js');
|
|
11
|
+
const clients_platformApi_client = require('./clients/platform-api.client.js');
|
|
12
|
+
const utils_authorization_utils = require('./utils/authorization.utils.js');
|
|
19
13
|
|
|
20
14
|
const GRANTED_FEATURE_CACHE_EXPIRATION_SECONDS = 5 * 60;
|
|
21
15
|
const PLATFORM_AUTHORIZE_PATH = '/internal_ms/authorization/authorize';
|
|
22
|
-
const PLATFORM_CAN_ACTIONS_IN_SCOPES_PATH = '/internal_ms/authorization/can_actions_in_scopes';
|
|
23
16
|
const ALLOWED_SDK_PLATFORM_PROFILES_KEY = 'allowed-sdk-platform-profiles';
|
|
24
17
|
const IN_RELEASE_SDK_PLATFORM_PROFILES_KEY = 'in-release-sdk-platform-profile';
|
|
25
18
|
const PLATFORM_PROFILE_RELEASE_FF = 'sdk-platform-profiles';
|
|
19
|
+
const NAVIGATE_CAN_ACTION_IN_SCOPE_TO_GRAPH_FF = 'navigate-can-action-in-scope-to-graph';
|
|
26
20
|
function setRequestFetchOptions(customMondayFetchOptions) {
|
|
27
21
|
authorizationInternalService.AuthorizationInternalService.setRequestFetchOptions(customMondayFetchOptions);
|
|
28
22
|
}
|
|
@@ -95,61 +89,49 @@ class AuthorizationService {
|
|
|
95
89
|
return attributionsService.PlatformProfile.INTERNAL;
|
|
96
90
|
}
|
|
97
91
|
static async canActionInScopeMultiple(accountId, userId, scopedActions) {
|
|
98
|
-
|
|
99
|
-
|
|
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 }
|
|
102
|
-
});
|
|
103
|
-
const attributionHeaders = attributionsService.getAttributionsFromApi();
|
|
104
|
-
const httpClient = tridentBackendApi.Api.getPart('httpClient');
|
|
105
|
-
let response;
|
|
106
|
-
try {
|
|
107
|
-
response = await httpClient.fetch({
|
|
108
|
-
url: {
|
|
109
|
-
appName: 'platform',
|
|
110
|
-
path: PLATFORM_CAN_ACTIONS_IN_SCOPES_PATH,
|
|
111
|
-
profile,
|
|
112
|
-
},
|
|
113
|
-
method: 'POST',
|
|
114
|
-
headers: {
|
|
115
|
-
Authorization: internalAuthToken,
|
|
116
|
-
'Content-Type': 'application/json',
|
|
117
|
-
...attributionHeaders,
|
|
118
|
-
},
|
|
119
|
-
body: JSON.stringify({
|
|
120
|
-
user_id: userId,
|
|
121
|
-
scoped_actions: scopedActionsPayload,
|
|
122
|
-
}),
|
|
123
|
-
}, {
|
|
124
|
-
timeout: authorizationInternalService.AuthorizationInternalService.getRequestTimeout(),
|
|
125
|
-
retryPolicy: authorizationInternalService.AuthorizationInternalService.getRetriesPolicy(),
|
|
126
|
-
});
|
|
92
|
+
if (scopedActions.length === 0) {
|
|
93
|
+
return [];
|
|
127
94
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
95
|
+
const shouldNavigateToGraph = Boolean(this.igniteClient?.isReleased(NAVIGATE_CAN_ACTION_IN_SCOPE_TO_GRAPH_FF, { accountId, userId }));
|
|
96
|
+
const internalAuthToken = authorizationInternalService.AuthorizationInternalService.generateInternalAuthToken(accountId, userId);
|
|
97
|
+
const startTime = perf_hooks.performance.now();
|
|
98
|
+
let scopedActionResponseObjects;
|
|
99
|
+
let apiType;
|
|
100
|
+
if (shouldNavigateToGraph) {
|
|
101
|
+
try {
|
|
102
|
+
scopedActionResponseObjects = await clients_graphApi_client.GraphApiClient.checkPermissions(internalAuthToken, scopedActions);
|
|
103
|
+
apiType = 'graph';
|
|
131
104
|
}
|
|
132
|
-
|
|
133
|
-
|
|
105
|
+
catch (error) {
|
|
106
|
+
const status = error instanceof mondayFetchApi.HttpFetcherError ? error.status : undefined;
|
|
107
|
+
authorizationInternalService.logger.warn({
|
|
108
|
+
tag: 'authorization-service',
|
|
109
|
+
error: error instanceof Error ? error.message : String(error),
|
|
110
|
+
accountId,
|
|
111
|
+
userId,
|
|
112
|
+
status,
|
|
113
|
+
}, 'Graph API authorization failed');
|
|
114
|
+
throw error;
|
|
134
115
|
}
|
|
135
116
|
}
|
|
136
|
-
|
|
137
|
-
|
|
117
|
+
else {
|
|
118
|
+
const profile = this.getProfile(accountId, userId);
|
|
119
|
+
scopedActionResponseObjects = await clients_platformApi_client.PlatformApiClient.checkPermissions(profile, internalAuthToken, userId, scopedActions);
|
|
120
|
+
apiType = 'platform';
|
|
138
121
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
122
|
+
const endTime = perf_hooks.performance.now();
|
|
123
|
+
const time = endTime - startTime;
|
|
124
|
+
// Record metrics for each authorization check
|
|
125
|
+
for (const obj of scopedActionResponseObjects) {
|
|
126
|
+
const { action, scope } = obj.scopedAction;
|
|
127
|
+
const { resourceType } = utils_authorization_utils.scopeToResource(scope);
|
|
128
|
+
const isAuthorized = obj.permit.can;
|
|
129
|
+
prometheusService.sendAuthorizationCheckResponseTimeMetric(resourceType, action, isAuthorized, 200, time, apiType);
|
|
130
|
+
if (obj.permit.can) {
|
|
131
|
+
prometheusService.incrementAuthorizationSuccess(resourceType, action, apiType);
|
|
132
|
+
}
|
|
142
133
|
}
|
|
143
|
-
|
|
144
|
-
const { scopedAction, permit } = responseObject;
|
|
145
|
-
const { scope } = scopedAction;
|
|
146
|
-
return {
|
|
147
|
-
...responseObject,
|
|
148
|
-
scopedAction: { ...scopedAction, scope: toCamelCase(scope) },
|
|
149
|
-
permit: toCamelCase(permit),
|
|
150
|
-
};
|
|
151
|
-
});
|
|
152
|
-
return scopedActionsResponseObjects;
|
|
134
|
+
return scopedActionResponseObjects;
|
|
153
135
|
}
|
|
154
136
|
static async isAuthorizedSingular(accountId, userId, resources, action) {
|
|
155
137
|
const { authorizationObjects } = createAuthorizationParams(resources, action);
|
|
@@ -185,7 +167,7 @@ class AuthorizationService {
|
|
|
185
167
|
});
|
|
186
168
|
}
|
|
187
169
|
catch (err) {
|
|
188
|
-
if (err instanceof
|
|
170
|
+
if (err instanceof mondayFetchApi.HttpFetcherError) {
|
|
189
171
|
authorizationInternalService.AuthorizationInternalService.throwOnHttpError(err.status, 'isAuthorizedMultiple');
|
|
190
172
|
}
|
|
191
173
|
else {
|
|
@@ -204,7 +186,7 @@ class AuthorizationService {
|
|
|
204
186
|
if (!isAuthorized) {
|
|
205
187
|
unauthorizedObjects.push(authorizationObject);
|
|
206
188
|
}
|
|
207
|
-
prometheusService.sendAuthorizationCheckResponseTimeMetric(authorizationObject.resource_type, authorizationObject.action, isAuthorized, 200, time);
|
|
189
|
+
prometheusService.sendAuthorizationCheckResponseTimeMetric(authorizationObject.resource_type, authorizationObject.action, isAuthorized, 200, time, 'platform');
|
|
208
190
|
});
|
|
209
191
|
if (unauthorizedObjects.length > 0) {
|
|
210
192
|
authorizationInternalService.logger.info({
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ScopedAction, ScopedActionResponseObject } from '../types/scoped-actions-contracts';
|
|
2
|
+
import { GraphIsAllowedDto, GraphIsAllowedResponse } from '../types/graph-api.types';
|
|
3
|
+
/**
|
|
4
|
+
* Client for handling Graph API authorization operations
|
|
5
|
+
*/
|
|
6
|
+
export declare class GraphApiClient {
|
|
7
|
+
/**
|
|
8
|
+
* Builds the request body for Graph API calls
|
|
9
|
+
*/
|
|
10
|
+
static buildRequestBody(scopedActions: ScopedAction[]): GraphIsAllowedDto;
|
|
11
|
+
/**
|
|
12
|
+
* Fetches authorization data from the Graph API
|
|
13
|
+
*/
|
|
14
|
+
static fetchPermissions(internalAuthToken: string, scopedActions: ScopedAction[]): Promise<GraphIsAllowedResponse>;
|
|
15
|
+
/**
|
|
16
|
+
* Maps Graph API response to the expected format
|
|
17
|
+
*/
|
|
18
|
+
static mapResponse(scopedActions: ScopedAction[], graphResponse: GraphIsAllowedResponse): ScopedActionResponseObject[];
|
|
19
|
+
/**
|
|
20
|
+
* Performs a complete authorization check using the Graph API
|
|
21
|
+
*/
|
|
22
|
+
static checkPermissions(internalAuthToken: string, scopedActions: ScopedAction[]): Promise<ScopedActionResponseObject[]>;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=graph-api.client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-api.client.d.ts","sourceRoot":"","sources":["../../src/clients/graph-api.client.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,YAAY,EACZ,0BAA0B,EAG3B,MAAM,mCAAmC,CAAC;AAG3C,OAAO,EACL,iBAAiB,EACjB,sBAAsB,EAIvB,MAAM,0BAA0B,CAAC;AAMlC;;GAEG;AACH,qBAAa,cAAc;IACzB;;OAEG;IACH,MAAM,CAAC,gBAAgB,CAAC,aAAa,EAAE,YAAY,EAAE,GAAG,iBAAiB;IAyBzE;;OAEG;WACU,gBAAgB,CAC3B,iBAAiB,EAAE,MAAM,EACzB,aAAa,EAAE,YAAY,EAAE,GAC5B,OAAO,CAAC,sBAAsB,CAAC;IA2ClC;;OAEG;IACH,MAAM,CAAC,WAAW,CAChB,aAAa,EAAE,YAAY,EAAE,EAC7B,aAAa,EAAE,sBAAsB,GACpC,0BAA0B,EAAE;IAsC/B;;OAEG;WACU,gBAAgB,CAC3B,iBAAiB,EAAE,MAAM,EACzB,aAAa,EAAE,YAAY,EAAE,GAC5B,OAAO,CAAC,0BAA0B,EAAE,CAAC;CAIzC"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
|
|
3
|
+
const tridentBackendApi = require('@mondaydotcomorg/trident-backend-api');
|
|
4
|
+
const mondayFetchApi = require('@mondaydotcomorg/monday-fetch-api');
|
|
5
|
+
const types_scopedActionsContracts = require('../types/scoped-actions-contracts.js');
|
|
6
|
+
const authorizationInternalService = require('../authorization-internal-service.js');
|
|
7
|
+
const attributionsService = require('../attributions-service.js');
|
|
8
|
+
const utils_authorization_utils = require('../utils/authorization.utils.js');
|
|
9
|
+
const prometheusService = require('../prometheus-service.js');
|
|
10
|
+
|
|
11
|
+
const CAN_ACTION_IN_SCOPE_GRAPH_PATH = '/permissions/is-allowed';
|
|
12
|
+
/**
|
|
13
|
+
* Client for handling Graph API authorization operations
|
|
14
|
+
*/
|
|
15
|
+
class GraphApiClient {
|
|
16
|
+
/**
|
|
17
|
+
* Builds the request body for Graph API calls
|
|
18
|
+
*/
|
|
19
|
+
static buildRequestBody(scopedActions) {
|
|
20
|
+
const resourcesAccumulator = {};
|
|
21
|
+
for (const { action, scope } of scopedActions) {
|
|
22
|
+
const { resourceType, resourceId } = utils_authorization_utils.scopeToResource(scope);
|
|
23
|
+
if (!resourcesAccumulator[resourceType]) {
|
|
24
|
+
resourcesAccumulator[resourceType] = {};
|
|
25
|
+
}
|
|
26
|
+
if (!resourcesAccumulator[resourceType][resourceId]) {
|
|
27
|
+
resourcesAccumulator[resourceType][resourceId] = new Set();
|
|
28
|
+
}
|
|
29
|
+
resourcesAccumulator[resourceType][resourceId].add(action);
|
|
30
|
+
}
|
|
31
|
+
const resourcesPayload = {};
|
|
32
|
+
for (const [resourceType, idMap] of Object.entries(resourcesAccumulator)) {
|
|
33
|
+
resourcesPayload[resourceType] = {};
|
|
34
|
+
for (const [idStr, actionsSet] of Object.entries(idMap)) {
|
|
35
|
+
const idNum = Number(idStr);
|
|
36
|
+
resourcesPayload[resourceType][idNum] = Array.from(actionsSet);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return resourcesPayload;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Fetches authorization data from the Graph API
|
|
43
|
+
*/
|
|
44
|
+
static async fetchPermissions(internalAuthToken, scopedActions) {
|
|
45
|
+
const httpClient = tridentBackendApi.Api.getPart('httpClient');
|
|
46
|
+
const attributionHeaders = attributionsService.getAttributionsFromApi();
|
|
47
|
+
const bodyPayload = this.buildRequestBody(scopedActions);
|
|
48
|
+
try {
|
|
49
|
+
const response = await httpClient.fetch({
|
|
50
|
+
url: {
|
|
51
|
+
appName: 'authorization-graph',
|
|
52
|
+
path: CAN_ACTION_IN_SCOPE_GRAPH_PATH,
|
|
53
|
+
},
|
|
54
|
+
method: 'POST',
|
|
55
|
+
headers: {
|
|
56
|
+
Authorization: internalAuthToken,
|
|
57
|
+
'Content-Type': 'application/json',
|
|
58
|
+
...attributionHeaders,
|
|
59
|
+
},
|
|
60
|
+
body: JSON.stringify(bodyPayload),
|
|
61
|
+
}, {
|
|
62
|
+
timeout: authorizationInternalService.AuthorizationInternalService.getRequestTimeout(),
|
|
63
|
+
retryPolicy: authorizationInternalService.AuthorizationInternalService.getRetriesPolicy(),
|
|
64
|
+
});
|
|
65
|
+
return response;
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
if (err instanceof mondayFetchApi.HttpFetcherError) {
|
|
69
|
+
authorizationInternalService.AuthorizationInternalService.throwOnHttpError(err.status, 'canActionInScopeMultiple');
|
|
70
|
+
if (scopedActions.length > 0) {
|
|
71
|
+
prometheusService.incrementAuthorizationError(utils_authorization_utils.scopeToResource(scopedActions[0].scope).resourceType, scopedActions[0].action, err.status, 'graph');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
throw err;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Maps Graph API response to the expected format
|
|
79
|
+
*/
|
|
80
|
+
static mapResponse(scopedActions, graphResponse) {
|
|
81
|
+
const resources = graphResponse ?? {};
|
|
82
|
+
return scopedActions.map(scopedAction => {
|
|
83
|
+
const { action, scope } = scopedAction;
|
|
84
|
+
const { resourceType, resourceId } = utils_authorization_utils.scopeToResource(scope);
|
|
85
|
+
const permissionResult = resources?.[resourceType]?.[String(resourceId)]?.[action];
|
|
86
|
+
const graphReason = permissionResult?.reason;
|
|
87
|
+
let reasonKey;
|
|
88
|
+
let additionalOptions = {};
|
|
89
|
+
let technicalReason = types_scopedActionsContracts.PermitTechnicalReason.NO_REASON;
|
|
90
|
+
if (typeof graphReason === 'string') {
|
|
91
|
+
reasonKey = graphReason;
|
|
92
|
+
}
|
|
93
|
+
else if (graphReason && typeof graphReason === 'object') {
|
|
94
|
+
reasonKey = graphReason.key ?? 'unknown';
|
|
95
|
+
additionalOptions = graphReason.additionalOptions ?? {};
|
|
96
|
+
if (graphReason.technicalReason !== undefined) {
|
|
97
|
+
technicalReason = (graphReason.technicalReason ?? types_scopedActionsContracts.PermitTechnicalReason.NO_REASON);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
reasonKey = 'unknown';
|
|
102
|
+
}
|
|
103
|
+
const permit = {
|
|
104
|
+
can: permissionResult?.can ?? false,
|
|
105
|
+
reason: {
|
|
106
|
+
key: reasonKey,
|
|
107
|
+
...additionalOptions,
|
|
108
|
+
},
|
|
109
|
+
technicalReason,
|
|
110
|
+
};
|
|
111
|
+
return { scopedAction, permit };
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Performs a complete authorization check using the Graph API
|
|
116
|
+
*/
|
|
117
|
+
static async checkPermissions(internalAuthToken, scopedActions) {
|
|
118
|
+
const response = await this.fetchPermissions(internalAuthToken, scopedActions);
|
|
119
|
+
return this.mapResponse(scopedActions, response);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
exports.GraphApiClient = GraphApiClient;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ScopedAction, ScopedActionResponseObject } from '../types/scoped-actions-contracts';
|
|
2
|
+
import { PlatformProfile } from '../attributions-service';
|
|
3
|
+
type ScopedActionPlatformPayload = Omit<ScopedAction, 'scope'> & {
|
|
4
|
+
scope: Record<string, number>;
|
|
5
|
+
};
|
|
6
|
+
interface CanActionsInScopesResponse {
|
|
7
|
+
result: ScopedActionResponseObject[];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Client for handling Platform API authorization operations
|
|
11
|
+
*/
|
|
12
|
+
export declare class PlatformApiClient {
|
|
13
|
+
/**
|
|
14
|
+
* Builds the request payload for Platform API calls
|
|
15
|
+
*/
|
|
16
|
+
static buildRequestPayload(scopedActions: ScopedAction[]): ScopedActionPlatformPayload[];
|
|
17
|
+
/**
|
|
18
|
+
* Fetches authorization data from the Platform API
|
|
19
|
+
*/
|
|
20
|
+
static fetchPermissions(profile: PlatformProfile, internalAuthToken: string, userId: number, scopedActionsPayload: ScopedActionPlatformPayload[]): Promise<CanActionsInScopesResponse>;
|
|
21
|
+
/**
|
|
22
|
+
* Maps Platform API response to the expected format
|
|
23
|
+
*/
|
|
24
|
+
static mapResponse(response: CanActionsInScopesResponse): ScopedActionResponseObject[];
|
|
25
|
+
/**
|
|
26
|
+
* Performs a complete authorization check using the Platform API
|
|
27
|
+
*/
|
|
28
|
+
static checkPermissions(profile: PlatformProfile, internalAuthToken: string, userId: number, scopedActions: ScopedAction[]): Promise<ScopedActionResponseObject[]>;
|
|
29
|
+
}
|
|
30
|
+
export {};
|
|
31
|
+
//# sourceMappingURL=platform-api.client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform-api.client.d.ts","sourceRoot":"","sources":["../../src/clients/platform-api.client.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,0BAA0B,EAAE,MAAM,mCAAmC,CAAC;AAE7F,OAAO,EAA0B,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAOlF,KAAK,2BAA2B,GAAG,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC,GAAG;IAC/D,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/B,CAAC;AAEF,UAAU,0BAA0B;IAClC,MAAM,EAAE,0BAA0B,EAAE,CAAC;CACtC;AAED;;GAEG;AACH,qBAAa,iBAAiB;IAC5B;;OAEG;IACH,MAAM,CAAC,mBAAmB,CAAC,aAAa,EAAE,YAAY,EAAE,GAAG,2BAA2B,EAAE;IAOxF;;OAEG;WACU,gBAAgB,CAC3B,OAAO,EAAE,eAAe,EACxB,iBAAiB,EAAE,MAAM,EACzB,MAAM,EAAE,MAAM,EACd,oBAAoB,EAAE,2BAA2B,EAAE,GAClD,OAAO,CAAC,0BAA0B,CAAC;IAuCtC;;OAEG;IACH,MAAM,CAAC,WAAW,CAAC,QAAQ,EAAE,0BAA0B,GAAG,0BAA0B,EAAE;IAkBtF;;OAEG;WACU,gBAAgB,CAC3B,OAAO,EAAE,eAAe,EACxB,iBAAiB,EAAE,MAAM,EACzB,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,YAAY,EAAE,GAC5B,OAAO,CAAC,0BAA0B,EAAE,CAAC;CAKzC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
|
|
3
|
+
const tridentBackendApi = require('@mondaydotcomorg/trident-backend-api');
|
|
4
|
+
const mondayFetchApi = require('@mondaydotcomorg/monday-fetch-api');
|
|
5
|
+
const authorizationInternalService = require('../authorization-internal-service.js');
|
|
6
|
+
const attributionsService = require('../attributions-service.js');
|
|
7
|
+
const utils_authorization_utils = require('../utils/authorization.utils.js');
|
|
8
|
+
const prometheusService = require('../prometheus-service.js');
|
|
9
|
+
|
|
10
|
+
const PLATFORM_CAN_ACTIONS_IN_SCOPES_PATH = '/internal_ms/authorization/can_actions_in_scopes';
|
|
11
|
+
/**
|
|
12
|
+
* Client for handling Platform API authorization operations
|
|
13
|
+
*/
|
|
14
|
+
class PlatformApiClient {
|
|
15
|
+
/**
|
|
16
|
+
* Builds the request payload for Platform API calls
|
|
17
|
+
*/
|
|
18
|
+
static buildRequestPayload(scopedActions) {
|
|
19
|
+
return scopedActions.map(scopedAction => ({
|
|
20
|
+
...scopedAction,
|
|
21
|
+
scope: utils_authorization_utils.toSnakeCase(scopedAction.scope),
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Fetches authorization data from the Platform API
|
|
26
|
+
*/
|
|
27
|
+
static async fetchPermissions(profile, internalAuthToken, userId, scopedActionsPayload) {
|
|
28
|
+
const attributionHeaders = attributionsService.getAttributionsFromApi();
|
|
29
|
+
const httpClient = tridentBackendApi.Api.getPart('httpClient');
|
|
30
|
+
try {
|
|
31
|
+
const response = await httpClient.fetch({
|
|
32
|
+
url: {
|
|
33
|
+
appName: 'platform',
|
|
34
|
+
path: PLATFORM_CAN_ACTIONS_IN_SCOPES_PATH,
|
|
35
|
+
profile,
|
|
36
|
+
},
|
|
37
|
+
method: 'POST',
|
|
38
|
+
headers: {
|
|
39
|
+
Authorization: internalAuthToken,
|
|
40
|
+
'Content-Type': 'application/json',
|
|
41
|
+
...attributionHeaders,
|
|
42
|
+
},
|
|
43
|
+
body: JSON.stringify({ user_id: userId, scoped_actions: scopedActionsPayload }),
|
|
44
|
+
}, {
|
|
45
|
+
timeout: authorizationInternalService.AuthorizationInternalService.getRequestTimeout(),
|
|
46
|
+
retryPolicy: authorizationInternalService.AuthorizationInternalService.getRetriesPolicy(),
|
|
47
|
+
});
|
|
48
|
+
return response;
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
if (err instanceof mondayFetchApi.HttpFetcherError) {
|
|
52
|
+
authorizationInternalService.AuthorizationInternalService.throwOnHttpError(err.status, 'canActionInScopeMultiple');
|
|
53
|
+
if (scopedActionsPayload.length > 0) {
|
|
54
|
+
const { resourceType } = utils_authorization_utils.scopeToResource(utils_authorization_utils.toCamelCase(scopedActionsPayload[0].scope));
|
|
55
|
+
prometheusService.incrementAuthorizationError(resourceType, scopedActionsPayload[0].action, err.status, 'platform');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
throw err;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Maps Platform API response to the expected format
|
|
63
|
+
*/
|
|
64
|
+
static mapResponse(response) {
|
|
65
|
+
if (!response) {
|
|
66
|
+
authorizationInternalService.logger.error({ tag: 'platform-api-client', response }, 'PlatformApiClient: missing response');
|
|
67
|
+
throw new Error('PlatformApiClient: missing response');
|
|
68
|
+
}
|
|
69
|
+
return response.result.map(responseObject => {
|
|
70
|
+
const { scopedAction, permit } = responseObject;
|
|
71
|
+
const { scope } = scopedAction;
|
|
72
|
+
return {
|
|
73
|
+
...responseObject,
|
|
74
|
+
scopedAction: { ...scopedAction, scope: utils_authorization_utils.toCamelCase(scope) },
|
|
75
|
+
permit: utils_authorization_utils.toCamelCase(permit),
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Performs a complete authorization check using the Platform API
|
|
81
|
+
*/
|
|
82
|
+
static async checkPermissions(profile, internalAuthToken, userId, scopedActions) {
|
|
83
|
+
const scopedActionsPayload = this.buildRequestPayload(scopedActions);
|
|
84
|
+
const platformResponse = await this.fetchPermissions(profile, internalAuthToken, userId, scopedActionsPayload);
|
|
85
|
+
return this.mapResponse(platformResponse);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
exports.PlatformApiClient = PlatformApiClient;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authorization-service.d.ts","sourceRoot":"","sources":["../../src/authorization-service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"authorization-service.d.ts","sourceRoot":"","sources":["../../src/authorization-service.ts"],"names":[],"mappings":"AACA,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;AAe1C,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;AAMD,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;mBA6DnB,oBAAoB;mBAUpB,oBAAoB;CAoF1C;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"}
|