@mondaydotcomorg/monday-authorization 1.0.53 → 1.1.1
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/CHANGELOG.md +18 -0
- package/README.md +40 -5
- package/dist/index.js +3 -3
- package/dist/lib/authorization-middleware.js +1 -1
- package/dist/lib/authorization-service.d.ts +2 -2
- package/dist/lib/authorization-service.js +24 -22
- package/dist/lib/types/general.d.ts +10 -10
- package/dist/lib/types/scoped-actions-contracts.d.ts +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Change Log
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
|
6
|
+
and this project adheres to [Semantic Versioning](http://semver.org/).
|
|
7
|
+
|
|
8
|
+
## [1.1.0] - 2023-08-09
|
|
9
|
+
|
|
10
|
+
### ⚠ BREAKING CHANGES
|
|
11
|
+
|
|
12
|
+
- `canActionInScope` now returns an object of type `{ can: boolean; reason: string; }` instead of `boolean`.
|
|
13
|
+
This version is considered minor because no one uses this function yet.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- [`feature/idan/can-action-in-scope/change-behavior-on-error (#3689)`](https://github.com/DaPulse/monday-npm-packages/pull/3689)
|
|
18
|
+
- `canActionInScope`, `canActionInScopeMultiple` and `isAuthorized` are now throwing an error instead of returning `false` when an error occurs as part of the authorization http request (status code is not 2XX)
|
package/README.md
CHANGED
|
@@ -15,6 +15,7 @@ show is the action, post is the resource.
|
|
|
15
15
|
```
|
|
16
16
|
yarn add @mondaydotcomorg/monday-authorization
|
|
17
17
|
```
|
|
18
|
+
|
|
18
19
|
First init the package in order for it to work properly <br>
|
|
19
20
|
app.ts:
|
|
20
21
|
|
|
@@ -34,8 +35,7 @@ startServer(...)
|
|
|
34
35
|
|
|
35
36
|
**Recommended** - optionally init authorization with redisClient so the granted feature results will be cached and reduce http calls.
|
|
36
37
|
|
|
37
|
-
- grantedFeatureRedisExpirationInSeconds - (optional), redis TTL for cached granted features, default set to 5 minutes
|
|
38
|
-
|
|
38
|
+
- grantedFeatureRedisExpirationInSeconds - (optional), redis TTL for cached granted features, default set to 5 minutes
|
|
39
39
|
|
|
40
40
|
## Usage
|
|
41
41
|
|
|
@@ -90,14 +90,49 @@ Add `authorizationCheckMiddleware` to make sure that all routes are covered by a
|
|
|
90
90
|
middleware before you define the routes.
|
|
91
91
|
If you want to skip authorization, use `skipAuthorizationMiddleware`.
|
|
92
92
|
|
|
93
|
-
|
|
94
93
|
### Granted Features API
|
|
95
94
|
|
|
96
95
|
Use `AuthorizationService.isUserGrantedWithFeature(accountId: number, userId: number, featureName: string): Promise<boolean>` to retrieve if a user is granted with a feature
|
|
97
96
|
|
|
98
97
|
```ts
|
|
99
|
-
import {AuthorizationService, init} from '@mondaydotcomorg/monday-authorization';
|
|
98
|
+
import { AuthorizationService, init } from '@mondaydotcomorg/monday-authorization';
|
|
100
99
|
|
|
101
|
-
|
|
100
|
+
const isFeatuerGranted = await AuthorizationService.isUserGrantedWithFeature(
|
|
101
|
+
739628,
|
|
102
|
+
2,
|
|
103
|
+
'unauthorized_content_change_message'
|
|
104
|
+
);
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### IsAuthorized API
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
const authorizationRequestObject = { resource_type: 'board', resource_id: 1234, action: 'show' };
|
|
111
|
+
const isAuthorizedResponse = await AuthorizationService.isAuthorized(accountId, userId, [authorizationRequestObject]);
|
|
112
|
+
const isAuthorized = isAuthorizedResponse.isAuthorized;
|
|
102
113
|
```
|
|
103
114
|
|
|
115
|
+
### canActionInScope API
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import { AuthorizationService, ScopedActionResponseObject } from '@mondaydotcomorg/monday-authorization';
|
|
119
|
+
|
|
120
|
+
const { can, reason } = await AuthorizationService.canActionInScope(accountId, userId, 'create_main_boards', {
|
|
121
|
+
workspaceId: 101,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// or in plural way
|
|
125
|
+
const canActionInScopeMultipleResponse: ScopedActionResponseObject[] =
|
|
126
|
+
await AuthorizationService.canActionInScopeMultiple(accountId, userId, [
|
|
127
|
+
{ action: 'create_main_boards', scope: { workspaceId: 101 } },
|
|
128
|
+
{ action: 'create_private_boards', scope: { workspaceId: 102 } },
|
|
129
|
+
]);
|
|
130
|
+
|
|
131
|
+
/***
|
|
132
|
+
* canActionInScopeMultipleResponse is an array containing:
|
|
133
|
+
* [
|
|
134
|
+
* { scopedAction: { action: 'create_main_boards', scope: { workspaceId: 101 } }, permit: { can: true, reason: { key: '' } } },
|
|
135
|
+
* { scopedAction: { action: 'create_private_boards', scope: { workspaceId: 102 } }, permit: { can: false, reason: { key: 'ms-authorization.enforcement.reasons.by_role_in_scope' } } }
|
|
136
|
+
* ]
|
|
137
|
+
* /
|
|
138
|
+
```
|
package/dist/index.js
CHANGED
|
@@ -5,13 +5,13 @@ const prometheus_service_1 = require("./lib/prometheus-service");
|
|
|
5
5
|
const authorization_service_1 = require("./lib/authorization-service");
|
|
6
6
|
function init(options = {}) {
|
|
7
7
|
if (options.prometheus) {
|
|
8
|
-
prometheus_service_1.setPrometheus(options.prometheus);
|
|
8
|
+
(0, prometheus_service_1.setPrometheus)(options.prometheus);
|
|
9
9
|
}
|
|
10
10
|
if (options.mondayFetchOptions) {
|
|
11
|
-
authorization_service_1.setRequestFetchOptions(options.mondayFetchOptions);
|
|
11
|
+
(0, authorization_service_1.setRequestFetchOptions)(options.mondayFetchOptions);
|
|
12
12
|
}
|
|
13
13
|
if (options.redisClient) {
|
|
14
|
-
authorization_service_1.setRedisClient(options.redisClient, options.grantedFeatureRedisExpirationInSeconds);
|
|
14
|
+
(0, authorization_service_1.setRedisClient)(options.redisClient, options.grantedFeatureRedisExpirationInSeconds);
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
exports.init = init;
|
|
@@ -40,7 +40,7 @@ function skipAuthorizationMiddleware(request, response, next) {
|
|
|
40
40
|
exports.skipAuthorizationMiddleware = skipAuthorizationMiddleware;
|
|
41
41
|
function authorizationCheckMiddleware(request, response, next) {
|
|
42
42
|
if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test') {
|
|
43
|
-
on_headers_1.default(response, function () {
|
|
43
|
+
(0, on_headers_1.default)(response, function () {
|
|
44
44
|
if (response.statusCode < 400) {
|
|
45
45
|
authorization_internal_service_1.AuthorizationInternalService.failIfNotCoveredByAuthorization(request);
|
|
46
46
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MondayFetchOptions } from '@mondaydotcomorg/monday-fetch';
|
|
2
2
|
import { Action, AuthorizationObject, Resource } from './types/general';
|
|
3
|
-
import { ScopedAction, ScopedActionResponseObject, ScopeOptions } from 'lib/types/scoped-actions-contracts';
|
|
3
|
+
import { ScopedAction, ScopedActionPermit, ScopedActionResponseObject, ScopeOptions } from 'lib/types/scoped-actions-contracts';
|
|
4
4
|
export interface AuthorizeResponse {
|
|
5
5
|
isAuthorized: boolean;
|
|
6
6
|
unauthorizedIds?: number[];
|
|
@@ -19,7 +19,7 @@ export declare class AuthorizationService {
|
|
|
19
19
|
static isUserGrantedWithFeature(accountId: number, userId: number, featureName: string): Promise<boolean>;
|
|
20
20
|
private static fetchIsUserGrantedWithFeature;
|
|
21
21
|
private static getCachedKeyName;
|
|
22
|
-
static canActionInScope(accountId: number, userId: number, action: string, scope: ScopeOptions): Promise<
|
|
22
|
+
static canActionInScope(accountId: number, userId: number, action: string, scope: ScopeOptions): Promise<ScopedActionPermit>;
|
|
23
23
|
static canActionInScopeMultiple(accountId: number, userId: number, scopedActions: ScopedAction[]): Promise<ScopedActionResponseObject[]>;
|
|
24
24
|
private static isAuthorizedSingular;
|
|
25
25
|
private static isAuthorizedMultiple;
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
3
|
if (k2 === undefined) k2 = k;
|
|
4
|
-
Object.
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
5
9
|
}) : (function(o, m, k, k2) {
|
|
6
10
|
if (k2 === undefined) k2 = k;
|
|
7
11
|
o[k2] = m[k];
|
|
@@ -106,16 +110,16 @@ class AuthorizationService {
|
|
|
106
110
|
return __awaiter(this, void 0, void 0, function* () {
|
|
107
111
|
const scopedActions = [{ action, scope }];
|
|
108
112
|
const scopedActionResponseObjects = yield this.canActionInScopeMultiple(accountId, userId, scopedActions);
|
|
109
|
-
return scopedActionResponseObjects[0].permit
|
|
113
|
+
return scopedActionResponseObjects[0].permit;
|
|
110
114
|
});
|
|
111
115
|
}
|
|
112
116
|
static canActionInScopeMultiple(accountId, userId, scopedActions) {
|
|
113
117
|
return __awaiter(this, void 0, void 0, function* () {
|
|
114
|
-
const internalAuthToken = monday_jwt_1.signAuthorizationHeader({ appName: INTERNAL_APP_NAME, accountId, userId });
|
|
115
|
-
const scopedActionsPayload = scopedActions.map(
|
|
116
|
-
return Object.assign(Object.assign({}, scopedAction), { scope: lodash_1.mapKeys(scopedAction.scope, (_, key) => lodash_1.snakeCase(key)) }); // for example: { workspaceId: 1 } => { workspace_id: 1 }
|
|
118
|
+
const internalAuthToken = (0, monday_jwt_1.signAuthorizationHeader)({ appName: INTERNAL_APP_NAME, accountId, userId });
|
|
119
|
+
const scopedActionsPayload = scopedActions.map(scopedAction => {
|
|
120
|
+
return Object.assign(Object.assign({}, scopedAction), { scope: (0, lodash_1.mapKeys)(scopedAction.scope, (_, key) => (0, lodash_1.snakeCase)(key)) }); // for example: { workspaceId: 1 } => { workspace_id: 1 }
|
|
117
121
|
});
|
|
118
|
-
const response = yield monday_fetch_1.fetch(getCanActionsInScopesUrl(), {
|
|
122
|
+
const response = yield (0, monday_fetch_1.fetch)(getCanActionsInScopesUrl(), {
|
|
119
123
|
method: 'POST',
|
|
120
124
|
headers: { Authorization: internalAuthToken, 'Content-Type': 'application/json' },
|
|
121
125
|
timeout: getRequestTimeout(),
|
|
@@ -124,10 +128,7 @@ class AuthorizationService {
|
|
|
124
128
|
scoped_actions: scopedActionsPayload,
|
|
125
129
|
}),
|
|
126
130
|
}, mondayFetchOptions);
|
|
127
|
-
|
|
128
|
-
logger.error({ status: response.status }, 'AuthorizationService: authorization request failed - canActionInScopeMultiple');
|
|
129
|
-
return scopedActions.map((scopedAction) => ({ scopedAction, permit: { can: false, reason: { key: 'internal error' } } }));
|
|
130
|
-
}
|
|
131
|
+
throwOnHttpErrorIfNeeded(response, 'canActionInScopeMultiple');
|
|
131
132
|
const responseBody = yield response.json();
|
|
132
133
|
return responseBody.result;
|
|
133
134
|
});
|
|
@@ -140,9 +141,9 @@ class AuthorizationService {
|
|
|
140
141
|
}
|
|
141
142
|
static isAuthorizedMultiple(accountId, userId, authorizationRequestObjects) {
|
|
142
143
|
return __awaiter(this, void 0, void 0, function* () {
|
|
143
|
-
const internalAuthToken = monday_jwt_1.signAuthorizationHeader({ appName: INTERNAL_APP_NAME, accountId, userId });
|
|
144
|
+
const internalAuthToken = (0, monday_jwt_1.signAuthorizationHeader)({ appName: INTERNAL_APP_NAME, accountId, userId });
|
|
144
145
|
const startTime = perf_hooks_1.performance.now();
|
|
145
|
-
const response = yield monday_fetch_1.fetch(getAuthorizeUrl(), {
|
|
146
|
+
const response = yield (0, monday_fetch_1.fetch)(getAuthorizeUrl(), {
|
|
146
147
|
method: 'POST',
|
|
147
148
|
headers: { Authorization: internalAuthToken, 'Content-Type': 'application/json' },
|
|
148
149
|
timeout: getRequestTimeout(),
|
|
@@ -154,15 +155,8 @@ class AuthorizationService {
|
|
|
154
155
|
const endTime = perf_hooks_1.performance.now();
|
|
155
156
|
const time = endTime - startTime;
|
|
156
157
|
const responseStatus = response.status;
|
|
157
|
-
prometheus_service_1.sendAuthorizationChecksPerRequestMetric(responseStatus, authorizationRequestObjects.length);
|
|
158
|
-
|
|
159
|
-
logger.error({ status: response.status }, 'AuthorizationService: authorization request failed');
|
|
160
|
-
const isAuthorized = false;
|
|
161
|
-
authorizationRequestObjects.forEach(function (authorizationObject) {
|
|
162
|
-
prometheus_service_1.sendAuthorizationCheckResponseTimeMetric(authorizationObject.resource_type, authorizationObject.action, isAuthorized, responseStatus, time);
|
|
163
|
-
});
|
|
164
|
-
return { isAuthorized };
|
|
165
|
-
}
|
|
158
|
+
(0, prometheus_service_1.sendAuthorizationChecksPerRequestMetric)(responseStatus, authorizationRequestObjects.length);
|
|
159
|
+
throwOnHttpErrorIfNeeded(response, 'isAuthorizedMultiple');
|
|
166
160
|
const responseBody = yield response.json();
|
|
167
161
|
const unauthorizedObjects = [];
|
|
168
162
|
responseBody.result.forEach(function (isAuthorized, index) {
|
|
@@ -170,7 +164,7 @@ class AuthorizationService {
|
|
|
170
164
|
if (!isAuthorized) {
|
|
171
165
|
unauthorizedObjects.push(authorizationObject);
|
|
172
166
|
}
|
|
173
|
-
prometheus_service_1.sendAuthorizationCheckResponseTimeMetric(authorizationObject.resource_type, authorizationObject.action, isAuthorized, responseStatus, time);
|
|
167
|
+
(0, prometheus_service_1.sendAuthorizationCheckResponseTimeMetric)(authorizationObject.resource_type, authorizationObject.action, isAuthorized, responseStatus, time);
|
|
174
168
|
});
|
|
175
169
|
if (unauthorizedObjects.length > 0) {
|
|
176
170
|
logger.info({
|
|
@@ -220,3 +214,11 @@ function getRequestTimeout() {
|
|
|
220
214
|
const isDevEnv = process.env.NODE_ENV === 'development';
|
|
221
215
|
return isDevEnv ? 60000 : 2000;
|
|
222
216
|
}
|
|
217
|
+
function throwOnHttpErrorIfNeeded(response, placement) {
|
|
218
|
+
if (response.ok) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const status = response.status;
|
|
222
|
+
logger.error({ tag: 'authorization-service', placement, status }, 'AuthorizationService: authorization request failed');
|
|
223
|
+
throw new Error(`AuthorizationService: [${placement}] authorization request failed with status ${status}`);
|
|
224
|
+
}
|
|
@@ -4,13 +4,13 @@ export interface Resource {
|
|
|
4
4
|
type: string;
|
|
5
5
|
wrapperData?: object;
|
|
6
6
|
}
|
|
7
|
-
export
|
|
8
|
-
export
|
|
7
|
+
export type Action = string;
|
|
8
|
+
export type ResourceGetter = (request: BaseRequest) => Resource[];
|
|
9
9
|
export interface Context {
|
|
10
10
|
accountId: number;
|
|
11
11
|
userId: number;
|
|
12
12
|
}
|
|
13
|
-
export
|
|
13
|
+
export type ContextGetter = (request: BaseRequest) => Context;
|
|
14
14
|
export interface AuthorizationObject {
|
|
15
15
|
resource_id?: Resource['id'];
|
|
16
16
|
resource_type: Resource['type'];
|
|
@@ -20,11 +20,11 @@ export interface AuthorizationObject {
|
|
|
20
20
|
export interface AuthorizationParams {
|
|
21
21
|
authorizationObjects: AuthorizationObject[];
|
|
22
22
|
}
|
|
23
|
-
|
|
24
|
-
export
|
|
25
|
-
export
|
|
26
|
-
export
|
|
27
|
-
export
|
|
28
|
-
export
|
|
29
|
-
export
|
|
23
|
+
type BasicObject = {};
|
|
24
|
+
export type BaseParameters = BasicObject;
|
|
25
|
+
export type BaseResponseBody = BasicObject;
|
|
26
|
+
export type BaseBodyParameters = BasicObject;
|
|
27
|
+
export type BaseQueryParameters = BasicObject;
|
|
28
|
+
export type BaseRequest = Request<BaseParameters, BaseResponseBody, BaseBodyParameters, BaseQueryParameters>;
|
|
29
|
+
export type BaseResponse = Response<BaseResponseBody>;
|
|
30
30
|
export {};
|
|
@@ -13,7 +13,7 @@ export interface AccountProductScope {
|
|
|
13
13
|
export interface AccountScope {
|
|
14
14
|
accountId: number;
|
|
15
15
|
}
|
|
16
|
-
export
|
|
16
|
+
export type ScopeOptions = WorkspaceScope | BoardScope | PulseScope | AccountProductScope | AccountScope;
|
|
17
17
|
export interface Translation {
|
|
18
18
|
key: string;
|
|
19
19
|
[option: string]: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mondaydotcomorg/monday-authorization",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"license": "BSD-3-Clause",
|
|
@@ -27,10 +27,10 @@
|
|
|
27
27
|
"on-headers": "^1.0.2",
|
|
28
28
|
"supertest": "^6.1.3",
|
|
29
29
|
"tsconfig-paths": "^3.9.0",
|
|
30
|
-
"typescript": "^
|
|
30
|
+
"typescript": "^5.1.6"
|
|
31
31
|
},
|
|
32
32
|
"files": [
|
|
33
33
|
"dist/"
|
|
34
34
|
],
|
|
35
|
-
"gitHead": "
|
|
35
|
+
"gitHead": "e9d53044c34ad17d0a2d3c347360d59ef70574c4"
|
|
36
36
|
}
|