@mondaydotcomorg/monday-authorization 3.2.3-feature-bashanye-navigate-can-action-in-scope-to-graph-af77c6b → 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 +0 -8
- package/dist/authorization-service.d.ts.map +1 -1
- package/dist/authorization-service.js +39 -151
- 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 +0 -8
- package/dist/esm/authorization-service.d.ts.map +1 -1
- package/dist/esm/authorization-service.mjs +40 -146
- 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 +2 -2
- package/CHANGELOG.md +0 -46
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.
|
|
@@ -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":"
|
|
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,30 +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';
|
|
26
19
|
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
20
|
function setRequestFetchOptions(customMondayFetchOptions) {
|
|
29
21
|
authorizationInternalService.AuthorizationInternalService.setRequestFetchOptions(customMondayFetchOptions);
|
|
30
22
|
}
|
|
@@ -97,153 +89,49 @@ class AuthorizationService {
|
|
|
97
89
|
return attributionsService.PlatformProfile.INTERNAL;
|
|
98
90
|
}
|
|
99
91
|
static async canActionInScopeMultiple(accountId, userId, scopedActions) {
|
|
92
|
+
if (scopedActions.length === 0) {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
100
95
|
const shouldNavigateToGraph = Boolean(this.igniteClient?.isReleased(NAVIGATE_CAN_ACTION_IN_SCOPE_TO_GRAPH_FF, { accountId, userId }));
|
|
101
96
|
const internalAuthToken = authorizationInternalService.AuthorizationInternalService.generateInternalAuthToken(accountId, userId);
|
|
97
|
+
const startTime = perf_hooks.performance.now();
|
|
98
|
+
let scopedActionResponseObjects;
|
|
99
|
+
let apiType;
|
|
102
100
|
if (shouldNavigateToGraph) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const profile = this.getProfile(accountId, userId);
|
|
107
|
-
const scopedActionsPayload = this.buildScopedActionsPayload(scopedActions);
|
|
108
|
-
const platformResponse = await this.fetchPlatformCanActions(profile, internalAuthToken, userId, scopedActionsPayload);
|
|
109
|
-
return this.mapPlatformResponse(platformResponse);
|
|
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] = {};
|
|
101
|
+
try {
|
|
102
|
+
scopedActionResponseObjects = await clients_graphApi_client.GraphApiClient.checkPermissions(internalAuthToken, scopedActions);
|
|
103
|
+
apiType = 'graph';
|
|
140
104
|
}
|
|
141
|
-
|
|
142
|
-
|
|
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;
|
|
143
115
|
}
|
|
144
|
-
resourcesAccumulator[resourceType][resourceId].add(action);
|
|
145
116
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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 };
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
static async fetchPlatformCanActions(profile, internalAuthToken, userId, scopedActionsPayload) {
|
|
201
|
-
const attributionHeaders = attributionsService.getAttributionsFromApi();
|
|
202
|
-
const httpClient = tridentBackendApi.Api.getPart('httpClient');
|
|
203
|
-
try {
|
|
204
|
-
const response = await httpClient.fetch({
|
|
205
|
-
url: {
|
|
206
|
-
appName: 'platform',
|
|
207
|
-
path: PLATFORM_CAN_ACTIONS_IN_SCOPES_PATH,
|
|
208
|
-
profile,
|
|
209
|
-
},
|
|
210
|
-
method: 'POST',
|
|
211
|
-
headers: {
|
|
212
|
-
Authorization: internalAuthToken,
|
|
213
|
-
'Content-Type': 'application/json',
|
|
214
|
-
...attributionHeaders,
|
|
215
|
-
},
|
|
216
|
-
body: JSON.stringify({ user_id: userId, scoped_actions: scopedActionsPayload }),
|
|
217
|
-
}, {
|
|
218
|
-
timeout: authorizationInternalService.AuthorizationInternalService.getRequestTimeout(),
|
|
219
|
-
retryPolicy: authorizationInternalService.AuthorizationInternalService.getRetriesPolicy(),
|
|
220
|
-
});
|
|
221
|
-
return response;
|
|
117
|
+
else {
|
|
118
|
+
const profile = this.getProfile(accountId, userId);
|
|
119
|
+
scopedActionResponseObjects = await clients_platformApi_client.PlatformApiClient.checkPermissions(profile, internalAuthToken, userId, scopedActions);
|
|
120
|
+
apiType = 'platform';
|
|
222
121
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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);
|
|
226
132
|
}
|
|
227
|
-
throw err;
|
|
228
133
|
}
|
|
229
|
-
|
|
230
|
-
static toCamelCase(obj) {
|
|
231
|
-
return mapKeys__default.default(obj, (_, key) => camelCase__default.default(key));
|
|
232
|
-
}
|
|
233
|
-
static mapPlatformResponse(response) {
|
|
234
|
-
if (!response) {
|
|
235
|
-
authorizationInternalService.logger.error({ tag: 'authorization-service', response }, 'AuthorizationService: missing response');
|
|
236
|
-
throw new Error('AuthorizationService: missing response');
|
|
237
|
-
}
|
|
238
|
-
return response.result.map(responseObject => {
|
|
239
|
-
const { scopedAction, permit } = responseObject;
|
|
240
|
-
const { scope } = scopedAction;
|
|
241
|
-
return {
|
|
242
|
-
...responseObject,
|
|
243
|
-
scopedAction: { ...scopedAction, scope: this.toCamelCase(scope) },
|
|
244
|
-
permit: this.toCamelCase(permit),
|
|
245
|
-
};
|
|
246
|
-
});
|
|
134
|
+
return scopedActionResponseObjects;
|
|
247
135
|
}
|
|
248
136
|
static async isAuthorizedSingular(accountId, userId, resources, action) {
|
|
249
137
|
const { authorizationObjects } = createAuthorizationParams(resources, action);
|
|
@@ -279,7 +167,7 @@ class AuthorizationService {
|
|
|
279
167
|
});
|
|
280
168
|
}
|
|
281
169
|
catch (err) {
|
|
282
|
-
if (err instanceof
|
|
170
|
+
if (err instanceof mondayFetchApi.HttpFetcherError) {
|
|
283
171
|
authorizationInternalService.AuthorizationInternalService.throwOnHttpError(err.status, 'isAuthorizedMultiple');
|
|
284
172
|
}
|
|
285
173
|
else {
|
|
@@ -298,7 +186,7 @@ class AuthorizationService {
|
|
|
298
186
|
if (!isAuthorized) {
|
|
299
187
|
unauthorizedObjects.push(authorizationObject);
|
|
300
188
|
}
|
|
301
|
-
prometheusService.sendAuthorizationCheckResponseTimeMetric(authorizationObject.resource_type, authorizationObject.action, isAuthorized, 200, time);
|
|
189
|
+
prometheusService.sendAuthorizationCheckResponseTimeMetric(authorizationObject.resource_type, authorizationObject.action, isAuthorized, 200, time, 'platform');
|
|
302
190
|
});
|
|
303
191
|
if (unauthorizedObjects.length > 0) {
|
|
304
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;
|
|
@@ -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":"
|
|
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"}
|