@mondaydotcomorg/monday-authorization 3.3.2 → 3.4.0-feature-bashanye-add-membership-create-delete-api-234505c

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 (45) hide show
  1. package/README.md +126 -3
  2. package/dist/authorization-service.js +1 -1
  3. package/dist/clients/graph-api.d.ts.map +1 -1
  4. package/dist/clients/graph-api.js +1 -1
  5. package/dist/esm/authorization-service.mjs +1 -1
  6. package/dist/esm/clients/graph-api.d.ts.map +1 -1
  7. package/dist/esm/clients/graph-api.mjs +1 -1
  8. package/dist/esm/index.d.ts +1 -0
  9. package/dist/esm/index.d.ts.map +1 -1
  10. package/dist/esm/index.mjs +1 -0
  11. package/dist/esm/memberships.d.ts +29 -0
  12. package/dist/esm/memberships.d.ts.map +1 -0
  13. package/dist/esm/memberships.mjs +97 -0
  14. package/dist/esm/metrics-service.d.ts +3 -3
  15. package/dist/esm/metrics-service.d.ts.map +1 -1
  16. package/dist/esm/metrics-service.mjs +6 -4
  17. package/dist/esm/types/memberships.d.ts +42 -0
  18. package/dist/esm/types/memberships.d.ts.map +1 -0
  19. package/dist/esm/types/memberships.mjs +1 -0
  20. package/dist/esm/utils/api-error-handler.d.ts +1 -1
  21. package/dist/esm/utils/api-error-handler.d.ts.map +1 -1
  22. package/dist/esm/utils/api-error-handler.mjs +4 -4
  23. package/dist/index.d.ts +1 -0
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +2 -0
  26. package/dist/memberships.d.ts +29 -0
  27. package/dist/memberships.d.ts.map +1 -0
  28. package/dist/memberships.js +99 -0
  29. package/dist/metrics-service.d.ts +3 -3
  30. package/dist/metrics-service.d.ts.map +1 -1
  31. package/dist/metrics-service.js +6 -4
  32. package/dist/types/memberships.d.ts +42 -0
  33. package/dist/types/memberships.d.ts.map +1 -0
  34. package/dist/types/memberships.js +1 -0
  35. package/dist/utils/api-error-handler.d.ts +1 -1
  36. package/dist/utils/api-error-handler.d.ts.map +1 -1
  37. package/dist/utils/api-error-handler.js +4 -4
  38. package/package.json +1 -1
  39. package/src/authorization-service.ts +1 -1
  40. package/src/clients/graph-api.ts +1 -1
  41. package/src/index.ts +1 -0
  42. package/src/memberships.ts +110 -0
  43. package/src/metrics-service.ts +11 -5
  44. package/src/types/memberships.ts +47 -0
  45. package/src/utils/api-error-handler.ts +9 -5
package/README.md CHANGED
@@ -25,7 +25,7 @@ import * as MondayAuthorization from '@mondaydotcomorg/monday-authorization';
25
25
 
26
26
  ...
27
27
 
28
- MondayAuthorization.init({
28
+ await MondayAuthorization.init({
29
29
  prometheus: getPrometheus(),
30
30
  metrics: {
31
31
  serviceName: process.env.APP_NAME,
@@ -47,8 +47,8 @@ startServer(...)
47
47
 
48
48
  - `prometheus` (optional) enables the legacy Prometheus summary `authorization_check_response_time` with labels `resourceType`, `action`, `isAuthorized`, and `responseStatus`.
49
49
  - `metrics` (optional) enables StatsD emission through `@mondaydotcomorg/monday-observability-kit` with:
50
- - `authorization.authorizationCheck.platform.duration` / `.graph.duration` (distributions per API path)
51
- - `authorization.authorizationCheck.platform.error` / `.graph.error` counters (with `statusCode` tag)
50
+ - `authorization.authorizationCheck.platform.<API>.duration` / `.graph.<API>.duration` (distributions per API path)
51
+ - `authorization.authorizationCheck.platform.<API>.error` / `.graph.<API>.error` counters (with `statusCode` tag)
52
52
  - When `metrics.disabled` is omitted, the SDK automatically disables StatsD in `test`/`development` environments.
53
53
  - StatsD requires `DOGSTATSD_HOST` / `DOGSTATSD_PORT` (or the defaults `localhost:8125`). Errors are logged and skipped if the client is unavailable.
54
54
 
@@ -404,6 +404,129 @@ interface RolesResponse {
404
404
  }
405
405
  ```
406
406
 
407
+ ### Memberships API
408
+
409
+ The Memberships API allows you to manage memberships (role assignments) for entities on resources. Use `MembershipsService` to create/update and delete memberships synchronously.
410
+
411
+ Important note: there is no validations for the user that create/delete memberships on authorization side.
412
+ It's on the caller responsibility to validate the user have the right permission to change these memberships.
413
+
414
+ #### Create/Update Memberships
415
+
416
+ Use `MembershipsService.upsertMemberships` to create or update memberships synchronously:
417
+
418
+ ```ts
419
+ import { MembershipsService, MembershipForCreate } from '@mondaydotcomorg/monday-authorization';
420
+
421
+ const membershipsService = new MembershipsService();
422
+ const accountId = 739630;
423
+ const memberships: MembershipForCreate[] = [
424
+ {
425
+ entityId: 123,
426
+ entityType: 'user',
427
+ resourceId: 456,
428
+ resourceType: 'workspace',
429
+ roleId: 5,
430
+ roleType: 'basic',
431
+ addedById: 789,
432
+ },
433
+ ];
434
+
435
+ const response = await membershipsService.upsertMemberships(accountId, memberships);
436
+ // Returns: { memberships: Membership[] }
437
+ ```
438
+
439
+ **Parameters:**
440
+
441
+ - `accountId` - The account ID
442
+ - `memberships` - Array of `MembershipForCreate` objects
443
+
444
+ #### Delete Memberships
445
+
446
+ Use `MembershipsService.deleteMemberships` to delete memberships synchronously:
447
+
448
+ ```ts
449
+ import { MembershipsService, MembershipForDelete } from '@mondaydotcomorg/monday-authorization';
450
+
451
+ const membershipsService = new MembershipsService();
452
+ const accountId = 739630;
453
+ const memberships: MembershipForDelete[] = [
454
+ {
455
+ entityId: 123,
456
+ entityType: 'user',
457
+ resourceId: 456,
458
+ resourceType: 'workspace',
459
+ },
460
+ ];
461
+
462
+ const response = await membershipsService.deleteMemberships(accountId, memberships);
463
+ // Returns: { memberships: Membership[] }
464
+ ```
465
+
466
+ **Parameters:**
467
+
468
+ - `accountId` - The account ID
469
+ - `memberships` - Array of `MembershipForDelete` objects
470
+
471
+ #### Types
472
+
473
+ The following types are available for working with memberships:
474
+
475
+ ```ts
476
+ import {
477
+ MembershipForCreate,
478
+ MembershipForDelete,
479
+ Membership,
480
+ MembershipCreateResponse,
481
+ MembershipDeleteResponse,
482
+ } from '@mondaydotcomorg/monday-authorization';
483
+
484
+ // MembershipForCreate interface
485
+ interface MembershipForCreate {
486
+ entityId: number;
487
+ entityType: string;
488
+ resourceId: number;
489
+ resourceType: string;
490
+ roleId: number;
491
+ roleType?: string;
492
+ addedById: number;
493
+ }
494
+
495
+ // MembershipForDelete interface
496
+ interface MembershipForDelete {
497
+ entityId?: number;
498
+ entityType: string;
499
+ resourceId?: number;
500
+ resourceType: string;
501
+ }
502
+
503
+ // Membership interface
504
+ interface Membership {
505
+ id: number;
506
+ entityId: number;
507
+ entityType: string;
508
+ resourceId: number;
509
+ resourceType: string;
510
+ roleId: number;
511
+ roleType: string;
512
+ addedById: null | number | undefined;
513
+ hops: number;
514
+ isNewRecord: boolean;
515
+ previousValues: Partial<Membership>;
516
+ walVersion: number | null | undefined;
517
+ }
518
+
519
+ // MembershipCreateResponse interface
520
+ interface MembershipCreateResponse {
521
+ memberships: Membership[];
522
+ }
523
+
524
+ // MembershipDeleteResponse interface
525
+ interface MembershipDeleteResponse {
526
+ memberships: Membership[];
527
+ }
528
+ ```
529
+
407
530
  ## Development
408
531
 
409
532
  ### Local Development and Testing
@@ -133,7 +133,7 @@ class AuthorizationService {
133
133
  const { resourceType } = utils_authorization_utils.scopeToResource(scope);
134
134
  const isAuthorized = obj.permit.can;
135
135
  prometheusService.sendAuthorizationCheckResponseTimeMetric(resourceType, action, isAuthorized, 200, time);
136
- metricsService.recordAuthorizationTiming(apiType, time);
136
+ metricsService.recordAuthorizationTiming(apiType, time, 'canActionInScopeMultiple');
137
137
  }
138
138
  return scopedActionResponseObjects;
139
139
  }
@@ -1 +1 @@
1
- {"version":3,"file":"graph-api.d.ts","sourceRoot":"","sources":["../../src/clients/graph-api.ts"],"names":[],"mappings":"AACA,OAAO,EACL,YAAY,EACZ,0BAA0B,EAG3B,MAAM,mCAAmC,CAAC;AAG3C,OAAO,EAEL,sBAAsB,EAMvB,MAAM,0BAA0B,CAAC;AASlC;;GAEG;AACH,qBAAa,QAAQ;IACnB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;IACxC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;;IAezC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAyB/B;;OAEG;IACG,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAgCzG;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,WAAW;IAiC1B;;OAEG;IACG,gBAAgB,CACpB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,YAAY,EAAE,GAC5B,OAAO,CAAC,0BAA0B,EAAE,CAAC;IAMxC,OAAO,CAAC,MAAM,CAAC,iBAAiB;CAWjC"}
1
+ {"version":3,"file":"graph-api.d.ts","sourceRoot":"","sources":["../../src/clients/graph-api.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,YAAY,EACZ,0BAA0B,EAG3B,MAAM,mCAAmC,CAAC;AAG3C,OAAO,EAEL,sBAAsB,EAMvB,MAAM,0BAA0B,CAAC;AAQlC;;GAEG;AACH,qBAAa,QAAQ;IACnB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;IACxC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;;IAezC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAyB/B;;OAEG;IACG,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAgCzG;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,WAAW;IAiC1B;;OAEG;IACG,gBAAgB,CACpB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,YAAY,EAAE,GAC5B,OAAO,CAAC,0BAA0B,EAAE,CAAC;IAMxC,OAAO,CAAC,MAAM,CAAC,iBAAiB;CAWjC"}
@@ -1,11 +1,11 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
 
3
3
  const tridentBackendApi = require('@mondaydotcomorg/trident-backend-api');
4
+ const mondayJwt = require('@mondaydotcomorg/monday-jwt');
4
5
  const types_scopedActionsContracts = require('../types/scoped-actions-contracts.js');
5
6
  const authorizationInternalService = require('../authorization-internal-service.js');
6
7
  const attributionsService = require('../attributions-service.js');
7
8
  const utils_authorization_utils = require('../utils/authorization.utils.js');
8
- const mondayJwt = require('@mondaydotcomorg/monday-jwt');
9
9
  const constants = require('../constants.js');
10
10
  const utils_apiErrorHandler = require('../utils/api-error-handler.js');
11
11
 
@@ -131,7 +131,7 @@ class AuthorizationService {
131
131
  const { resourceType } = scopeToResource(scope);
132
132
  const isAuthorized = obj.permit.can;
133
133
  sendAuthorizationCheckResponseTimeMetric(resourceType, action, isAuthorized, 200, time);
134
- recordAuthorizationTiming(apiType, time);
134
+ recordAuthorizationTiming(apiType, time, 'canActionInScopeMultiple');
135
135
  }
136
136
  return scopedActionResponseObjects;
137
137
  }
@@ -1 +1 @@
1
- {"version":3,"file":"graph-api.d.ts","sourceRoot":"","sources":["../../../src/clients/graph-api.ts"],"names":[],"mappings":"AACA,OAAO,EACL,YAAY,EACZ,0BAA0B,EAG3B,MAAM,mCAAmC,CAAC;AAG3C,OAAO,EAEL,sBAAsB,EAMvB,MAAM,0BAA0B,CAAC;AASlC;;GAEG;AACH,qBAAa,QAAQ;IACnB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;IACxC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;;IAezC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAyB/B;;OAEG;IACG,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAgCzG;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,WAAW;IAiC1B;;OAEG;IACG,gBAAgB,CACpB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,YAAY,EAAE,GAC5B,OAAO,CAAC,0BAA0B,EAAE,CAAC;IAMxC,OAAO,CAAC,MAAM,CAAC,iBAAiB;CAWjC"}
1
+ {"version":3,"file":"graph-api.d.ts","sourceRoot":"","sources":["../../../src/clients/graph-api.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,YAAY,EACZ,0BAA0B,EAG3B,MAAM,mCAAmC,CAAC;AAG3C,OAAO,EAEL,sBAAsB,EAMvB,MAAM,0BAA0B,CAAC;AAQlC;;GAEG;AACH,qBAAa,QAAQ;IACnB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;IACxC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;;IAezC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAyB/B;;OAEG;IACG,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAgCzG;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,WAAW;IAiC1B;;OAEG;IACG,gBAAgB,CACpB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,YAAY,EAAE,GAC5B,OAAO,CAAC,0BAA0B,EAAE,CAAC;IAMxC,OAAO,CAAC,MAAM,CAAC,iBAAiB;CAWjC"}
@@ -1,9 +1,9 @@
1
1
  import { Api } from '@mondaydotcomorg/trident-backend-api';
2
+ import { signAuthorizationHeader } from '@mondaydotcomorg/monday-jwt';
2
3
  import { PermitTechnicalReason } from '../types/scoped-actions-contracts.mjs';
3
4
  import { AuthorizationInternalService } from '../authorization-internal-service.mjs';
4
5
  import { getAttributionsFromApi } from '../attributions-service.mjs';
5
6
  import { scopeToResource } from '../utils/authorization.utils.mjs';
6
- import { signAuthorizationHeader } from '@mondaydotcomorg/monday-jwt';
7
7
  import { GRAPH_APP_NAME } from '../constants.mjs';
8
8
  import { handleApiError } from '../utils/api-error-handler.mjs';
9
9
 
@@ -20,6 +20,7 @@ export { authorizationCheckMiddleware, getAuthorizationMiddleware, skipAuthoriza
20
20
  export { AuthorizationService, AuthorizeResponse } from './authorization-service';
21
21
  export { AuthorizationAttributesService } from './authorization-attributes-service';
22
22
  export { RolesService } from './roles-service';
23
+ export { MembershipsService } from './memberships';
23
24
  export { AuthorizationObject, Resource, BaseRequest, ResourceGetter, ContextGetter } from './types/general';
24
25
  export { Translation, ScopedAction, ScopedActionResponseObject, ScopedActionPermit, } from './types/scoped-actions-contracts';
25
26
  export { CustomRole, BasicRole, RoleType, RoleCreateRequest, RoleUpdateRequest, RolesResponse } from './types/roles';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAGnE,OAAO,EAAqB,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,KAAK,OAAO,MAAM,WAAW,CAAC;AAErC,UAAU,kBAAkB;IAC1B,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,CAAC,EAAE,GAAG,CAAC;IACjB,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC,WAAW,CAAC,EAAE,GAAG,CAAC;IAClB,sCAAsC,CAAC,EAAE,MAAM,CAAC;IAChD,OAAO,CAAC,EAAE,kBAAkB,CAAC;CAC9B;AAED,wBAAsB,IAAI,CAAC,OAAO,GAAE,WAAgB,iBA6BnD;AAED,OAAO,EACL,4BAA4B,EAC5B,0BAA0B,EAC1B,2BAA2B,GAC5B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAClF,OAAO,EAAE,8BAA8B,EAAE,MAAM,oCAAoC,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,QAAQ,EAAE,WAAW,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC5G,OAAO,EACL,WAAW,EACX,YAAY,EACZ,0BAA0B,EAC1B,kBAAkB,GACnB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAErH,OAAO,EAAE,OAAO,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAGnE,OAAO,EAAqB,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,KAAK,OAAO,MAAM,WAAW,CAAC;AAErC,UAAU,kBAAkB;IAC1B,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,CAAC,EAAE,GAAG,CAAC;IACjB,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC,WAAW,CAAC,EAAE,GAAG,CAAC;IAClB,sCAAsC,CAAC,EAAE,MAAM,CAAC;IAChD,OAAO,CAAC,EAAE,kBAAkB,CAAC;CAC9B;AAED,wBAAsB,IAAI,CAAC,OAAO,GAAE,WAAgB,iBA6BnD;AAED,OAAO,EACL,4BAA4B,EAC5B,0BAA0B,EAC1B,2BAA2B,GAC5B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAClF,OAAO,EAAE,8BAA8B,EAAE,MAAM,oCAAoC,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,QAAQ,EAAE,WAAW,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC5G,OAAO,EACL,WAAW,EACX,YAAY,EACZ,0BAA0B,EAC1B,kBAAkB,GACnB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAErH,OAAO,EAAE,OAAO,EAAE,CAAC"}
@@ -7,6 +7,7 @@ export { testKit_index as TestKit };
7
7
  export { authorizationCheckMiddleware, getAuthorizationMiddleware, skipAuthorizationMiddleware } from './authorization-middleware.mjs';
8
8
  export { AuthorizationAttributesService } from './authorization-attributes-service.mjs';
9
9
  export { RolesService } from './roles-service.mjs';
10
+ export { MembershipsService } from './memberships.mjs';
10
11
  export { RoleType } from './types/roles.mjs';
11
12
 
12
13
  async function init(options = {}) {
@@ -0,0 +1,29 @@
1
+ import { FetcherConfig, HttpClient } from '@mondaydotcomorg/trident-backend-api';
2
+ import { RecursivePartial } from '@mondaydotcomorg/monday-fetch-api';
3
+ import { MembershipCreateResponse, MembershipDeleteResponse, MembershipForCreate, MembershipForDelete } from './types/memberships';
4
+ export declare class MembershipsService {
5
+ private static API_PATHS;
6
+ private httpClient;
7
+ private fetchOptions;
8
+ /**
9
+ * Public constructor to create the MembershipsService instance.
10
+ * @param httpClient The HTTP client to use for API requests, if not provided, the default HTTP client from Api will be used.
11
+ * @param fetchOptions The fetch options to use for API requests, if not provided, the default fetch options will be used.
12
+ */
13
+ constructor(httpClient?: HttpClient, fetchOptions?: RecursivePartial<FetcherConfig>);
14
+ /**
15
+ * Upsert memberships synchronously, performing http call to the authorization MS to assign the given memberships.
16
+ * @param accountId
17
+ * @param memberships - Array of memberships to upsert
18
+ * @returns MembershipCreateResponse - The affected (created and updated) memberships.
19
+ */
20
+ upsertMemberships(accountId: number, memberships: MembershipForCreate[]): Promise<MembershipCreateResponse>;
21
+ /**
22
+ * Delete memberships synchronously, performing http call to the authorization MS to delete the given memberships.
23
+ * @param accountId
24
+ * @param memberships - Array of memberships to delete.
25
+ * @returns MembershipDeleteResponse - The affected (deleted) memberships.
26
+ */
27
+ deleteMemberships(accountId: number, memberships: MembershipForDelete[]): Promise<MembershipDeleteResponse>;
28
+ }
29
+ //# sourceMappingURL=memberships.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memberships.d.ts","sourceRoot":"","sources":["../../src/memberships.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,aAAa,EAAE,UAAU,EAAE,MAAM,sCAAsC,CAAC;AACtF,OAAO,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AACrE,OAAO,EACL,wBAAwB,EACxB,wBAAwB,EACxB,mBAAmB,EACnB,mBAAmB,EACpB,MAAM,mBAAmB,CAAC;AAM3B,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,MAAM,CAAC,SAAS,CAGb;IACX,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,YAAY,CAAkC;IAEtD;;;;OAIG;gBACS,UAAU,CAAC,EAAE,UAAU,EAAE,YAAY,CAAC,EAAE,gBAAgB,CAAC,aAAa,CAAC;IAoBnF;;;;;OAKG;IACG,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,mBAAmB,EAAE,GAAG,OAAO,CAAC,wBAAwB,CAAC;IA0BjH;;;;;OAKG;IACG,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,mBAAmB,EAAE,GAAG,OAAO,CAAC,wBAAwB,CAAC;CAyBlH"}
@@ -0,0 +1,97 @@
1
+ import { Api } from '@mondaydotcomorg/trident-backend-api';
2
+ import { handleApiError } from './utils/api-error-handler.mjs';
3
+ import { getAttributionsFromApi } from './attributions-service.mjs';
4
+ import { ERROR_MESSAGES, DEFAULT_FETCH_OPTIONS, APP_NAME } from './constants.mjs';
5
+
6
+ class MembershipsService {
7
+ static API_PATHS = {
8
+ UPSERT_MEMBERSHIPS: '/memberships/account/{accountId}',
9
+ DELETE_MEMBERSHIPS: '/memberships/account/{accountId}',
10
+ };
11
+ httpClient;
12
+ fetchOptions;
13
+ /**
14
+ * Public constructor to create the MembershipsService instance.
15
+ * @param httpClient The HTTP client to use for API requests, if not provided, the default HTTP client from Api will be used.
16
+ * @param fetchOptions The fetch options to use for API requests, if not provided, the default fetch options will be used.
17
+ */
18
+ constructor(httpClient, fetchOptions) {
19
+ if (!httpClient) {
20
+ httpClient = Api.getPart('httpClient');
21
+ if (!httpClient) {
22
+ throw new Error(ERROR_MESSAGES.HTTP_CLIENT_NOT_INITIALIZED);
23
+ }
24
+ }
25
+ if (!fetchOptions) {
26
+ fetchOptions = DEFAULT_FETCH_OPTIONS;
27
+ }
28
+ else {
29
+ fetchOptions = {
30
+ ...DEFAULT_FETCH_OPTIONS,
31
+ ...fetchOptions,
32
+ };
33
+ }
34
+ this.httpClient = httpClient;
35
+ this.fetchOptions = fetchOptions;
36
+ }
37
+ /**
38
+ * Upsert memberships synchronously, performing http call to the authorization MS to assign the given memberships.
39
+ * @param accountId
40
+ * @param memberships - Array of memberships to upsert
41
+ * @returns MembershipCreateResponse - The affected (created and updated) memberships.
42
+ */
43
+ async upsertMemberships(accountId, memberships) {
44
+ const attributionHeaders = getAttributionsFromApi();
45
+ try {
46
+ return await this.httpClient.fetch({
47
+ url: {
48
+ appName: APP_NAME,
49
+ path: MembershipsService.API_PATHS.UPSERT_MEMBERSHIPS.replace('{accountId}', accountId.toString()),
50
+ },
51
+ method: 'PUT',
52
+ query: {
53
+ useAStyleRoleId: 'true',
54
+ },
55
+ headers: {
56
+ 'Content-Type': 'application/json',
57
+ ...attributionHeaders,
58
+ },
59
+ body: JSON.stringify({ memberships }),
60
+ }, this.fetchOptions);
61
+ }
62
+ catch (err) {
63
+ return handleApiError(err, 'authorization', 'upsertMemberships');
64
+ }
65
+ }
66
+ /**
67
+ * Delete memberships synchronously, performing http call to the authorization MS to delete the given memberships.
68
+ * @param accountId
69
+ * @param memberships - Array of memberships to delete.
70
+ * @returns MembershipDeleteResponse - The affected (deleted) memberships.
71
+ */
72
+ async deleteMemberships(accountId, memberships) {
73
+ const attributionHeaders = getAttributionsFromApi();
74
+ try {
75
+ return await this.httpClient.fetch({
76
+ url: {
77
+ appName: APP_NAME,
78
+ path: MembershipsService.API_PATHS.DELETE_MEMBERSHIPS.replace('{accountId}', accountId.toString()),
79
+ },
80
+ method: 'DELETE',
81
+ query: {
82
+ useAStyleRoleId: 'true',
83
+ },
84
+ headers: {
85
+ 'Content-Type': 'application/json',
86
+ ...attributionHeaders,
87
+ },
88
+ body: JSON.stringify({ memberships }),
89
+ }, this.fetchOptions);
90
+ }
91
+ catch (err) {
92
+ return handleApiError(err, 'authorization', 'deleteMemberships');
93
+ }
94
+ }
95
+ }
96
+
97
+ export { MembershipsService };
@@ -1,5 +1,5 @@
1
1
  import { Metric } from '@mondaydotcomorg/monday-observability-kit';
2
- type ApiType = 'platform' | 'graph';
2
+ type ApiType = 'platform' | 'graph' | 'authorization';
3
3
  export type MetricsClient = Pick<typeof Metric, 'distribution' | 'increment'>;
4
4
  interface InitializeMetricsOptions {
5
5
  client?: MetricsClient;
@@ -9,7 +9,7 @@ interface InitializeMetricsOptions {
9
9
  disabled?: boolean;
10
10
  }
11
11
  export declare function initializeMetrics(options: InitializeMetricsOptions): void;
12
- export declare function recordAuthorizationTiming(apiType: ApiType, duration: number): void;
13
- export declare function recordAuthorizationError(apiType: ApiType, statusCode: number): void;
12
+ export declare function recordAuthorizationTiming(apiType: ApiType, duration: number, placement: string): void;
13
+ export declare function recordAuthorizationError(apiType: ApiType, statusCode: number, placement: string): void;
14
14
  export {};
15
15
  //# sourceMappingURL=metrics-service.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"metrics-service.d.ts","sourceRoot":"","sources":["../../src/metrics-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAGnE,KAAK,OAAO,GAAG,UAAU,GAAG,OAAO,CAAC;AAEpC,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,MAAM,EAAE,cAAc,GAAG,WAAW,CAAC,CAAC;AAE9E,UAAU,wBAAwB;IAChC,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAID,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,IAAI,CAiCzE;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAmBlF;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAmBnF"}
1
+ {"version":3,"file":"metrics-service.d.ts","sourceRoot":"","sources":["../../src/metrics-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAGnE,KAAK,OAAO,GAAG,UAAU,GAAG,OAAO,GAAG,eAAe,CAAC;AAEtD,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,MAAM,EAAE,cAAc,GAAG,WAAW,CAAC,CAAC;AAE9E,UAAU,wBAAwB;IAChC,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAID,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,IAAI,CAiCzE;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAoBrG;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAwBtG"}
@@ -32,35 +32,37 @@ function initializeMetrics(options) {
32
32
  logger.warn({ tag: 'monday-authorization-sdk', error }, 'Failed to initialize metrics');
33
33
  }
34
34
  }
35
- function recordAuthorizationTiming(apiType, duration) {
35
+ function recordAuthorizationTiming(apiType, duration, placement) {
36
36
  if (!metricsClient) {
37
37
  return;
38
38
  }
39
39
  try {
40
- metricsClient.distribution(`authorization.authorizationCheck.${apiType}.duration`, duration);
40
+ metricsClient.distribution(`authorization.authorizationCheck.${apiType}.${placement}.duration`, duration);
41
41
  }
42
42
  catch (error) {
43
43
  logger.warn({
44
44
  tag: 'monday-authorization-sdk',
45
45
  metric: 'authorizationCheckDuration',
46
46
  apiType,
47
+ placement,
47
48
  duration,
48
49
  error,
49
50
  }, 'Failed to emit authorization timing metric');
50
51
  }
51
52
  }
52
- function recordAuthorizationError(apiType, statusCode) {
53
+ function recordAuthorizationError(apiType, statusCode, placement) {
53
54
  if (!metricsClient) {
54
55
  return;
55
56
  }
56
57
  try {
57
- metricsClient.increment(`authorization.authorizationCheck.${apiType}.error`, { statusCode: String(statusCode) }, 1);
58
+ metricsClient.increment(`authorization.authorizationCheck.${apiType}.${placement}.error`, { statusCode: String(statusCode) }, 1);
58
59
  }
59
60
  catch (error) {
60
61
  logger.warn({
61
62
  tag: 'monday-authorization-sdk',
62
63
  metric: 'authorizationCheckError',
63
64
  apiType,
65
+ placement,
64
66
  statusCode,
65
67
  error,
66
68
  }, 'Failed to emit authorization error metric');
@@ -0,0 +1,42 @@
1
+ export interface MembershipForCreate {
2
+ entityId: number;
3
+ entityType: string;
4
+ resourceId: number;
5
+ resourceType: string;
6
+ roleId: number;
7
+ roleType?: string;
8
+ addedById: number;
9
+ }
10
+ export interface MembershipForDelete {
11
+ entityId?: number;
12
+ entityType: string;
13
+ resourceId: number;
14
+ resourceType: string;
15
+ }
16
+ export interface Membership {
17
+ id: number;
18
+ entityId: number;
19
+ entityType: string;
20
+ resourceId: number;
21
+ resourceType: string;
22
+ roleId: number;
23
+ roleType: string;
24
+ addedById: null | number | undefined;
25
+ hops: number;
26
+ isNewRecord: boolean;
27
+ previousValues: Partial<Membership>;
28
+ walVersion: number | null | undefined;
29
+ }
30
+ export interface MembershipCreateResponse {
31
+ memberships: Membership[];
32
+ }
33
+ export interface MembershipDeleteResponse {
34
+ memberships: Membership[];
35
+ }
36
+ export interface MembershipCreateRequest {
37
+ memberships: MembershipForCreate[];
38
+ }
39
+ export interface MembershipDeleteRequest {
40
+ memberships: MembershipForDelete[];
41
+ }
42
+ //# sourceMappingURL=memberships.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memberships.d.ts","sourceRoot":"","sources":["../../../src/types/memberships.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,IAAI,GAAG,MAAM,GAAG,SAAS,CAAC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,OAAO,CAAC;IACrB,cAAc,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IACpC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CACvC;AAED,MAAM,WAAW,wBAAwB;IACvC,WAAW,EAAE,UAAU,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,wBAAwB;IACvC,WAAW,EAAE,UAAU,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,mBAAmB,EAAE,CAAC;CACpC;AAED,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,mBAAmB,EAAE,CAAC;CACpC"}
@@ -0,0 +1 @@
1
+
@@ -1,2 +1,2 @@
1
- export declare function handleApiError(err: unknown, apiType: 'platform' | 'graph', placement: string): never;
1
+ export declare function handleApiError(err: unknown, apiType: 'platform' | 'graph' | 'authorization', placement: string): never;
2
2
  //# sourceMappingURL=api-error-handler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"api-error-handler.d.ts","sourceRoot":"","sources":["../../../src/utils/api-error-handler.ts"],"names":[],"mappings":"AAIA,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,KAAK,CAgBpG"}
1
+ {"version":3,"file":"api-error-handler.d.ts","sourceRoot":"","sources":["../../../src/utils/api-error-handler.ts"],"names":[],"mappings":"AAIA,wBAAgB,cAAc,CAC5B,GAAG,EAAE,OAAO,EACZ,OAAO,EAAE,UAAU,GAAG,OAAO,GAAG,eAAe,EAC/C,SAAS,EAAE,MAAM,GAChB,KAAK,CAgBP"}
@@ -4,13 +4,13 @@ import { recordAuthorizationError } from '../metrics-service.mjs';
4
4
 
5
5
  function handleApiError(err, apiType, placement) {
6
6
  if (err instanceof HttpFetcherError) {
7
- logger.error({ tag: `${apiType}-api`, status: err.status, error: err.message }, `${apiType.charAt(0).toUpperCase() + apiType.slice(1)} API authorization request failed`);
8
- recordAuthorizationError(apiType, err.status);
7
+ logger.error({ tag: `${apiType}-api`, status: err.status, error: err.message }, `${apiType.charAt(0).toUpperCase() + apiType.slice(1)} API ${placement} request failed`);
8
+ recordAuthorizationError(apiType, err.status, placement);
9
9
  AuthorizationInternalService.throwOnHttpError(err.status, placement);
10
10
  }
11
11
  else {
12
- logger.error({ tag: `${apiType}-api`, error: err instanceof Error ? err.message : String(err) }, `${apiType.charAt(0).toUpperCase() + apiType.slice(1)} API authorization request failed`);
13
- recordAuthorizationError(apiType, 500);
12
+ logger.error({ tag: `${apiType}-api`, error: err instanceof Error ? err.message : String(err) }, `${apiType.charAt(0).toUpperCase() + apiType.slice(1)} API ${placement} request failed`);
13
+ recordAuthorizationError(apiType, 500, placement);
14
14
  throw err;
15
15
  }
16
16
  }
package/dist/index.d.ts CHANGED
@@ -20,6 +20,7 @@ export { authorizationCheckMiddleware, getAuthorizationMiddleware, skipAuthoriza
20
20
  export { AuthorizationService, AuthorizeResponse } from './authorization-service';
21
21
  export { AuthorizationAttributesService } from './authorization-attributes-service';
22
22
  export { RolesService } from './roles-service';
23
+ export { MembershipsService } from './memberships';
23
24
  export { AuthorizationObject, Resource, BaseRequest, ResourceGetter, ContextGetter } from './types/general';
24
25
  export { Translation, ScopedAction, ScopedActionResponseObject, ScopedActionPermit, } from './types/scoped-actions-contracts';
25
26
  export { CustomRole, BasicRole, RoleType, RoleCreateRequest, RoleUpdateRequest, RolesResponse } from './types/roles';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAGnE,OAAO,EAAqB,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,KAAK,OAAO,MAAM,WAAW,CAAC;AAErC,UAAU,kBAAkB;IAC1B,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,CAAC,EAAE,GAAG,CAAC;IACjB,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC,WAAW,CAAC,EAAE,GAAG,CAAC;IAClB,sCAAsC,CAAC,EAAE,MAAM,CAAC;IAChD,OAAO,CAAC,EAAE,kBAAkB,CAAC;CAC9B;AAED,wBAAsB,IAAI,CAAC,OAAO,GAAE,WAAgB,iBA6BnD;AAED,OAAO,EACL,4BAA4B,EAC5B,0BAA0B,EAC1B,2BAA2B,GAC5B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAClF,OAAO,EAAE,8BAA8B,EAAE,MAAM,oCAAoC,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,QAAQ,EAAE,WAAW,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC5G,OAAO,EACL,WAAW,EACX,YAAY,EACZ,0BAA0B,EAC1B,kBAAkB,GACnB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAErH,OAAO,EAAE,OAAO,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAGnE,OAAO,EAAqB,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,KAAK,OAAO,MAAM,WAAW,CAAC;AAErC,UAAU,kBAAkB;IAC1B,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,CAAC,EAAE,GAAG,CAAC;IACjB,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC,WAAW,CAAC,EAAE,GAAG,CAAC;IAClB,sCAAsC,CAAC,EAAE,MAAM,CAAC;IAChD,OAAO,CAAC,EAAE,kBAAkB,CAAC;CAC9B;AAED,wBAAsB,IAAI,CAAC,OAAO,GAAE,WAAgB,iBA6BnD;AAED,OAAO,EACL,4BAA4B,EAC5B,0BAA0B,EAC1B,2BAA2B,GAC5B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAClF,OAAO,EAAE,8BAA8B,EAAE,MAAM,oCAAoC,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,QAAQ,EAAE,WAAW,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC5G,OAAO,EACL,WAAW,EACX,YAAY,EACZ,0BAA0B,EAC1B,kBAAkB,GACnB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAErH,OAAO,EAAE,OAAO,EAAE,CAAC"}
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ const testKit_index = require('./testKit/index.js');
7
7
  const authorizationMiddleware = require('./authorization-middleware.js');
8
8
  const authorizationAttributesService = require('./authorization-attributes-service.js');
9
9
  const rolesService = require('./roles-service.js');
10
+ const memberships = require('./memberships.js');
10
11
  const types_roles = require('./types/roles.js');
11
12
 
12
13
  async function init(options = {}) {
@@ -44,6 +45,7 @@ exports.getAuthorizationMiddleware = authorizationMiddleware.getAuthorizationMid
44
45
  exports.skipAuthorizationMiddleware = authorizationMiddleware.skipAuthorizationMiddleware;
45
46
  exports.AuthorizationAttributesService = authorizationAttributesService.AuthorizationAttributesService;
46
47
  exports.RolesService = rolesService.RolesService;
48
+ exports.MembershipsService = memberships.MembershipsService;
47
49
  Object.defineProperty(exports, 'RoleType', {
48
50
  enumerable: true,
49
51
  get: () => types_roles.RoleType
@@ -0,0 +1,29 @@
1
+ import { FetcherConfig, HttpClient } from '@mondaydotcomorg/trident-backend-api';
2
+ import { RecursivePartial } from '@mondaydotcomorg/monday-fetch-api';
3
+ import { MembershipCreateResponse, MembershipDeleteResponse, MembershipForCreate, MembershipForDelete } from './types/memberships';
4
+ export declare class MembershipsService {
5
+ private static API_PATHS;
6
+ private httpClient;
7
+ private fetchOptions;
8
+ /**
9
+ * Public constructor to create the MembershipsService instance.
10
+ * @param httpClient The HTTP client to use for API requests, if not provided, the default HTTP client from Api will be used.
11
+ * @param fetchOptions The fetch options to use for API requests, if not provided, the default fetch options will be used.
12
+ */
13
+ constructor(httpClient?: HttpClient, fetchOptions?: RecursivePartial<FetcherConfig>);
14
+ /**
15
+ * Upsert memberships synchronously, performing http call to the authorization MS to assign the given memberships.
16
+ * @param accountId
17
+ * @param memberships - Array of memberships to upsert
18
+ * @returns MembershipCreateResponse - The affected (created and updated) memberships.
19
+ */
20
+ upsertMemberships(accountId: number, memberships: MembershipForCreate[]): Promise<MembershipCreateResponse>;
21
+ /**
22
+ * Delete memberships synchronously, performing http call to the authorization MS to delete the given memberships.
23
+ * @param accountId
24
+ * @param memberships - Array of memberships to delete.
25
+ * @returns MembershipDeleteResponse - The affected (deleted) memberships.
26
+ */
27
+ deleteMemberships(accountId: number, memberships: MembershipForDelete[]): Promise<MembershipDeleteResponse>;
28
+ }
29
+ //# sourceMappingURL=memberships.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memberships.d.ts","sourceRoot":"","sources":["../src/memberships.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,aAAa,EAAE,UAAU,EAAE,MAAM,sCAAsC,CAAC;AACtF,OAAO,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AACrE,OAAO,EACL,wBAAwB,EACxB,wBAAwB,EACxB,mBAAmB,EACnB,mBAAmB,EACpB,MAAM,mBAAmB,CAAC;AAM3B,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,MAAM,CAAC,SAAS,CAGb;IACX,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,YAAY,CAAkC;IAEtD;;;;OAIG;gBACS,UAAU,CAAC,EAAE,UAAU,EAAE,YAAY,CAAC,EAAE,gBAAgB,CAAC,aAAa,CAAC;IAoBnF;;;;;OAKG;IACG,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,mBAAmB,EAAE,GAAG,OAAO,CAAC,wBAAwB,CAAC;IA0BjH;;;;;OAKG;IACG,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,mBAAmB,EAAE,GAAG,OAAO,CAAC,wBAAwB,CAAC;CAyBlH"}
@@ -0,0 +1,99 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+
3
+ const tridentBackendApi = require('@mondaydotcomorg/trident-backend-api');
4
+ const utils_apiErrorHandler = require('./utils/api-error-handler.js');
5
+ const attributionsService = require('./attributions-service.js');
6
+ const constants = require('./constants.js');
7
+
8
+ class MembershipsService {
9
+ static API_PATHS = {
10
+ UPSERT_MEMBERSHIPS: '/memberships/account/{accountId}',
11
+ DELETE_MEMBERSHIPS: '/memberships/account/{accountId}',
12
+ };
13
+ httpClient;
14
+ fetchOptions;
15
+ /**
16
+ * Public constructor to create the MembershipsService instance.
17
+ * @param httpClient The HTTP client to use for API requests, if not provided, the default HTTP client from Api will be used.
18
+ * @param fetchOptions The fetch options to use for API requests, if not provided, the default fetch options will be used.
19
+ */
20
+ constructor(httpClient, fetchOptions) {
21
+ if (!httpClient) {
22
+ httpClient = tridentBackendApi.Api.getPart('httpClient');
23
+ if (!httpClient) {
24
+ throw new Error(constants.ERROR_MESSAGES.HTTP_CLIENT_NOT_INITIALIZED);
25
+ }
26
+ }
27
+ if (!fetchOptions) {
28
+ fetchOptions = constants.DEFAULT_FETCH_OPTIONS;
29
+ }
30
+ else {
31
+ fetchOptions = {
32
+ ...constants.DEFAULT_FETCH_OPTIONS,
33
+ ...fetchOptions,
34
+ };
35
+ }
36
+ this.httpClient = httpClient;
37
+ this.fetchOptions = fetchOptions;
38
+ }
39
+ /**
40
+ * Upsert memberships synchronously, performing http call to the authorization MS to assign the given memberships.
41
+ * @param accountId
42
+ * @param memberships - Array of memberships to upsert
43
+ * @returns MembershipCreateResponse - The affected (created and updated) memberships.
44
+ */
45
+ async upsertMemberships(accountId, memberships) {
46
+ const attributionHeaders = attributionsService.getAttributionsFromApi();
47
+ try {
48
+ return await this.httpClient.fetch({
49
+ url: {
50
+ appName: constants.APP_NAME,
51
+ path: MembershipsService.API_PATHS.UPSERT_MEMBERSHIPS.replace('{accountId}', accountId.toString()),
52
+ },
53
+ method: 'PUT',
54
+ query: {
55
+ useAStyleRoleId: 'true',
56
+ },
57
+ headers: {
58
+ 'Content-Type': 'application/json',
59
+ ...attributionHeaders,
60
+ },
61
+ body: JSON.stringify({ memberships }),
62
+ }, this.fetchOptions);
63
+ }
64
+ catch (err) {
65
+ return utils_apiErrorHandler.handleApiError(err, 'authorization', 'upsertMemberships');
66
+ }
67
+ }
68
+ /**
69
+ * Delete memberships synchronously, performing http call to the authorization MS to delete the given memberships.
70
+ * @param accountId
71
+ * @param memberships - Array of memberships to delete.
72
+ * @returns MembershipDeleteResponse - The affected (deleted) memberships.
73
+ */
74
+ async deleteMemberships(accountId, memberships) {
75
+ const attributionHeaders = attributionsService.getAttributionsFromApi();
76
+ try {
77
+ return await this.httpClient.fetch({
78
+ url: {
79
+ appName: constants.APP_NAME,
80
+ path: MembershipsService.API_PATHS.DELETE_MEMBERSHIPS.replace('{accountId}', accountId.toString()),
81
+ },
82
+ method: 'DELETE',
83
+ query: {
84
+ useAStyleRoleId: 'true',
85
+ },
86
+ headers: {
87
+ 'Content-Type': 'application/json',
88
+ ...attributionHeaders,
89
+ },
90
+ body: JSON.stringify({ memberships }),
91
+ }, this.fetchOptions);
92
+ }
93
+ catch (err) {
94
+ return utils_apiErrorHandler.handleApiError(err, 'authorization', 'deleteMemberships');
95
+ }
96
+ }
97
+ }
98
+
99
+ exports.MembershipsService = MembershipsService;
@@ -1,5 +1,5 @@
1
1
  import { Metric } from '@mondaydotcomorg/monday-observability-kit';
2
- type ApiType = 'platform' | 'graph';
2
+ type ApiType = 'platform' | 'graph' | 'authorization';
3
3
  export type MetricsClient = Pick<typeof Metric, 'distribution' | 'increment'>;
4
4
  interface InitializeMetricsOptions {
5
5
  client?: MetricsClient;
@@ -9,7 +9,7 @@ interface InitializeMetricsOptions {
9
9
  disabled?: boolean;
10
10
  }
11
11
  export declare function initializeMetrics(options: InitializeMetricsOptions): void;
12
- export declare function recordAuthorizationTiming(apiType: ApiType, duration: number): void;
13
- export declare function recordAuthorizationError(apiType: ApiType, statusCode: number): void;
12
+ export declare function recordAuthorizationTiming(apiType: ApiType, duration: number, placement: string): void;
13
+ export declare function recordAuthorizationError(apiType: ApiType, statusCode: number, placement: string): void;
14
14
  export {};
15
15
  //# sourceMappingURL=metrics-service.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"metrics-service.d.ts","sourceRoot":"","sources":["../src/metrics-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAGnE,KAAK,OAAO,GAAG,UAAU,GAAG,OAAO,CAAC;AAEpC,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,MAAM,EAAE,cAAc,GAAG,WAAW,CAAC,CAAC;AAE9E,UAAU,wBAAwB;IAChC,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAID,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,IAAI,CAiCzE;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAmBlF;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAmBnF"}
1
+ {"version":3,"file":"metrics-service.d.ts","sourceRoot":"","sources":["../src/metrics-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAGnE,KAAK,OAAO,GAAG,UAAU,GAAG,OAAO,GAAG,eAAe,CAAC;AAEtD,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,MAAM,EAAE,cAAc,GAAG,WAAW,CAAC,CAAC;AAE9E,UAAU,wBAAwB;IAChC,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAID,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,IAAI,CAiCzE;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAoBrG;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAwBtG"}
@@ -34,35 +34,37 @@ function initializeMetrics(options) {
34
34
  authorizationInternalService.logger.warn({ tag: 'monday-authorization-sdk', error }, 'Failed to initialize metrics');
35
35
  }
36
36
  }
37
- function recordAuthorizationTiming(apiType, duration) {
37
+ function recordAuthorizationTiming(apiType, duration, placement) {
38
38
  if (!metricsClient) {
39
39
  return;
40
40
  }
41
41
  try {
42
- metricsClient.distribution(`authorization.authorizationCheck.${apiType}.duration`, duration);
42
+ metricsClient.distribution(`authorization.authorizationCheck.${apiType}.${placement}.duration`, duration);
43
43
  }
44
44
  catch (error) {
45
45
  authorizationInternalService.logger.warn({
46
46
  tag: 'monday-authorization-sdk',
47
47
  metric: 'authorizationCheckDuration',
48
48
  apiType,
49
+ placement,
49
50
  duration,
50
51
  error,
51
52
  }, 'Failed to emit authorization timing metric');
52
53
  }
53
54
  }
54
- function recordAuthorizationError(apiType, statusCode) {
55
+ function recordAuthorizationError(apiType, statusCode, placement) {
55
56
  if (!metricsClient) {
56
57
  return;
57
58
  }
58
59
  try {
59
- metricsClient.increment(`authorization.authorizationCheck.${apiType}.error`, { statusCode: String(statusCode) }, 1);
60
+ metricsClient.increment(`authorization.authorizationCheck.${apiType}.${placement}.error`, { statusCode: String(statusCode) }, 1);
60
61
  }
61
62
  catch (error) {
62
63
  authorizationInternalService.logger.warn({
63
64
  tag: 'monday-authorization-sdk',
64
65
  metric: 'authorizationCheckError',
65
66
  apiType,
67
+ placement,
66
68
  statusCode,
67
69
  error,
68
70
  }, 'Failed to emit authorization error metric');
@@ -0,0 +1,42 @@
1
+ export interface MembershipForCreate {
2
+ entityId: number;
3
+ entityType: string;
4
+ resourceId: number;
5
+ resourceType: string;
6
+ roleId: number;
7
+ roleType?: string;
8
+ addedById: number;
9
+ }
10
+ export interface MembershipForDelete {
11
+ entityId?: number;
12
+ entityType: string;
13
+ resourceId: number;
14
+ resourceType: string;
15
+ }
16
+ export interface Membership {
17
+ id: number;
18
+ entityId: number;
19
+ entityType: string;
20
+ resourceId: number;
21
+ resourceType: string;
22
+ roleId: number;
23
+ roleType: string;
24
+ addedById: null | number | undefined;
25
+ hops: number;
26
+ isNewRecord: boolean;
27
+ previousValues: Partial<Membership>;
28
+ walVersion: number | null | undefined;
29
+ }
30
+ export interface MembershipCreateResponse {
31
+ memberships: Membership[];
32
+ }
33
+ export interface MembershipDeleteResponse {
34
+ memberships: Membership[];
35
+ }
36
+ export interface MembershipCreateRequest {
37
+ memberships: MembershipForCreate[];
38
+ }
39
+ export interface MembershipDeleteRequest {
40
+ memberships: MembershipForDelete[];
41
+ }
42
+ //# sourceMappingURL=memberships.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memberships.d.ts","sourceRoot":"","sources":["../../src/types/memberships.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,IAAI,GAAG,MAAM,GAAG,SAAS,CAAC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,OAAO,CAAC;IACrB,cAAc,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IACpC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CACvC;AAED,MAAM,WAAW,wBAAwB;IACvC,WAAW,EAAE,UAAU,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,wBAAwB;IACvC,WAAW,EAAE,UAAU,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,mBAAmB,EAAE,CAAC;CACpC;AAED,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,mBAAmB,EAAE,CAAC;CACpC"}
@@ -0,0 +1 @@
1
+
@@ -1,2 +1,2 @@
1
- export declare function handleApiError(err: unknown, apiType: 'platform' | 'graph', placement: string): never;
1
+ export declare function handleApiError(err: unknown, apiType: 'platform' | 'graph' | 'authorization', placement: string): never;
2
2
  //# sourceMappingURL=api-error-handler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"api-error-handler.d.ts","sourceRoot":"","sources":["../../src/utils/api-error-handler.ts"],"names":[],"mappings":"AAIA,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,KAAK,CAgBpG"}
1
+ {"version":3,"file":"api-error-handler.d.ts","sourceRoot":"","sources":["../../src/utils/api-error-handler.ts"],"names":[],"mappings":"AAIA,wBAAgB,cAAc,CAC5B,GAAG,EAAE,OAAO,EACZ,OAAO,EAAE,UAAU,GAAG,OAAO,GAAG,eAAe,EAC/C,SAAS,EAAE,MAAM,GAChB,KAAK,CAgBP"}
@@ -6,13 +6,13 @@ const metricsService = require('../metrics-service.js');
6
6
 
7
7
  function handleApiError(err, apiType, placement) {
8
8
  if (err instanceof mondayFetchApi.HttpFetcherError) {
9
- authorizationInternalService.logger.error({ tag: `${apiType}-api`, status: err.status, error: err.message }, `${apiType.charAt(0).toUpperCase() + apiType.slice(1)} API authorization request failed`);
10
- metricsService.recordAuthorizationError(apiType, err.status);
9
+ authorizationInternalService.logger.error({ tag: `${apiType}-api`, status: err.status, error: err.message }, `${apiType.charAt(0).toUpperCase() + apiType.slice(1)} API ${placement} request failed`);
10
+ metricsService.recordAuthorizationError(apiType, err.status, placement);
11
11
  authorizationInternalService.AuthorizationInternalService.throwOnHttpError(err.status, placement);
12
12
  }
13
13
  else {
14
- authorizationInternalService.logger.error({ tag: `${apiType}-api`, error: err instanceof Error ? err.message : String(err) }, `${apiType.charAt(0).toUpperCase() + apiType.slice(1)} API authorization request failed`);
15
- metricsService.recordAuthorizationError(apiType, 500);
14
+ authorizationInternalService.logger.error({ tag: `${apiType}-api`, error: err instanceof Error ? err.message : String(err) }, `${apiType.charAt(0).toUpperCase() + apiType.slice(1)} API ${placement} request failed`);
15
+ metricsService.recordAuthorizationError(apiType, 500, placement);
16
16
  throw err;
17
17
  }
18
18
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mondaydotcomorg/monday-authorization",
3
- "version": "3.3.2",
3
+ "version": "3.4.0-feature-bashanye-add-membership-create-delete-api-234505c",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "license": "BSD-3-Clause",
@@ -213,7 +213,7 @@ export class AuthorizationService {
213
213
  const { resourceType } = scopeToResource(scope);
214
214
  const isAuthorized = obj.permit.can;
215
215
  sendAuthorizationCheckResponseTimeMetric(resourceType, action, isAuthorized, 200, time);
216
- recordAuthorizationTiming(apiType, time);
216
+ recordAuthorizationTiming(apiType, time, 'canActionInScopeMultiple');
217
217
  }
218
218
 
219
219
  return scopedActionResponseObjects;
@@ -1,4 +1,5 @@
1
1
  import { Api, HttpClient } from '@mondaydotcomorg/trident-backend-api';
2
+ import { signAuthorizationHeader } from '@mondaydotcomorg/monday-jwt';
2
3
  import {
3
4
  ScopedAction,
4
5
  ScopedActionResponseObject,
@@ -17,7 +18,6 @@ import {
17
18
  GraphPermissionReason,
18
19
  } from '../types/graph-api.types';
19
20
  import { scopeToResource } from '../utils/authorization.utils';
20
- import { signAuthorizationHeader } from '@mondaydotcomorg/monday-jwt';
21
21
  import { GRAPH_APP_NAME } from '../constants';
22
22
  import { handleApiError } from '../utils/api-error-handler';
23
23
 
package/src/index.ts CHANGED
@@ -59,6 +59,7 @@ export {
59
59
  export { AuthorizationService, AuthorizeResponse } from './authorization-service';
60
60
  export { AuthorizationAttributesService } from './authorization-attributes-service';
61
61
  export { RolesService } from './roles-service';
62
+ export { MembershipsService } from './memberships';
62
63
  export { AuthorizationObject, Resource, BaseRequest, ResourceGetter, ContextGetter } from './types/general';
63
64
  export {
64
65
  Translation,
@@ -0,0 +1,110 @@
1
+ import { Api, FetcherConfig, HttpClient } from '@mondaydotcomorg/trident-backend-api';
2
+ import { RecursivePartial } from '@mondaydotcomorg/monday-fetch-api';
3
+ import {
4
+ MembershipCreateResponse,
5
+ MembershipDeleteResponse,
6
+ MembershipForCreate,
7
+ MembershipForDelete,
8
+ } from 'types/memberships';
9
+ import { handleApiError } from 'utils/api-error-handler';
10
+ import { getAttributionsFromApi } from './attributions-service';
11
+
12
+ import { APP_NAME, DEFAULT_FETCH_OPTIONS, ERROR_MESSAGES } from './constants';
13
+
14
+ export class MembershipsService {
15
+ private static API_PATHS = {
16
+ UPSERT_MEMBERSHIPS: '/memberships/account/{accountId}',
17
+ DELETE_MEMBERSHIPS: '/memberships/account/{accountId}',
18
+ } as const;
19
+ private httpClient: HttpClient;
20
+ private fetchOptions: RecursivePartial<FetcherConfig>;
21
+
22
+ /**
23
+ * Public constructor to create the MembershipsService instance.
24
+ * @param httpClient The HTTP client to use for API requests, if not provided, the default HTTP client from Api will be used.
25
+ * @param fetchOptions The fetch options to use for API requests, if not provided, the default fetch options will be used.
26
+ */
27
+ constructor(httpClient?: HttpClient, fetchOptions?: RecursivePartial<FetcherConfig>) {
28
+ if (!httpClient) {
29
+ httpClient = Api.getPart('httpClient');
30
+ if (!httpClient) {
31
+ throw new Error(ERROR_MESSAGES.HTTP_CLIENT_NOT_INITIALIZED);
32
+ }
33
+ }
34
+
35
+ if (!fetchOptions) {
36
+ fetchOptions = DEFAULT_FETCH_OPTIONS;
37
+ } else {
38
+ fetchOptions = {
39
+ ...DEFAULT_FETCH_OPTIONS,
40
+ ...fetchOptions,
41
+ };
42
+ }
43
+ this.httpClient = httpClient;
44
+ this.fetchOptions = fetchOptions;
45
+ }
46
+
47
+ /**
48
+ * Upsert memberships synchronously, performing http call to the authorization MS to assign the given memberships.
49
+ * @param accountId
50
+ * @param memberships - Array of memberships to upsert
51
+ * @returns MembershipCreateResponse - The affected (created and updated) memberships.
52
+ */
53
+ async upsertMemberships(accountId: number, memberships: MembershipForCreate[]): Promise<MembershipCreateResponse> {
54
+ const attributionHeaders = getAttributionsFromApi();
55
+ try {
56
+ return await this.httpClient.fetch<MembershipCreateResponse>(
57
+ {
58
+ url: {
59
+ appName: APP_NAME,
60
+ path: MembershipsService.API_PATHS.UPSERT_MEMBERSHIPS.replace('{accountId}', accountId.toString()),
61
+ },
62
+ method: 'PUT',
63
+ query: {
64
+ useAStyleRoleId: 'true',
65
+ },
66
+ headers: {
67
+ 'Content-Type': 'application/json',
68
+ ...attributionHeaders,
69
+ },
70
+ body: JSON.stringify({ memberships }),
71
+ },
72
+ this.fetchOptions
73
+ );
74
+ } catch (err) {
75
+ return handleApiError(err, 'authorization', 'upsertMemberships');
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Delete memberships synchronously, performing http call to the authorization MS to delete the given memberships.
81
+ * @param accountId
82
+ * @param memberships - Array of memberships to delete.
83
+ * @returns MembershipDeleteResponse - The affected (deleted) memberships.
84
+ */
85
+ async deleteMemberships(accountId: number, memberships: MembershipForDelete[]): Promise<MembershipDeleteResponse> {
86
+ const attributionHeaders = getAttributionsFromApi();
87
+ try {
88
+ return await this.httpClient.fetch<MembershipDeleteResponse>(
89
+ {
90
+ url: {
91
+ appName: APP_NAME,
92
+ path: MembershipsService.API_PATHS.DELETE_MEMBERSHIPS.replace('{accountId}', accountId.toString()),
93
+ },
94
+ method: 'DELETE',
95
+ query: {
96
+ useAStyleRoleId: 'true',
97
+ },
98
+ headers: {
99
+ 'Content-Type': 'application/json',
100
+ ...attributionHeaders,
101
+ },
102
+ body: JSON.stringify({ memberships }),
103
+ },
104
+ this.fetchOptions
105
+ );
106
+ } catch (err) {
107
+ return handleApiError(err, 'authorization', 'deleteMemberships');
108
+ }
109
+ }
110
+ }
@@ -1,7 +1,7 @@
1
1
  import { Metric } from '@mondaydotcomorg/monday-observability-kit';
2
2
  import { logger } from './authorization-internal-service';
3
3
 
4
- type ApiType = 'platform' | 'graph';
4
+ type ApiType = 'platform' | 'graph' | 'authorization';
5
5
 
6
6
  export type MetricsClient = Pick<typeof Metric, 'distribution' | 'increment'>;
7
7
 
@@ -50,19 +50,20 @@ export function initializeMetrics(options: InitializeMetricsOptions): void {
50
50
  }
51
51
  }
52
52
 
53
- export function recordAuthorizationTiming(apiType: ApiType, duration: number): void {
53
+ export function recordAuthorizationTiming(apiType: ApiType, duration: number, placement: string): void {
54
54
  if (!metricsClient) {
55
55
  return;
56
56
  }
57
57
 
58
58
  try {
59
- metricsClient.distribution(`authorization.authorizationCheck.${apiType}.duration`, duration);
59
+ metricsClient.distribution(`authorization.authorizationCheck.${apiType}.${placement}.duration`, duration);
60
60
  } catch (error) {
61
61
  logger.warn(
62
62
  {
63
63
  tag: 'monday-authorization-sdk',
64
64
  metric: 'authorizationCheckDuration',
65
65
  apiType,
66
+ placement,
66
67
  duration,
67
68
  error,
68
69
  },
@@ -71,19 +72,24 @@ export function recordAuthorizationTiming(apiType: ApiType, duration: number): v
71
72
  }
72
73
  }
73
74
 
74
- export function recordAuthorizationError(apiType: ApiType, statusCode: number): void {
75
+ export function recordAuthorizationError(apiType: ApiType, statusCode: number, placement: string): void {
75
76
  if (!metricsClient) {
76
77
  return;
77
78
  }
78
79
 
79
80
  try {
80
- metricsClient.increment(`authorization.authorizationCheck.${apiType}.error`, { statusCode: String(statusCode) }, 1);
81
+ metricsClient.increment(
82
+ `authorization.authorizationCheck.${apiType}.${placement}.error`,
83
+ { statusCode: String(statusCode) },
84
+ 1
85
+ );
81
86
  } catch (error) {
82
87
  logger.warn(
83
88
  {
84
89
  tag: 'monday-authorization-sdk',
85
90
  metric: 'authorizationCheckError',
86
91
  apiType,
92
+ placement,
87
93
  statusCode,
88
94
  error,
89
95
  },
@@ -0,0 +1,47 @@
1
+ export interface MembershipForCreate {
2
+ entityId: number; // Which entity has the role?
3
+ entityType: string; // What type of entity is this?
4
+ resourceId: number; // What resource is this membership for?
5
+ resourceType: string; // What type of resource is this membership for?
6
+ roleId: number; // What role is this membership for?
7
+ roleType?: string; // What type of role is this membership for? (basic/custom)
8
+ addedById: number; // Who added this membership? for logging purposes
9
+ }
10
+
11
+ export interface MembershipForDelete {
12
+ entityId?: number; // Which entity has the role? omit to delete all memberships to the resource
13
+ entityType: string; // What type of entity is this?
14
+ resourceId: number; // What resource is this membership for?
15
+ resourceType: string; // What type of resource is this membership for?
16
+ }
17
+
18
+ export interface Membership {
19
+ id: number;
20
+ entityId: number;
21
+ entityType: string;
22
+ resourceId: number;
23
+ resourceType: string;
24
+ roleId: number;
25
+ roleType: string;
26
+ addedById: null | number | undefined;
27
+ hops: number;
28
+ isNewRecord: boolean;
29
+ previousValues: Partial<Membership>;
30
+ walVersion: number | null | undefined;
31
+ }
32
+
33
+ export interface MembershipCreateResponse {
34
+ memberships: Membership[];
35
+ }
36
+
37
+ export interface MembershipDeleteResponse {
38
+ memberships: Membership[];
39
+ }
40
+
41
+ export interface MembershipCreateRequest {
42
+ memberships: MembershipForCreate[];
43
+ }
44
+
45
+ export interface MembershipDeleteRequest {
46
+ memberships: MembershipForDelete[];
47
+ }
@@ -2,20 +2,24 @@ import { HttpFetcherError } from '@mondaydotcomorg/monday-fetch-api';
2
2
  import { AuthorizationInternalService, logger } from '../authorization-internal-service';
3
3
  import { recordAuthorizationError } from '../metrics-service';
4
4
 
5
- export function handleApiError(err: unknown, apiType: 'platform' | 'graph', placement: string): never {
5
+ export function handleApiError(
6
+ err: unknown,
7
+ apiType: 'platform' | 'graph' | 'authorization',
8
+ placement: string
9
+ ): never {
6
10
  if (err instanceof HttpFetcherError) {
7
11
  logger.error(
8
12
  { tag: `${apiType}-api`, status: err.status, error: err.message },
9
- `${apiType.charAt(0).toUpperCase() + apiType.slice(1)} API authorization request failed`
13
+ `${apiType.charAt(0).toUpperCase() + apiType.slice(1)} API ${placement} request failed`
10
14
  );
11
- recordAuthorizationError(apiType, err.status);
15
+ recordAuthorizationError(apiType, err.status, placement);
12
16
  AuthorizationInternalService.throwOnHttpError(err.status, placement);
13
17
  } else {
14
18
  logger.error(
15
19
  { tag: `${apiType}-api`, error: err instanceof Error ? err.message : String(err) },
16
- `${apiType.charAt(0).toUpperCase() + apiType.slice(1)} API authorization request failed`
20
+ `${apiType.charAt(0).toUpperCase() + apiType.slice(1)} API ${placement} request failed`
17
21
  );
18
- recordAuthorizationError(apiType, 500);
22
+ recordAuthorizationError(apiType, 500, placement);
19
23
  throw err;
20
24
  }
21
25
  }