@mondaydotcomorg/monday-authorization 3.3.0-feature-bashanye-navigate-can-action-in-scope-to-graph-9e68432 → 3.3.0-feature-bashanye-navigate-can-action-in-scope-to-graph-7ce3f8a

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.
Files changed (37) hide show
  1. package/dist/authorization-service.d.ts +0 -8
  2. package/dist/authorization-service.d.ts.map +1 -1
  3. package/dist/authorization-service.js +12 -160
  4. package/dist/clients/graph-api.client.d.ts +24 -0
  5. package/dist/clients/graph-api.client.d.ts.map +1 -0
  6. package/dist/clients/graph-api.client.js +102 -0
  7. package/dist/clients/platform-api.client.d.ts +31 -0
  8. package/dist/clients/platform-api.client.d.ts.map +1 -0
  9. package/dist/clients/platform-api.client.js +86 -0
  10. package/dist/esm/authorization-service.d.ts +0 -8
  11. package/dist/esm/authorization-service.d.ts.map +1 -1
  12. package/dist/esm/authorization-service.mjs +13 -155
  13. package/dist/esm/clients/graph-api.client.d.ts +24 -0
  14. package/dist/esm/clients/graph-api.client.d.ts.map +1 -0
  15. package/dist/esm/clients/graph-api.client.mjs +100 -0
  16. package/dist/esm/clients/platform-api.client.d.ts +31 -0
  17. package/dist/esm/clients/platform-api.client.d.ts.map +1 -0
  18. package/dist/esm/clients/platform-api.client.mjs +84 -0
  19. package/dist/esm/prometheus-service.d.ts +3 -3
  20. package/dist/esm/prometheus-service.d.ts.map +1 -1
  21. package/dist/esm/prometheus-service.mjs +65 -3
  22. package/dist/esm/types/graph-api.types.d.ts +11 -0
  23. package/dist/esm/types/graph-api.types.d.ts.map +1 -0
  24. package/dist/esm/types/graph-api.types.mjs +1 -0
  25. package/dist/esm/utils/authorization.utils.d.ts +22 -0
  26. package/dist/esm/utils/authorization.utils.d.ts.map +1 -0
  27. package/dist/esm/utils/authorization.utils.mjs +39 -0
  28. package/dist/prometheus-service.d.ts +3 -3
  29. package/dist/prometheus-service.d.ts.map +1 -1
  30. package/dist/prometheus-service.js +65 -3
  31. package/dist/types/graph-api.types.d.ts +11 -0
  32. package/dist/types/graph-api.types.d.ts.map +1 -0
  33. package/dist/types/graph-api.types.js +1 -0
  34. package/dist/utils/authorization.utils.d.ts +22 -0
  35. package/dist/utils/authorization.utils.d.ts.map +1 -0
  36. package/dist/utils/authorization.utils.js +49 -0
  37. package/package.json +1 -1
@@ -1,18 +1,16 @@
1
1
  import { performance } from 'perf_hooks';
2
- import snakeCase from 'lodash/snakeCase.js';
3
- import camelCase from 'lodash/camelCase.js';
4
- import mapKeys from 'lodash/mapKeys.js';
5
2
  import { Api } from '@mondaydotcomorg/trident-backend-api';
6
3
  import { HttpFetcherError } from '@mondaydotcomorg/monday-fetch-api';
7
4
  import { getIgniteClient } from '@mondaydotcomorg/ignite-sdk';
8
- import { sendAuthorizationCheckResponseTimeMetric, setGraphAvailability, incrementAuthorizationError } from './prometheus-service.mjs';
5
+ import { sendAuthorizationCheckResponseTimeMetric, incrementAuthorizationSuccess } from './prometheus-service.mjs';
9
6
  import { AuthorizationInternalService, logger } from './authorization-internal-service.mjs';
10
7
  import { getProfile, PlatformProfile, getAttributionsFromApi } from './attributions-service.mjs';
8
+ import { GraphApiClient } from './clients/graph-api.client.mjs';
9
+ import { PlatformApiClient } from './clients/platform-api.client.mjs';
10
+ import { scopeToResource } from './utils/authorization.utils.mjs';
11
11
 
12
12
  const GRANTED_FEATURE_CACHE_EXPIRATION_SECONDS = 5 * 60;
13
13
  const PLATFORM_AUTHORIZE_PATH = '/internal_ms/authorization/authorize';
14
- const PLATFORM_CAN_ACTIONS_IN_SCOPES_PATH = '/internal_ms/authorization/can_actions_in_scopes';
15
- const CAN_ACTION_IN_SCOPE_GRAPH_PATH = '/permissions/is-allowed';
16
14
  const ALLOWED_SDK_PLATFORM_PROFILES_KEY = 'allowed-sdk-platform-profiles';
17
15
  const IN_RELEASE_SDK_PLATFORM_PROFILES_KEY = 'in-release-sdk-platform-profile';
18
16
  const PLATFORM_PROFILE_RELEASE_FF = 'sdk-platform-profiles';
@@ -94,166 +92,26 @@ class AuthorizationService {
94
92
  const startTime = performance.now();
95
93
  let scopedActionResponseObjects;
96
94
  if (shouldNavigateToGraph) {
97
- const response = await this.fetchGraphIsAllowed(internalAuthToken, scopedActions);
98
- scopedActionResponseObjects = this.mapGraphResponse(scopedActions, userId, response);
95
+ scopedActionResponseObjects = await GraphApiClient.checkPermissions(internalAuthToken, scopedActions);
99
96
  }
100
97
  else {
101
98
  const profile = this.getProfile(accountId, userId);
102
- const scopedActionsPayload = this.buildScopedActionsPayload(scopedActions);
103
- const platformResponse = await this.fetchPlatformCanActions(profile, internalAuthToken, userId, scopedActionsPayload);
104
- scopedActionResponseObjects = this.mapPlatformResponse(platformResponse);
99
+ scopedActionResponseObjects = await PlatformApiClient.checkPermissions(profile, internalAuthToken, userId, scopedActions);
105
100
  }
106
101
  const endTime = performance.now();
107
102
  const time = endTime - startTime;
108
103
  const apiType = shouldNavigateToGraph ? 'graph' : 'platform';
104
+ // Record metrics for each authorization check
109
105
  for (const obj of scopedActionResponseObjects) {
110
- const { action } = obj.scopedAction;
111
- const resource_type = this.scopeToResource(obj.scopedAction.scope).resourceType;
106
+ const { action, scope } = obj.scopedAction;
107
+ const { resourceType } = scopeToResource(scope);
112
108
  const isAuthorized = obj.permit.can;
113
- sendAuthorizationCheckResponseTimeMetric(resource_type, action, isAuthorized, 200, time, apiType);
114
- if (obj.permit.can) ;
115
- }
116
- return scopedActionResponseObjects;
117
- }
118
- static buildScopedActionsPayload(scopedActions) {
119
- return scopedActions.map(scopedAction => {
120
- return { ...scopedAction, scope: mapKeys(scopedAction.scope, (_, key) => snakeCase(key)) };
121
- });
122
- }
123
- static scopeToResource(scope) {
124
- if ('workspaceId' in scope) {
125
- return { resourceType: 'workspace', resourceId: scope.workspaceId };
126
- }
127
- if ('boardId' in scope) {
128
- return { resourceType: 'board', resourceId: scope.boardId };
129
- }
130
- if ('pulseId' in scope) {
131
- return { resourceType: 'pulse', resourceId: scope.pulseId };
132
- }
133
- if ('accountProductId' in scope) {
134
- return { resourceType: 'account_product', resourceId: scope.accountProductId };
135
- }
136
- if ('accountId' in scope) {
137
- return { resourceType: 'account', resourceId: scope.accountId };
138
- }
139
- throw new Error('Unsupported scope provided');
140
- }
141
- static buildGraphRequestBody(scopedActions) {
142
- const resourcesAccumulator = {};
143
- for (const { action, scope } of scopedActions) {
144
- const { resourceType, resourceId } = this.scopeToResource(scope);
145
- if (!resourcesAccumulator[resourceType]) {
146
- resourcesAccumulator[resourceType] = {};
147
- }
148
- if (!resourcesAccumulator[resourceType][resourceId]) {
149
- resourcesAccumulator[resourceType][resourceId] = new Set();
150
- }
151
- resourcesAccumulator[resourceType][resourceId].add(action);
152
- }
153
- const resourcesPayload = {};
154
- for (const [resourceType, idMap] of Object.entries(resourcesAccumulator)) {
155
- resourcesPayload[resourceType] = {};
156
- for (const [idStr, actionsSet] of Object.entries(idMap)) {
157
- const idNum = Number(idStr);
158
- resourcesPayload[resourceType][idNum] = Array.from(actionsSet);
159
- }
160
- }
161
- return resourcesPayload;
162
- }
163
- static async fetchGraphIsAllowed(internalAuthToken, scopedActions) {
164
- const httpClient = Api.getPart('httpClient');
165
- const attributionHeaders = getAttributionsFromApi();
166
- const bodyPayload = this.buildGraphRequestBody(scopedActions);
167
- try {
168
- const response = await httpClient.fetch({
169
- url: {
170
- appName: 'authorization-graph',
171
- path: CAN_ACTION_IN_SCOPE_GRAPH_PATH,
172
- },
173
- method: 'POST',
174
- headers: {
175
- Authorization: internalAuthToken,
176
- 'Content-Type': 'application/json',
177
- ...attributionHeaders,
178
- },
179
- body: JSON.stringify(bodyPayload),
180
- }, {
181
- timeout: AuthorizationInternalService.getRequestTimeout(),
182
- retryPolicy: AuthorizationInternalService.getRetriesPolicy(),
183
- });
184
- setGraphAvailability(true);
185
- return response;
186
- }
187
- catch (err) {
188
- if (err instanceof HttpFetcherError) {
189
- AuthorizationInternalService.throwOnHttpError(err.status, 'canActionInScopeMultiple');
190
- incrementAuthorizationError(this.scopeToResource(scopedActions[0].scope).resourceType, scopedActions[0].action, err.status);
109
+ sendAuthorizationCheckResponseTimeMetric(resourceType, action, isAuthorized, 200, time, apiType);
110
+ if (obj.permit.can) {
111
+ incrementAuthorizationSuccess(resourceType, action);
191
112
  }
192
- throw err;
193
113
  }
194
- }
195
- static mapGraphResponse(scopedActions, userId, graphResponse) {
196
- const resources = graphResponse ?? {};
197
- return scopedActions.map(scopedAction => {
198
- const { action, scope } = scopedAction;
199
- const { resourceType, resourceId } = this.scopeToResource(scope);
200
- const permissionResult = resources?.[resourceType]?.[String(resourceId)]?.[action];
201
- const permit = {
202
- can: permissionResult?.can ?? false,
203
- reason: { key: permissionResult?.reason ?? 'unknown' },
204
- technicalReason: 0,
205
- };
206
- return { scopedAction, permit };
207
- });
208
- }
209
- static async fetchPlatformCanActions(profile, internalAuthToken, userId, scopedActionsPayload) {
210
- const attributionHeaders = getAttributionsFromApi();
211
- const httpClient = Api.getPart('httpClient');
212
- try {
213
- const response = await httpClient.fetch({
214
- url: {
215
- appName: 'platform',
216
- path: PLATFORM_CAN_ACTIONS_IN_SCOPES_PATH,
217
- profile,
218
- },
219
- method: 'POST',
220
- headers: {
221
- Authorization: internalAuthToken,
222
- 'Content-Type': 'application/json',
223
- ...attributionHeaders,
224
- },
225
- body: JSON.stringify({ user_id: userId, scoped_actions: scopedActionsPayload }),
226
- }, {
227
- timeout: AuthorizationInternalService.getRequestTimeout(),
228
- retryPolicy: AuthorizationInternalService.getRetriesPolicy(),
229
- });
230
- return response;
231
- }
232
- catch (err) {
233
- if (err instanceof HttpFetcherError) {
234
- AuthorizationInternalService.throwOnHttpError(err.status, 'canActionInScopeMultiple');
235
- incrementAuthorizationError(this.scopeToResource(this.toCamelCase(scopedActionsPayload[0].scope)).resourceType, scopedActionsPayload[0].action, err.status);
236
- }
237
- throw err;
238
- }
239
- }
240
- static toCamelCase(obj) {
241
- return mapKeys(obj, (_, key) => camelCase(key));
242
- }
243
- static mapPlatformResponse(response) {
244
- if (!response) {
245
- logger.error({ tag: 'authorization-service', response }, 'AuthorizationService: missing response');
246
- throw new Error('AuthorizationService: missing response');
247
- }
248
- return response.result.map(responseObject => {
249
- const { scopedAction, permit } = responseObject;
250
- const { scope } = scopedAction;
251
- return {
252
- ...responseObject,
253
- scopedAction: { ...scopedAction, scope: this.toCamelCase(scope) },
254
- permit: this.toCamelCase(permit),
255
- };
256
- });
114
+ return scopedActionResponseObjects;
257
115
  }
258
116
  static async isAuthorizedSingular(accountId, userId, resources, action) {
259
117
  const { authorizationObjects } = createAuthorizationParams(resources, action);
@@ -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,EAAE,YAAY,EAAE,0BAA0B,EAAsB,MAAM,mCAAmC,CAAC;AAGjH,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;IAyClC;;OAEG;IACH,MAAM,CAAC,WAAW,CAChB,aAAa,EAAE,YAAY,EAAE,EAC7B,aAAa,EAAE,sBAAsB,GACpC,0BAA0B,EAAE;IAkB/B;;OAEG;WACU,gBAAgB,CAC3B,iBAAiB,EAAE,MAAM,EACzB,aAAa,EAAE,YAAY,EAAE,GAC5B,OAAO,CAAC,0BAA0B,EAAE,CAAC;CAIzC"}
@@ -0,0 +1,100 @@
1
+ import { Api } from '@mondaydotcomorg/trident-backend-api';
2
+ import { HttpFetcherError } from '@mondaydotcomorg/monday-fetch-api';
3
+ import { AuthorizationInternalService } from '../authorization-internal-service.mjs';
4
+ import { getAttributionsFromApi } from '../attributions-service.mjs';
5
+ import { scopeToResource } from '../utils/authorization.utils.mjs';
6
+ import { setGraphAvailability, incrementAuthorizationError } from '../prometheus-service.mjs';
7
+
8
+ const CAN_ACTION_IN_SCOPE_GRAPH_PATH = '/permissions/is-allowed';
9
+ /**
10
+ * Client for handling Graph API authorization operations
11
+ */
12
+ class GraphApiClient {
13
+ /**
14
+ * Builds the request body for Graph API calls
15
+ */
16
+ static buildRequestBody(scopedActions) {
17
+ const resourcesAccumulator = {};
18
+ for (const { action, scope } of scopedActions) {
19
+ const { resourceType, resourceId } = scopeToResource(scope);
20
+ if (!resourcesAccumulator[resourceType]) {
21
+ resourcesAccumulator[resourceType] = {};
22
+ }
23
+ if (!resourcesAccumulator[resourceType][resourceId]) {
24
+ resourcesAccumulator[resourceType][resourceId] = new Set();
25
+ }
26
+ resourcesAccumulator[resourceType][resourceId].add(action);
27
+ }
28
+ const resourcesPayload = {};
29
+ for (const [resourceType, idMap] of Object.entries(resourcesAccumulator)) {
30
+ resourcesPayload[resourceType] = {};
31
+ for (const [idStr, actionsSet] of Object.entries(idMap)) {
32
+ const idNum = Number(idStr);
33
+ resourcesPayload[resourceType][idNum] = Array.from(actionsSet);
34
+ }
35
+ }
36
+ return resourcesPayload;
37
+ }
38
+ /**
39
+ * Fetches authorization data from the Graph API
40
+ */
41
+ static async fetchPermissions(internalAuthToken, scopedActions) {
42
+ const httpClient = Api.getPart('httpClient');
43
+ const attributionHeaders = getAttributionsFromApi();
44
+ const bodyPayload = this.buildRequestBody(scopedActions);
45
+ try {
46
+ const response = await httpClient.fetch({
47
+ url: {
48
+ appName: 'authorization-graph',
49
+ path: CAN_ACTION_IN_SCOPE_GRAPH_PATH,
50
+ },
51
+ method: 'POST',
52
+ headers: {
53
+ Authorization: internalAuthToken,
54
+ 'Content-Type': 'application/json',
55
+ ...attributionHeaders,
56
+ },
57
+ body: JSON.stringify(bodyPayload),
58
+ }, {
59
+ timeout: AuthorizationInternalService.getRequestTimeout(),
60
+ retryPolicy: AuthorizationInternalService.getRetriesPolicy(),
61
+ });
62
+ setGraphAvailability(true);
63
+ return response;
64
+ }
65
+ catch (err) {
66
+ setGraphAvailability(false);
67
+ if (err instanceof HttpFetcherError) {
68
+ AuthorizationInternalService.throwOnHttpError(err.status, 'canActionInScopeMultiple');
69
+ incrementAuthorizationError(scopeToResource(scopedActions[0].scope).resourceType, scopedActions[0].action, err.status);
70
+ }
71
+ throw err;
72
+ }
73
+ }
74
+ /**
75
+ * Maps Graph API response to the expected format
76
+ */
77
+ static mapResponse(scopedActions, graphResponse) {
78
+ const resources = graphResponse ?? {};
79
+ return scopedActions.map(scopedAction => {
80
+ const { action, scope } = scopedAction;
81
+ const { resourceType, resourceId } = scopeToResource(scope);
82
+ const permissionResult = resources?.[resourceType]?.[String(resourceId)]?.[action];
83
+ const permit = {
84
+ can: permissionResult?.can ?? false,
85
+ reason: { key: permissionResult?.reason ?? 'unknown' },
86
+ technicalReason: 0,
87
+ };
88
+ return { scopedAction, permit };
89
+ });
90
+ }
91
+ /**
92
+ * Performs a complete authorization check using the Graph API
93
+ */
94
+ static async checkPermissions(internalAuthToken, scopedActions) {
95
+ const response = await this.fetchPermissions(internalAuthToken, scopedActions);
96
+ return this.mapResponse(scopedActions, response);
97
+ }
98
+ }
99
+
100
+ export { 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,84 @@
1
+ import { Api } from '@mondaydotcomorg/trident-backend-api';
2
+ import { HttpFetcherError } from '@mondaydotcomorg/monday-fetch-api';
3
+ import { AuthorizationInternalService, logger } from '../authorization-internal-service.mjs';
4
+ import { getAttributionsFromApi } from '../attributions-service.mjs';
5
+ import { toSnakeCase, scopeToResource, toCamelCase } from '../utils/authorization.utils.mjs';
6
+ import { incrementAuthorizationError } from '../prometheus-service.mjs';
7
+
8
+ const PLATFORM_CAN_ACTIONS_IN_SCOPES_PATH = '/internal_ms/authorization/can_actions_in_scopes';
9
+ /**
10
+ * Client for handling Platform API authorization operations
11
+ */
12
+ class PlatformApiClient {
13
+ /**
14
+ * Builds the request payload for Platform API calls
15
+ */
16
+ static buildRequestPayload(scopedActions) {
17
+ return scopedActions.map(scopedAction => ({
18
+ ...scopedAction,
19
+ scope: toSnakeCase(scopedAction.scope),
20
+ }));
21
+ }
22
+ /**
23
+ * Fetches authorization data from the Platform API
24
+ */
25
+ static async fetchPermissions(profile, internalAuthToken, userId, scopedActionsPayload) {
26
+ const attributionHeaders = getAttributionsFromApi();
27
+ const httpClient = Api.getPart('httpClient');
28
+ try {
29
+ const response = await httpClient.fetch({
30
+ url: {
31
+ appName: 'platform',
32
+ path: PLATFORM_CAN_ACTIONS_IN_SCOPES_PATH,
33
+ profile,
34
+ },
35
+ method: 'POST',
36
+ headers: {
37
+ Authorization: internalAuthToken,
38
+ 'Content-Type': 'application/json',
39
+ ...attributionHeaders,
40
+ },
41
+ body: JSON.stringify({ user_id: userId, scoped_actions: scopedActionsPayload }),
42
+ }, {
43
+ timeout: AuthorizationInternalService.getRequestTimeout(),
44
+ retryPolicy: AuthorizationInternalService.getRetriesPolicy(),
45
+ });
46
+ return response;
47
+ }
48
+ catch (err) {
49
+ if (err instanceof HttpFetcherError) {
50
+ AuthorizationInternalService.throwOnHttpError(err.status, 'canActionInScopeMultiple');
51
+ incrementAuthorizationError(scopeToResource(toCamelCase(scopedActionsPayload[0].scope)).resourceType, scopedActionsPayload[0].action, err.status);
52
+ }
53
+ throw err;
54
+ }
55
+ }
56
+ /**
57
+ * Maps Platform API response to the expected format
58
+ */
59
+ static mapResponse(response) {
60
+ if (!response) {
61
+ logger.error({ tag: 'platform-api-client', response }, 'PlatformApiClient: missing response');
62
+ throw new Error('PlatformApiClient: missing response');
63
+ }
64
+ return response.result.map(responseObject => {
65
+ const { scopedAction, permit } = responseObject;
66
+ const { scope } = scopedAction;
67
+ return {
68
+ ...responseObject,
69
+ scopedAction: { ...scopedAction, scope: toCamelCase(scope) },
70
+ permit: toCamelCase(permit),
71
+ };
72
+ });
73
+ }
74
+ /**
75
+ * Performs a complete authorization check using the Platform API
76
+ */
77
+ static async checkPermissions(profile, internalAuthToken, userId, scopedActions) {
78
+ const scopedActionsPayload = this.buildRequestPayload(scopedActions);
79
+ const platformResponse = await this.fetchPermissions(profile, internalAuthToken, userId, scopedActionsPayload);
80
+ return this.mapResponse(platformResponse);
81
+ }
82
+ }
83
+
84
+ export { PlatformApiClient };
@@ -7,7 +7,7 @@ export declare const METRICS: {
7
7
  export declare function setPrometheus(customPrometheus: any): void;
8
8
  export declare function getMetricsManager(): any;
9
9
  export declare function sendAuthorizationCheckResponseTimeMetric(resourceType: string, action: Action, isAuthorized: boolean, responseStatus: number, time: number, apiType?: 'platform' | 'graph'): void;
10
- export declare function incrementAuthorizationSuccess(_resourceType: string, _action: Action): void;
11
- export declare function incrementAuthorizationError(_resourceType: string, _action: Action, _statusCode: number): void;
12
- export declare function setGraphAvailability(_isAvailable: boolean): void;
10
+ export declare function incrementAuthorizationSuccess(resourceType: string, action: Action): void;
11
+ export declare function incrementAuthorizationError(resourceType: string, action: Action, statusCode: number): void;
12
+ export declare function setGraphAvailability(isAvailable: boolean): void;
13
13
  //# sourceMappingURL=prometheus-service.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"prometheus-service.d.ts","sourceRoot":"","sources":["../../src/prometheus-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAKzC,eAAO,MAAM,OAAO;;;;CAInB,CAAC;AAQF,wBAAgB,aAAa,CAAC,gBAAgB,KAAA,QAiB7C;AAED,wBAAgB,iBAAiB,QAEhC;AAED,wBAAgB,wCAAwC,CACtD,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,OAAO,EACrB,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,UAAU,GAAG,OAAoB,QAW3C;AAED,wBAAgB,6BAA6B,CAAC,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,QAAI;AAExF,wBAAgB,2BAA2B,CAAC,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,QAAI;AAE3G,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,OAAO,QAAI"}
1
+ {"version":3,"file":"prometheus-service.d.ts","sourceRoot":"","sources":["../../src/prometheus-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAQzC,eAAO,MAAM,OAAO;;;;CAInB,CAAC;AAQF,wBAAgB,aAAa,CAAC,gBAAgB,KAAA,QAsB7C;AAED,wBAAgB,iBAAiB,QAEhC;AAED,wBAAgB,wCAAwC,CACtD,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,OAAO,EACrB,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,UAAU,GAAG,OAAoB,QAW3C;AAoBD,wBAAgB,6BAA6B,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAQjF;AAED,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,QAQnG;AAED,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,OAAO,QAQxD"}
@@ -1,5 +1,8 @@
1
1
  let prometheus = null;
2
2
  let authorizationCheckResponseTimeMetric = null;
3
+ let authorizationSuccessMetric = null;
4
+ let authorizationErrorMetric = null;
5
+ let graphAvailabilityMetric = null;
3
6
  const METRICS = {
4
7
  AUTHORIZATION_CHECK: 'authorization_check',
5
8
  AUTHORIZATION_CHECKS_PER_REQUEST: 'authorization_checks_per_request',
@@ -14,12 +17,16 @@ function setPrometheus(customPrometheus) {
14
17
  prometheus = customPrometheus;
15
18
  if (!prometheus) {
16
19
  authorizationCheckResponseTimeMetric = null;
20
+ authorizationSuccessMetric = null;
21
+ authorizationErrorMetric = null;
22
+ graphAvailabilityMetric = null;
17
23
  return;
18
24
  }
19
25
  const { METRICS_TYPES } = prometheus;
20
26
  const metricsManager = getMetricsManager();
21
27
  if (metricsManager) {
22
28
  authorizationCheckResponseTimeMetric = metricsManager.addMetric(METRICS_TYPES.SUMMARY, authorizationCheckResponseTimeMetricConfig.name, authorizationCheckResponseTimeMetricConfig.labels, authorizationCheckResponseTimeMetricConfig.description);
29
+ initializeAdditionalMetrics();
23
30
  }
24
31
  }
25
32
  function getMetricsManager() {
@@ -37,8 +44,63 @@ function sendAuthorizationCheckResponseTimeMetric(resourceType, action, isAuthor
37
44
  // ignore
38
45
  }
39
46
  }
40
- function incrementAuthorizationSuccess(_resourceType, _action) { }
41
- function incrementAuthorizationError(_resourceType, _action, _statusCode) { }
42
- function setGraphAvailability(_isAvailable) { }
47
+ const authorizationSuccessMetricConfig = {
48
+ name: 'authorization_success_total',
49
+ labels: ['resourceType', 'action'],
50
+ description: 'Total number of successful authorization checks',
51
+ };
52
+ const authorizationErrorMetricConfig = {
53
+ name: 'authorization_error_total',
54
+ labels: ['resourceType', 'action', 'statusCode'],
55
+ description: 'Total number of authorization errors',
56
+ };
57
+ const graphAvailabilityMetricConfig = {
58
+ name: 'graph_api_availability',
59
+ labels: ['available'],
60
+ description: 'Graph API availability status',
61
+ };
62
+ function incrementAuthorizationSuccess(resourceType, action) {
63
+ try {
64
+ if (authorizationSuccessMetric) {
65
+ authorizationSuccessMetric.labels(resourceType, action).inc();
66
+ }
67
+ }
68
+ catch (e) {
69
+ // ignore
70
+ }
71
+ }
72
+ function incrementAuthorizationError(resourceType, action, statusCode) {
73
+ try {
74
+ if (authorizationErrorMetric) {
75
+ authorizationErrorMetric.labels(resourceType, action, statusCode).inc();
76
+ }
77
+ }
78
+ catch (e) {
79
+ // ignore
80
+ }
81
+ }
82
+ function setGraphAvailability(isAvailable) {
83
+ try {
84
+ if (graphAvailabilityMetric) {
85
+ graphAvailabilityMetric.labels(isAvailable ? 'true' : 'false').set(isAvailable ? 1 : 0);
86
+ }
87
+ }
88
+ catch (e) {
89
+ // ignore
90
+ }
91
+ }
92
+ // Initialize additional metrics when prometheus is set
93
+ function initializeAdditionalMetrics() {
94
+ if (!prometheus) {
95
+ return;
96
+ }
97
+ const { METRICS_TYPES } = prometheus;
98
+ const metricsManager = getMetricsManager();
99
+ if (metricsManager) {
100
+ authorizationSuccessMetric = metricsManager.addMetric(METRICS_TYPES.COUNTER, authorizationSuccessMetricConfig.name, authorizationSuccessMetricConfig.labels, authorizationSuccessMetricConfig.description);
101
+ authorizationErrorMetric = metricsManager.addMetric(METRICS_TYPES.COUNTER, authorizationErrorMetricConfig.name, authorizationErrorMetricConfig.labels, authorizationErrorMetricConfig.description);
102
+ graphAvailabilityMetric = metricsManager.addMetric(METRICS_TYPES.GAUGE, graphAvailabilityMetricConfig.name, graphAvailabilityMetricConfig.labels, graphAvailabilityMetricConfig.description);
103
+ }
104
+ }
43
105
 
44
106
  export { METRICS, getMetricsManager, incrementAuthorizationError, incrementAuthorizationSuccess, sendAuthorizationCheckResponseTimeMetric, setGraphAvailability, setPrometheus };
@@ -0,0 +1,11 @@
1
+ export type ResourceType = string;
2
+ export type ResourceId = number;
3
+ export type ActionName = string;
4
+ export type GraphIsAllowedDto = Record<ResourceType, Record<ResourceId, ActionName[]>>;
5
+ export type GraphPermissionResult = {
6
+ can: boolean;
7
+ reason: string;
8
+ };
9
+ export type GraphPermissionResults = Record<ActionName, GraphPermissionResult>;
10
+ export type GraphIsAllowedResponse = Record<ResourceType, Record<string, GraphPermissionResults>>;
11
+ //# sourceMappingURL=graph-api.types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph-api.types.d.ts","sourceRoot":"","sources":["../../../src/types/graph-api.types.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC;AAClC,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC;AAChC,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC;AAEhC,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,UAAU,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC;AAEvF,MAAM,MAAM,qBAAqB,GAAG;IAClC,GAAG,EAAE,OAAO,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAGF,MAAM,MAAM,sBAAsB,GAAG,MAAM,CAAC,UAAU,EAAE,qBAAqB,CAAC,CAAC;AAI/E,MAAM,MAAM,sBAAsB,GAAG,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,22 @@
1
+ import { ScopeOptions } from '../types/scoped-actions-contracts';
2
+ import { ResourceType, ResourceId } from '../types/graph-api.types';
3
+ export type CamelCase<S extends string> = S extends `${infer F}_${infer R}` ? `${F}${Capitalize<CamelCase<R>>}` : S;
4
+ export type CamelCaseKeys<T> = T extends object ? {
5
+ [K in keyof T as K extends string ? CamelCase<K> : K]: CamelCaseKeys<T[K]>;
6
+ } : T;
7
+ /**
8
+ * Converts a scope object to resource type and resource ID
9
+ */
10
+ export declare function scopeToResource(scope: ScopeOptions): {
11
+ resourceType: ResourceType;
12
+ resourceId: ResourceId;
13
+ };
14
+ /**
15
+ * Converts object keys from snake_case to camelCase
16
+ */
17
+ export declare function toCamelCase<T extends object>(obj: T): CamelCaseKeys<T>;
18
+ /**
19
+ * Converts object keys from camelCase to snake_case
20
+ */
21
+ export declare function toSnakeCase<T extends object>(obj: T): Record<string, any>;
22
+ //# sourceMappingURL=authorization.utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authorization.utils.d.ts","sourceRoot":"","sources":["../../../src/utils/authorization.utils.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAEpE,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,MAAM,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,IAAI,MAAM,CAAC,EAAE,GAAG,GAAG,CAAC,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AACpH,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI,CAAC,SAAS,MAAM,GAC3C;KAAG,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,GAC9E,CAAC,CAAC;AAEN;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,YAAY,GAAG;IAAE,YAAY,EAAE,YAAY,CAAC;IAAC,UAAU,EAAE,UAAU,CAAA;CAAE,CAiB3G;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAEtE;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAEzE"}
@@ -0,0 +1,39 @@
1
+ import snakeCase from 'lodash/snakeCase.js';
2
+ import camelCase from 'lodash/camelCase.js';
3
+ import mapKeys from 'lodash/mapKeys.js';
4
+
5
+ /**
6
+ * Converts a scope object to resource type and resource ID
7
+ */
8
+ function scopeToResource(scope) {
9
+ if ('workspaceId' in scope) {
10
+ return { resourceType: 'workspace', resourceId: scope.workspaceId };
11
+ }
12
+ if ('boardId' in scope) {
13
+ return { resourceType: 'board', resourceId: scope.boardId };
14
+ }
15
+ if ('pulseId' in scope) {
16
+ return { resourceType: 'pulse', resourceId: scope.pulseId };
17
+ }
18
+ if ('accountProductId' in scope) {
19
+ return { resourceType: 'account_product', resourceId: scope.accountProductId };
20
+ }
21
+ if ('accountId' in scope) {
22
+ return { resourceType: 'account', resourceId: scope.accountId };
23
+ }
24
+ throw new Error('Unsupported scope provided');
25
+ }
26
+ /**
27
+ * Converts object keys from snake_case to camelCase
28
+ */
29
+ function toCamelCase(obj) {
30
+ return mapKeys(obj, (_, key) => camelCase(key));
31
+ }
32
+ /**
33
+ * Converts object keys from camelCase to snake_case
34
+ */
35
+ function toSnakeCase(obj) {
36
+ return mapKeys(obj, (_, key) => snakeCase(key));
37
+ }
38
+
39
+ export { scopeToResource, toCamelCase, toSnakeCase };