mirror of
https://github.com/sasjs/server.git
synced 2025-12-10 19:34:34 +00:00
Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e67d27d264 | ||
|
|
53033ccc96 | ||
|
|
6131ed1cbe | ||
|
|
5d624e3399 | ||
| ee17d37aa1 | |||
|
|
476f834a80 | ||
|
|
8b8739a873 | ||
| bce83cb6fb | |||
| 3a3c90d9e6 | |||
|
|
e63eaa5302 | ||
|
|
65de1bb175 | ||
|
|
a5ee2f2923 | ||
| 98ea2ac9b9 | |||
|
|
e94c56b23f | ||
|
|
64f80e958d | ||
| bd97363c13 | |||
| 02e88ae728 | |||
| 882bedd5d5 | |||
| 8780b800a3 | |||
| 4c11082796 | |||
| a9b25b8880 | |||
| b06993ab9e | |||
|
|
f736e67517 | ||
|
|
0f4a60c0c7 | ||
|
|
f8bb7327a8 | ||
|
|
abce135da2 | ||
|
|
a6c014946a | ||
| f27ac51fc4 | |||
|
|
cb5be1be21 | ||
|
|
d90fa9e5dd | ||
| d99fdd1ec7 | |||
|
|
399b5edad0 | ||
|
|
1dbc12e96b | ||
| e215958b8b | |||
| 9227cd449d | |||
| c67d3ee2f1 | |||
| 6ef40b954a | |||
|
|
0d913baff1 | ||
|
|
3671736c3d | ||
| 34cd84d8a9 | |||
|
|
f7fcc7741a | ||
|
|
18052fdbf6 | ||
|
|
5966016853 | ||
|
|
87c03c5f8d | ||
| 7a162eda8f | |||
| 754704bca8 | |||
|
|
77f8d30baf | ||
|
|
78bea7c154 | ||
|
|
9c3b155c12 | ||
|
|
98e501334f | ||
|
|
bbfd53e79e | ||
| 254bc07da7 | |||
| f978814ca7 | |||
| 68515f95a6 | |||
| d3a516c36e | |||
| c3e3befc17 | |||
|
|
275de9478e | ||
|
|
1a3ef62cb2 | ||
| 79b7827b7c | |||
| 37e1aa9b61 | |||
| 7e504008b7 | |||
| 5d5a9d3788 | |||
| b7dff341f0 | |||
| 8a3054e19a |
116
CHANGELOG.md
116
CHANGELOG.md
@@ -1,3 +1,119 @@
|
|||||||
|
## [0.17.2](https://github.com/sasjs/server/compare/v0.17.1...v0.17.2) (2022-08-31)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* addition of SASINITIALFOLDER startup option. Closes [#260](https://github.com/sasjs/server/issues/260) ([a5ee2f2](https://github.com/sasjs/server/commit/a5ee2f292384f90e9d95d003d652311c0d91a7a7))
|
||||||
|
|
||||||
|
## [0.17.1](https://github.com/sasjs/server/compare/v0.17.0...v0.17.1) (2022-08-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* typo mistake ([ee17d37](https://github.com/sasjs/server/commit/ee17d37aa188b0ca43cea0e89d6cd1a566b765cb))
|
||||||
|
|
||||||
|
# [0.17.0](https://github.com/sasjs/server/compare/v0.16.1...v0.17.0) (2022-08-25)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* allow underscores in file name ([bce83cb](https://github.com/sasjs/server/commit/bce83cb6fbc98f8198564c9399821f5829acc767))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add the functionality of saving file by ctrl + s in editor ([3a3c90d](https://github.com/sasjs/server/commit/3a3c90d9e690ac5267bf1acc834b5b5c5b4dadb6))
|
||||||
|
|
||||||
|
## [0.16.1](https://github.com/sasjs/server/compare/v0.16.0...v0.16.1) (2022-08-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* update response of /SASjsApi/stp/execute and /SASjsApi/code/execute ([98ea2ac](https://github.com/sasjs/server/commit/98ea2ac9b98631605e39e5900e533727ea0e3d85))
|
||||||
|
|
||||||
|
# [0.16.0](https://github.com/sasjs/server/compare/v0.15.3...v0.16.0) (2022-08-17)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add a new variable _SASJS_WEBOUT_HEADERS to code.js and code.py ([882bedd](https://github.com/sasjs/server/commit/882bedd5d5da22de6ed45c03d0a261aadfb3a33c))
|
||||||
|
* update content for code.sas file ([02e88ae](https://github.com/sasjs/server/commit/02e88ae7280d020a753bc2c095a931c79ac392d1))
|
||||||
|
* update default content type for python and js runtimes ([8780b80](https://github.com/sasjs/server/commit/8780b800a34aa618631821e5d97e26e8b0f15806))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* implement the logic for running python stored programs ([b06993a](https://github.com/sasjs/server/commit/b06993ab9ea24b28d9e553763187387685aaa666))
|
||||||
|
|
||||||
|
## [0.15.3](https://github.com/sasjs/server/compare/v0.15.2...v0.15.3) (2022-08-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* adding proc printto in precode to enable print output in log. Closes [#253](https://github.com/sasjs/server/issues/253) ([f8bb732](https://github.com/sasjs/server/commit/f8bb7327a8a4649ac77bb6237e31cea075d46bb9))
|
||||||
|
|
||||||
|
## [0.15.2](https://github.com/sasjs/server/compare/v0.15.1...v0.15.2) (2022-08-10)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* remove vulnerabitities ([f27ac51](https://github.com/sasjs/server/commit/f27ac51fc4beb21070d0ab551cfdaec1f6ba39e0))
|
||||||
|
|
||||||
|
## [0.15.1](https://github.com/sasjs/server/compare/v0.15.0...v0.15.1) (2022-08-10)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **web:** fix UI responsiveness ([d99fdd1](https://github.com/sasjs/server/commit/d99fdd1ec7991b94a0d98338d7a7a6216f46ce45))
|
||||||
|
|
||||||
|
# [0.15.0](https://github.com/sasjs/server/compare/v0.14.1...v0.15.0) (2022-08-05)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* after selecting file in sidebar collapse sidebar in mobile view ([e215958](https://github.com/sasjs/server/commit/e215958b8b05d7a8ce9d82395e0640b5b37fb40d))
|
||||||
|
* improve mobile view for studio page ([c67d3ee](https://github.com/sasjs/server/commit/c67d3ee2f102155e2e9781e13d5d33c1ab227cb4))
|
||||||
|
* improve responsiveness for mobile view ([6ef40b9](https://github.com/sasjs/server/commit/6ef40b954a87ebb0a2621119064f38d58ea85148))
|
||||||
|
* improve user experience for adding permissions ([7a162ed](https://github.com/sasjs/server/commit/7a162eda8fc60383ff647d93e6611799e2e6af7a))
|
||||||
|
* show logout button only when user is logged in ([9227cd4](https://github.com/sasjs/server/commit/9227cd449dc46fd960a488eb281804a9b9ffc284))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add multiple permission for same combination of type and principal at once ([754704b](https://github.com/sasjs/server/commit/754704bca89ecbdbcc3bd4ef04b94124c4f24167))
|
||||||
|
|
||||||
|
## [0.14.1](https://github.com/sasjs/server/compare/v0.14.0...v0.14.1) (2022-08-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **apps:** App Stream logo fix ([87c03c5](https://github.com/sasjs/server/commit/87c03c5f8dbdfc151d4ff3722ecbcd3f7e409aea))
|
||||||
|
* **cookie:** XSRF cookie is removed and passed token in head section ([77f8d30](https://github.com/sasjs/server/commit/77f8d30baf9b1077279c29f1c3e5ca02a5436bc0))
|
||||||
|
* **env:** check added for not providing WHITELIST ([5966016](https://github.com/sasjs/server/commit/5966016853369146b27ac5781808cb51d65c887f))
|
||||||
|
* **web:** show login on logged-out state ([f7fcc77](https://github.com/sasjs/server/commit/f7fcc7741aa2af93a4a2b1e651003704c9bbff0c))
|
||||||
|
|
||||||
|
# [0.14.0](https://github.com/sasjs/server/compare/v0.13.3...v0.14.0) (2022-08-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add restriction on add/remove user to public group ([d3a516c](https://github.com/sasjs/server/commit/d3a516c36e45aa1cc76c30c744e6a0e5bd553165))
|
||||||
|
* call jwt.verify in synchronous way ([254bc07](https://github.com/sasjs/server/commit/254bc07da744a9708109bfb792be70aa3f6284f4))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add public group to DB on seed ([c3e3bef](https://github.com/sasjs/server/commit/c3e3befc17102ee1754e1403193040b4f79fb2a7))
|
||||||
|
* bypass authentication when route is enabled for public group ([68515f9](https://github.com/sasjs/server/commit/68515f95a65d422e29c0ed6028f3ea0ae8d9b1bf))
|
||||||
|
|
||||||
|
## [0.13.3](https://github.com/sasjs/server/compare/v0.13.2...v0.13.3) (2022-08-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* show non-admin user his own permissions only ([8a3054e](https://github.com/sasjs/server/commit/8a3054e19ade82e2792cfb0f2a8af9e502c5eb52))
|
||||||
|
* update schema of Permission ([5d5a9d3](https://github.com/sasjs/server/commit/5d5a9d3788281d75c56f68f0dff231abc9c9c275))
|
||||||
|
|
||||||
## [0.13.2](https://github.com/sasjs/server/compare/v0.13.1...v0.13.2) (2022-08-01)
|
## [0.13.2](https://github.com/sasjs/server/compare/v0.13.1...v0.13.2) (2022-08-01)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
22
README.md
22
README.md
@@ -64,12 +64,27 @@ Example contents of a `.env` file:
|
|||||||
# Server mode is multi-user and suitable for intranet / internet use
|
# Server mode is multi-user and suitable for intranet / internet use
|
||||||
MODE=
|
MODE=
|
||||||
|
|
||||||
|
# A comma separated string that defines the available runTimes.
|
||||||
|
# Priority is given to the runtime that comes first in the string.
|
||||||
|
# Possible options at the moment are sas and js
|
||||||
|
|
||||||
|
# This string sets the priority of the available analytic runtimes
|
||||||
|
# Valid runtimes are SAS (sas), JavaScript (js) and Python (py)
|
||||||
|
# For each option provided, there should be a corresponding path,
|
||||||
|
# eg SAS_PATH, NODE_PATH or PYTHON_PATH
|
||||||
|
# Priority is given to runtimes earlier in the string
|
||||||
|
# Example options: [sas,js,py | js,py | sas | sas,js]
|
||||||
|
RUN_TIMES=
|
||||||
|
|
||||||
# Path to SAS executable (sas.exe / sas.sh)
|
# Path to SAS executable (sas.exe / sas.sh)
|
||||||
SAS_PATH=/path/to/sas/executable.exe
|
SAS_PATH=/path/to/sas/executable.exe
|
||||||
|
|
||||||
# Path to Node.js executable
|
# Path to Node.js executable
|
||||||
NODE_PATH=~/.nvm/versions/node/v16.14.0/bin/node
|
NODE_PATH=~/.nvm/versions/node/v16.14.0/bin/node
|
||||||
|
|
||||||
|
# Path to Python executable
|
||||||
|
PYTHON_PATH=/usr/bin/python
|
||||||
|
|
||||||
# Path to working directory
|
# Path to working directory
|
||||||
# This location is for SAS WORK, staged files, DRIVE, configuration etc
|
# This location is for SAS WORK, staged files, DRIVE, configuration etc
|
||||||
SASJS_ROOT=./sasjs_root
|
SASJS_ROOT=./sasjs_root
|
||||||
@@ -139,13 +154,6 @@ LOG_FORMAT_MORGAN=
|
|||||||
# This location is for server logs with classical UNIX logrotate behavior
|
# This location is for server logs with classical UNIX logrotate behavior
|
||||||
LOG_LOCATION=./sasjs_root/logs
|
LOG_LOCATION=./sasjs_root/logs
|
||||||
|
|
||||||
# A comma separated string that defines the available runTimes.
|
|
||||||
# Priority is given to the runtime that comes first in the string.
|
|
||||||
# Possible options at the moment are sas and js
|
|
||||||
|
|
||||||
# options: [sas,js|js,sas|sas|js] default:sas
|
|
||||||
RUN_TIMES=
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Persisting the Session
|
## Persisting the Session
|
||||||
|
|||||||
@@ -14,9 +14,10 @@ HELMET_COEP=[true|false] if omitted HELMET default will be used
|
|||||||
|
|
||||||
DB_CONNECT=mongodb+srv://<DB_USERNAME>:<DB_PASSWORD>@<CLUSTER>/<DB_NAME>?retryWrites=true&w=majority
|
DB_CONNECT=mongodb+srv://<DB_USERNAME>:<DB_PASSWORD>@<CLUSTER>/<DB_NAME>?retryWrites=true&w=majority
|
||||||
|
|
||||||
RUN_TIMES=[sas|js|sas,js|js,sas] default considered as sas
|
RUN_TIMES=[sas,js,py | js,py | sas | sas,js] default considered as sas
|
||||||
SAS_PATH=/opt/sas/sas9/SASHome/SASFoundation/9.4/sas
|
SAS_PATH=/opt/sas/sas9/SASHome/SASFoundation/9.4/sas
|
||||||
NODE_PATH=~/.nvm/versions/node/v16.14.0/bin/node
|
NODE_PATH=~/.nvm/versions/node/v16.14.0/bin/node
|
||||||
|
PYTHON_PATH=/usr/bin/python
|
||||||
|
|
||||||
SASJS_ROOT=./sasjs_root
|
SASJS_ROOT=./sasjs_root
|
||||||
|
|
||||||
|
|||||||
503
api/package-lock.json
generated
503
api/package-lock.json
generated
@@ -23,7 +23,7 @@
|
|||||||
"mongoose": "^6.0.12",
|
"mongoose": "^6.0.12",
|
||||||
"mongoose-sequence": "^5.3.1",
|
"mongoose-sequence": "^5.3.1",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"multer": "^1.4.3",
|
"multer": "^1.4.5-lts.1",
|
||||||
"rotating-file-stream": "^3.0.4",
|
"rotating-file-stream": "^3.0.4",
|
||||||
"swagger-ui-express": "4.3.0",
|
"swagger-ui-express": "4.3.0",
|
||||||
"unzipper": "^0.10.11",
|
"unzipper": "^0.10.11",
|
||||||
@@ -2184,9 +2184,9 @@
|
|||||||
"integrity": "sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q=="
|
"integrity": "sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q=="
|
||||||
},
|
},
|
||||||
"node_modules/@types/whatwg-url": {
|
"node_modules/@types/whatwg-url": {
|
||||||
"version": "8.2.1",
|
"version": "8.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz",
|
||||||
"integrity": "sha512-2YubE1sjj5ifxievI5Ge1sckb9k/Er66HyR2c+3+I6VDUUg1TLPdYYTEbQ+DjRkS4nTxMJhgWfSfMRD2sl2EYQ==",
|
"integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
"@types/webidl-conversions": "*"
|
"@types/webidl-conversions": "*"
|
||||||
@@ -2834,9 +2834,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/bson": {
|
"node_modules/bson": {
|
||||||
"version": "4.5.4",
|
"version": "4.6.5",
|
||||||
"resolved": "https://registry.npmjs.org/bson/-/bson-4.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/bson/-/bson-4.6.5.tgz",
|
||||||
"integrity": "sha512-wIt0bPACnx8Ju9r6IsS2wVtGDHBr9Dxb+U29A1YED2pu8XOhS8aKjOnLZ8sxyXkPwanoK7iWWVhS1+coxde6xA==",
|
"integrity": "sha512-uqrgcjyOaZsHfz7ea8zLRCLe1u+QGUSzMZmvXqO24CDW7DWoW1qiN9folSwa7hSneTSgM2ykDIzF5kcQQ8cwNw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"buffer": "^5.6.0"
|
"buffer": "^5.6.0"
|
||||||
},
|
},
|
||||||
@@ -2903,38 +2903,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/busboy": {
|
"node_modules/busboy": {
|
||||||
"version": "0.2.14",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz",
|
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
|
||||||
"integrity": "sha512-InWFDomvlkEj+xWLBfU3AvnbVYqeTWmQopiW0tWWEy5yehYm2YkGEc59sUmw/4ty5Zj/b0WHGs1LgecuBSBGrg==",
|
"integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dicer": "0.2.5",
|
"streamsearch": "^1.1.0"
|
||||||
"readable-stream": "1.1.x"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.8.0"
|
"node": ">=10.16.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/busboy/node_modules/isarray": {
|
|
||||||
"version": "0.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
|
||||||
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
|
|
||||||
},
|
|
||||||
"node_modules/busboy/node_modules/readable-stream": {
|
|
||||||
"version": "1.1.14",
|
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
|
|
||||||
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
|
|
||||||
"dependencies": {
|
|
||||||
"core-util-is": "~1.0.0",
|
|
||||||
"inherits": "~2.0.1",
|
|
||||||
"isarray": "0.0.1",
|
|
||||||
"string_decoder": "~0.10.x"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/busboy/node_modules/string_decoder": {
|
|
||||||
"version": "0.10.31",
|
|
||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
|
||||||
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
|
|
||||||
},
|
|
||||||
"node_modules/bytes": {
|
"node_modules/bytes": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
|
||||||
@@ -3566,39 +3544,6 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/dicer": {
|
|
||||||
"version": "0.2.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz",
|
|
||||||
"integrity": "sha512-FDvbtnq7dzlPz0wyYlOExifDEZcu8h+rErEXgfxqmLfRfC/kJidEFh4+effJRO3P0xmfqyPbSMG0LveNRfTKVg==",
|
|
||||||
"dependencies": {
|
|
||||||
"readable-stream": "1.1.x",
|
|
||||||
"streamsearch": "0.1.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.8.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/dicer/node_modules/isarray": {
|
|
||||||
"version": "0.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
|
||||||
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
|
|
||||||
},
|
|
||||||
"node_modules/dicer/node_modules/readable-stream": {
|
|
||||||
"version": "1.1.14",
|
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
|
|
||||||
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
|
|
||||||
"dependencies": {
|
|
||||||
"core-util-is": "~1.0.0",
|
|
||||||
"inherits": "~2.0.1",
|
|
||||||
"isarray": "0.0.1",
|
|
||||||
"string_decoder": "~0.10.x"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/dicer/node_modules/string_decoder": {
|
|
||||||
"version": "0.10.31",
|
|
||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
|
||||||
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
|
|
||||||
},
|
|
||||||
"node_modules/diff": {
|
"node_modules/diff": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
||||||
@@ -4946,6 +4891,11 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ip": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ=="
|
||||||
|
},
|
||||||
"node_modules/ipaddr.js": {
|
"node_modules/ipaddr.js": {
|
||||||
"version": "1.9.1",
|
"version": "1.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||||
@@ -6788,9 +6738,9 @@
|
|||||||
"integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="
|
"integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="
|
||||||
},
|
},
|
||||||
"node_modules/kareem": {
|
"node_modules/kareem": {
|
||||||
"version": "2.3.2",
|
"version": "2.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.4.1.tgz",
|
||||||
"integrity": "sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ=="
|
"integrity": "sha512-aJ9opVoXroQUPfovYP5kaj2lM7Jn02Gw13bL0lg9v0V7SaUc0qavPs0Eue7d2DcC3NjqI6QAUElXNsuZSeM+EA=="
|
||||||
},
|
},
|
||||||
"node_modules/kleur": {
|
"node_modules/kleur": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
@@ -7093,13 +7043,14 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/mongodb": {
|
"node_modules/mongodb": {
|
||||||
"version": "4.1.4",
|
"version": "4.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.8.1.tgz",
|
||||||
"integrity": "sha512-Cv/sk8on/tpvvqbEvR1h03mdyNdyvvO+WhtFlL4jrZ+DSsN/oSQHVqmJQI/sBCqqbOArFcYCAYDfyzqFwV4GSQ==",
|
"integrity": "sha512-/NyiM3Ox9AwP5zrfT9TXjRKDJbXlLaUDQ9Rg//2lbg8D2A8GXV0VidYYnA/gfdK6uwbnL4FnAflH7FbGw3TS7w==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bson": "^4.5.4",
|
"bson": "^4.6.5",
|
||||||
"denque": "^2.0.1",
|
"denque": "^2.0.1",
|
||||||
"mongodb-connection-string-url": "^2.1.0"
|
"mongodb-connection-string-url": "^2.5.2",
|
||||||
|
"socks": "^2.6.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.9.0"
|
"node": ">=12.9.0"
|
||||||
@@ -7109,21 +7060,40 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mongodb-connection-string-url": {
|
"node_modules/mongodb-connection-string-url": {
|
||||||
"version": "2.1.0",
|
"version": "2.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.5.3.tgz",
|
||||||
"integrity": "sha512-Qf9Zw7KGiRljWvMrrUFDdVqo46KIEiDuCzvEN97rh/PcKzk2bd6n9KuzEwBwW9xo5glwx69y1mI6s+jFUD/aIQ==",
|
"integrity": "sha512-f+/WsED+xF4B74l3k9V/XkTVj5/fxFH2o5ToKXd8Iyi5UhM+sO9u0Ape17Mvl/GkZaFtM0HQnzAG5OTmhKw+tQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/whatwg-url": "^8.2.1",
|
"@types/whatwg-url": "^8.2.1",
|
||||||
"whatwg-url": "^9.1.0"
|
"whatwg-url": "^11.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mongodb-connection-string-url/node_modules/tr46": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
|
||||||
|
"dependencies": {
|
||||||
|
"punycode": "^2.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mongodb-connection-string-url/node_modules/webidl-conversions": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mongodb-connection-string-url/node_modules/whatwg-url": {
|
"node_modules/mongodb-connection-string-url/node_modules/whatwg-url": {
|
||||||
"version": "9.1.0",
|
"version": "11.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-9.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
|
||||||
"integrity": "sha512-CQ0UcrPHyomtlOCot1TL77WyMIm/bCwrJ2D6AOKGwEczU9EpyoqAokfqrf/MioU9kHcMsmJZcg1egXix2KYEsA==",
|
"integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tr46": "^2.1.0",
|
"tr46": "^3.0.0",
|
||||||
"webidl-conversions": "^6.1.0"
|
"webidl-conversions": "^7.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
@@ -7221,19 +7191,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mongoose": {
|
"node_modules/mongoose": {
|
||||||
"version": "6.0.12",
|
"version": "6.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.0.12.tgz",
|
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.5.2.tgz",
|
||||||
"integrity": "sha512-BvsZk7zEEhb1AgQFLtxN9C+7qgy5edRuA3ZDDwHU+kHG/HM44vI6FdKV5m6HVdAUeCHHQTiVv+YQh8BRsToSHw==",
|
"integrity": "sha512-3CFDrSLtK2qjM1pZeZpLTUyqPRkc11Iuh74ZrwS4IwEJ3K2PqGnmyPLw7ex4Kzu37ujIMp3MAuiBlUjfrcb6hw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bson": "^4.2.2",
|
"bson": "^4.6.5",
|
||||||
"kareem": "2.3.2",
|
"kareem": "2.4.1",
|
||||||
"mongodb": "4.1.3",
|
"mongodb": "4.8.1",
|
||||||
"mpath": "0.8.4",
|
"mpath": "0.9.0",
|
||||||
"mquery": "4.0.0",
|
"mquery": "4.0.3",
|
||||||
"ms": "2.1.2",
|
"ms": "2.1.3",
|
||||||
"regexp-clone": "1.0.0",
|
"sift": "16.0.0"
|
||||||
"sift": "13.5.2",
|
|
||||||
"sliced": "1.0.1"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.0.0"
|
"node": ">=12.0.0"
|
||||||
@@ -7255,26 +7223,10 @@
|
|||||||
"mongoose": ">=4"
|
"mongoose": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mongoose/node_modules/mongodb": {
|
|
||||||
"version": "4.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.1.3.tgz",
|
|
||||||
"integrity": "sha512-lHvTqODBiSpuqjpCj48DOyYWS6Iq6ElJNUiH9HWdQtONyOfjgsKzJULipWduMGsSzaNO4nFi/kmlMFCLvjox/Q==",
|
|
||||||
"dependencies": {
|
|
||||||
"bson": "^4.5.2",
|
|
||||||
"denque": "^2.0.1",
|
|
||||||
"mongodb-connection-string-url": "^2.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12.9.0"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"saslprep": "^1.0.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/mongoose/node_modules/ms": {
|
"node_modules/mongoose/node_modules/ms": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||||
},
|
},
|
||||||
"node_modules/morgan": {
|
"node_modules/morgan": {
|
||||||
"version": "1.10.0",
|
"version": "1.10.0",
|
||||||
@@ -7300,30 +7252,28 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mpath": {
|
"node_modules/mpath": {
|
||||||
"version": "0.8.4",
|
"version": "0.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.4.tgz",
|
"resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
|
||||||
"integrity": "sha512-DTxNZomBcTWlrMW76jy1wvV37X/cNNxPW1y2Jzd4DZkAaC5ZGsm8bfGfNOthcDuRJujXLqiuS6o3Tpy0JEoh7g==",
|
"integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4.0.0"
|
"node": ">=4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mquery": {
|
"node_modules/mquery": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.3.tgz",
|
||||||
"integrity": "sha512-nGjm89lHja+T/b8cybAby6H0YgA4qYC/lx6UlwvHGqvTq8bDaNeCwl1sY8uRELrNbVWJzIihxVd+vphGGn1vBw==",
|
"integrity": "sha512-J5heI+P08I6VJ2Ky3+33IpCdAvlYGTSUjwTPxkAr8i8EoduPMBX2OY/wa3IKZIQl7MU4SbFk8ndgSKyB/cl1zA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": "4.x",
|
"debug": "4.x"
|
||||||
"regexp-clone": "^1.0.0",
|
|
||||||
"sliced": "1.0.1"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.0.0"
|
"node": ">=12.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mquery/node_modules/debug": {
|
"node_modules/mquery/node_modules/debug": {
|
||||||
"version": "4.3.2",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||||
"integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
|
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "2.1.2"
|
"ms": "2.1.2"
|
||||||
},
|
},
|
||||||
@@ -7347,22 +7297,20 @@
|
|||||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||||
},
|
},
|
||||||
"node_modules/multer": {
|
"node_modules/multer": {
|
||||||
"version": "1.4.4",
|
"version": "1.4.5-lts.1",
|
||||||
"resolved": "https://registry.npmjs.org/multer/-/multer-1.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz",
|
||||||
"integrity": "sha512-2wY2+xD4udX612aMqMcB8Ws2Voq6NIUPEtD1be6m411T4uDH/VtL9i//xvcyFlTVfRdaBsk7hV5tgrGQqhuBiw==",
|
"integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==",
|
||||||
"deprecated": "Multer 1.x is affected by CVE-2022-24434. This is fixed in v1.4.4-lts.1 which drops support for versions of Node.js before 6. Please upgrade to at least Node.js 6 and version 1.4.4-lts.1 of Multer. If you need support for older versions of Node.js, we are open to accepting patches that would fix the CVE on the main 1.x release line, whilst maintaining compatibility with Node.js 0.10.",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"append-field": "^1.0.0",
|
"append-field": "^1.0.0",
|
||||||
"busboy": "^0.2.11",
|
"busboy": "^1.0.0",
|
||||||
"concat-stream": "^1.5.2",
|
"concat-stream": "^1.5.2",
|
||||||
"mkdirp": "^0.5.4",
|
"mkdirp": "^0.5.4",
|
||||||
"object-assign": "^4.1.1",
|
"object-assign": "^4.1.1",
|
||||||
"on-finished": "^2.3.0",
|
|
||||||
"type-is": "^1.6.4",
|
"type-is": "^1.6.4",
|
||||||
"xtend": "^4.0.0"
|
"xtend": "^4.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.10.0"
|
"node": ">= 6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/multer/node_modules/mkdirp": {
|
"node_modules/multer/node_modules/mkdirp": {
|
||||||
@@ -8353,11 +8301,6 @@
|
|||||||
"node": ">=8.10.0"
|
"node": ">=8.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/regexp-clone": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw=="
|
|
||||||
},
|
|
||||||
"node_modules/require-directory": {
|
"node_modules/require-directory": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||||
@@ -8606,9 +8549,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/sift": {
|
"node_modules/sift": {
|
||||||
"version": "13.5.2",
|
"version": "16.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/sift/-/sift-13.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/sift/-/sift-16.0.0.tgz",
|
||||||
"integrity": "sha512-+gxdEOMA2J+AI+fVsCqeNn7Tgx3M9ZN9jdi95939l1IJ8cZsqS8sqpJyOkic2SJk+1+98Uwryt/gL6XDaV+UZA=="
|
"integrity": "sha512-ILTjdP2Mv9V1kIxWMXeMTIRbOBrqKc4JAXmFMnFq3fKeyQ2Qwa3Dw1ubcye3vR+Y6ofA0b9gNDr/y2t6eUeIzQ=="
|
||||||
},
|
},
|
||||||
"node_modules/signal-exit": {
|
"node_modules/signal-exit": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
@@ -8706,10 +8649,27 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/sliced": {
|
"node_modules/smart-buffer": {
|
||||||
"version": "1.0.1",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
|
||||||
"integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E="
|
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6.0.0",
|
||||||
|
"npm": ">= 3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/socks": {
|
||||||
|
"version": "2.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/socks/-/socks-2.7.0.tgz",
|
||||||
|
"integrity": "sha512-scnOe9y4VuiNUULJN72GrM26BNOjVsfPXI+j+98PkyEfsIXroa5ofyjT+FzGvn/xHs73U2JtoBYAVx9Hl4quSA==",
|
||||||
|
"dependencies": {
|
||||||
|
"ip": "^2.0.0",
|
||||||
|
"smart-buffer": "^4.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.13.0",
|
||||||
|
"npm": ">= 3.0.0"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/source-map": {
|
"node_modules/source-map": {
|
||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
@@ -8808,11 +8768,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/streamsearch": {
|
"node_modules/streamsearch": {
|
||||||
"version": "0.1.2",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
|
||||||
"integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=",
|
"integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.8.0"
|
"node": ">=10.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/string_decoder": {
|
"node_modules/string_decoder": {
|
||||||
@@ -9296,6 +9256,7 @@
|
|||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz",
|
||||||
"integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==",
|
"integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==",
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"punycode": "^2.1.1"
|
"punycode": "^2.1.1"
|
||||||
},
|
},
|
||||||
@@ -9719,6 +9680,7 @@
|
|||||||
"version": "6.1.0",
|
"version": "6.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz",
|
||||||
"integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==",
|
"integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==",
|
||||||
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.4"
|
"node": ">=10.4"
|
||||||
}
|
}
|
||||||
@@ -11724,9 +11686,9 @@
|
|||||||
"integrity": "sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q=="
|
"integrity": "sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q=="
|
||||||
},
|
},
|
||||||
"@types/whatwg-url": {
|
"@types/whatwg-url": {
|
||||||
"version": "8.2.1",
|
"version": "8.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz",
|
||||||
"integrity": "sha512-2YubE1sjj5ifxievI5Ge1sckb9k/Er66HyR2c+3+I6VDUUg1TLPdYYTEbQ+DjRkS4nTxMJhgWfSfMRD2sl2EYQ==",
|
"integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
"@types/webidl-conversions": "*"
|
"@types/webidl-conversions": "*"
|
||||||
@@ -12240,9 +12202,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bson": {
|
"bson": {
|
||||||
"version": "4.5.4",
|
"version": "4.6.5",
|
||||||
"resolved": "https://registry.npmjs.org/bson/-/bson-4.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/bson/-/bson-4.6.5.tgz",
|
||||||
"integrity": "sha512-wIt0bPACnx8Ju9r6IsS2wVtGDHBr9Dxb+U29A1YED2pu8XOhS8aKjOnLZ8sxyXkPwanoK7iWWVhS1+coxde6xA==",
|
"integrity": "sha512-uqrgcjyOaZsHfz7ea8zLRCLe1u+QGUSzMZmvXqO24CDW7DWoW1qiN9folSwa7hSneTSgM2ykDIzF5kcQQ8cwNw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"buffer": "^5.6.0"
|
"buffer": "^5.6.0"
|
||||||
}
|
}
|
||||||
@@ -12283,35 +12245,11 @@
|
|||||||
"integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ=="
|
"integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ=="
|
||||||
},
|
},
|
||||||
"busboy": {
|
"busboy": {
|
||||||
"version": "0.2.14",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz",
|
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
|
||||||
"integrity": "sha512-InWFDomvlkEj+xWLBfU3AvnbVYqeTWmQopiW0tWWEy5yehYm2YkGEc59sUmw/4ty5Zj/b0WHGs1LgecuBSBGrg==",
|
"integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"dicer": "0.2.5",
|
"streamsearch": "^1.1.0"
|
||||||
"readable-stream": "1.1.x"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"isarray": {
|
|
||||||
"version": "0.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
|
||||||
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
|
|
||||||
},
|
|
||||||
"readable-stream": {
|
|
||||||
"version": "1.1.14",
|
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
|
|
||||||
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
|
|
||||||
"requires": {
|
|
||||||
"core-util-is": "~1.0.0",
|
|
||||||
"inherits": "~2.0.1",
|
|
||||||
"isarray": "0.0.1",
|
|
||||||
"string_decoder": "~0.10.x"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"string_decoder": {
|
|
||||||
"version": "0.10.31",
|
|
||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
|
||||||
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bytes": {
|
"bytes": {
|
||||||
@@ -12813,38 +12751,6 @@
|
|||||||
"integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==",
|
"integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"dicer": {
|
|
||||||
"version": "0.2.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz",
|
|
||||||
"integrity": "sha512-FDvbtnq7dzlPz0wyYlOExifDEZcu8h+rErEXgfxqmLfRfC/kJidEFh4+effJRO3P0xmfqyPbSMG0LveNRfTKVg==",
|
|
||||||
"requires": {
|
|
||||||
"readable-stream": "1.1.x",
|
|
||||||
"streamsearch": "0.1.2"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"isarray": {
|
|
||||||
"version": "0.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
|
||||||
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
|
|
||||||
},
|
|
||||||
"readable-stream": {
|
|
||||||
"version": "1.1.14",
|
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
|
|
||||||
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
|
|
||||||
"requires": {
|
|
||||||
"core-util-is": "~1.0.0",
|
|
||||||
"inherits": "~2.0.1",
|
|
||||||
"isarray": "0.0.1",
|
|
||||||
"string_decoder": "~0.10.x"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"string_decoder": {
|
|
||||||
"version": "0.10.31",
|
|
||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
|
||||||
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"diff": {
|
"diff": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
||||||
@@ -13868,6 +13774,11 @@
|
|||||||
"p-is-promise": "^3.0.0"
|
"p-is-promise": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ip": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ=="
|
||||||
|
},
|
||||||
"ipaddr.js": {
|
"ipaddr.js": {
|
||||||
"version": "1.9.1",
|
"version": "1.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||||
@@ -15248,9 +15159,9 @@
|
|||||||
"integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="
|
"integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="
|
||||||
},
|
},
|
||||||
"kareem": {
|
"kareem": {
|
||||||
"version": "2.3.2",
|
"version": "2.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.4.1.tgz",
|
||||||
"integrity": "sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ=="
|
"integrity": "sha512-aJ9opVoXroQUPfovYP5kaj2lM7Jn02Gw13bL0lg9v0V7SaUc0qavPs0Eue7d2DcC3NjqI6QAUElXNsuZSeM+EA=="
|
||||||
},
|
},
|
||||||
"kleur": {
|
"kleur": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
@@ -15486,32 +15397,46 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"mongodb": {
|
"mongodb": {
|
||||||
"version": "4.1.4",
|
"version": "4.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.8.1.tgz",
|
||||||
"integrity": "sha512-Cv/sk8on/tpvvqbEvR1h03mdyNdyvvO+WhtFlL4jrZ+DSsN/oSQHVqmJQI/sBCqqbOArFcYCAYDfyzqFwV4GSQ==",
|
"integrity": "sha512-/NyiM3Ox9AwP5zrfT9TXjRKDJbXlLaUDQ9Rg//2lbg8D2A8GXV0VidYYnA/gfdK6uwbnL4FnAflH7FbGw3TS7w==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"bson": "^4.5.4",
|
"bson": "^4.6.5",
|
||||||
"denque": "^2.0.1",
|
"denque": "^2.0.1",
|
||||||
"mongodb-connection-string-url": "^2.1.0",
|
"mongodb-connection-string-url": "^2.5.2",
|
||||||
"saslprep": "^1.0.3"
|
"saslprep": "^1.0.3",
|
||||||
|
"socks": "^2.6.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mongodb-connection-string-url": {
|
"mongodb-connection-string-url": {
|
||||||
"version": "2.1.0",
|
"version": "2.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.5.3.tgz",
|
||||||
"integrity": "sha512-Qf9Zw7KGiRljWvMrrUFDdVqo46KIEiDuCzvEN97rh/PcKzk2bd6n9KuzEwBwW9xo5glwx69y1mI6s+jFUD/aIQ==",
|
"integrity": "sha512-f+/WsED+xF4B74l3k9V/XkTVj5/fxFH2o5ToKXd8Iyi5UhM+sO9u0Ape17Mvl/GkZaFtM0HQnzAG5OTmhKw+tQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/whatwg-url": "^8.2.1",
|
"@types/whatwg-url": "^8.2.1",
|
||||||
"whatwg-url": "^9.1.0"
|
"whatwg-url": "^11.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"whatwg-url": {
|
"tr46": {
|
||||||
"version": "9.1.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-9.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
|
||||||
"integrity": "sha512-CQ0UcrPHyomtlOCot1TL77WyMIm/bCwrJ2D6AOKGwEczU9EpyoqAokfqrf/MioU9kHcMsmJZcg1egXix2KYEsA==",
|
"integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"tr46": "^2.1.0",
|
"punycode": "^2.1.1"
|
||||||
"webidl-conversions": "^6.1.0"
|
}
|
||||||
|
},
|
||||||
|
"webidl-conversions": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="
|
||||||
|
},
|
||||||
|
"whatwg-url": {
|
||||||
|
"version": "11.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
|
||||||
|
"integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
|
||||||
|
"requires": {
|
||||||
|
"tr46": "^3.0.0",
|
||||||
|
"webidl-conversions": "^7.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15583,36 +15508,23 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mongoose": {
|
"mongoose": {
|
||||||
"version": "6.0.12",
|
"version": "6.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.0.12.tgz",
|
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.5.2.tgz",
|
||||||
"integrity": "sha512-BvsZk7zEEhb1AgQFLtxN9C+7qgy5edRuA3ZDDwHU+kHG/HM44vI6FdKV5m6HVdAUeCHHQTiVv+YQh8BRsToSHw==",
|
"integrity": "sha512-3CFDrSLtK2qjM1pZeZpLTUyqPRkc11Iuh74ZrwS4IwEJ3K2PqGnmyPLw7ex4Kzu37ujIMp3MAuiBlUjfrcb6hw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"bson": "^4.2.2",
|
"bson": "^4.6.5",
|
||||||
"kareem": "2.3.2",
|
"kareem": "2.4.1",
|
||||||
"mongodb": "4.1.3",
|
"mongodb": "4.8.1",
|
||||||
"mpath": "0.8.4",
|
"mpath": "0.9.0",
|
||||||
"mquery": "4.0.0",
|
"mquery": "4.0.3",
|
||||||
"ms": "2.1.2",
|
"ms": "2.1.3",
|
||||||
"regexp-clone": "1.0.0",
|
"sift": "16.0.0"
|
||||||
"sift": "13.5.2",
|
|
||||||
"sliced": "1.0.1"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"mongodb": {
|
|
||||||
"version": "4.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.1.3.tgz",
|
|
||||||
"integrity": "sha512-lHvTqODBiSpuqjpCj48DOyYWS6Iq6ElJNUiH9HWdQtONyOfjgsKzJULipWduMGsSzaNO4nFi/kmlMFCLvjox/Q==",
|
|
||||||
"requires": {
|
|
||||||
"bson": "^4.5.2",
|
|
||||||
"denque": "^2.0.1",
|
|
||||||
"mongodb-connection-string-url": "^2.0.0",
|
|
||||||
"saslprep": "^1.0.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ms": {
|
"ms": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -15645,24 +15557,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mpath": {
|
"mpath": {
|
||||||
"version": "0.8.4",
|
"version": "0.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.4.tgz",
|
"resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
|
||||||
"integrity": "sha512-DTxNZomBcTWlrMW76jy1wvV37X/cNNxPW1y2Jzd4DZkAaC5ZGsm8bfGfNOthcDuRJujXLqiuS6o3Tpy0JEoh7g=="
|
"integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew=="
|
||||||
},
|
},
|
||||||
"mquery": {
|
"mquery": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.3.tgz",
|
||||||
"integrity": "sha512-nGjm89lHja+T/b8cybAby6H0YgA4qYC/lx6UlwvHGqvTq8bDaNeCwl1sY8uRELrNbVWJzIihxVd+vphGGn1vBw==",
|
"integrity": "sha512-J5heI+P08I6VJ2Ky3+33IpCdAvlYGTSUjwTPxkAr8i8EoduPMBX2OY/wa3IKZIQl7MU4SbFk8ndgSKyB/cl1zA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"debug": "4.x",
|
"debug": "4.x"
|
||||||
"regexp-clone": "^1.0.0",
|
|
||||||
"sliced": "1.0.1"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": {
|
"debug": {
|
||||||
"version": "4.3.2",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||||
"integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
|
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"ms": "2.1.2"
|
"ms": "2.1.2"
|
||||||
}
|
}
|
||||||
@@ -15680,16 +15590,15 @@
|
|||||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||||
},
|
},
|
||||||
"multer": {
|
"multer": {
|
||||||
"version": "1.4.4",
|
"version": "1.4.5-lts.1",
|
||||||
"resolved": "https://registry.npmjs.org/multer/-/multer-1.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz",
|
||||||
"integrity": "sha512-2wY2+xD4udX612aMqMcB8Ws2Voq6NIUPEtD1be6m411T4uDH/VtL9i//xvcyFlTVfRdaBsk7hV5tgrGQqhuBiw==",
|
"integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"append-field": "^1.0.0",
|
"append-field": "^1.0.0",
|
||||||
"busboy": "^0.2.11",
|
"busboy": "^1.0.0",
|
||||||
"concat-stream": "^1.5.2",
|
"concat-stream": "^1.5.2",
|
||||||
"mkdirp": "^0.5.4",
|
"mkdirp": "^0.5.4",
|
||||||
"object-assign": "^4.1.1",
|
"object-assign": "^4.1.1",
|
||||||
"on-finished": "^2.3.0",
|
|
||||||
"type-is": "^1.6.4",
|
"type-is": "^1.6.4",
|
||||||
"xtend": "^4.0.0"
|
"xtend": "^4.0.0"
|
||||||
},
|
},
|
||||||
@@ -16416,11 +16325,6 @@
|
|||||||
"picomatch": "^2.2.1"
|
"picomatch": "^2.2.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"regexp-clone": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw=="
|
|
||||||
},
|
|
||||||
"require-directory": {
|
"require-directory": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||||
@@ -16605,9 +16509,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sift": {
|
"sift": {
|
||||||
"version": "13.5.2",
|
"version": "16.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/sift/-/sift-13.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/sift/-/sift-16.0.0.tgz",
|
||||||
"integrity": "sha512-+gxdEOMA2J+AI+fVsCqeNn7Tgx3M9ZN9jdi95939l1IJ8cZsqS8sqpJyOkic2SJk+1+98Uwryt/gL6XDaV+UZA=="
|
"integrity": "sha512-ILTjdP2Mv9V1kIxWMXeMTIRbOBrqKc4JAXmFMnFq3fKeyQ2Qwa3Dw1ubcye3vR+Y6ofA0b9gNDr/y2t6eUeIzQ=="
|
||||||
},
|
},
|
||||||
"signal-exit": {
|
"signal-exit": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
@@ -16677,10 +16581,19 @@
|
|||||||
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
|
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"sliced": {
|
"smart-buffer": {
|
||||||
"version": "1.0.1",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
|
||||||
"integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E="
|
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="
|
||||||
|
},
|
||||||
|
"socks": {
|
||||||
|
"version": "2.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/socks/-/socks-2.7.0.tgz",
|
||||||
|
"integrity": "sha512-scnOe9y4VuiNUULJN72GrM26BNOjVsfPXI+j+98PkyEfsIXroa5ofyjT+FzGvn/xHs73U2JtoBYAVx9Hl4quSA==",
|
||||||
|
"requires": {
|
||||||
|
"ip": "^2.0.0",
|
||||||
|
"smart-buffer": "^4.2.0"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"source-map": {
|
"source-map": {
|
||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
@@ -16771,9 +16684,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"streamsearch": {
|
"streamsearch": {
|
||||||
"version": "0.1.2",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
|
||||||
"integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo="
|
"integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="
|
||||||
},
|
},
|
||||||
"string_decoder": {
|
"string_decoder": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
@@ -17140,6 +17053,7 @@
|
|||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz",
|
||||||
"integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==",
|
"integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"punycode": "^2.1.1"
|
"punycode": "^2.1.1"
|
||||||
}
|
}
|
||||||
@@ -17456,7 +17370,8 @@
|
|||||||
"webidl-conversions": {
|
"webidl-conversions": {
|
||||||
"version": "6.1.0",
|
"version": "6.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz",
|
||||||
"integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w=="
|
"integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"whatwg-encoding": {
|
"whatwg-encoding": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
|
|||||||
@@ -62,7 +62,7 @@
|
|||||||
"mongoose": "^6.0.12",
|
"mongoose": "^6.0.12",
|
||||||
"mongoose-sequence": "^5.3.1",
|
"mongoose-sequence": "^5.3.1",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"multer": "^1.4.3",
|
"multer": "^1.4.5-lts.1",
|
||||||
"rotating-file-stream": "^3.0.4",
|
"rotating-file-stream": "^3.0.4",
|
||||||
"swagger-ui-express": "4.3.0",
|
"swagger-ui-express": "4.3.0",
|
||||||
"unzipper": "^0.10.11",
|
"unzipper": "^0.10.11",
|
||||||
|
|||||||
@@ -470,12 +470,89 @@ components:
|
|||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
AuthorizedRoutesResponse:
|
AuthorizedRoutesResponse:
|
||||||
properties:
|
properties:
|
||||||
URIs:
|
paths:
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
type: array
|
type: array
|
||||||
required:
|
required:
|
||||||
- URIs
|
- paths
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
PermissionDetailsResponse:
|
||||||
|
properties:
|
||||||
|
permissionId:
|
||||||
|
type: number
|
||||||
|
format: double
|
||||||
|
path:
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
setting:
|
||||||
|
type: string
|
||||||
|
user:
|
||||||
|
$ref: '#/components/schemas/UserResponse'
|
||||||
|
group:
|
||||||
|
$ref: '#/components/schemas/GroupDetailsResponse'
|
||||||
|
required:
|
||||||
|
- permissionId
|
||||||
|
- path
|
||||||
|
- type
|
||||||
|
- setting
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
PermissionType:
|
||||||
|
enum:
|
||||||
|
- Route
|
||||||
|
type: string
|
||||||
|
PermissionSettingForRoute:
|
||||||
|
enum:
|
||||||
|
- Grant
|
||||||
|
- Deny
|
||||||
|
type: string
|
||||||
|
PrincipalType:
|
||||||
|
enum:
|
||||||
|
- user
|
||||||
|
- group
|
||||||
|
type: string
|
||||||
|
RegisterPermissionPayload:
|
||||||
|
properties:
|
||||||
|
path:
|
||||||
|
type: string
|
||||||
|
description: 'Name of affected resource'
|
||||||
|
example: /SASjsApi/code/execute
|
||||||
|
type:
|
||||||
|
$ref: '#/components/schemas/PermissionType'
|
||||||
|
description: 'Type of affected resource'
|
||||||
|
example: Route
|
||||||
|
setting:
|
||||||
|
$ref: '#/components/schemas/PermissionSettingForRoute'
|
||||||
|
description: 'The indication of whether (and to what extent) access is provided'
|
||||||
|
example: Grant
|
||||||
|
principalType:
|
||||||
|
$ref: '#/components/schemas/PrincipalType'
|
||||||
|
description: 'Indicates the type of principal'
|
||||||
|
example: user
|
||||||
|
principalId:
|
||||||
|
type: number
|
||||||
|
format: double
|
||||||
|
description: 'The id of user or group to which a rule is assigned.'
|
||||||
|
example: 123
|
||||||
|
required:
|
||||||
|
- path
|
||||||
|
- type
|
||||||
|
- setting
|
||||||
|
- principalType
|
||||||
|
- principalId
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
UpdatePermissionPayload:
|
||||||
|
properties:
|
||||||
|
setting:
|
||||||
|
$ref: '#/components/schemas/PermissionSettingForRoute'
|
||||||
|
description: 'The indication of whether (and to what extent) access is provided'
|
||||||
|
example: Grant
|
||||||
|
required:
|
||||||
|
- setting
|
||||||
type: object
|
type: object
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
ExecuteReturnJsonPayload:
|
ExecuteReturnJsonPayload:
|
||||||
@@ -521,71 +598,6 @@ components:
|
|||||||
- clientId
|
- clientId
|
||||||
type: object
|
type: object
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
PermissionDetailsResponse:
|
|
||||||
properties:
|
|
||||||
permissionId:
|
|
||||||
type: number
|
|
||||||
format: double
|
|
||||||
uri:
|
|
||||||
type: string
|
|
||||||
setting:
|
|
||||||
type: string
|
|
||||||
user:
|
|
||||||
$ref: '#/components/schemas/UserResponse'
|
|
||||||
group:
|
|
||||||
$ref: '#/components/schemas/GroupDetailsResponse'
|
|
||||||
required:
|
|
||||||
- permissionId
|
|
||||||
- uri
|
|
||||||
- setting
|
|
||||||
type: object
|
|
||||||
additionalProperties: false
|
|
||||||
PermissionSetting:
|
|
||||||
enum:
|
|
||||||
- Grant
|
|
||||||
- Deny
|
|
||||||
type: string
|
|
||||||
PrincipalType:
|
|
||||||
enum:
|
|
||||||
- user
|
|
||||||
- group
|
|
||||||
type: string
|
|
||||||
RegisterPermissionPayload:
|
|
||||||
properties:
|
|
||||||
uri:
|
|
||||||
type: string
|
|
||||||
description: 'Name of affected resource'
|
|
||||||
example: /SASjsApi/code/execute
|
|
||||||
setting:
|
|
||||||
$ref: '#/components/schemas/PermissionSetting'
|
|
||||||
description: 'The indication of whether (and to what extent) access is provided'
|
|
||||||
example: Grant
|
|
||||||
principalType:
|
|
||||||
$ref: '#/components/schemas/PrincipalType'
|
|
||||||
description: 'Indicates the type of principal'
|
|
||||||
example: user
|
|
||||||
principalId:
|
|
||||||
type: number
|
|
||||||
format: double
|
|
||||||
description: 'The id of user or group to which a rule is assigned.'
|
|
||||||
example: 123
|
|
||||||
required:
|
|
||||||
- uri
|
|
||||||
- setting
|
|
||||||
- principalType
|
|
||||||
- principalId
|
|
||||||
type: object
|
|
||||||
additionalProperties: false
|
|
||||||
UpdatePermissionPayload:
|
|
||||||
properties:
|
|
||||||
setting:
|
|
||||||
$ref: '#/components/schemas/PermissionSetting'
|
|
||||||
description: 'The indication of whether (and to what extent) access is provided'
|
|
||||||
example: Grant
|
|
||||||
required:
|
|
||||||
- setting
|
|
||||||
type: object
|
|
||||||
additionalProperties: false
|
|
||||||
securitySchemes:
|
securitySchemes:
|
||||||
bearerAuth:
|
bearerAuth:
|
||||||
type: http
|
type: http
|
||||||
@@ -1598,12 +1610,165 @@ paths:
|
|||||||
$ref: '#/components/schemas/AuthorizedRoutesResponse'
|
$ref: '#/components/schemas/AuthorizedRoutesResponse'
|
||||||
examples:
|
examples:
|
||||||
'Example 1':
|
'Example 1':
|
||||||
value: { URIs: [/AppStream, /SASjsApi/stp/execute] }
|
value: { paths: [/AppStream, /SASjsApi/stp/execute] }
|
||||||
summary: 'Get authorized routes.'
|
summary: 'Get the list of available routes to which permissions can be applied. Used to populate the dialog in the URI Permissions feature.'
|
||||||
tags:
|
tags:
|
||||||
- Info
|
- Info
|
||||||
security: []
|
security: []
|
||||||
parameters: []
|
parameters: []
|
||||||
|
/SASjsApi/permission:
|
||||||
|
get:
|
||||||
|
operationId: GetAllPermissions
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Ok
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/PermissionDetailsResponse'
|
||||||
|
type: array
|
||||||
|
examples:
|
||||||
|
'Example 1':
|
||||||
|
value:
|
||||||
|
[
|
||||||
|
{
|
||||||
|
permissionId: 123,
|
||||||
|
path: /SASjsApi/code/execute,
|
||||||
|
type: Route,
|
||||||
|
setting: Grant,
|
||||||
|
user:
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
username: johnSnow01,
|
||||||
|
displayName: 'John Snow',
|
||||||
|
isAdmin: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
permissionId: 124,
|
||||||
|
path: /SASjsApi/code/execute,
|
||||||
|
type: Route,
|
||||||
|
setting: Grant,
|
||||||
|
group:
|
||||||
|
{
|
||||||
|
groupId: 1,
|
||||||
|
name: DCGroup,
|
||||||
|
description: 'This group represents Data Controller Users',
|
||||||
|
isActive: true,
|
||||||
|
users: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
description: "Get the list of permission rules applicable the authenticated user.\nIf the user is an admin, all rules are returned."
|
||||||
|
summary: 'Get the list of permission rules. If the user is admin, all rules are returned.'
|
||||||
|
tags:
|
||||||
|
- Permission
|
||||||
|
security:
|
||||||
|
- bearerAuth: []
|
||||||
|
parameters: []
|
||||||
|
post:
|
||||||
|
operationId: CreatePermission
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Ok
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PermissionDetailsResponse'
|
||||||
|
examples:
|
||||||
|
'Example 1':
|
||||||
|
value:
|
||||||
|
{
|
||||||
|
permissionId: 123,
|
||||||
|
path: /SASjsApi/code/execute,
|
||||||
|
type: Route,
|
||||||
|
setting: Grant,
|
||||||
|
user:
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
username: johnSnow01,
|
||||||
|
displayName: 'John Snow',
|
||||||
|
isAdmin: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
summary: 'Create a new permission. Admin only.'
|
||||||
|
tags:
|
||||||
|
- Permission
|
||||||
|
security:
|
||||||
|
- bearerAuth: []
|
||||||
|
parameters: []
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/RegisterPermissionPayload'
|
||||||
|
'/SASjsApi/permission/{permissionId}':
|
||||||
|
patch:
|
||||||
|
operationId: UpdatePermission
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Ok
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PermissionDetailsResponse'
|
||||||
|
examples:
|
||||||
|
'Example 1':
|
||||||
|
value:
|
||||||
|
{
|
||||||
|
permissionId: 123,
|
||||||
|
path: /SASjsApi/code/execute,
|
||||||
|
type: Route,
|
||||||
|
setting: Grant,
|
||||||
|
user:
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
username: johnSnow01,
|
||||||
|
displayName: 'John Snow',
|
||||||
|
isAdmin: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
summary: 'Update permission setting. Admin only'
|
||||||
|
tags:
|
||||||
|
- Permission
|
||||||
|
security:
|
||||||
|
- bearerAuth: []
|
||||||
|
parameters:
|
||||||
|
- description: "The permission's identifier"
|
||||||
|
in: path
|
||||||
|
name: permissionId
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
format: double
|
||||||
|
type: number
|
||||||
|
example: 1234
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/UpdatePermissionPayload'
|
||||||
|
delete:
|
||||||
|
operationId: DeletePermission
|
||||||
|
responses:
|
||||||
|
'204':
|
||||||
|
description: 'No content'
|
||||||
|
summary: 'Delete a permission. Admin only.'
|
||||||
|
tags:
|
||||||
|
- Permission
|
||||||
|
security:
|
||||||
|
- bearerAuth: []
|
||||||
|
parameters:
|
||||||
|
- description: "The user's identifier"
|
||||||
|
in: path
|
||||||
|
name: permissionId
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
format: double
|
||||||
|
type: number
|
||||||
|
example: 1234
|
||||||
/SASjsApi/session:
|
/SASjsApi/session:
|
||||||
get:
|
get:
|
||||||
operationId: Session
|
operationId: Session
|
||||||
@@ -1788,154 +1953,6 @@ paths:
|
|||||||
- Web
|
- Web
|
||||||
security: []
|
security: []
|
||||||
parameters: []
|
parameters: []
|
||||||
/SASjsApi/permission:
|
|
||||||
get:
|
|
||||||
operationId: GetAllPermissions
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Ok
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
items:
|
|
||||||
$ref: '#/components/schemas/PermissionDetailsResponse'
|
|
||||||
type: array
|
|
||||||
examples:
|
|
||||||
'Example 1':
|
|
||||||
value:
|
|
||||||
[
|
|
||||||
{
|
|
||||||
permissionId: 123,
|
|
||||||
uri: /SASjsApi/code/execute,
|
|
||||||
setting: Grant,
|
|
||||||
user:
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
username: johnSnow01,
|
|
||||||
displayName: 'John Snow',
|
|
||||||
isAdmin: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
permissionId: 124,
|
|
||||||
uri: /SASjsApi/code/execute,
|
|
||||||
setting: Grant,
|
|
||||||
group:
|
|
||||||
{
|
|
||||||
groupId: 1,
|
|
||||||
name: DCGroup,
|
|
||||||
description: 'This group represents Data Controller Users',
|
|
||||||
isActive: true,
|
|
||||||
users: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
summary: 'Get list of all permissions (uri, setting and userDetail).'
|
|
||||||
tags:
|
|
||||||
- Permission
|
|
||||||
security:
|
|
||||||
- bearerAuth: []
|
|
||||||
parameters: []
|
|
||||||
post:
|
|
||||||
operationId: CreatePermission
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Ok
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/PermissionDetailsResponse'
|
|
||||||
examples:
|
|
||||||
'Example 1':
|
|
||||||
value:
|
|
||||||
{
|
|
||||||
permissionId: 123,
|
|
||||||
uri: /SASjsApi/code/execute,
|
|
||||||
setting: Grant,
|
|
||||||
user:
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
username: johnSnow01,
|
|
||||||
displayName: 'John Snow',
|
|
||||||
isAdmin: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
summary: 'Create a new permission. Admin only.'
|
|
||||||
tags:
|
|
||||||
- Permission
|
|
||||||
security:
|
|
||||||
- bearerAuth: []
|
|
||||||
parameters: []
|
|
||||||
requestBody:
|
|
||||||
required: true
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/RegisterPermissionPayload'
|
|
||||||
'/SASjsApi/permission/{permissionId}':
|
|
||||||
patch:
|
|
||||||
operationId: UpdatePermission
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Ok
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/PermissionDetailsResponse'
|
|
||||||
examples:
|
|
||||||
'Example 1':
|
|
||||||
value:
|
|
||||||
{
|
|
||||||
permissionId: 123,
|
|
||||||
uri: /SASjsApi/code/execute,
|
|
||||||
setting: Grant,
|
|
||||||
user:
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
username: johnSnow01,
|
|
||||||
displayName: 'John Snow',
|
|
||||||
isAdmin: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
summary: 'Update permission setting. Admin only'
|
|
||||||
tags:
|
|
||||||
- Permission
|
|
||||||
security:
|
|
||||||
- bearerAuth: []
|
|
||||||
parameters:
|
|
||||||
- description: "The permission's identifier"
|
|
||||||
in: path
|
|
||||||
name: permissionId
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
format: double
|
|
||||||
type: number
|
|
||||||
example: 1234
|
|
||||||
requestBody:
|
|
||||||
required: true
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/UpdatePermissionPayload'
|
|
||||||
delete:
|
|
||||||
operationId: DeletePermission
|
|
||||||
responses:
|
|
||||||
'204':
|
|
||||||
description: 'No content'
|
|
||||||
summary: 'Delete a permission. Admin only.'
|
|
||||||
tags:
|
|
||||||
- Permission
|
|
||||||
security:
|
|
||||||
- bearerAuth: []
|
|
||||||
parameters:
|
|
||||||
- description: "The user's identifier"
|
|
||||||
in: path
|
|
||||||
name: permissionId
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
format: double
|
|
||||||
type: number
|
|
||||||
example: 1234
|
|
||||||
servers:
|
servers:
|
||||||
- url: /
|
- url: /
|
||||||
tags:
|
tags:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import express, { ErrorRequestHandler } from 'express'
|
import express, { ErrorRequestHandler } from 'express'
|
||||||
import csrf from 'csurf'
|
import csrf, { CookieOptions } from 'csurf'
|
||||||
import cookieParser from 'cookie-parser'
|
import cookieParser from 'cookie-parser'
|
||||||
import dotenv from 'dotenv'
|
import dotenv from 'dotenv'
|
||||||
|
|
||||||
@@ -32,9 +32,10 @@ const app = express()
|
|||||||
|
|
||||||
const { PROTOCOL } = process.env
|
const { PROTOCOL } = process.env
|
||||||
|
|
||||||
export const cookieOptions = {
|
export const cookieOptions: CookieOptions = {
|
||||||
secure: PROTOCOL === ProtocolType.HTTPS,
|
secure: PROTOCOL === ProtocolType.HTTPS,
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
|
sameSite: PROTOCOL === ProtocolType.HTTPS ? 'none' : undefined,
|
||||||
maxAge: 24 * 60 * 60 * 1000 // 24 hours
|
maxAge: 24 * 60 * 60 * 1000 // 24 hours
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import express from 'express'
|
import express from 'express'
|
||||||
import { Request, Security, Route, Tags, Post, Body } from 'tsoa'
|
import { Request, Security, Route, Tags, Post, Body } from 'tsoa'
|
||||||
import { ExecuteReturnJson, ExecutionController } from './internal'
|
import { ExecutionController } from './internal'
|
||||||
import { ExecuteReturnJsonResponse } from '.'
|
|
||||||
import {
|
import {
|
||||||
getPreProgramVariables,
|
getPreProgramVariables,
|
||||||
getUserAutoExec,
|
getUserAutoExec,
|
||||||
@@ -35,7 +34,7 @@ export class CodeController {
|
|||||||
public async executeCode(
|
public async executeCode(
|
||||||
@Request() request: express.Request,
|
@Request() request: express.Request,
|
||||||
@Body() body: ExecuteCodePayload
|
@Body() body: ExecuteCodePayload
|
||||||
): Promise<ExecuteReturnJsonResponse> {
|
): Promise<string | Buffer> {
|
||||||
return executeCode(request, body)
|
return executeCode(request, body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,22 +50,15 @@ const executeCode = async (
|
|||||||
: await getUserAutoExec()
|
: await getUserAutoExec()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { webout, log, httpHeaders } =
|
const { result } = await new ExecutionController().executeProgram({
|
||||||
(await new ExecutionController().executeProgram({
|
program: code,
|
||||||
program: code,
|
preProgramVariables: getPreProgramVariables(req),
|
||||||
preProgramVariables: getPreProgramVariables(req),
|
vars: { ...req.query, _debug: 131 },
|
||||||
vars: { ...req.query, _debug: 131 },
|
otherArgs: { userAutoExec },
|
||||||
otherArgs: { userAutoExec },
|
runTime: runTime
|
||||||
returnJson: true,
|
})
|
||||||
runTime: runTime
|
|
||||||
})) as ExecuteReturnJson
|
|
||||||
|
|
||||||
return {
|
return result
|
||||||
status: 'success',
|
|
||||||
_webout: webout as string,
|
|
||||||
log: parseLogToArray(log),
|
|
||||||
httpHeaders
|
|
||||||
}
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
throw {
|
throw {
|
||||||
code: 400,
|
code: 400,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
Body
|
Body
|
||||||
} from 'tsoa'
|
} from 'tsoa'
|
||||||
|
|
||||||
import Group, { GroupPayload } from '../model/Group'
|
import Group, { GroupPayload, PUBLIC_GROUP_NAME } from '../model/Group'
|
||||||
import User from '../model/User'
|
import User from '../model/User'
|
||||||
import { UserResponse } from './user'
|
import { UserResponse } from './user'
|
||||||
|
|
||||||
@@ -241,6 +241,13 @@ const updateUsersListInGroup = async (
|
|||||||
message: 'Group not found.'
|
message: 'Group not found.'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (group.name === PUBLIC_GROUP_NAME)
|
||||||
|
throw {
|
||||||
|
code: 400,
|
||||||
|
status: 'Bad Request',
|
||||||
|
message: `Can't add/remove user to '${PUBLIC_GROUP_NAME}' group.`
|
||||||
|
}
|
||||||
|
|
||||||
const user = await User.findOne({ id: userId })
|
const user = await User.findOne({ id: userId })
|
||||||
if (!user)
|
if (!user)
|
||||||
throw {
|
throw {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Route, Tags, Example, Get } from 'tsoa'
|
import { Route, Tags, Example, Get } from 'tsoa'
|
||||||
import { getAuthorizedRoutes } from '../utils'
|
import { getAuthorizedRoutes } from '../utils'
|
||||||
export interface AuthorizedRoutesResponse {
|
export interface AuthorizedRoutesResponse {
|
||||||
URIs: string[]
|
paths: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InfoResponse {
|
export interface InfoResponse {
|
||||||
@@ -42,16 +42,16 @@ export class InfoController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Get authorized routes.
|
* @summary Get the list of available routes to which permissions can be applied. Used to populate the dialog in the URI Permissions feature.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@Example<AuthorizedRoutesResponse>({
|
@Example<AuthorizedRoutesResponse>({
|
||||||
URIs: ['/AppStream', '/SASjsApi/stp/execute']
|
paths: ['/AppStream', '/SASjsApi/stp/execute']
|
||||||
})
|
})
|
||||||
@Get('/authorizedRoutes')
|
@Get('/authorizedRoutes')
|
||||||
public authorizedRoutes(): AuthorizedRoutesResponse {
|
public authorizedRoutes(): AuthorizedRoutesResponse {
|
||||||
const response = {
|
const response = {
|
||||||
URIs: getAuthorizedRoutes()
|
paths: getAuthorizedRoutes()
|
||||||
}
|
}
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,12 +20,6 @@ export interface ExecuteReturnRaw {
|
|||||||
result: string | Buffer
|
result: string | Buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExecuteReturnJson {
|
|
||||||
httpHeaders: HTTPHeaders
|
|
||||||
webout: string | Buffer
|
|
||||||
log?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ExecuteFileParams {
|
interface ExecuteFileParams {
|
||||||
programPath: string
|
programPath: string
|
||||||
preProgramVariables: PreProgramVars
|
preProgramVariables: PreProgramVars
|
||||||
@@ -68,10 +62,9 @@ export class ExecutionController {
|
|||||||
preProgramVariables,
|
preProgramVariables,
|
||||||
vars,
|
vars,
|
||||||
otherArgs,
|
otherArgs,
|
||||||
returnJson,
|
|
||||||
session: sessionByFileUpload,
|
session: sessionByFileUpload,
|
||||||
runTime
|
runTime
|
||||||
}: ExecuteProgramParams): Promise<ExecuteReturnRaw | ExecuteReturnJson> {
|
}: ExecuteProgramParams): Promise<ExecuteReturnRaw> {
|
||||||
const sessionController = getSessionController(runTime)
|
const sessionController = getSessionController(runTime)
|
||||||
|
|
||||||
const session =
|
const session =
|
||||||
@@ -96,6 +89,7 @@ export class ExecutionController {
|
|||||||
vars,
|
vars,
|
||||||
session,
|
session,
|
||||||
weboutPath,
|
weboutPath,
|
||||||
|
headersPath,
|
||||||
tokenFile,
|
tokenFile,
|
||||||
runTime,
|
runTime,
|
||||||
logPath,
|
logPath,
|
||||||
@@ -107,10 +101,7 @@ export class ExecutionController {
|
|||||||
? await readFile(headersPath)
|
? await readFile(headersPath)
|
||||||
: ''
|
: ''
|
||||||
const httpHeaders: HTTPHeaders = extractHeaders(headersContent)
|
const httpHeaders: HTTPHeaders = extractHeaders(headersContent)
|
||||||
const fileResponse: boolean =
|
const fileResponse: boolean = httpHeaders.hasOwnProperty('content-type')
|
||||||
httpHeaders.hasOwnProperty('content-type') &&
|
|
||||||
!returnJson && // not a POST Request
|
|
||||||
!isDebugOn(vars) // Debug is not enabled
|
|
||||||
|
|
||||||
const webout = (await fileExists(weboutPath))
|
const webout = (await fileExists(weboutPath))
|
||||||
? fileResponse
|
? fileResponse
|
||||||
@@ -121,19 +112,11 @@ export class ExecutionController {
|
|||||||
// it should be deleted by scheduleSessionDestroy
|
// it should be deleted by scheduleSessionDestroy
|
||||||
session.inUse = false
|
session.inUse = false
|
||||||
|
|
||||||
if (returnJson) {
|
|
||||||
return {
|
|
||||||
httpHeaders,
|
|
||||||
webout,
|
|
||||||
log: isDebugOn(vars) || session.crashed ? log : undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
httpHeaders,
|
httpHeaders,
|
||||||
result:
|
result:
|
||||||
isDebugOn(vars) || session.crashed
|
isDebugOn(vars) || session.crashed
|
||||||
? `<html><body>${webout}<div style="text-align:left"><hr /><h2>SAS Log</h2><pre>${log}</pre></div></body></html>`
|
? `${webout}\n${process.logsUUID}\n${log}`
|
||||||
: webout
|
: webout
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,6 +101,8 @@ ${autoExecContent}`
|
|||||||
session.path,
|
session.path,
|
||||||
'-AUTOEXEC',
|
'-AUTOEXEC',
|
||||||
autoExecPath,
|
autoExecPath,
|
||||||
|
'-SASINITIALFOLDER',
|
||||||
|
session.path,
|
||||||
process.sasLoc!.endsWith('sas.exe') ? '-nosplash' : '',
|
process.sasLoc!.endsWith('sas.exe') ? '-nosplash' : '',
|
||||||
process.sasLoc!.endsWith('sas.exe') ? '-icon' : '',
|
process.sasLoc!.endsWith('sas.exe') ? '-icon' : '',
|
||||||
process.sasLoc!.endsWith('sas.exe') ? '-nodms' : '',
|
process.sasLoc!.endsWith('sas.exe') ? '-nodms' : '',
|
||||||
@@ -190,7 +192,39 @@ export class JSSessionController extends SessionController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const headersPath = path.join(session.path, 'stpsrv_header.txt')
|
const headersPath = path.join(session.path, 'stpsrv_header.txt')
|
||||||
await createFile(headersPath, 'Content-type: application/json')
|
await createFile(headersPath, 'Content-type: text/plain')
|
||||||
|
|
||||||
|
this.sessions.push(session)
|
||||||
|
return session
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PythonSessionController extends SessionController {
|
||||||
|
protected async createSession(): Promise<Session> {
|
||||||
|
const sessionId = generateUniqueFileName(generateTimestamp())
|
||||||
|
const sessionFolder = path.join(getSessionsFolder(), sessionId)
|
||||||
|
|
||||||
|
const creationTimeStamp = sessionId.split('-').pop() as string
|
||||||
|
// death time of session is 15 mins from creation
|
||||||
|
const deathTimeStamp = (
|
||||||
|
parseInt(creationTimeStamp) +
|
||||||
|
15 * 60 * 1000 -
|
||||||
|
1000
|
||||||
|
).toString()
|
||||||
|
|
||||||
|
const session: Session = {
|
||||||
|
id: sessionId,
|
||||||
|
ready: true,
|
||||||
|
inUse: true,
|
||||||
|
consumed: false,
|
||||||
|
completed: false,
|
||||||
|
creationTimeStamp,
|
||||||
|
deathTimeStamp,
|
||||||
|
path: sessionFolder
|
||||||
|
}
|
||||||
|
|
||||||
|
const headersPath = path.join(session.path, 'stpsrv_header.txt')
|
||||||
|
await createFile(headersPath, 'Content-type: text/plain')
|
||||||
|
|
||||||
this.sessions.push(session)
|
this.sessions.push(session)
|
||||||
return session
|
return session
|
||||||
@@ -199,7 +233,7 @@ export class JSSessionController extends SessionController {
|
|||||||
|
|
||||||
export const getSessionController = (
|
export const getSessionController = (
|
||||||
runTime: RunTimeType
|
runTime: RunTimeType
|
||||||
): SASSessionController | JSSessionController => {
|
): SASSessionController | JSSessionController | PythonSessionController => {
|
||||||
if (runTime === RunTimeType.SAS) {
|
if (runTime === RunTimeType.SAS) {
|
||||||
return getSASSessionController()
|
return getSASSessionController()
|
||||||
}
|
}
|
||||||
@@ -208,6 +242,10 @@ export const getSessionController = (
|
|||||||
return getJSSessionController()
|
return getJSSessionController()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (runTime === RunTimeType.PY) {
|
||||||
|
return getPythonSessionController()
|
||||||
|
}
|
||||||
|
|
||||||
throw new Error('No Runtime is configured')
|
throw new Error('No Runtime is configured')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,6 +265,14 @@ const getJSSessionController = (): JSSessionController => {
|
|||||||
return process.jsSessionController
|
return process.jsSessionController
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getPythonSessionController = (): PythonSessionController => {
|
||||||
|
if (process.pythonSessionController) return process.pythonSessionController
|
||||||
|
|
||||||
|
process.pythonSessionController = new PythonSessionController()
|
||||||
|
|
||||||
|
return process.pythonSessionController
|
||||||
|
}
|
||||||
|
|
||||||
const autoExecContent = `
|
const autoExecContent = `
|
||||||
data _null_;
|
data _null_;
|
||||||
/* remove the dummy SYSIN */
|
/* remove the dummy SYSIN */
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export const createJSProgram = async (
|
|||||||
vars: ExecutionVars,
|
vars: ExecutionVars,
|
||||||
session: Session,
|
session: Session,
|
||||||
weboutPath: string,
|
weboutPath: string,
|
||||||
|
headersPath: string,
|
||||||
tokenFile: string,
|
tokenFile: string,
|
||||||
otherArgs?: any
|
otherArgs?: any
|
||||||
) => {
|
) => {
|
||||||
@@ -23,15 +24,16 @@ let _webout = '';
|
|||||||
const weboutPath = '${
|
const weboutPath = '${
|
||||||
isWindows() ? weboutPath.replace(/\\/g, '\\\\') : weboutPath
|
isWindows() ? weboutPath.replace(/\\/g, '\\\\') : weboutPath
|
||||||
}';
|
}';
|
||||||
const _sasjs_tokenfile = '${
|
const _SASJS_TOKENFILE = '${
|
||||||
isWindows() ? tokenFile.replace(/\\/g, '\\\\') : tokenFile
|
isWindows() ? tokenFile.replace(/\\/g, '\\\\') : tokenFile
|
||||||
}';
|
}';
|
||||||
const _sasjs_username = '${preProgramVariables?.username}';
|
const _SASJS_WEBOUT_HEADERS = '${headersPath}';
|
||||||
const _sasjs_userid = '${preProgramVariables?.userId}';
|
const _SASJS_USERNAME = '${preProgramVariables?.username}';
|
||||||
const _sasjs_displayname = '${preProgramVariables?.displayName}';
|
const _SASJS_USERID = '${preProgramVariables?.userId}';
|
||||||
const _metaperson = _sasjs_displayname;
|
const _SASJS_DISPLAYNAME = '${preProgramVariables?.displayName}';
|
||||||
const _metauser = _sasjs_username;
|
const _METAPERSON = _SASJS_DISPLAYNAME;
|
||||||
const sasjsprocessmode = 'Stored Program';
|
const _METAUSER = _SASJS_USERNAME;
|
||||||
|
const SASJSPROCESSMODE = 'Stored Program';
|
||||||
`
|
`
|
||||||
|
|
||||||
const requiredModules = `const fs = require('fs')`
|
const requiredModules = `const fs = require('fs')`
|
||||||
@@ -55,14 +57,15 @@ if (_webout) {
|
|||||||
`
|
`
|
||||||
// if no files are uploaded filesNamesMap will be undefined
|
// if no files are uploaded filesNamesMap will be undefined
|
||||||
if (otherArgs?.filesNamesMap) {
|
if (otherArgs?.filesNamesMap) {
|
||||||
const uploadJSCode = await generateFileUploadJSCode(
|
const uploadJsCode = await generateFileUploadJSCode(
|
||||||
otherArgs.filesNamesMap,
|
otherArgs.filesNamesMap,
|
||||||
session.path
|
session.path
|
||||||
)
|
)
|
||||||
|
|
||||||
//If js code for the file is generated it will be appended to the top of jsCode
|
// If any files are uploaded, the program needs to be updated with some
|
||||||
if (uploadJSCode.length > 0) {
|
// dynamically generated variables (pointers) for ease of ingestion
|
||||||
program = `${uploadJSCode}\n` + program
|
if (uploadJsCode.length > 0) {
|
||||||
|
program = `${uploadJsCode}\n` + program
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return requiredModules + program
|
return requiredModules + program
|
||||||
|
|||||||
68
api/src/controllers/internal/createPythonProgram.ts
Normal file
68
api/src/controllers/internal/createPythonProgram.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { isWindows } from '@sasjs/utils'
|
||||||
|
import { PreProgramVars, Session } from '../../types'
|
||||||
|
import { generateFileUploadPythonCode } from '../../utils'
|
||||||
|
import { ExecutionVars } from './'
|
||||||
|
|
||||||
|
export const createPythonProgram = async (
|
||||||
|
program: string,
|
||||||
|
preProgramVariables: PreProgramVars,
|
||||||
|
vars: ExecutionVars,
|
||||||
|
session: Session,
|
||||||
|
weboutPath: string,
|
||||||
|
headersPath: string,
|
||||||
|
tokenFile: string,
|
||||||
|
otherArgs?: any
|
||||||
|
) => {
|
||||||
|
const varStatments = Object.keys(vars).reduce(
|
||||||
|
(computed: string, key: string) => `${computed}${key} = '${vars[key]}';\n`,
|
||||||
|
''
|
||||||
|
)
|
||||||
|
|
||||||
|
const preProgramVarStatments = `
|
||||||
|
_SASJS_SESSION_PATH = '${
|
||||||
|
isWindows() ? session.path.replace(/\\/g, '\\\\') : session.path
|
||||||
|
}';
|
||||||
|
_WEBOUT = '${isWindows() ? weboutPath.replace(/\\/g, '\\\\') : weboutPath}';
|
||||||
|
_SASJS_WEBOUT_HEADERS = '${headersPath}';
|
||||||
|
_SASJS_TOKENFILE = '${
|
||||||
|
isWindows() ? tokenFile.replace(/\\/g, '\\\\') : tokenFile
|
||||||
|
}';
|
||||||
|
_SASJS_USERNAME = '${preProgramVariables?.username}';
|
||||||
|
_SASJS_USERID = '${preProgramVariables?.userId}';
|
||||||
|
_SASJS_DISPLAYNAME = '${preProgramVariables?.displayName}';
|
||||||
|
_METAPERSON = _SASJS_DISPLAYNAME;
|
||||||
|
_METAUSER = _SASJS_USERNAME;
|
||||||
|
SASJSPROCESSMODE = 'Stored Program';
|
||||||
|
`
|
||||||
|
|
||||||
|
const requiredModules = `import os`
|
||||||
|
|
||||||
|
program = `
|
||||||
|
# runtime vars
|
||||||
|
${varStatments}
|
||||||
|
|
||||||
|
# dynamic user-provided vars
|
||||||
|
${preProgramVarStatments}
|
||||||
|
|
||||||
|
# change working directory to session folder
|
||||||
|
os.chdir(_SASJS_SESSION_PATH)
|
||||||
|
|
||||||
|
# actual job code
|
||||||
|
${program}
|
||||||
|
|
||||||
|
`
|
||||||
|
// if no files are uploaded filesNamesMap will be undefined
|
||||||
|
if (otherArgs?.filesNamesMap) {
|
||||||
|
const uploadPythonCode = await generateFileUploadPythonCode(
|
||||||
|
otherArgs.filesNamesMap,
|
||||||
|
session.path
|
||||||
|
)
|
||||||
|
|
||||||
|
// If any files are uploaded, the program needs to be updated with some
|
||||||
|
// dynamically generated variables (pointers) for ease of ingestion
|
||||||
|
if (uploadPythonCode.length > 0) {
|
||||||
|
program = `${uploadPythonCode}\n` + program
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return requiredModules + program
|
||||||
|
}
|
||||||
@@ -23,10 +23,14 @@ export const createSASProgram = async (
|
|||||||
%let _sasjs_displayname=${preProgramVariables?.displayName};
|
%let _sasjs_displayname=${preProgramVariables?.displayName};
|
||||||
%let _sasjs_apiserverurl=${preProgramVariables?.serverUrl};
|
%let _sasjs_apiserverurl=${preProgramVariables?.serverUrl};
|
||||||
%let _sasjs_apipath=/SASjsApi/stp/execute;
|
%let _sasjs_apipath=/SASjsApi/stp/execute;
|
||||||
|
%let _sasjs_webout_headers=%sysfunc(pathname(work))/../stpsrv_header.txt;
|
||||||
%let _metaperson=&_sasjs_displayname;
|
%let _metaperson=&_sasjs_displayname;
|
||||||
%let _metauser=&_sasjs_username;
|
%let _metauser=&_sasjs_username;
|
||||||
|
|
||||||
|
/* the below is here for compatibility and will be removed in a future release */
|
||||||
|
%let sasjs_stpsrv_header_loc=&_sasjs_webout_headers;
|
||||||
|
|
||||||
%let sasjsprocessmode=Stored Program;
|
%let sasjsprocessmode=Stored Program;
|
||||||
%let sasjs_stpsrv_header_loc=%sysfunc(pathname(work))/../stpsrv_header.txt;
|
|
||||||
|
|
||||||
%global SYSPROCESSMODE SYSTCPIPHOSTNAME SYSHOSTINFOLONG;
|
%global SYSPROCESSMODE SYSTCPIPHOSTNAME SYSHOSTINFOLONG;
|
||||||
%macro _sasjs_server_init();
|
%macro _sasjs_server_init();
|
||||||
@@ -34,6 +38,9 @@ export const createSASProgram = async (
|
|||||||
%if "&SYSTCPIPHOSTNAME"="" %then %let SYSTCPIPHOSTNAME=&_sasjs_apiserverurl;
|
%if "&SYSTCPIPHOSTNAME"="" %then %let SYSTCPIPHOSTNAME=&_sasjs_apiserverurl;
|
||||||
%mend;
|
%mend;
|
||||||
%_sasjs_server_init()
|
%_sasjs_server_init()
|
||||||
|
|
||||||
|
proc printto print="%sysfunc(getoption(log))";
|
||||||
|
run;
|
||||||
`
|
`
|
||||||
|
|
||||||
program = `
|
program = `
|
||||||
@@ -60,7 +67,8 @@ ${program}`
|
|||||||
session.path
|
session.path
|
||||||
)
|
)
|
||||||
|
|
||||||
//If sas code for the file is generated it will be appended to the top of sasCode
|
// If any files are uploaded, the program needs to be updated with some
|
||||||
|
// dynamically generated variables (pointers) for ease of ingestion
|
||||||
if (uploadSasCode.length > 0) {
|
if (uploadSasCode.length > 0) {
|
||||||
program = `${uploadSasCode}` + program
|
program = `${uploadSasCode}` + program
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,4 +4,5 @@ export * from './Execution'
|
|||||||
export * from './FileUploadController'
|
export * from './FileUploadController'
|
||||||
export * from './createSASProgram'
|
export * from './createSASProgram'
|
||||||
export * from './createJSProgram'
|
export * from './createJSProgram'
|
||||||
|
export * from './createPythonProgram'
|
||||||
export * from './processProgram'
|
export * from './processProgram'
|
||||||
|
|||||||
@@ -5,7 +5,12 @@ import { once } from 'stream'
|
|||||||
import { createFile, moveFile } from '@sasjs/utils'
|
import { createFile, moveFile } from '@sasjs/utils'
|
||||||
import { PreProgramVars, Session } from '../../types'
|
import { PreProgramVars, Session } from '../../types'
|
||||||
import { RunTimeType } from '../../utils'
|
import { RunTimeType } from '../../utils'
|
||||||
import { ExecutionVars, createSASProgram, createJSProgram } from './'
|
import {
|
||||||
|
ExecutionVars,
|
||||||
|
createSASProgram,
|
||||||
|
createJSProgram,
|
||||||
|
createPythonProgram
|
||||||
|
} from './'
|
||||||
|
|
||||||
export const processProgram = async (
|
export const processProgram = async (
|
||||||
program: string,
|
program: string,
|
||||||
@@ -13,6 +18,7 @@ export const processProgram = async (
|
|||||||
vars: ExecutionVars,
|
vars: ExecutionVars,
|
||||||
session: Session,
|
session: Session,
|
||||||
weboutPath: string,
|
weboutPath: string,
|
||||||
|
headersPath: string,
|
||||||
tokenFile: string,
|
tokenFile: string,
|
||||||
runTime: RunTimeType,
|
runTime: RunTimeType,
|
||||||
logPath: string,
|
logPath: string,
|
||||||
@@ -25,6 +31,7 @@ export const processProgram = async (
|
|||||||
vars,
|
vars,
|
||||||
session,
|
session,
|
||||||
weboutPath,
|
weboutPath,
|
||||||
|
headersPath,
|
||||||
tokenFile,
|
tokenFile,
|
||||||
otherArgs
|
otherArgs
|
||||||
)
|
)
|
||||||
@@ -47,6 +54,43 @@ export const processProgram = async (
|
|||||||
// copy the code.js program to log and end write stream
|
// copy the code.js program to log and end write stream
|
||||||
writeStream.end(program)
|
writeStream.end(program)
|
||||||
|
|
||||||
|
session.completed = true
|
||||||
|
console.log('session completed', session)
|
||||||
|
} catch (err: any) {
|
||||||
|
session.completed = true
|
||||||
|
session.crashed = err.toString()
|
||||||
|
console.log('session crashed', session.id, session.crashed)
|
||||||
|
}
|
||||||
|
} else if (runTime === RunTimeType.PY) {
|
||||||
|
program = await createPythonProgram(
|
||||||
|
program,
|
||||||
|
preProgramVariables,
|
||||||
|
vars,
|
||||||
|
session,
|
||||||
|
weboutPath,
|
||||||
|
headersPath,
|
||||||
|
tokenFile,
|
||||||
|
otherArgs
|
||||||
|
)
|
||||||
|
|
||||||
|
const codePath = path.join(session.path, 'code.py')
|
||||||
|
|
||||||
|
try {
|
||||||
|
await createFile(codePath, program)
|
||||||
|
|
||||||
|
// create a stream that will write to console outputs to log file
|
||||||
|
const writeStream = fs.createWriteStream(logPath)
|
||||||
|
|
||||||
|
// waiting for the open event so that we can have underlying file descriptor
|
||||||
|
await once(writeStream, 'open')
|
||||||
|
|
||||||
|
execFileSync(process.pythonLoc!, [codePath], {
|
||||||
|
stdio: ['ignore', writeStream, writeStream]
|
||||||
|
})
|
||||||
|
|
||||||
|
// copy the code.py program to log and end write stream
|
||||||
|
writeStream.end(program)
|
||||||
|
|
||||||
session.completed = true
|
session.completed = true
|
||||||
console.log('session completed', session)
|
console.log('session completed', session)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import express from 'express'
|
||||||
import {
|
import {
|
||||||
Security,
|
Security,
|
||||||
Route,
|
Route,
|
||||||
@@ -8,7 +9,8 @@ import {
|
|||||||
Post,
|
Post,
|
||||||
Patch,
|
Patch,
|
||||||
Delete,
|
Delete,
|
||||||
Body
|
Body,
|
||||||
|
Request
|
||||||
} from 'tsoa'
|
} from 'tsoa'
|
||||||
|
|
||||||
import Permission from '../model/Permission'
|
import Permission from '../model/Permission'
|
||||||
@@ -17,12 +19,16 @@ import Group from '../model/Group'
|
|||||||
import { UserResponse } from './user'
|
import { UserResponse } from './user'
|
||||||
import { GroupDetailsResponse } from './group'
|
import { GroupDetailsResponse } from './group'
|
||||||
|
|
||||||
|
export enum PermissionType {
|
||||||
|
route = 'Route'
|
||||||
|
}
|
||||||
|
|
||||||
export enum PrincipalType {
|
export enum PrincipalType {
|
||||||
user = 'user',
|
user = 'user',
|
||||||
group = 'group'
|
group = 'group'
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum PermissionSetting {
|
export enum PermissionSettingForRoute {
|
||||||
grant = 'Grant',
|
grant = 'Grant',
|
||||||
deny = 'Deny'
|
deny = 'Deny'
|
||||||
}
|
}
|
||||||
@@ -32,12 +38,17 @@ interface RegisterPermissionPayload {
|
|||||||
* Name of affected resource
|
* Name of affected resource
|
||||||
* @example "/SASjsApi/code/execute"
|
* @example "/SASjsApi/code/execute"
|
||||||
*/
|
*/
|
||||||
uri: string
|
path: string
|
||||||
|
/**
|
||||||
|
* Type of affected resource
|
||||||
|
* @example "Route"
|
||||||
|
*/
|
||||||
|
type: PermissionType
|
||||||
/**
|
/**
|
||||||
* The indication of whether (and to what extent) access is provided
|
* The indication of whether (and to what extent) access is provided
|
||||||
* @example "Grant"
|
* @example "Grant"
|
||||||
*/
|
*/
|
||||||
setting: PermissionSetting
|
setting: PermissionSettingForRoute
|
||||||
/**
|
/**
|
||||||
* Indicates the type of principal
|
* Indicates the type of principal
|
||||||
* @example "user"
|
* @example "user"
|
||||||
@@ -55,12 +66,13 @@ interface UpdatePermissionPayload {
|
|||||||
* The indication of whether (and to what extent) access is provided
|
* The indication of whether (and to what extent) access is provided
|
||||||
* @example "Grant"
|
* @example "Grant"
|
||||||
*/
|
*/
|
||||||
setting: PermissionSetting
|
setting: PermissionSettingForRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PermissionDetailsResponse {
|
export interface PermissionDetailsResponse {
|
||||||
permissionId: number
|
permissionId: number
|
||||||
uri: string
|
path: string
|
||||||
|
type: string
|
||||||
setting: string
|
setting: string
|
||||||
user?: UserResponse
|
user?: UserResponse
|
||||||
group?: GroupDetailsResponse
|
group?: GroupDetailsResponse
|
||||||
@@ -71,13 +83,17 @@ export interface PermissionDetailsResponse {
|
|||||||
@Tags('Permission')
|
@Tags('Permission')
|
||||||
export class PermissionController {
|
export class PermissionController {
|
||||||
/**
|
/**
|
||||||
* @summary Get list of all permissions (uri, setting and userDetail).
|
* Get the list of permission rules applicable the authenticated user.
|
||||||
|
* If the user is an admin, all rules are returned.
|
||||||
|
*
|
||||||
|
* @summary Get the list of permission rules. If the user is admin, all rules are returned.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@Example<PermissionDetailsResponse[]>([
|
@Example<PermissionDetailsResponse[]>([
|
||||||
{
|
{
|
||||||
permissionId: 123,
|
permissionId: 123,
|
||||||
uri: '/SASjsApi/code/execute',
|
path: '/SASjsApi/code/execute',
|
||||||
|
type: 'Route',
|
||||||
setting: 'Grant',
|
setting: 'Grant',
|
||||||
user: {
|
user: {
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -88,7 +104,8 @@ export class PermissionController {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
permissionId: 124,
|
permissionId: 124,
|
||||||
uri: '/SASjsApi/code/execute',
|
path: '/SASjsApi/code/execute',
|
||||||
|
type: 'Route',
|
||||||
setting: 'Grant',
|
setting: 'Grant',
|
||||||
group: {
|
group: {
|
||||||
groupId: 1,
|
groupId: 1,
|
||||||
@@ -100,8 +117,10 @@ export class PermissionController {
|
|||||||
}
|
}
|
||||||
])
|
])
|
||||||
@Get('/')
|
@Get('/')
|
||||||
public async getAllPermissions(): Promise<PermissionDetailsResponse[]> {
|
public async getAllPermissions(
|
||||||
return getAllPermissions()
|
@Request() request: express.Request
|
||||||
|
): Promise<PermissionDetailsResponse[]> {
|
||||||
|
return getAllPermissions(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -110,7 +129,8 @@ export class PermissionController {
|
|||||||
*/
|
*/
|
||||||
@Example<PermissionDetailsResponse>({
|
@Example<PermissionDetailsResponse>({
|
||||||
permissionId: 123,
|
permissionId: 123,
|
||||||
uri: '/SASjsApi/code/execute',
|
path: '/SASjsApi/code/execute',
|
||||||
|
type: 'Route',
|
||||||
setting: 'Grant',
|
setting: 'Grant',
|
||||||
user: {
|
user: {
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -133,7 +153,8 @@ export class PermissionController {
|
|||||||
*/
|
*/
|
||||||
@Example<PermissionDetailsResponse>({
|
@Example<PermissionDetailsResponse>({
|
||||||
permissionId: 123,
|
permissionId: 123,
|
||||||
uri: '/SASjsApi/code/execute',
|
path: '/SASjsApi/code/execute',
|
||||||
|
type: 'Route',
|
||||||
setting: 'Grant',
|
setting: 'Grant',
|
||||||
user: {
|
user: {
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -161,33 +182,43 @@ export class PermissionController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAllPermissions = async (): Promise<PermissionDetailsResponse[]> =>
|
const getAllPermissions = async (
|
||||||
(await Permission.find({})
|
req: express.Request
|
||||||
.select({
|
): Promise<PermissionDetailsResponse[]> => {
|
||||||
_id: 0,
|
const { user } = req
|
||||||
permissionId: 1,
|
|
||||||
uri: 1,
|
if (user?.isAdmin) return await Permission.get({})
|
||||||
setting: 1
|
else {
|
||||||
})
|
const permissions: PermissionDetailsResponse[] = []
|
||||||
.populate({ path: 'user', select: 'id username displayName isAdmin -_id' })
|
|
||||||
.populate({
|
const dbUser = await User.findOne({ id: user?.userId })
|
||||||
path: 'group',
|
if (!dbUser)
|
||||||
select: 'groupId name description -_id',
|
throw {
|
||||||
populate: {
|
code: 404,
|
||||||
path: 'users',
|
status: 'Not Found',
|
||||||
select: 'id username displayName isAdmin -_id',
|
message: 'User not found.'
|
||||||
options: { limit: 15 }
|
|
||||||
}
|
}
|
||||||
})) as unknown as PermissionDetailsResponse[]
|
|
||||||
|
permissions.push(...(await Permission.get({ user: dbUser._id })))
|
||||||
|
|
||||||
|
for (const group of dbUser.groups) {
|
||||||
|
permissions.push(...(await Permission.get({ group })))
|
||||||
|
}
|
||||||
|
|
||||||
|
return permissions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const createPermission = async ({
|
const createPermission = async ({
|
||||||
uri,
|
path,
|
||||||
|
type,
|
||||||
setting,
|
setting,
|
||||||
principalType,
|
principalType,
|
||||||
principalId
|
principalId
|
||||||
}: RegisterPermissionPayload): Promise<PermissionDetailsResponse> => {
|
}: RegisterPermissionPayload): Promise<PermissionDetailsResponse> => {
|
||||||
const permission = new Permission({
|
const permission = new Permission({
|
||||||
uri,
|
path,
|
||||||
|
type,
|
||||||
setting
|
setting
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -212,7 +243,8 @@ const createPermission = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const alreadyExists = await Permission.findOne({
|
const alreadyExists = await Permission.findOne({
|
||||||
uri,
|
path,
|
||||||
|
type,
|
||||||
user: userInDB._id
|
user: userInDB._id
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -220,7 +252,8 @@ const createPermission = async ({
|
|||||||
throw {
|
throw {
|
||||||
code: 409,
|
code: 409,
|
||||||
status: 'Conflict',
|
status: 'Conflict',
|
||||||
message: 'Permission already exists with provided URI and User.'
|
message:
|
||||||
|
'Permission already exists with provided Path, Type and User.'
|
||||||
}
|
}
|
||||||
|
|
||||||
permission.user = userInDB._id
|
permission.user = userInDB._id
|
||||||
@@ -243,14 +276,16 @@ const createPermission = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const alreadyExists = await Permission.findOne({
|
const alreadyExists = await Permission.findOne({
|
||||||
uri,
|
path,
|
||||||
|
type,
|
||||||
group: groupInDB._id
|
group: groupInDB._id
|
||||||
})
|
})
|
||||||
if (alreadyExists)
|
if (alreadyExists)
|
||||||
throw {
|
throw {
|
||||||
code: 409,
|
code: 409,
|
||||||
status: 'Conflict',
|
status: 'Conflict',
|
||||||
message: 'Permission already exists with provided URI and Group.'
|
message:
|
||||||
|
'Permission already exists with provided Path, Type and Group.'
|
||||||
}
|
}
|
||||||
|
|
||||||
permission.group = groupInDB._id
|
permission.group = groupInDB._id
|
||||||
@@ -280,7 +315,8 @@ const createPermission = async ({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
permissionId: savedPermission.permissionId,
|
permissionId: savedPermission.permissionId,
|
||||||
uri: savedPermission.uri,
|
path: savedPermission.path,
|
||||||
|
type: savedPermission.type,
|
||||||
setting: savedPermission.setting,
|
setting: savedPermission.setting,
|
||||||
user,
|
user,
|
||||||
group
|
group
|
||||||
@@ -301,7 +337,8 @@ const updatePermission = async (
|
|||||||
.select({
|
.select({
|
||||||
_id: 0,
|
_id: 0,
|
||||||
permissionId: 1,
|
permissionId: 1,
|
||||||
uri: 1,
|
path: 1,
|
||||||
|
type: 1,
|
||||||
setting: 1
|
setting: 1
|
||||||
})
|
})
|
||||||
.populate({ path: 'user', select: 'id username displayName isAdmin -_id' })
|
.populate({ path: 'user', select: 'id username displayName isAdmin -_id' })
|
||||||
|
|||||||
@@ -1,33 +1,16 @@
|
|||||||
import express from 'express'
|
import express from 'express'
|
||||||
import {
|
import { Request, Security, Route, Tags, Post, Body, Get, Query } from 'tsoa'
|
||||||
Request,
|
import { ExecutionController, ExecutionVars } from './internal'
|
||||||
Security,
|
|
||||||
Route,
|
|
||||||
Tags,
|
|
||||||
Post,
|
|
||||||
Body,
|
|
||||||
Get,
|
|
||||||
Query,
|
|
||||||
Example
|
|
||||||
} from 'tsoa'
|
|
||||||
import {
|
|
||||||
ExecuteReturnJson,
|
|
||||||
ExecuteReturnRaw,
|
|
||||||
ExecutionController,
|
|
||||||
ExecutionVars
|
|
||||||
} from './internal'
|
|
||||||
import {
|
import {
|
||||||
getPreProgramVariables,
|
getPreProgramVariables,
|
||||||
HTTPHeaders,
|
HTTPHeaders,
|
||||||
isDebugOn,
|
|
||||||
LogLine,
|
LogLine,
|
||||||
makeFilesNamesMap,
|
makeFilesNamesMap,
|
||||||
parseLogToArray,
|
|
||||||
getRunTimeAndFilePath
|
getRunTimeAndFilePath
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import { MulterFile } from '../types/Upload'
|
import { MulterFile } from '../types/Upload'
|
||||||
|
|
||||||
interface ExecuteReturnJsonPayload {
|
interface ExecutePostRequestPayload {
|
||||||
/**
|
/**
|
||||||
* Location of SAS program
|
* Location of SAS program
|
||||||
* @example "/Public/somefolder/some.file"
|
* @example "/Public/somefolder/some.file"
|
||||||
@@ -35,17 +18,6 @@ interface ExecuteReturnJsonPayload {
|
|||||||
_program?: string
|
_program?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IRecordOfAny {
|
|
||||||
[key: string]: any
|
|
||||||
}
|
|
||||||
export interface ExecuteReturnJsonResponse {
|
|
||||||
status: string
|
|
||||||
_webout: string | IRecordOfAny
|
|
||||||
log: LogLine[]
|
|
||||||
message?: string
|
|
||||||
httpHeaders: HTTPHeaders
|
|
||||||
}
|
|
||||||
|
|
||||||
@Security('bearerAuth')
|
@Security('bearerAuth')
|
||||||
@Route('SASjsApi/stp')
|
@Route('SASjsApi/stp')
|
||||||
@Tags('STP')
|
@Tags('STP')
|
||||||
@@ -62,11 +34,12 @@ export class STPController {
|
|||||||
* @example _program "/Projects/myApp/some/program"
|
* @example _program "/Projects/myApp/some/program"
|
||||||
*/
|
*/
|
||||||
@Get('/execute')
|
@Get('/execute')
|
||||||
public async executeReturnRaw(
|
public async executeGetRequest(
|
||||||
@Request() request: express.Request,
|
@Request() request: express.Request,
|
||||||
@Query() _program: string
|
@Query() _program: string
|
||||||
): Promise<string | Buffer> {
|
): Promise<string | Buffer> {
|
||||||
return executeReturnRaw(request, _program)
|
const vars = request.query as ExecutionVars
|
||||||
|
return execute(request, _program, vars)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -87,50 +60,42 @@ export class STPController {
|
|||||||
* @param _program Location of SAS or JS code
|
* @param _program Location of SAS or JS code
|
||||||
* @example _program "/Projects/myApp/some/program"
|
* @example _program "/Projects/myApp/some/program"
|
||||||
*/
|
*/
|
||||||
@Example<ExecuteReturnJsonResponse>({
|
|
||||||
status: 'success',
|
|
||||||
_webout: 'webout content',
|
|
||||||
log: [],
|
|
||||||
httpHeaders: {
|
|
||||||
'Content-type': 'application/zip',
|
|
||||||
'Cache-Control': 'public, max-age=1000'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@Post('/execute')
|
@Post('/execute')
|
||||||
public async executeReturnJson(
|
public async executePostRequest(
|
||||||
@Request() request: express.Request,
|
@Request() request: express.Request,
|
||||||
@Body() body?: ExecuteReturnJsonPayload,
|
@Body() body?: ExecutePostRequestPayload,
|
||||||
@Query() _program?: string
|
@Query() _program?: string
|
||||||
): Promise<ExecuteReturnJsonResponse> {
|
): Promise<string | Buffer> {
|
||||||
const program = _program ?? body?._program
|
const program = _program ?? body?._program
|
||||||
return executeReturnJson(request, program!)
|
const vars = { ...request.query, ...request.body }
|
||||||
|
const filesNamesMap = request.files?.length
|
||||||
|
? makeFilesNamesMap(request.files as MulterFile[])
|
||||||
|
: null
|
||||||
|
const otherArgs = { filesNamesMap: filesNamesMap }
|
||||||
|
|
||||||
|
return execute(request, program!, vars, otherArgs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const executeReturnRaw = async (
|
const execute = async (
|
||||||
req: express.Request,
|
req: express.Request,
|
||||||
_program: string
|
_program: string,
|
||||||
|
vars: ExecutionVars,
|
||||||
|
otherArgs?: any
|
||||||
): Promise<string | Buffer> => {
|
): Promise<string | Buffer> => {
|
||||||
const query = req.query as ExecutionVars
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { codePath, runTime } = await getRunTimeAndFilePath(_program)
|
const { codePath, runTime } = await getRunTimeAndFilePath(_program)
|
||||||
|
|
||||||
const { result, httpHeaders } =
|
const { result, httpHeaders } = await new ExecutionController().executeFile(
|
||||||
(await new ExecutionController().executeFile({
|
{
|
||||||
programPath: codePath,
|
programPath: codePath,
|
||||||
|
runTime,
|
||||||
preProgramVariables: getPreProgramVariables(req),
|
preProgramVariables: getPreProgramVariables(req),
|
||||||
vars: query,
|
vars,
|
||||||
runTime
|
otherArgs,
|
||||||
})) as ExecuteReturnRaw
|
session: req.sasjsSession
|
||||||
|
}
|
||||||
// Should over-ride response header for debug
|
)
|
||||||
// on GET request to see entire log rendering on browser.
|
|
||||||
if (isDebugOn(query)) {
|
|
||||||
httpHeaders['content-type'] = 'text/plain'
|
|
||||||
}
|
|
||||||
|
|
||||||
req.res?.set(httpHeaders)
|
|
||||||
|
|
||||||
if (result instanceof Buffer) {
|
if (result instanceof Buffer) {
|
||||||
;(req as any).sasHeaders = httpHeaders
|
;(req as any).sasHeaders = httpHeaders
|
||||||
@@ -146,48 +111,3 @@ const executeReturnRaw = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const executeReturnJson = async (
|
|
||||||
req: express.Request,
|
|
||||||
_program: string
|
|
||||||
): Promise<ExecuteReturnJsonResponse> => {
|
|
||||||
const filesNamesMap = req.files?.length
|
|
||||||
? makeFilesNamesMap(req.files as MulterFile[])
|
|
||||||
: null
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { codePath, runTime } = await getRunTimeAndFilePath(_program)
|
|
||||||
|
|
||||||
const { webout, log, httpHeaders } =
|
|
||||||
(await new ExecutionController().executeFile({
|
|
||||||
programPath: codePath,
|
|
||||||
preProgramVariables: getPreProgramVariables(req),
|
|
||||||
vars: { ...req.query, ...req.body },
|
|
||||||
otherArgs: { filesNamesMap: filesNamesMap },
|
|
||||||
returnJson: true,
|
|
||||||
session: req.sasjsSession,
|
|
||||||
runTime
|
|
||||||
})) as ExecuteReturnJson
|
|
||||||
|
|
||||||
let weboutRes: string | IRecordOfAny = webout
|
|
||||||
if (httpHeaders['content-type']?.toLowerCase() === 'application/json') {
|
|
||||||
try {
|
|
||||||
weboutRes = JSON.parse(webout as string)
|
|
||||||
} catch (_) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
status: 'success',
|
|
||||||
_webout: weboutRes,
|
|
||||||
log: parseLogToArray(log),
|
|
||||||
httpHeaders
|
|
||||||
}
|
|
||||||
} catch (err: any) {
|
|
||||||
throw {
|
|
||||||
code: 400,
|
|
||||||
status: 'failure',
|
|
||||||
message: 'Job execution failed.',
|
|
||||||
error: typeof err === 'object' ? err.toString() : err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import {
|
|||||||
fetchLatestAutoExec,
|
fetchLatestAutoExec,
|
||||||
ModeType,
|
ModeType,
|
||||||
verifyTokenInDB,
|
verifyTokenInDB,
|
||||||
isAuthorizingRoute
|
isAuthorizingRoute,
|
||||||
|
isPublicRoute,
|
||||||
|
publicUser
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import { desktopUser } from './desktop'
|
import { desktopUser } from './desktop'
|
||||||
import { authorize } from './authorize'
|
import { authorize } from './authorize'
|
||||||
@@ -41,7 +43,7 @@ export const authenticateAccessToken: RequestHandler = async (
|
|||||||
return res.sendStatus(401)
|
return res.sendStatus(401)
|
||||||
}
|
}
|
||||||
|
|
||||||
authenticateToken(
|
await authenticateToken(
|
||||||
req,
|
req,
|
||||||
res,
|
res,
|
||||||
nextFunction,
|
nextFunction,
|
||||||
@@ -50,8 +52,12 @@ export const authenticateAccessToken: RequestHandler = async (
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const authenticateRefreshToken: RequestHandler = (req, res, next) => {
|
export const authenticateRefreshToken: RequestHandler = async (
|
||||||
authenticateToken(
|
req,
|
||||||
|
res,
|
||||||
|
next
|
||||||
|
) => {
|
||||||
|
await authenticateToken(
|
||||||
req,
|
req,
|
||||||
res,
|
res,
|
||||||
next,
|
next,
|
||||||
@@ -60,7 +66,7 @@ export const authenticateRefreshToken: RequestHandler = (req, res, next) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const authenticateToken = (
|
const authenticateToken = async (
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
next: NextFunction,
|
next: NextFunction,
|
||||||
@@ -83,12 +89,12 @@ const authenticateToken = (
|
|||||||
|
|
||||||
const authHeader = req.headers['authorization']
|
const authHeader = req.headers['authorization']
|
||||||
const token = authHeader?.split(' ')[1]
|
const token = authHeader?.split(' ')[1]
|
||||||
if (!token) return res.sendStatus(401)
|
|
||||||
|
|
||||||
jwt.verify(token, key, async (err: any, data: any) => {
|
try {
|
||||||
if (err) return res.sendStatus(401)
|
if (!token) throw 'Unauthorized'
|
||||||
|
|
||||||
|
const data: any = jwt.verify(token, key)
|
||||||
|
|
||||||
// verify this valid token's entry in DB
|
|
||||||
const user = await verifyTokenInDB(
|
const user = await verifyTokenInDB(
|
||||||
data?.userId,
|
data?.userId,
|
||||||
data?.clientId,
|
data?.clientId,
|
||||||
@@ -101,8 +107,16 @@ const authenticateToken = (
|
|||||||
req.user = user
|
req.user = user
|
||||||
if (tokenType === 'accessToken') req.accessToken = token
|
if (tokenType === 'accessToken') req.accessToken = token
|
||||||
return next()
|
return next()
|
||||||
} else return res.sendStatus(401)
|
} else throw 'Unauthorized'
|
||||||
}
|
}
|
||||||
return res.sendStatus(401)
|
|
||||||
})
|
throw 'Unauthorized'
|
||||||
|
} catch (error) {
|
||||||
|
if (await isPublicRoute(req)) {
|
||||||
|
req.user = publicUser
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
|
||||||
|
res.sendStatus(401)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import { RequestHandler } from 'express'
|
import { RequestHandler } from 'express'
|
||||||
import User from '../model/User'
|
import User from '../model/User'
|
||||||
import Permission from '../model/Permission'
|
import Permission from '../model/Permission'
|
||||||
import { PermissionSetting } from '../controllers/permission'
|
import {
|
||||||
import { getUri } from '../utils'
|
PermissionSettingForRoute,
|
||||||
|
PermissionType
|
||||||
|
} from '../controllers/permission'
|
||||||
|
import { getPath, isPublicRoute } from '../utils'
|
||||||
|
|
||||||
export const authorize: RequestHandler = async (req, res, next) => {
|
export const authorize: RequestHandler = async (req, res, next) => {
|
||||||
const { user } = req
|
const { user } = req
|
||||||
@@ -14,23 +17,35 @@ export const authorize: RequestHandler = async (req, res, next) => {
|
|||||||
// no need to check for permissions when user is admin
|
// no need to check for permissions when user is admin
|
||||||
if (user.isAdmin) return next()
|
if (user.isAdmin) return next()
|
||||||
|
|
||||||
|
// no need to check for permissions when route is Public
|
||||||
|
if (await isPublicRoute(req)) return next()
|
||||||
|
|
||||||
const dbUser = await User.findOne({ id: user.userId })
|
const dbUser = await User.findOne({ id: user.userId })
|
||||||
if (!dbUser) return res.sendStatus(401)
|
if (!dbUser) return res.sendStatus(401)
|
||||||
|
|
||||||
const uri = getUri(req)
|
const path = getPath(req)
|
||||||
|
|
||||||
// find permission w.r.t user
|
// find permission w.r.t user
|
||||||
const permission = await Permission.findOne({ uri, user: dbUser._id })
|
const permission = await Permission.findOne({
|
||||||
|
path,
|
||||||
|
type: PermissionType.route,
|
||||||
|
user: dbUser._id
|
||||||
|
})
|
||||||
|
|
||||||
if (permission) {
|
if (permission) {
|
||||||
if (permission.setting === PermissionSetting.grant) return next()
|
if (permission.setting === PermissionSettingForRoute.grant) return next()
|
||||||
else return res.sendStatus(401)
|
else return res.sendStatus(401)
|
||||||
}
|
}
|
||||||
|
|
||||||
// find permission w.r.t user's groups
|
// find permission w.r.t user's groups
|
||||||
for (const group of dbUser.groups) {
|
for (const group of dbUser.groups) {
|
||||||
const groupPermission = await Permission.findOne({ uri, group })
|
const groupPermission = await Permission.findOne({
|
||||||
if (groupPermission?.setting === PermissionSetting.grant) return next()
|
path,
|
||||||
|
type: PermissionType.route,
|
||||||
|
group
|
||||||
|
})
|
||||||
|
if (groupPermission?.setting === PermissionSettingForRoute.grant)
|
||||||
|
return next()
|
||||||
}
|
}
|
||||||
return res.sendStatus(401)
|
return res.sendStatus(401)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { GroupDetailsResponse } from '../controllers'
|
|||||||
import User, { IUser } from './User'
|
import User, { IUser } from './User'
|
||||||
const AutoIncrement = require('mongoose-sequence')(mongoose)
|
const AutoIncrement = require('mongoose-sequence')(mongoose)
|
||||||
|
|
||||||
|
export const PUBLIC_GROUP_NAME = 'Public'
|
||||||
|
|
||||||
export interface GroupPayload {
|
export interface GroupPayload {
|
||||||
/**
|
/**
|
||||||
* Name of the group
|
* Name of the group
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
import mongoose, { Schema, model, Document, Model } from 'mongoose'
|
import mongoose, { Schema, model, Document, Model } from 'mongoose'
|
||||||
const AutoIncrement = require('mongoose-sequence')(mongoose)
|
const AutoIncrement = require('mongoose-sequence')(mongoose)
|
||||||
|
import { PermissionDetailsResponse } from '../controllers'
|
||||||
|
|
||||||
|
interface GetPermissionBy {
|
||||||
|
user?: Schema.Types.ObjectId
|
||||||
|
group?: Schema.Types.ObjectId
|
||||||
|
}
|
||||||
|
|
||||||
interface IPermissionDocument extends Document {
|
interface IPermissionDocument extends Document {
|
||||||
uri: string
|
path: string
|
||||||
|
type: string
|
||||||
setting: string
|
setting: string
|
||||||
permissionId: number
|
permissionId: number
|
||||||
user: Schema.Types.ObjectId
|
user: Schema.Types.ObjectId
|
||||||
@@ -11,10 +18,16 @@ interface IPermissionDocument extends Document {
|
|||||||
|
|
||||||
interface IPermission extends IPermissionDocument {}
|
interface IPermission extends IPermissionDocument {}
|
||||||
|
|
||||||
interface IPermissionModel extends Model<IPermission> {}
|
interface IPermissionModel extends Model<IPermission> {
|
||||||
|
get(getBy: GetPermissionBy): Promise<PermissionDetailsResponse[]>
|
||||||
|
}
|
||||||
|
|
||||||
const permissionSchema = new Schema<IPermissionDocument>({
|
const permissionSchema = new Schema<IPermissionDocument>({
|
||||||
uri: {
|
path: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
type: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
@@ -28,6 +41,30 @@ const permissionSchema = new Schema<IPermissionDocument>({
|
|||||||
|
|
||||||
permissionSchema.plugin(AutoIncrement, { inc_field: 'permissionId' })
|
permissionSchema.plugin(AutoIncrement, { inc_field: 'permissionId' })
|
||||||
|
|
||||||
|
// Static Methods
|
||||||
|
permissionSchema.static('get', async function (getBy: GetPermissionBy): Promise<
|
||||||
|
PermissionDetailsResponse[]
|
||||||
|
> {
|
||||||
|
return (await this.find(getBy)
|
||||||
|
.select({
|
||||||
|
_id: 0,
|
||||||
|
permissionId: 1,
|
||||||
|
path: 1,
|
||||||
|
type: 1,
|
||||||
|
setting: 1
|
||||||
|
})
|
||||||
|
.populate({ path: 'user', select: 'id username displayName isAdmin -_id' })
|
||||||
|
.populate({
|
||||||
|
path: 'group',
|
||||||
|
select: 'groupId name description -_id',
|
||||||
|
populate: {
|
||||||
|
path: 'users',
|
||||||
|
select: 'id username displayName isAdmin -_id',
|
||||||
|
options: { limit: 15 }
|
||||||
|
}
|
||||||
|
})) as unknown as PermissionDetailsResponse[]
|
||||||
|
})
|
||||||
|
|
||||||
export const Permission: IPermissionModel = model<
|
export const Permission: IPermissionModel = model<
|
||||||
IPermission,
|
IPermission,
|
||||||
IPermissionModel
|
IPermissionModel
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const controller = new PermissionController()
|
|||||||
|
|
||||||
permissionRouter.get('/', async (req, res) => {
|
permissionRouter.get('/', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const response = await controller.getAllPermissions()
|
const response = await controller.getAllPermissions(req)
|
||||||
res.send(response)
|
res.send(response)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const statusCode = err.code
|
const statusCode = err.code
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ import appPromise from '../../../app'
|
|||||||
import {
|
import {
|
||||||
UserController,
|
UserController,
|
||||||
PermissionController,
|
PermissionController,
|
||||||
PermissionSetting,
|
PermissionType,
|
||||||
|
PermissionSettingForRoute,
|
||||||
PrincipalType
|
PrincipalType
|
||||||
} from '../../../controllers/'
|
} from '../../../controllers/'
|
||||||
import { getTreeExample } from '../../../controllers/internal'
|
import { getTreeExample } from '../../../controllers/internal'
|
||||||
@@ -48,6 +49,12 @@ const user = {
|
|||||||
isActive: true
|
isActive: true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const permission = {
|
||||||
|
type: PermissionType.route,
|
||||||
|
principalType: PrincipalType.user,
|
||||||
|
setting: PermissionSettingForRoute.grant
|
||||||
|
}
|
||||||
|
|
||||||
describe('drive', () => {
|
describe('drive', () => {
|
||||||
let app: Express
|
let app: Express
|
||||||
let con: Mongoose
|
let con: Mongoose
|
||||||
@@ -66,34 +73,29 @@ describe('drive', () => {
|
|||||||
const dbUser = await controller.createUser(user)
|
const dbUser = await controller.createUser(user)
|
||||||
accessToken = await generateAndSaveToken(dbUser.id)
|
accessToken = await generateAndSaveToken(dbUser.id)
|
||||||
await permissionController.createPermission({
|
await permissionController.createPermission({
|
||||||
uri: '/SASjsApi/drive/deploy',
|
...permission,
|
||||||
principalType: PrincipalType.user,
|
path: '/SASjsApi/drive/deploy',
|
||||||
principalId: dbUser.id,
|
principalId: dbUser.id
|
||||||
setting: PermissionSetting.grant
|
|
||||||
})
|
})
|
||||||
await permissionController.createPermission({
|
await permissionController.createPermission({
|
||||||
uri: '/SASjsApi/drive/deploy/upload',
|
...permission,
|
||||||
principalType: PrincipalType.user,
|
path: '/SASjsApi/drive/deploy/upload',
|
||||||
principalId: dbUser.id,
|
principalId: dbUser.id
|
||||||
setting: PermissionSetting.grant
|
|
||||||
})
|
})
|
||||||
await permissionController.createPermission({
|
await permissionController.createPermission({
|
||||||
uri: '/SASjsApi/drive/file',
|
...permission,
|
||||||
principalType: PrincipalType.user,
|
path: '/SASjsApi/drive/file',
|
||||||
principalId: dbUser.id,
|
principalId: dbUser.id
|
||||||
setting: PermissionSetting.grant
|
|
||||||
})
|
})
|
||||||
await permissionController.createPermission({
|
await permissionController.createPermission({
|
||||||
uri: '/SASjsApi/drive/folder',
|
...permission,
|
||||||
principalType: PrincipalType.user,
|
path: '/SASjsApi/drive/folder',
|
||||||
principalId: dbUser.id,
|
principalId: dbUser.id
|
||||||
setting: PermissionSetting.grant
|
|
||||||
})
|
})
|
||||||
await permissionController.createPermission({
|
await permissionController.createPermission({
|
||||||
uri: '/SASjsApi/drive/rename',
|
...permission,
|
||||||
principalType: PrincipalType.user,
|
path: '/SASjsApi/drive/rename',
|
||||||
principalId: dbUser.id,
|
principalId: dbUser.id
|
||||||
setting: PermissionSetting.grant
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import request from 'supertest'
|
|||||||
import appPromise from '../../../app'
|
import appPromise from '../../../app'
|
||||||
import { UserController, GroupController } from '../../../controllers/'
|
import { UserController, GroupController } from '../../../controllers/'
|
||||||
import { generateAccessToken, saveTokensInDB } from '../../../utils'
|
import { generateAccessToken, saveTokensInDB } from '../../../utils'
|
||||||
|
import { PUBLIC_GROUP_NAME } from '../../../model/Group'
|
||||||
|
|
||||||
const clientId = 'someclientID'
|
const clientId = 'someclientID'
|
||||||
const adminUser = {
|
const adminUser = {
|
||||||
@@ -27,6 +28,12 @@ const group = {
|
|||||||
description: 'DC group for testing purposes.'
|
description: 'DC group for testing purposes.'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PUBLIC_GROUP = {
|
||||||
|
name: PUBLIC_GROUP_NAME,
|
||||||
|
description:
|
||||||
|
'A special group that can be used to bypass authentication for particular routes.'
|
||||||
|
}
|
||||||
|
|
||||||
const userController = new UserController()
|
const userController = new UserController()
|
||||||
const groupController = new GroupController()
|
const groupController = new GroupController()
|
||||||
|
|
||||||
@@ -535,6 +542,24 @@ describe('group', () => {
|
|||||||
expect(res.text).toEqual('User not found.')
|
expect(res.text).toEqual('User not found.')
|
||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should respond with Bad Request when adding user to Public group', async () => {
|
||||||
|
const dbGroup = await groupController.createGroup(PUBLIC_GROUP)
|
||||||
|
const dbUser = await userController.createUser({
|
||||||
|
...user,
|
||||||
|
username: 'publicUser'
|
||||||
|
})
|
||||||
|
|
||||||
|
const res = await request(app)
|
||||||
|
.post(`/SASjsApi/group/${dbGroup.groupId}/${dbUser.id}`)
|
||||||
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
|
.send()
|
||||||
|
.expect(400)
|
||||||
|
|
||||||
|
expect(res.text).toEqual(
|
||||||
|
`Can't add/remove user to '${PUBLIC_GROUP_NAME}' group.`
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('RemoveUser', () => {
|
describe('RemoveUser', () => {
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ import {
|
|||||||
DriveController,
|
DriveController,
|
||||||
UserController,
|
UserController,
|
||||||
GroupController,
|
GroupController,
|
||||||
ClientController,
|
|
||||||
PermissionController,
|
PermissionController,
|
||||||
PrincipalType,
|
PrincipalType,
|
||||||
PermissionSetting
|
PermissionType,
|
||||||
|
PermissionSettingForRoute
|
||||||
} from '../../../controllers/'
|
} from '../../../controllers/'
|
||||||
import {
|
import {
|
||||||
UserDetailsResponse,
|
UserDetailsResponse,
|
||||||
@@ -56,10 +56,10 @@ const user = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const permission = {
|
const permission = {
|
||||||
uri: '/SASjsApi/code/execute',
|
path: '/SASjsApi/code/execute',
|
||||||
setting: PermissionSetting.grant,
|
type: PermissionType.route,
|
||||||
principalType: PrincipalType.user,
|
setting: PermissionSettingForRoute.grant,
|
||||||
principalId: 123
|
principalType: PrincipalType.user
|
||||||
}
|
}
|
||||||
|
|
||||||
const group = {
|
const group = {
|
||||||
@@ -69,7 +69,6 @@ const group = {
|
|||||||
|
|
||||||
const userController = new UserController()
|
const userController = new UserController()
|
||||||
const groupController = new GroupController()
|
const groupController = new GroupController()
|
||||||
const clientController = new ClientController()
|
|
||||||
const permissionController = new PermissionController()
|
const permissionController = new PermissionController()
|
||||||
|
|
||||||
describe('permission', () => {
|
describe('permission', () => {
|
||||||
@@ -108,7 +107,8 @@ describe('permission', () => {
|
|||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(res.body.permissionId).toBeTruthy()
|
expect(res.body.permissionId).toBeTruthy()
|
||||||
expect(res.body.uri).toEqual(permission.uri)
|
expect(res.body.path).toEqual(permission.path)
|
||||||
|
expect(res.body.type).toEqual(permission.type)
|
||||||
expect(res.body.setting).toEqual(permission.setting)
|
expect(res.body.setting).toEqual(permission.setting)
|
||||||
expect(res.body.user).toBeTruthy()
|
expect(res.body.user).toBeTruthy()
|
||||||
})
|
})
|
||||||
@@ -127,7 +127,8 @@ describe('permission', () => {
|
|||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(res.body.permissionId).toBeTruthy()
|
expect(res.body.permissionId).toBeTruthy()
|
||||||
expect(res.body.uri).toEqual(permission.uri)
|
expect(res.body.path).toEqual(permission.path)
|
||||||
|
expect(res.body.type).toEqual(permission.type)
|
||||||
expect(res.body.setting).toEqual(permission.setting)
|
expect(res.body.setting).toEqual(permission.setting)
|
||||||
expect(res.body.group).toBeTruthy()
|
expect(res.body.group).toBeTruthy()
|
||||||
})
|
})
|
||||||
@@ -142,53 +143,74 @@ describe('permission', () => {
|
|||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should respond with Unauthorized if access token is not of an admin account even if user has permission', async () => {
|
it('should respond with Unauthorized if access token is not of an admin account', async () => {
|
||||||
const accessToken = await generateAndSaveToken(dbUser.id)
|
const accessToken = await generateAndSaveToken(dbUser.id)
|
||||||
|
|
||||||
await permissionController.createPermission({
|
|
||||||
uri: '/SASjsApi/permission',
|
|
||||||
principalType: PrincipalType.user,
|
|
||||||
principalId: dbUser.id,
|
|
||||||
setting: PermissionSetting.grant
|
|
||||||
})
|
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.post('/SASjsApi/permission')
|
.post('/SASjsApi/permission')
|
||||||
.auth(accessToken, { type: 'bearer' })
|
.auth(accessToken, { type: 'bearer' })
|
||||||
.send()
|
.send(permission)
|
||||||
.expect(401)
|
.expect(401)
|
||||||
|
|
||||||
expect(res.text).toEqual('Admin account required')
|
expect(res.text).toEqual('Admin account required')
|
||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should respond with Bad Request if uri is missing', async () => {
|
it('should respond with Bad Request if path is missing', async () => {
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.post('/SASjsApi/permission')
|
.post('/SASjsApi/permission')
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send({
|
.send({
|
||||||
...permission,
|
...permission,
|
||||||
uri: undefined
|
path: undefined
|
||||||
})
|
})
|
||||||
.expect(400)
|
.expect(400)
|
||||||
|
|
||||||
expect(res.text).toEqual(`"uri" is required`)
|
expect(res.text).toEqual(`"path" is required`)
|
||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should respond with Bad Request if uri is not valid', async () => {
|
it('should respond with Bad Request if path is not valid', async () => {
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.post('/SASjsApi/permission')
|
.post('/SASjsApi/permission')
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send({
|
.send({
|
||||||
...permission,
|
...permission,
|
||||||
uri: '/some/random/api/endpoint'
|
path: '/some/random/api/endpoint'
|
||||||
})
|
})
|
||||||
.expect(400)
|
.expect(400)
|
||||||
|
|
||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should respond with Bad Request if type is not valid', async () => {
|
||||||
|
const res = await request(app)
|
||||||
|
.post('/SASjsApi/permission')
|
||||||
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
|
.send({
|
||||||
|
...permission,
|
||||||
|
type: 'invalid'
|
||||||
|
})
|
||||||
|
.expect(400)
|
||||||
|
|
||||||
|
expect(res.text).toEqual('"type" must be [Route]')
|
||||||
|
expect(res.body).toEqual({})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should respond with Bad Request if type is missing', async () => {
|
||||||
|
const res = await request(app)
|
||||||
|
.post('/SASjsApi/permission')
|
||||||
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
|
.send({
|
||||||
|
...permission,
|
||||||
|
type: undefined
|
||||||
|
})
|
||||||
|
.expect(400)
|
||||||
|
|
||||||
|
expect(res.text).toEqual(`"type" is required`)
|
||||||
|
expect(res.body).toEqual({})
|
||||||
|
})
|
||||||
|
|
||||||
it('should respond with Bad Request if setting is missing', async () => {
|
it('should respond with Bad Request if setting is missing', async () => {
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.post('/SASjsApi/permission')
|
.post('/SASjsApi/permission')
|
||||||
@@ -203,6 +225,20 @@ describe('permission', () => {
|
|||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should respond with Bad Request if setting is not valid', async () => {
|
||||||
|
const res = await request(app)
|
||||||
|
.post('/SASjsApi/permission')
|
||||||
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
|
.send({
|
||||||
|
...permission,
|
||||||
|
setting: 'invalid'
|
||||||
|
})
|
||||||
|
.expect(400)
|
||||||
|
|
||||||
|
expect(res.text).toEqual('"setting" must be one of [Grant, Deny]')
|
||||||
|
expect(res.body).toEqual({})
|
||||||
|
})
|
||||||
|
|
||||||
it('should respond with Bad Request if principalType is missing', async () => {
|
it('should respond with Bad Request if principalType is missing', async () => {
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.post('/SASjsApi/permission')
|
.post('/SASjsApi/permission')
|
||||||
@@ -217,20 +253,6 @@ describe('permission', () => {
|
|||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should respond with Bad Request if principalId is missing', async () => {
|
|
||||||
const res = await request(app)
|
|
||||||
.post('/SASjsApi/permission')
|
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
|
||||||
.send({
|
|
||||||
...permission,
|
|
||||||
principalId: undefined
|
|
||||||
})
|
|
||||||
.expect(400)
|
|
||||||
|
|
||||||
expect(res.text).toEqual(`"principalId" is required`)
|
|
||||||
expect(res.body).toEqual({})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should respond with Bad Request if principal type is not valid', async () => {
|
it('should respond with Bad Request if principal type is not valid', async () => {
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.post('/SASjsApi/permission')
|
.post('/SASjsApi/permission')
|
||||||
@@ -245,17 +267,17 @@ describe('permission', () => {
|
|||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should respond with Bad Request if setting is not valid', async () => {
|
it('should respond with Bad Request if principalId is missing', async () => {
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.post('/SASjsApi/permission')
|
.post('/SASjsApi/permission')
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send({
|
.send({
|
||||||
...permission,
|
...permission,
|
||||||
setting: 'invalid'
|
principalId: undefined
|
||||||
})
|
})
|
||||||
.expect(400)
|
.expect(400)
|
||||||
|
|
||||||
expect(res.text).toEqual('"setting" must be one of [Grant, Deny]')
|
expect(res.text).toEqual(`"principalId" is required`)
|
||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -313,7 +335,8 @@ describe('permission', () => {
|
|||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send({
|
.send({
|
||||||
...permission,
|
...permission,
|
||||||
principalType: 'group'
|
principalType: 'group',
|
||||||
|
principalId: 123
|
||||||
})
|
})
|
||||||
.expect(404)
|
.expect(404)
|
||||||
|
|
||||||
@@ -334,7 +357,7 @@ describe('permission', () => {
|
|||||||
.expect(409)
|
.expect(409)
|
||||||
|
|
||||||
expect(res.text).toEqual(
|
expect(res.text).toEqual(
|
||||||
'Permission already exists with provided URI and User.'
|
'Permission already exists with provided Path, Type and User.'
|
||||||
)
|
)
|
||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
@@ -357,7 +380,7 @@ describe('permission', () => {
|
|||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.patch(`/SASjsApi/permission/${dbPermission?.permissionId}`)
|
.patch(`/SASjsApi/permission/${dbPermission?.permissionId}`)
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send({ setting: 'Deny' })
|
.send({ setting: PermissionSettingForRoute.deny })
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(res.body.setting).toEqual('Deny')
|
expect(res.body.setting).toEqual('Deny')
|
||||||
@@ -366,7 +389,7 @@ describe('permission', () => {
|
|||||||
it('should respond with Unauthorized if access token is not present', async () => {
|
it('should respond with Unauthorized if access token is not present', async () => {
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.patch(`/SASjsApi/permission/${dbPermission?.permissionId}`)
|
.patch(`/SASjsApi/permission/${dbPermission?.permissionId}`)
|
||||||
.send(permission)
|
.send()
|
||||||
.expect(401)
|
.expect(401)
|
||||||
|
|
||||||
expect(res.text).toEqual('Unauthorized')
|
expect(res.text).toEqual('Unauthorized')
|
||||||
@@ -400,12 +423,11 @@ describe('permission', () => {
|
|||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should respond with Bad Request if setting is not valid', async () => {
|
it('should respond with Bad Request if setting is invalid', async () => {
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.post('/SASjsApi/permission')
|
.patch(`/SASjsApi/permission/${dbPermission?.permissionId}`)
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send({
|
.send({
|
||||||
...permission,
|
|
||||||
setting: 'invalid'
|
setting: 'invalid'
|
||||||
})
|
})
|
||||||
.expect(400)
|
.expect(400)
|
||||||
@@ -414,12 +436,12 @@ describe('permission', () => {
|
|||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should respond with not found (404) if permission with provided id does not exists', async () => {
|
it('should respond with not found (404) if permission with provided id does not exist', async () => {
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.patch('/SASjsApi/permission/123')
|
.patch('/SASjsApi/permission/123')
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send({
|
.send({
|
||||||
setting: PermissionSetting.deny
|
setting: PermissionSettingForRoute.deny
|
||||||
})
|
})
|
||||||
.expect(404)
|
.expect(404)
|
||||||
|
|
||||||
@@ -458,12 +480,12 @@ describe('permission', () => {
|
|||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await permissionController.createPermission({
|
await permissionController.createPermission({
|
||||||
...permission,
|
...permission,
|
||||||
uri: '/test-1',
|
path: '/test-1',
|
||||||
principalId: dbUser.id
|
principalId: dbUser.id
|
||||||
})
|
})
|
||||||
await permissionController.createPermission({
|
await permissionController.createPermission({
|
||||||
...permission,
|
...permission,
|
||||||
uri: '/test-2',
|
path: '/test-2',
|
||||||
principalId: dbUser.id
|
principalId: dbUser.id
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -478,34 +500,37 @@ describe('permission', () => {
|
|||||||
expect(res.body).toHaveLength(2)
|
expect(res.body).toHaveLength(2)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should give a list of all permissions when user is not admin', async () => {
|
it(`should give a list of user's own permissions when user is not admin`, async () => {
|
||||||
const dbUser = await userController.createUser({
|
const nonAdminUser = await userController.createUser({
|
||||||
...user,
|
...user,
|
||||||
username: 'get' + user.username
|
username: 'get' + user.username
|
||||||
})
|
})
|
||||||
const accessToken = await generateAndSaveToken(dbUser.id)
|
const accessToken = await generateAndSaveToken(nonAdminUser.id)
|
||||||
await permissionController.createPermission({
|
await permissionController.createPermission({
|
||||||
uri: '/SASjsApi/permission',
|
path: '/test-1',
|
||||||
|
type: PermissionType.route,
|
||||||
principalType: PrincipalType.user,
|
principalType: PrincipalType.user,
|
||||||
principalId: dbUser.id,
|
principalId: nonAdminUser.id,
|
||||||
setting: PermissionSetting.grant
|
setting: PermissionSettingForRoute.grant
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const permissionCount = 1
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.get('/SASjsApi/permission/')
|
.get('/SASjsApi/permission/')
|
||||||
.auth(accessToken, { type: 'bearer' })
|
.auth(accessToken, { type: 'bearer' })
|
||||||
.send()
|
.send()
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(res.body).toHaveLength(3)
|
expect(res.body).toHaveLength(permissionCount)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe.only('verify', () => {
|
describe('verify', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await permissionController.createPermission({
|
await permissionController.createPermission({
|
||||||
...permission,
|
...permission,
|
||||||
uri: '/SASjsApi/drive/deploy',
|
path: '/SASjsApi/drive/deploy',
|
||||||
principalId: dbUser.id
|
principalId: dbUser.id
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ import appPromise from '../../../app'
|
|||||||
import {
|
import {
|
||||||
UserController,
|
UserController,
|
||||||
PermissionController,
|
PermissionController,
|
||||||
PermissionSetting,
|
PermissionType,
|
||||||
|
PermissionSettingForRoute,
|
||||||
PrincipalType
|
PrincipalType
|
||||||
} from '../../../controllers/'
|
} from '../../../controllers/'
|
||||||
import {
|
import {
|
||||||
@@ -21,7 +22,8 @@ import {
|
|||||||
import { createFile, generateTimestamp, deleteFolder } from '@sasjs/utils'
|
import { createFile, generateTimestamp, deleteFolder } from '@sasjs/utils'
|
||||||
import {
|
import {
|
||||||
SASSessionController,
|
SASSessionController,
|
||||||
JSSessionController
|
JSSessionController,
|
||||||
|
PythonSessionController
|
||||||
} from '../../../controllers/internal'
|
} from '../../../controllers/internal'
|
||||||
import * as ProcessProgramModule from '../../../controllers/internal/processProgram'
|
import * as ProcessProgramModule from '../../../controllers/internal/processProgram'
|
||||||
import { Session } from '../../../types'
|
import { Session } from '../../../types'
|
||||||
@@ -38,14 +40,17 @@ const user = {
|
|||||||
|
|
||||||
const sampleSasProgram = '%put hello world!;'
|
const sampleSasProgram = '%put hello world!;'
|
||||||
const sampleJsProgram = `console.log('hello world!/')`
|
const sampleJsProgram = `console.log('hello world!/')`
|
||||||
|
const samplePyProgram = `print('hello world!/')`
|
||||||
|
|
||||||
const filesFolder = getFilesFolder()
|
const filesFolder = getFilesFolder()
|
||||||
|
const testFilesFolder = `test-stp-${generateTimestamp()}`
|
||||||
|
|
||||||
|
let app: Express
|
||||||
|
let accessToken: string
|
||||||
|
|
||||||
describe('stp', () => {
|
describe('stp', () => {
|
||||||
let app: Express
|
|
||||||
let con: Mongoose
|
let con: Mongoose
|
||||||
let mongoServer: MongoMemoryServer
|
let mongoServer: MongoMemoryServer
|
||||||
let accessToken: string
|
|
||||||
const userController = new UserController()
|
const userController = new UserController()
|
||||||
const permissionController = new PermissionController()
|
const permissionController = new PermissionController()
|
||||||
|
|
||||||
@@ -56,10 +61,11 @@ describe('stp', () => {
|
|||||||
const dbUser = await userController.createUser(user)
|
const dbUser = await userController.createUser(user)
|
||||||
accessToken = await generateAndSaveToken(dbUser.id)
|
accessToken = await generateAndSaveToken(dbUser.id)
|
||||||
await permissionController.createPermission({
|
await permissionController.createPermission({
|
||||||
uri: '/SASjsApi/stp/execute',
|
path: '/SASjsApi/stp/execute',
|
||||||
|
type: PermissionType.route,
|
||||||
principalType: PrincipalType.user,
|
principalType: PrincipalType.user,
|
||||||
principalId: dbUser.id,
|
principalId: dbUser.id,
|
||||||
setting: PermissionSetting.grant
|
setting: PermissionSettingForRoute.grant
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -70,8 +76,6 @@ describe('stp', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('execute', () => {
|
describe('execute', () => {
|
||||||
const testFilesFolder = `test-stp-${generateTimestamp()}`
|
|
||||||
|
|
||||||
describe('get', () => {
|
describe('get', () => {
|
||||||
describe('with runtime js', () => {
|
describe('with runtime js', () => {
|
||||||
const testFilesFolder = `test-stp-${generateTimestamp()}`
|
const testFilesFolder = `test-stp-${generateTimestamp()}`
|
||||||
@@ -91,41 +95,45 @@ describe('stp', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should execute js program when both js and sas program are present', async () => {
|
it('should execute js program when both js and sas program are present', async () => {
|
||||||
const programPath = path.join(testFilesFolder, 'program')
|
await makeRequestAndAssert(
|
||||||
const sasProgramPath = path.join(filesFolder, `${programPath}.sas`)
|
[RunTimeType.JS, RunTimeType.SAS],
|
||||||
const jsProgramPath = path.join(filesFolder, `${programPath}.js`)
|
200,
|
||||||
await createFile(sasProgramPath, sampleSasProgram)
|
RunTimeType.JS
|
||||||
await createFile(jsProgramPath, sampleJsProgram)
|
|
||||||
|
|
||||||
await request(app)
|
|
||||||
.get(`/SASjsApi/stp/execute?_program=${programPath}`)
|
|
||||||
.auth(accessToken, { type: 'bearer' })
|
|
||||||
.send()
|
|
||||||
.expect(200)
|
|
||||||
|
|
||||||
expect(ProcessProgramModule.processProgram).toHaveBeenCalledWith(
|
|
||||||
expect.anything(),
|
|
||||||
expect.anything(),
|
|
||||||
expect.anything(),
|
|
||||||
expect.anything(),
|
|
||||||
expect.anything(),
|
|
||||||
expect.anything(),
|
|
||||||
RunTimeType.JS,
|
|
||||||
expect.anything(),
|
|
||||||
undefined
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should throw error when js program is not present but sas program exists', async () => {
|
it('should throw error when js program is not present but sas program exists', async () => {
|
||||||
const programPath = path.join(testFilesFolder, 'program')
|
await makeRequestAndAssert([], 400)
|
||||||
const sasProgramPath = path.join(filesFolder, `${programPath}.sas`)
|
})
|
||||||
await createFile(sasProgramPath, sampleSasProgram)
|
})
|
||||||
|
|
||||||
await request(app)
|
describe('with runtime py', () => {
|
||||||
.get(`/SASjsApi/stp/execute?_program=${programPath}`)
|
const testFilesFolder = `test-stp-${generateTimestamp()}`
|
||||||
.auth(accessToken, { type: 'bearer' })
|
|
||||||
.send()
|
beforeAll(() => {
|
||||||
.expect(400)
|
process.runTimes = [RunTimeType.PY]
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetModules() // it clears the cache
|
||||||
|
setupMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
jest.resetAllMocks()
|
||||||
|
await deleteFolder(path.join(filesFolder, testFilesFolder))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should execute python program when python, js and sas programs are present', async () => {
|
||||||
|
await makeRequestAndAssert(
|
||||||
|
[RunTimeType.PY, RunTimeType.SAS, RunTimeType.JS],
|
||||||
|
200,
|
||||||
|
RunTimeType.PY
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw error when py program is not present but js or sas program exists', async () => {
|
||||||
|
await makeRequestAndAssert([], 400)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -145,41 +153,11 @@ describe('stp', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should execute sas program when both sas and js programs are present', async () => {
|
it('should execute sas program when both sas and js programs are present', async () => {
|
||||||
const programPath = path.join(testFilesFolder, 'program')
|
await makeRequestAndAssert([RunTimeType.SAS], 200, RunTimeType.SAS)
|
||||||
const sasProgramPath = path.join(filesFolder, `${programPath}.sas`)
|
|
||||||
const jsProgramPath = path.join(filesFolder, `${programPath}.js`)
|
|
||||||
await createFile(sasProgramPath, sampleSasProgram)
|
|
||||||
await createFile(jsProgramPath, sampleJsProgram)
|
|
||||||
|
|
||||||
await request(app)
|
|
||||||
.get(`/SASjsApi/stp/execute?_program=${programPath}`)
|
|
||||||
.auth(accessToken, { type: 'bearer' })
|
|
||||||
.send()
|
|
||||||
.expect(200)
|
|
||||||
|
|
||||||
expect(ProcessProgramModule.processProgram).toHaveBeenCalledWith(
|
|
||||||
expect.anything(),
|
|
||||||
expect.anything(),
|
|
||||||
expect.anything(),
|
|
||||||
expect.anything(),
|
|
||||||
expect.anything(),
|
|
||||||
expect.anything(),
|
|
||||||
RunTimeType.SAS,
|
|
||||||
expect.anything(),
|
|
||||||
undefined
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should throw error when sas program do not exit but js exists', async () => {
|
it('should throw error when sas program do not exit but js exists', async () => {
|
||||||
const programPath = path.join(testFilesFolder, 'program')
|
await makeRequestAndAssert([], 400)
|
||||||
const jsProgramPath = path.join(filesFolder, `${programPath}.js`)
|
|
||||||
await createFile(jsProgramPath, sampleJsProgram)
|
|
||||||
|
|
||||||
await request(app)
|
|
||||||
.get(`/SASjsApi/stp/execute?_program=${programPath}`)
|
|
||||||
.auth(accessToken, { type: 'bearer' })
|
|
||||||
.send()
|
|
||||||
.expect(400)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -199,63 +177,51 @@ describe('stp', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should execute js program when both js and sas program are present', async () => {
|
it('should execute js program when both js and sas program are present', async () => {
|
||||||
const programPath = path.join(testFilesFolder, 'program')
|
await makeRequestAndAssert(
|
||||||
const sasProgramPath = path.join(filesFolder, `${programPath}.sas`)
|
[RunTimeType.SAS, RunTimeType.JS],
|
||||||
const jsProgramPath = path.join(filesFolder, `${programPath}.js`)
|
200,
|
||||||
await createFile(sasProgramPath, sampleSasProgram)
|
RunTimeType.JS
|
||||||
await createFile(jsProgramPath, sampleJsProgram)
|
|
||||||
|
|
||||||
await request(app)
|
|
||||||
.get(`/SASjsApi/stp/execute?_program=${programPath}`)
|
|
||||||
.auth(accessToken, { type: 'bearer' })
|
|
||||||
.send()
|
|
||||||
.expect(200)
|
|
||||||
|
|
||||||
expect(ProcessProgramModule.processProgram).toHaveBeenCalledWith(
|
|
||||||
expect.anything(),
|
|
||||||
expect.anything(),
|
|
||||||
expect.anything(),
|
|
||||||
expect.anything(),
|
|
||||||
expect.anything(),
|
|
||||||
expect.anything(),
|
|
||||||
RunTimeType.JS,
|
|
||||||
expect.anything(),
|
|
||||||
undefined
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should execute sas program when js program is not present but sas program exists', async () => {
|
it('should execute sas program when js program is not present but sas program exists', async () => {
|
||||||
const programPath = path.join(testFilesFolder, 'program')
|
await makeRequestAndAssert([RunTimeType.SAS], 200, RunTimeType.SAS)
|
||||||
const sasProgramPath = path.join(filesFolder, `${programPath}.sas`)
|
|
||||||
await createFile(sasProgramPath, sampleSasProgram)
|
|
||||||
|
|
||||||
await request(app)
|
|
||||||
.get(`/SASjsApi/stp/execute?_program=${programPath}`)
|
|
||||||
.auth(accessToken, { type: 'bearer' })
|
|
||||||
.send()
|
|
||||||
.expect(200)
|
|
||||||
|
|
||||||
expect(ProcessProgramModule.processProgram).toHaveBeenCalledWith(
|
|
||||||
expect.anything(),
|
|
||||||
expect.anything(),
|
|
||||||
expect.anything(),
|
|
||||||
expect.anything(),
|
|
||||||
expect.anything(),
|
|
||||||
expect.anything(),
|
|
||||||
RunTimeType.SAS,
|
|
||||||
expect.anything(),
|
|
||||||
undefined
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should throw error when both sas and js programs do not exist', async () => {
|
it('should throw error when both sas and js programs do not exist', async () => {
|
||||||
const programPath = path.join(testFilesFolder, 'program')
|
await makeRequestAndAssert([], 400)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
await request(app)
|
describe('with runtime py and sas', () => {
|
||||||
.get(`/SASjsApi/stp/execute?_program=${programPath}`)
|
beforeAll(() => {
|
||||||
.auth(accessToken, { type: 'bearer' })
|
process.runTimes = [RunTimeType.PY, RunTimeType.SAS]
|
||||||
.send()
|
})
|
||||||
.expect(400)
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetModules() // it clears the cache
|
||||||
|
setupMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
jest.resetAllMocks()
|
||||||
|
await deleteFolder(path.join(filesFolder, testFilesFolder))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should execute python program when both python and sas program are present', async () => {
|
||||||
|
await makeRequestAndAssert(
|
||||||
|
[RunTimeType.PY, RunTimeType.SAS],
|
||||||
|
200,
|
||||||
|
RunTimeType.PY
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should execute sas program when python program is not present but sas program exists', async () => {
|
||||||
|
await makeRequestAndAssert([RunTimeType.SAS], 200, RunTimeType.SAS)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw error when both sas and js programs do not exist', async () => {
|
||||||
|
await makeRequestAndAssert([], 400)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -275,76 +241,220 @@ describe('stp', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should execute sas program when both sas and js programs exist', async () => {
|
it('should execute sas program when both sas and js programs exist', async () => {
|
||||||
const programPath = path.join(testFilesFolder, 'program')
|
await makeRequestAndAssert(
|
||||||
const sasProgramPath = path.join(filesFolder, `${programPath}.sas`)
|
[RunTimeType.SAS, RunTimeType.JS],
|
||||||
const jsProgramPath = path.join(filesFolder, `${programPath}.js`)
|
200,
|
||||||
await createFile(sasProgramPath, sampleSasProgram)
|
RunTimeType.SAS
|
||||||
await createFile(jsProgramPath, sampleJsProgram)
|
|
||||||
|
|
||||||
await request(app)
|
|
||||||
.get(`/SASjsApi/stp/execute?_program=${programPath}`)
|
|
||||||
.auth(accessToken, { type: 'bearer' })
|
|
||||||
.send()
|
|
||||||
.expect(200)
|
|
||||||
|
|
||||||
expect(ProcessProgramModule.processProgram).toHaveBeenCalledWith(
|
|
||||||
expect.anything(),
|
|
||||||
expect.anything(),
|
|
||||||
expect.anything(),
|
|
||||||
expect.anything(),
|
|
||||||
expect.anything(),
|
|
||||||
expect.anything(),
|
|
||||||
RunTimeType.SAS,
|
|
||||||
expect.anything(),
|
|
||||||
undefined
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should execute js program when sas program is not present but js program exists', async () => {
|
it('should execute js program when sas program is not present but js program exists', async () => {
|
||||||
const programPath = path.join(testFilesFolder, 'program')
|
await makeRequestAndAssert([RunTimeType.JS], 200, RunTimeType.JS)
|
||||||
const jsProgramPath = path.join(filesFolder, `${programPath}.js`)
|
|
||||||
await createFile(jsProgramPath, sampleJsProgram)
|
|
||||||
|
|
||||||
await request(app)
|
|
||||||
.get(`/SASjsApi/stp/execute?_program=${programPath}`)
|
|
||||||
.auth(accessToken, { type: 'bearer' })
|
|
||||||
.send()
|
|
||||||
.expect(200)
|
|
||||||
|
|
||||||
expect(ProcessProgramModule.processProgram).toHaveBeenCalledWith(
|
|
||||||
expect.anything(),
|
|
||||||
expect.anything(),
|
|
||||||
expect.anything(),
|
|
||||||
expect.anything(),
|
|
||||||
expect.anything(),
|
|
||||||
expect.anything(),
|
|
||||||
RunTimeType.JS,
|
|
||||||
expect.anything(),
|
|
||||||
undefined
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should throw error when both sas and js programs do not exist', async () => {
|
it('should throw error when both sas and js programs do not exist', async () => {
|
||||||
const programPath = path.join(testFilesFolder, 'program')
|
await makeRequestAndAssert([], 400)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
await request(app)
|
describe('with runtime sas and py', () => {
|
||||||
.get(`/SASjsApi/stp/execute?_program=${programPath}`)
|
beforeAll(() => {
|
||||||
.auth(accessToken, { type: 'bearer' })
|
process.runTimes = [RunTimeType.SAS, RunTimeType.PY]
|
||||||
.send()
|
})
|
||||||
.expect(400)
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetModules() // it clears the cache
|
||||||
|
setupMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
jest.resetAllMocks()
|
||||||
|
await deleteFolder(path.join(filesFolder, testFilesFolder))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should execute sas program when both sas and python programs exist', async () => {
|
||||||
|
await makeRequestAndAssert(
|
||||||
|
[RunTimeType.SAS, RunTimeType.PY],
|
||||||
|
200,
|
||||||
|
RunTimeType.SAS
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should execute python program when sas program is not present but python program exists', async () => {
|
||||||
|
await makeRequestAndAssert([RunTimeType.PY], 200, RunTimeType.PY)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw error when both sas and python programs do not exist', async () => {
|
||||||
|
await makeRequestAndAssert([], 400)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('with runtime sas, js and py', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
process.runTimes = [RunTimeType.SAS, RunTimeType.JS, RunTimeType.PY]
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetModules() // it clears the cache
|
||||||
|
setupMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
jest.resetAllMocks()
|
||||||
|
await deleteFolder(path.join(filesFolder, testFilesFolder))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should execute sas program when it exists, no matter js and python programs exist or not', async () => {
|
||||||
|
await makeRequestAndAssert(
|
||||||
|
[RunTimeType.SAS, RunTimeType.PY, RunTimeType.JS],
|
||||||
|
200,
|
||||||
|
RunTimeType.SAS
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should execute js program when sas program is absent but js and python programs are present', async () => {
|
||||||
|
await makeRequestAndAssert(
|
||||||
|
[RunTimeType.JS, RunTimeType.PY],
|
||||||
|
200,
|
||||||
|
RunTimeType.JS
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should execute python program when both sas and js programs are not present', async () => {
|
||||||
|
await makeRequestAndAssert([RunTimeType.PY], 200, RunTimeType.PY)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw error when no program exists', async () => {
|
||||||
|
await makeRequestAndAssert([], 400)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('with runtime js, sas and py', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
process.runTimes = [RunTimeType.JS, RunTimeType.SAS, RunTimeType.PY]
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetModules() // it clears the cache
|
||||||
|
setupMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
jest.resetAllMocks()
|
||||||
|
await deleteFolder(path.join(filesFolder, testFilesFolder))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should execute js program when it exists, no matter sas and python programs exist or not', async () => {
|
||||||
|
await makeRequestAndAssert(
|
||||||
|
[RunTimeType.JS, RunTimeType.SAS, RunTimeType.PY],
|
||||||
|
200,
|
||||||
|
RunTimeType.JS
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should execute sas program when js program is absent but sas and python programs are present', async () => {
|
||||||
|
await makeRequestAndAssert(
|
||||||
|
[RunTimeType.SAS, RunTimeType.PY],
|
||||||
|
200,
|
||||||
|
RunTimeType.SAS
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should execute python program when both sas and js programs are not present', async () => {
|
||||||
|
await makeRequestAndAssert([RunTimeType.PY], 200, RunTimeType.PY)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw error when no program exists', async () => {
|
||||||
|
await makeRequestAndAssert([], 400)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('with runtime py, sas and js', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
process.runTimes = [RunTimeType.PY, RunTimeType.SAS, RunTimeType.JS]
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetModules() // it clears the cache
|
||||||
|
setupMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
jest.resetAllMocks()
|
||||||
|
await deleteFolder(path.join(filesFolder, testFilesFolder))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should execute python program when it exists, no matter sas and js programs exist or not', async () => {
|
||||||
|
await makeRequestAndAssert(
|
||||||
|
[RunTimeType.PY, RunTimeType.SAS, RunTimeType.JS],
|
||||||
|
200,
|
||||||
|
RunTimeType.PY
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should execute sas program when python program is absent but sas and js programs are present', async () => {
|
||||||
|
await makeRequestAndAssert(
|
||||||
|
[RunTimeType.SAS, RunTimeType.JS],
|
||||||
|
200,
|
||||||
|
RunTimeType.SAS
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should execute js program when both sas and python programs are not present', async () => {
|
||||||
|
await makeRequestAndAssert([RunTimeType.JS], 200, RunTimeType.JS)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw error when no program exists', async () => {
|
||||||
|
await makeRequestAndAssert([], 400)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const generateSaveTokenAndCreateUser = async (
|
const makeRequestAndAssert = async (
|
||||||
someUser: any
|
programTypes: RunTimeType[],
|
||||||
): Promise<string> => {
|
expectedStatusCode: number,
|
||||||
const userController = new UserController()
|
expectedRuntime?: RunTimeType
|
||||||
const dbUser = await userController.createUser(someUser)
|
) => {
|
||||||
|
const programPath = path.join(testFilesFolder, 'program')
|
||||||
|
for (const programType of programTypes) {
|
||||||
|
if (programType === RunTimeType.JS)
|
||||||
|
await createFile(
|
||||||
|
path.join(filesFolder, `${programPath}.js`),
|
||||||
|
sampleJsProgram
|
||||||
|
)
|
||||||
|
else if (programType === RunTimeType.PY)
|
||||||
|
await createFile(
|
||||||
|
path.join(filesFolder, `${programPath}.py`),
|
||||||
|
samplePyProgram
|
||||||
|
)
|
||||||
|
else if (programType === RunTimeType.SAS)
|
||||||
|
await createFile(
|
||||||
|
path.join(filesFolder, `${programPath}.sas`),
|
||||||
|
sampleSasProgram
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return generateAndSaveToken(dbUser.id)
|
await request(app)
|
||||||
|
.get(`/SASjsApi/stp/execute?_program=${programPath}`)
|
||||||
|
.auth(accessToken, { type: 'bearer' })
|
||||||
|
.send()
|
||||||
|
.expect(expectedStatusCode)
|
||||||
|
|
||||||
|
if (expectedRuntime)
|
||||||
|
expect(ProcessProgramModule.processProgram).toHaveBeenCalledWith(
|
||||||
|
expect.anything(),
|
||||||
|
expect.anything(),
|
||||||
|
expect.anything(),
|
||||||
|
expect.anything(),
|
||||||
|
expect.anything(),
|
||||||
|
expect.anything(),
|
||||||
|
expect.anything(),
|
||||||
|
expectedRuntime,
|
||||||
|
expect.anything(),
|
||||||
|
undefined
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const generateAndSaveToken = async (userId: number) => {
|
const generateAndSaveToken = async (userId: number) => {
|
||||||
@@ -365,6 +475,10 @@ const setupMocks = async () => {
|
|||||||
.spyOn(JSSessionController.prototype, 'getSession')
|
.spyOn(JSSessionController.prototype, 'getSession')
|
||||||
.mockImplementation(mockedGetSession)
|
.mockImplementation(mockedGetSession)
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(PythonSessionController.prototype, 'getSession')
|
||||||
|
.mockImplementation(mockedGetSession)
|
||||||
|
|
||||||
jest
|
jest
|
||||||
.spyOn(ProcessProgramModule, 'processProgram')
|
.spyOn(ProcessProgramModule, 'processProgram')
|
||||||
.mockImplementation(() => Promise.resolve())
|
.mockImplementation(() => Promise.resolve())
|
||||||
|
|||||||
@@ -39,12 +39,11 @@ describe('web', () => {
|
|||||||
|
|
||||||
describe('home', () => {
|
describe('home', () => {
|
||||||
it('should respond with CSRF Token', async () => {
|
it('should respond with CSRF Token', async () => {
|
||||||
await request(app)
|
const res = await request(app).get('/').expect(200)
|
||||||
.get('/')
|
|
||||||
.expect(
|
expect(res.text).toMatch(
|
||||||
'set-cookie',
|
/<script>document.cookie = '(XSRF-TOKEN=.*; Max-Age=86400; SameSite=Strict; Path=\/;)'<\/script>/
|
||||||
/_csrf=.*; Max-Age=86400000; Path=\/; HttpOnly,XSRF-TOKEN=.*; Path=\//
|
)
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -154,10 +153,10 @@ describe('web', () => {
|
|||||||
|
|
||||||
const getCSRF = async (app: Express) => {
|
const getCSRF = async (app: Express) => {
|
||||||
// make request to get CSRF
|
// make request to get CSRF
|
||||||
const { header } = await request(app).get('/')
|
const { header, text } = await request(app).get('/')
|
||||||
const cookies = header['set-cookie'].join()
|
const cookies = header['set-cookie'].join()
|
||||||
|
|
||||||
const csrfToken = extractCSRF(cookies)
|
const csrfToken = extractCSRF(text)
|
||||||
return { csrfToken, cookies }
|
return { csrfToken, cookies }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,7 +176,7 @@ const performLogin = async (
|
|||||||
return { cookies: newCookies }
|
return { cookies: newCookies }
|
||||||
}
|
}
|
||||||
|
|
||||||
const extractCSRF = (cookies: string) =>
|
const extractCSRF = (text: string) =>
|
||||||
/_csrf=(.*); Max-Age=86400000; Path=\/; HttpOnly,XSRF-TOKEN=(.*); Path=\//.exec(
|
/<script>document.cookie = 'XSRF-TOKEN=(.*); Max-Age=86400; SameSite=Strict; Path=\/;'<\/script>/.exec(
|
||||||
cookies
|
text
|
||||||
)![2]
|
)![1]
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ stpRouter.get('/execute', async (req, res) => {
|
|||||||
if (error) return res.status(400).send(error.details[0].message)
|
if (error) return res.status(400).send(error.details[0].message)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await controller.executeReturnRaw(req, query._program)
|
const response = await controller.executeGetRequest(req, query._program)
|
||||||
|
|
||||||
if (response instanceof Buffer) {
|
if (response instanceof Buffer) {
|
||||||
res.writeHead(200, (req as any).sasHeaders)
|
res.writeHead(200, (req as any).sasHeaders)
|
||||||
@@ -42,7 +42,7 @@ stpRouter.post(
|
|||||||
// if (errQ && errB) return res.status(400).send(errB.details[0].message)
|
// if (errQ && errB) return res.status(400).send(errB.details[0].message)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await controller.executeReturnJson(
|
const response = await controller.executePostRequest(
|
||||||
req,
|
req,
|
||||||
req.body,
|
req.body,
|
||||||
req.query?._program as string
|
req.query?._program as string
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export const style = `<style>
|
|||||||
}
|
}
|
||||||
.app-container .app img{
|
.app-container .app img{
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: calc(100% - 30px);
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,11 +11,15 @@ webRouter.get('/', async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
response = await controller.home()
|
response = await controller.home()
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
response = 'Web Build is not present'
|
response = '<html><head></head><body>Web Build is not present</body></html>'
|
||||||
} finally {
|
} finally {
|
||||||
res.cookie('XSRF-TOKEN', req.csrfToken())
|
const codeToInject = `<script>document.cookie = 'XSRF-TOKEN=${req.csrfToken()}; Max-Age=86400; SameSite=Strict; Path=/;'</script>`
|
||||||
|
const injectedContent = response?.replace(
|
||||||
|
'</head>',
|
||||||
|
`${codeToInject}</head>`
|
||||||
|
)
|
||||||
|
|
||||||
return res.send(response)
|
return res.send(injectedContent)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
3
api/src/types/system/process.d.ts
vendored
3
api/src/types/system/process.d.ts
vendored
@@ -2,10 +2,13 @@ declare namespace NodeJS {
|
|||||||
export interface Process {
|
export interface Process {
|
||||||
sasLoc?: string
|
sasLoc?: string
|
||||||
nodeLoc?: string
|
nodeLoc?: string
|
||||||
|
pythonLoc?: string
|
||||||
driveLoc: string
|
driveLoc: string
|
||||||
logsLoc: string
|
logsLoc: string
|
||||||
|
logsUUID: string
|
||||||
sasSessionController?: import('../../controllers/internal').SASSessionController
|
sasSessionController?: import('../../controllers/internal').SASSessionController
|
||||||
jsSessionController?: import('../../controllers/internal').JSSessionController
|
jsSessionController?: import('../../controllers/internal').JSSessionController
|
||||||
|
pythonSessionController?: import('../../controllers/internal').PythonSessionController
|
||||||
appStreamConfig: import('../').AppStreamConfig
|
appStreamConfig: import('../').AppStreamConfig
|
||||||
logger: import('@sasjs/utils/logger').Logger
|
logger: import('@sasjs/utils/logger').Logger
|
||||||
runTimes: import('../../utils').RunTimeType[]
|
runTimes: import('../../utils').RunTimeType[]
|
||||||
|
|||||||
@@ -9,8 +9,7 @@ const StaticAuthorizedRoutes = [
|
|||||||
'/SASjsApi/drive/file',
|
'/SASjsApi/drive/file',
|
||||||
'/SASjsApi/drive/folder',
|
'/SASjsApi/drive/folder',
|
||||||
'/SASjsApi/drive/fileTree',
|
'/SASjsApi/drive/fileTree',
|
||||||
'/SASjsApi/drive/rename',
|
'/SASjsApi/drive/rename'
|
||||||
'/SASjsApi/permission'
|
|
||||||
]
|
]
|
||||||
|
|
||||||
export const getAuthorizedRoutes = () => {
|
export const getAuthorizedRoutes = () => {
|
||||||
@@ -19,7 +18,7 @@ export const getAuthorizedRoutes = () => {
|
|||||||
return [...StaticAuthorizedRoutes, ...streamingAppsRoutes]
|
return [...StaticAuthorizedRoutes, ...streamingAppsRoutes]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getUri = (req: Request) => {
|
export const getPath = (req: Request) => {
|
||||||
const { baseUrl, path: reqPath } = req
|
const { baseUrl, path: reqPath } = req
|
||||||
|
|
||||||
if (baseUrl === '/AppStream') {
|
if (baseUrl === '/AppStream') {
|
||||||
@@ -33,4 +32,4 @@ export const getUri = (req: Request) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const isAuthorizingRoute = (req: Request): boolean =>
|
export const isAuthorizingRoute = (req: Request): boolean =>
|
||||||
getAuthorizedRoutes().includes(getUri(req))
|
getAuthorizedRoutes().includes(getPath(req))
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import { createFolder, fileExists, folderExists, isWindows } from '@sasjs/utils'
|
|||||||
import { RunTimeType } from './verifyEnvVariables'
|
import { RunTimeType } from './verifyEnvVariables'
|
||||||
|
|
||||||
export const getDesktopFields = async () => {
|
export const getDesktopFields = async () => {
|
||||||
const { SAS_PATH, NODE_PATH } = process.env
|
const { SAS_PATH, NODE_PATH, PYTHON_PATH } = process.env
|
||||||
|
|
||||||
let sasLoc, nodeLoc
|
let sasLoc, nodeLoc, pythonLoc
|
||||||
|
|
||||||
if (process.runTimes.includes(RunTimeType.SAS)) {
|
if (process.runTimes.includes(RunTimeType.SAS)) {
|
||||||
sasLoc = SAS_PATH ?? (await getSASLocation())
|
sasLoc = SAS_PATH ?? (await getSASLocation())
|
||||||
@@ -16,7 +16,11 @@ export const getDesktopFields = async () => {
|
|||||||
nodeLoc = NODE_PATH ?? (await getNodeLocation())
|
nodeLoc = NODE_PATH ?? (await getNodeLocation())
|
||||||
}
|
}
|
||||||
|
|
||||||
return { sasLoc, nodeLoc }
|
if (process.runTimes.includes(RunTimeType.PY)) {
|
||||||
|
pythonLoc = PYTHON_PATH ?? (await getPythonLocation())
|
||||||
|
}
|
||||||
|
|
||||||
|
return { sasLoc, nodeLoc, pythonLoc }
|
||||||
}
|
}
|
||||||
|
|
||||||
const getDriveLocation = async (): Promise<string> => {
|
const getDriveLocation = async (): Promise<string> => {
|
||||||
@@ -91,3 +95,25 @@ const getNodeLocation = async (): Promise<string> => {
|
|||||||
|
|
||||||
return targetName
|
return targetName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getPythonLocation = async (): Promise<string> => {
|
||||||
|
const validator = async (filePath: string) => {
|
||||||
|
if (!filePath) return 'Path to Python executable is required.'
|
||||||
|
|
||||||
|
if (!(await fileExists(filePath))) {
|
||||||
|
return 'No file found at provided path.'
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultLocation = isWindows() ? 'C:\\Python' : '/usr/bin/python'
|
||||||
|
|
||||||
|
const targetName = await getString(
|
||||||
|
'Please enter full path to a Python executable: ',
|
||||||
|
validator,
|
||||||
|
defaultLocation
|
||||||
|
)
|
||||||
|
|
||||||
|
return targetName
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { RunTimeType } from '.'
|
|||||||
|
|
||||||
export const getRunTimeAndFilePath = async (programPath: string) => {
|
export const getRunTimeAndFilePath = async (programPath: string) => {
|
||||||
const ext = path.extname(programPath)
|
const ext = path.extname(programPath)
|
||||||
// If programPath (_program) is provided with a ".sas" or ".js" extension
|
// If programPath (_program) is provided with a ".sas", ".js" or ".py" extension
|
||||||
// we should use that extension to determine the appropriate runTime
|
// we should use that extension to determine the appropriate runTime
|
||||||
if (ext && Object.values(RunTimeType).includes(ext.slice(1) as RunTimeType)) {
|
if (ext && Object.values(RunTimeType).includes(ext.slice(1) as RunTimeType)) {
|
||||||
const runTime = ext.slice(1)
|
const runTime = ext.slice(1)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export * from './getRunTimeAndFilePath'
|
|||||||
export * from './getServerUrl'
|
export * from './getServerUrl'
|
||||||
export * from './instantiateLogger'
|
export * from './instantiateLogger'
|
||||||
export * from './isDebugOn'
|
export * from './isDebugOn'
|
||||||
|
export * from './isPublicRoute'
|
||||||
export * from './zipped'
|
export * from './zipped'
|
||||||
export * from './parseLogToArray'
|
export * from './parseLogToArray'
|
||||||
export * from './removeTokensInDB'
|
export * from './removeTokensInDB'
|
||||||
|
|||||||
31
api/src/utils/isPublicRoute.ts
Normal file
31
api/src/utils/isPublicRoute.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { Request } from 'express'
|
||||||
|
import { getPath } from './getAuthorizedRoutes'
|
||||||
|
import Group, { PUBLIC_GROUP_NAME } from '../model/Group'
|
||||||
|
import Permission from '../model/Permission'
|
||||||
|
import { PermissionSettingForRoute } from '../controllers'
|
||||||
|
import { RequestUser } from '../types'
|
||||||
|
|
||||||
|
export const isPublicRoute = async (req: Request): Promise<boolean> => {
|
||||||
|
const group = await Group.findOne({ name: PUBLIC_GROUP_NAME })
|
||||||
|
if (group) {
|
||||||
|
const path = getPath(req)
|
||||||
|
|
||||||
|
const groupPermission = await Permission.findOne({
|
||||||
|
path,
|
||||||
|
group: group?._id
|
||||||
|
})
|
||||||
|
if (groupPermission?.setting === PermissionSettingForRoute.grant)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
export const publicUser: RequestUser = {
|
||||||
|
userId: 0,
|
||||||
|
clientId: 'public_app',
|
||||||
|
username: 'publicUser',
|
||||||
|
displayName: 'Public User',
|
||||||
|
isAdmin: false,
|
||||||
|
isActive: true
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import Client from '../model/Client'
|
import Client from '../model/Client'
|
||||||
import Group from '../model/Group'
|
import Group, { PUBLIC_GROUP_NAME } from '../model/Group'
|
||||||
import User from '../model/User'
|
import User from '../model/User'
|
||||||
import Configuration, { ConfigurationType } from '../model/Configuration'
|
import Configuration, { ConfigurationType } from '../model/Configuration'
|
||||||
|
|
||||||
@@ -31,6 +31,15 @@ export const seedDB = async (): Promise<ConfigurationType> => {
|
|||||||
console.log(`DB Seed - Group created: ${GROUP.name}`)
|
console.log(`DB Seed - Group created: ${GROUP.name}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Checking if 'Public' Group is already in the database
|
||||||
|
const publicGroupExist = await Group.findOne({ name: PUBLIC_GROUP.name })
|
||||||
|
if (!publicGroupExist) {
|
||||||
|
const group = new Group(PUBLIC_GROUP)
|
||||||
|
await group.save()
|
||||||
|
|
||||||
|
console.log(`DB Seed - Group created: ${PUBLIC_GROUP.name}`)
|
||||||
|
}
|
||||||
|
|
||||||
// Checking if user is already in the database
|
// Checking if user is already in the database
|
||||||
let usernameExist = await User.findOne({ username: ADMIN_USER.username })
|
let usernameExist = await User.findOne({ username: ADMIN_USER.username })
|
||||||
if (!usernameExist) {
|
if (!usernameExist) {
|
||||||
@@ -68,6 +77,13 @@ const GROUP = {
|
|||||||
name: 'AllUsers',
|
name: 'AllUsers',
|
||||||
description: 'Group contains all users'
|
description: 'Group contains all users'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PUBLIC_GROUP = {
|
||||||
|
name: PUBLIC_GROUP_NAME,
|
||||||
|
description:
|
||||||
|
'A special group that can be used to bypass authentication for particular routes.'
|
||||||
|
}
|
||||||
|
|
||||||
const CLIENT = {
|
const CLIENT = {
|
||||||
clientId: 'clientID1',
|
clientId: 'clientID1',
|
||||||
clientSecret: 'clientSecret'
|
clientSecret: 'clientSecret'
|
||||||
|
|||||||
@@ -28,11 +28,13 @@ export const setProcessVariables = async () => {
|
|||||||
if (MODE === ModeType.Server) {
|
if (MODE === ModeType.Server) {
|
||||||
process.sasLoc = process.env.SAS_PATH
|
process.sasLoc = process.env.SAS_PATH
|
||||||
process.nodeLoc = process.env.NODE_PATH
|
process.nodeLoc = process.env.NODE_PATH
|
||||||
|
process.pythonLoc = process.env.PYTHON_PATH
|
||||||
} else {
|
} else {
|
||||||
const { sasLoc, nodeLoc } = await getDesktopFields()
|
const { sasLoc, nodeLoc, pythonLoc } = await getDesktopFields()
|
||||||
|
|
||||||
process.sasLoc = sasLoc
|
process.sasLoc = sasLoc
|
||||||
process.nodeLoc = nodeLoc
|
process.nodeLoc = nodeLoc
|
||||||
|
process.pythonLoc = pythonLoc
|
||||||
}
|
}
|
||||||
|
|
||||||
const { SASJS_ROOT } = process.env
|
const { SASJS_ROOT } = process.env
|
||||||
@@ -48,6 +50,8 @@ export const setProcessVariables = async () => {
|
|||||||
await createFolder(absLogsPath)
|
await createFolder(absLogsPath)
|
||||||
process.logsLoc = getRealPath(absLogsPath)
|
process.logsLoc = getRealPath(absLogsPath)
|
||||||
|
|
||||||
|
process.logsUUID = 'SASJS_LOGS_SEPARATOR_163ee17b6ff24f028928972d80a26784'
|
||||||
|
|
||||||
console.log('sasLoc: ', process.sasLoc)
|
console.log('sasLoc: ', process.sasLoc)
|
||||||
console.log('sasDrive: ', process.driveLoc)
|
console.log('sasDrive: ', process.driveLoc)
|
||||||
console.log('sasLogs: ', process.logsLoc)
|
console.log('sasLogs: ', process.logsLoc)
|
||||||
|
|||||||
@@ -126,9 +126,34 @@ export const generateFileUploadJSCode = async (
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (fileCount) {
|
uploadCode += `\nconst _WEBIN_FILE_COUNT = ${fileCount}`
|
||||||
uploadCode = `\nconst _WEBIN_FILE_COUNT = ${fileCount}` + uploadCode
|
|
||||||
}
|
return uploadCode
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the python code that references uploaded files in the concurrent request
|
||||||
|
* @param filesNamesMap object that maps hashed file names and original file names
|
||||||
|
* @param sessionFolder name of the folder that is created for the purpose of files in concurrent request
|
||||||
|
* @returns generated python code
|
||||||
|
*/
|
||||||
|
export const generateFileUploadPythonCode = async (
|
||||||
|
filesNamesMap: FilenamesMap,
|
||||||
|
sessionFolder: string
|
||||||
|
) => {
|
||||||
|
let uploadCode = ''
|
||||||
|
let fileCount = 0
|
||||||
|
|
||||||
|
const sessionFolderList: string[] = await listFilesInFolder(sessionFolder)
|
||||||
|
sessionFolderList.forEach(async (fileName) => {
|
||||||
|
if (fileName.includes('req_file')) {
|
||||||
|
fileCount++
|
||||||
|
uploadCode += `\n_WEBIN_FILENAME${fileCount} = '${filesNamesMap[fileName].originalName}'`
|
||||||
|
uploadCode += `\n_WEBIN_NAME${fileCount} = '${filesNamesMap[fileName].fieldName}'`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
uploadCode += `\n_WEBIN_FILE_COUNT = ${fileCount}`
|
||||||
|
|
||||||
return uploadCode
|
return uploadCode
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import Joi from 'joi'
|
import Joi from 'joi'
|
||||||
import { PermissionSetting, PrincipalType } from '../controllers/permission'
|
import {
|
||||||
|
PermissionType,
|
||||||
|
PermissionSettingForRoute,
|
||||||
|
PrincipalType
|
||||||
|
} from '../controllers/permission'
|
||||||
import { getAuthorizedRoutes } from './getAuthorizedRoutes'
|
import { getAuthorizedRoutes } from './getAuthorizedRoutes'
|
||||||
|
|
||||||
const usernameSchema = Joi.string().lowercase().alphanum().min(3).max(16)
|
const usernameSchema = Joi.string().lowercase().alphanum().min(3).max(16)
|
||||||
@@ -89,12 +93,15 @@ export const registerClientValidation = (data: any): Joi.ValidationResult =>
|
|||||||
|
|
||||||
export const registerPermissionValidation = (data: any): Joi.ValidationResult =>
|
export const registerPermissionValidation = (data: any): Joi.ValidationResult =>
|
||||||
Joi.object({
|
Joi.object({
|
||||||
uri: Joi.string()
|
path: Joi.string()
|
||||||
.required()
|
.required()
|
||||||
.valid(...getAuthorizedRoutes()),
|
.valid(...getAuthorizedRoutes()),
|
||||||
|
type: Joi.string()
|
||||||
|
.required()
|
||||||
|
.valid(...Object.values(PermissionType)),
|
||||||
setting: Joi.string()
|
setting: Joi.string()
|
||||||
.required()
|
.required()
|
||||||
.valid(...Object.values(PermissionSetting)),
|
.valid(...Object.values(PermissionSettingForRoute)),
|
||||||
principalType: Joi.string()
|
principalType: Joi.string()
|
||||||
.required()
|
.required()
|
||||||
.valid(...Object.values(PrincipalType)),
|
.valid(...Object.values(PrincipalType)),
|
||||||
@@ -105,7 +112,7 @@ export const updatePermissionValidation = (data: any): Joi.ValidationResult =>
|
|||||||
Joi.object({
|
Joi.object({
|
||||||
setting: Joi.string()
|
setting: Joi.string()
|
||||||
.required()
|
.required()
|
||||||
.valid(...Object.values(PermissionSetting))
|
.valid(...Object.values(PermissionSettingForRoute))
|
||||||
}).validate(data)
|
}).validate(data)
|
||||||
|
|
||||||
export const deployValidation = (data: any): Joi.ValidationResult =>
|
export const deployValidation = (data: any): Joi.ValidationResult =>
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ export enum LOG_FORMAT_MORGANType {
|
|||||||
|
|
||||||
export enum RunTimeType {
|
export enum RunTimeType {
|
||||||
SAS = 'sas',
|
SAS = 'sas',
|
||||||
JS = 'js'
|
JS = 'js',
|
||||||
|
PY = 'py'
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ReturnCode {
|
export enum ReturnCode {
|
||||||
@@ -125,8 +126,27 @@ const verifyCORS = (): string[] => {
|
|||||||
|
|
||||||
if (CORS) {
|
if (CORS) {
|
||||||
const corsTypes = Object.values(CorsType)
|
const corsTypes = Object.values(CorsType)
|
||||||
|
|
||||||
if (!corsTypes.includes(CORS as CorsType))
|
if (!corsTypes.includes(CORS as CorsType))
|
||||||
errors.push(`- CORS '${CORS}'\n - valid options ${corsTypes}`)
|
errors.push(`- CORS '${CORS}'\n - valid options ${corsTypes}`)
|
||||||
|
|
||||||
|
if (CORS === CorsType.ENABLED) {
|
||||||
|
const { WHITELIST } = process.env
|
||||||
|
|
||||||
|
const urls = WHITELIST?.trim()
|
||||||
|
.split(' ')
|
||||||
|
.filter((url) => !!url)
|
||||||
|
if (urls?.length) {
|
||||||
|
urls.forEach((url) => {
|
||||||
|
if (!url.startsWith('http://') && !url.startsWith('https://'))
|
||||||
|
errors.push(
|
||||||
|
`- CORS '${CORS}'\n - provided WHITELIST ${url} is not valid`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
errors.push(`- CORS '${CORS}'\n - provide at least one WHITELIST URL`)
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const { MODE } = process.env
|
const { MODE } = process.env
|
||||||
process.env.CORS =
|
process.env.CORS =
|
||||||
@@ -209,7 +229,7 @@ const verifyRUN_TIMES = (): string[] => {
|
|||||||
|
|
||||||
const verifyExecutablePaths = () => {
|
const verifyExecutablePaths = () => {
|
||||||
const errors: string[] = []
|
const errors: string[] = []
|
||||||
const { RUN_TIMES, SAS_PATH, NODE_PATH, MODE } = process.env
|
const { RUN_TIMES, SAS_PATH, NODE_PATH, PYTHON_PATH, MODE } = process.env
|
||||||
|
|
||||||
if (MODE === ModeType.Server) {
|
if (MODE === ModeType.Server) {
|
||||||
const runTimes = RUN_TIMES?.split(',')
|
const runTimes = RUN_TIMES?.split(',')
|
||||||
@@ -221,6 +241,10 @@ const verifyExecutablePaths = () => {
|
|||||||
if (runTimes?.includes(RunTimeType.JS) && !NODE_PATH) {
|
if (runTimes?.includes(RunTimeType.JS) && !NODE_PATH) {
|
||||||
errors.push(`- NODE_PATH is required for ${RunTimeType.JS} run time`)
|
errors.push(`- NODE_PATH is required for ${RunTimeType.JS} run time`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (runTimes?.includes(RunTimeType.PY) && !PYTHON_PATH) {
|
||||||
|
errors.push(`- PYTHON_PATH is required for ${RunTimeType.PY} run time`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors
|
return errors
|
||||||
|
|||||||
331
web/package-lock.json
generated
331
web/package-lock.json
generated
@@ -2284,6 +2284,58 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@jridgewell/gen-mapping": {
|
||||||
|
"version": "0.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
|
||||||
|
"integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/set-array": "^1.0.1",
|
||||||
|
"@jridgewell/sourcemap-codec": "^1.4.10",
|
||||||
|
"@jridgewell/trace-mapping": "^0.3.9"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@jridgewell/resolve-uri": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@jridgewell/set-array": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@jridgewell/source-map": {
|
||||||
|
"version": "0.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz",
|
||||||
|
"integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/gen-mapping": "^0.3.0",
|
||||||
|
"@jridgewell/trace-mapping": "^0.3.9"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@jridgewell/sourcemap-codec": {
|
||||||
|
"version": "1.4.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
|
||||||
|
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
|
||||||
|
},
|
||||||
|
"node_modules/@jridgewell/trace-mapping": {
|
||||||
|
"version": "0.3.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz",
|
||||||
|
"integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/resolve-uri": "^3.0.3",
|
||||||
|
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@mui/core": {
|
"node_modules/@mui/core": {
|
||||||
"version": "5.0.0-alpha.54",
|
"version": "5.0.0-alpha.54",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/core/-/core-5.0.0-alpha.54.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/core/-/core-5.0.0-alpha.54.tgz",
|
||||||
@@ -3937,11 +3989,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/acorn": {
|
"node_modules/acorn": {
|
||||||
"version": "7.4.1",
|
"version": "8.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz",
|
||||||
"integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
|
"integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==",
|
||||||
"dev": true,
|
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -6522,18 +6572,6 @@
|
|||||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/espree/node_modules/acorn": {
|
|
||||||
"version": "8.7.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
|
|
||||||
"integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==",
|
|
||||||
"dev": true,
|
|
||||||
"bin": {
|
|
||||||
"acorn": "bin/acorn"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/espree/node_modules/eslint-visitor-keys": {
|
"node_modules/espree/node_modules/eslint-visitor-keys": {
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz",
|
||||||
@@ -7197,20 +7235,6 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/html-minifier-terser/node_modules/acorn": {
|
|
||||||
"version": "8.7.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
|
|
||||||
"integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==",
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
|
||||||
"acorn": "bin/acorn"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/html-minifier-terser/node_modules/commander": {
|
"node_modules/html-minifier-terser/node_modules/commander": {
|
||||||
"version": "8.3.0",
|
"version": "8.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
|
||||||
@@ -7220,46 +7244,6 @@
|
|||||||
"node": ">= 12"
|
"node": ">= 12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/html-minifier-terser/node_modules/source-map": {
|
|
||||||
"version": "0.7.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
|
|
||||||
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
|
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/html-minifier-terser/node_modules/terser": {
|
|
||||||
"version": "5.10.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz",
|
|
||||||
"integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"commander": "^2.20.0",
|
|
||||||
"source-map": "~0.7.2",
|
|
||||||
"source-map-support": "~0.5.20"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"terser": "bin/terser"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"acorn": "^8.5.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"acorn": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/html-minifier-terser/node_modules/terser/node_modules/commander": {
|
|
||||||
"version": "2.20.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
|
||||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/html-webpack-plugin": {
|
"node_modules/html-webpack-plugin": {
|
||||||
"version": "5.5.0",
|
"version": "5.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz",
|
||||||
@@ -10287,6 +10271,28 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/terser": {
|
||||||
|
"version": "5.14.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz",
|
||||||
|
"integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/source-map": "^0.3.2",
|
||||||
|
"acorn": "^8.5.0",
|
||||||
|
"commander": "^2.20.0",
|
||||||
|
"source-map-support": "~0.5.20"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"terser": "bin/terser"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/terser/node_modules/commander": {
|
||||||
|
"version": "2.20.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||||
|
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
|
||||||
|
},
|
||||||
"node_modules/text-table": {
|
"node_modules/text-table": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
|
||||||
@@ -11124,17 +11130,6 @@
|
|||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/webpack/node_modules/acorn": {
|
|
||||||
"version": "8.7.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
|
|
||||||
"integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==",
|
|
||||||
"bin": {
|
|
||||||
"acorn": "bin/acorn"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/webpack/node_modules/acorn-import-assertions": {
|
"node_modules/webpack/node_modules/acorn-import-assertions": {
|
||||||
"version": "1.8.0",
|
"version": "1.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz",
|
||||||
@@ -11143,11 +11138,6 @@
|
|||||||
"acorn": "^8"
|
"acorn": "^8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/webpack/node_modules/commander": {
|
|
||||||
"version": "2.20.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
|
||||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
|
|
||||||
},
|
|
||||||
"node_modules/webpack/node_modules/has-flag": {
|
"node_modules/webpack/node_modules/has-flag": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||||
@@ -11216,30 +11206,6 @@
|
|||||||
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/webpack/node_modules/terser": {
|
|
||||||
"version": "5.10.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz",
|
|
||||||
"integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==",
|
|
||||||
"dependencies": {
|
|
||||||
"commander": "^2.20.0",
|
|
||||||
"source-map": "~0.7.2",
|
|
||||||
"source-map-support": "~0.5.20"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"terser": "bin/terser"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"acorn": "^8.5.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"acorn": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/webpack/node_modules/terser-webpack-plugin": {
|
"node_modules/webpack/node_modules/terser-webpack-plugin": {
|
||||||
"version": "5.3.1",
|
"version": "5.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.1.tgz",
|
||||||
@@ -11273,14 +11239,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/webpack/node_modules/terser/node_modules/source-map": {
|
|
||||||
"version": "0.7.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
|
|
||||||
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/webpack/node_modules/webpack-sources": {
|
"node_modules/webpack/node_modules/webpack-sources": {
|
||||||
"version": "3.2.3",
|
"version": "3.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
|
||||||
@@ -12914,6 +12872,49 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@jridgewell/gen-mapping": {
|
||||||
|
"version": "0.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
|
||||||
|
"integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
|
||||||
|
"requires": {
|
||||||
|
"@jridgewell/set-array": "^1.0.1",
|
||||||
|
"@jridgewell/sourcemap-codec": "^1.4.10",
|
||||||
|
"@jridgewell/trace-mapping": "^0.3.9"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@jridgewell/resolve-uri": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w=="
|
||||||
|
},
|
||||||
|
"@jridgewell/set-array": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw=="
|
||||||
|
},
|
||||||
|
"@jridgewell/source-map": {
|
||||||
|
"version": "0.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz",
|
||||||
|
"integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==",
|
||||||
|
"requires": {
|
||||||
|
"@jridgewell/gen-mapping": "^0.3.0",
|
||||||
|
"@jridgewell/trace-mapping": "^0.3.9"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@jridgewell/sourcemap-codec": {
|
||||||
|
"version": "1.4.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
|
||||||
|
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
|
||||||
|
},
|
||||||
|
"@jridgewell/trace-mapping": {
|
||||||
|
"version": "0.3.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz",
|
||||||
|
"integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==",
|
||||||
|
"requires": {
|
||||||
|
"@jridgewell/resolve-uri": "^3.0.3",
|
||||||
|
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@mui/core": {
|
"@mui/core": {
|
||||||
"version": "5.0.0-alpha.54",
|
"version": "5.0.0-alpha.54",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/core/-/core-5.0.0-alpha.54.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/core/-/core-5.0.0-alpha.54.tgz",
|
||||||
@@ -14110,11 +14111,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"acorn": {
|
"acorn": {
|
||||||
"version": "7.4.1",
|
"version": "8.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz",
|
||||||
"integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
|
"integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w=="
|
||||||
"dev": true,
|
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"acorn-jsx": {
|
"acorn-jsx": {
|
||||||
"version": "5.3.2",
|
"version": "5.3.2",
|
||||||
@@ -16061,12 +16060,6 @@
|
|||||||
"eslint-visitor-keys": "^3.3.0"
|
"eslint-visitor-keys": "^3.3.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"acorn": {
|
|
||||||
"version": "8.7.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
|
|
||||||
"integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"eslint-visitor-keys": {
|
"eslint-visitor-keys": {
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz",
|
||||||
@@ -16585,44 +16578,11 @@
|
|||||||
"terser": "^5.10.0"
|
"terser": "^5.10.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"acorn": {
|
|
||||||
"version": "8.7.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
|
|
||||||
"integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==",
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"peer": true
|
|
||||||
},
|
|
||||||
"commander": {
|
"commander": {
|
||||||
"version": "8.3.0",
|
"version": "8.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
|
||||||
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
|
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
|
||||||
"source-map": {
|
|
||||||
"version": "0.7.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
|
|
||||||
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"terser": {
|
|
||||||
"version": "5.10.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz",
|
|
||||||
"integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"commander": "^2.20.0",
|
|
||||||
"source-map": "~0.7.2",
|
|
||||||
"source-map-support": "~0.5.20"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"commander": {
|
|
||||||
"version": "2.20.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
|
||||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
|
||||||
"dev": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -18903,6 +18863,24 @@
|
|||||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
|
||||||
"integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ=="
|
"integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ=="
|
||||||
},
|
},
|
||||||
|
"terser": {
|
||||||
|
"version": "5.14.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz",
|
||||||
|
"integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==",
|
||||||
|
"requires": {
|
||||||
|
"@jridgewell/source-map": "^0.3.2",
|
||||||
|
"acorn": "^8.5.0",
|
||||||
|
"commander": "^2.20.0",
|
||||||
|
"source-map-support": "~0.5.20"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"commander": {
|
||||||
|
"version": "2.20.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||||
|
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"text-table": {
|
"text-table": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
|
||||||
@@ -19259,22 +19237,12 @@
|
|||||||
"webpack-sources": "^3.2.2"
|
"webpack-sources": "^3.2.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"acorn": {
|
|
||||||
"version": "8.7.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
|
|
||||||
"integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ=="
|
|
||||||
},
|
|
||||||
"acorn-import-assertions": {
|
"acorn-import-assertions": {
|
||||||
"version": "1.8.0",
|
"version": "1.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz",
|
||||||
"integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==",
|
"integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==",
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
"commander": {
|
|
||||||
"version": "2.20.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
|
||||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
|
|
||||||
},
|
|
||||||
"has-flag": {
|
"has-flag": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||||
@@ -19321,23 +19289,6 @@
|
|||||||
"has-flag": "^4.0.0"
|
"has-flag": "^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"terser": {
|
|
||||||
"version": "5.10.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz",
|
|
||||||
"integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==",
|
|
||||||
"requires": {
|
|
||||||
"commander": "^2.20.0",
|
|
||||||
"source-map": "~0.7.2",
|
|
||||||
"source-map-support": "~0.5.20"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"source-map": {
|
|
||||||
"version": "0.7.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
|
|
||||||
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ=="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"terser-webpack-plugin": {
|
"terser-webpack-plugin": {
|
||||||
"version": "5.3.1",
|
"version": "5.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.1.tgz",
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ function App() {
|
|||||||
<HashRouter>
|
<HashRouter>
|
||||||
<Header />
|
<Header />
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<Login />} />
|
<Route path="*" element={<Login />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</HashRouter>
|
</HashRouter>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const FilePathInputModal = ({
|
|||||||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const value = event.target.value
|
const value = event.target.value
|
||||||
|
|
||||||
const specialChars = /[`!@#$%^&*()_+\-=[\]{};':"\\|,<>?~]/
|
const specialChars = /[`!@#$%^&*()+\-=[\]{};':"\\|,<>?~]/
|
||||||
const fileExtension = /\.(exe|sh|htaccess)$/i
|
const fileExtension = /\.(exe|sh|htaccess)$/i
|
||||||
|
|
||||||
if (specialChars.test(value)) {
|
if (specialChars.test(value)) {
|
||||||
|
|||||||
@@ -2,16 +2,18 @@ import React, { useState, useEffect, useContext } from 'react'
|
|||||||
import { Link, useNavigate, useLocation } from 'react-router-dom'
|
import { Link, useNavigate, useLocation } from 'react-router-dom'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
Box,
|
||||||
AppBar,
|
AppBar,
|
||||||
Toolbar,
|
Toolbar,
|
||||||
Tabs,
|
Tabs,
|
||||||
Tab,
|
Tab,
|
||||||
Button,
|
Button,
|
||||||
Menu,
|
Menu,
|
||||||
MenuItem
|
MenuItem,
|
||||||
|
IconButton,
|
||||||
|
Typography
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import OpenInNewIcon from '@mui/icons-material/OpenInNew'
|
import { OpenInNew, Settings, Menu as MenuIcon } from '@mui/icons-material'
|
||||||
import SettingsIcon from '@mui/icons-material/Settings'
|
|
||||||
|
|
||||||
import Username from './username'
|
import Username from './username'
|
||||||
import { AppContext } from '../context/appContext'
|
import { AppContext } from '../context/appContext'
|
||||||
@@ -30,31 +32,38 @@ const Header = (props: any) => {
|
|||||||
const [tabValue, setTabValue] = useState(
|
const [tabValue, setTabValue] = useState(
|
||||||
validTabs.includes(pathname) ? pathname : '/'
|
validTabs.includes(pathname) ? pathname : '/'
|
||||||
)
|
)
|
||||||
const [anchorEl, setAnchorEl] = useState<
|
|
||||||
(EventTarget & HTMLButtonElement) | null
|
const [anchorElNav, setAnchorElNav] = React.useState<null | HTMLElement>(null)
|
||||||
>(null)
|
const [anchorElUser, setAnchorElUser] = React.useState<null | HTMLElement>(
|
||||||
|
null
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleOpenNavMenu = (event: React.MouseEvent<HTMLElement>) => {
|
||||||
|
setAnchorElNav(event.currentTarget)
|
||||||
|
}
|
||||||
|
const handleOpenUserMenu = (event: React.MouseEvent<HTMLElement>) => {
|
||||||
|
setAnchorElUser(event.currentTarget)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCloseNavMenu = () => {
|
||||||
|
setAnchorElNav(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCloseUserMenu = () => {
|
||||||
|
setAnchorElUser(null)
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTabValue(validTabs.includes(pathname) ? pathname : '/')
|
setTabValue(validTabs.includes(pathname) ? pathname : '/')
|
||||||
}, [pathname])
|
}, [pathname])
|
||||||
|
|
||||||
const handleMenu = (
|
|
||||||
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
|
|
||||||
) => {
|
|
||||||
setAnchorEl(event.currentTarget)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleClose = () => {
|
|
||||||
setAnchorEl(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleTabChange = (event: React.SyntheticEvent, value: string) => {
|
const handleTabChange = (event: React.SyntheticEvent, value: string) => {
|
||||||
setTabValue(value)
|
setTabValue(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
if (appContext.logout) {
|
if (appContext.logout) {
|
||||||
handleClose()
|
handleCloseUserMenu()
|
||||||
appContext.logout()
|
appContext.logout()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,43 +73,129 @@ const Header = (props: any) => {
|
|||||||
sx={{ zIndex: (theme) => theme.zIndex.drawer + 1 }}
|
sx={{ zIndex: (theme) => theme.zIndex.drawer + 1 }}
|
||||||
>
|
>
|
||||||
<Toolbar variant="dense">
|
<Toolbar variant="dense">
|
||||||
<img
|
<Box sx={{ display: { xs: 'none', md: 'flex' } }}>
|
||||||
src="logo.png"
|
<img
|
||||||
alt="logo"
|
src="logo.png"
|
||||||
style={{
|
alt="logo"
|
||||||
width: '35px',
|
style={{
|
||||||
cursor: 'pointer',
|
width: '35px',
|
||||||
marginRight: '25px'
|
height: '35px',
|
||||||
}}
|
marginTop: '9px',
|
||||||
onClick={() => {
|
cursor: 'pointer',
|
||||||
setTabValue('/')
|
marginRight: '25px'
|
||||||
navigate('/')
|
}}
|
||||||
}}
|
onClick={() => {
|
||||||
/>
|
setTabValue('/')
|
||||||
<Tabs
|
navigate('/')
|
||||||
indicatorColor="secondary"
|
}}
|
||||||
value={tabValue}
|
|
||||||
onChange={handleTabChange}
|
|
||||||
>
|
|
||||||
<Tab label="Home" value="/" to="/" component={Link} />
|
|
||||||
<Tab
|
|
||||||
label="Studio"
|
|
||||||
value="/SASjsStudio"
|
|
||||||
to="/SASjsStudio"
|
|
||||||
component={Link}
|
|
||||||
/>
|
/>
|
||||||
</Tabs>
|
<Tabs
|
||||||
<Button
|
indicatorColor="secondary"
|
||||||
href={`${baseUrl}/AppStream`}
|
value={tabValue}
|
||||||
target="_blank"
|
onChange={handleTabChange}
|
||||||
rel="noreferrer"
|
>
|
||||||
variant="contained"
|
<Tab label="Home" value="/" to="/" component={Link} />
|
||||||
color="primary"
|
<Tab
|
||||||
size="large"
|
label="Studio"
|
||||||
endIcon={<OpenInNewIcon />}
|
value="/SASjsStudio"
|
||||||
>
|
to="/SASjsStudio"
|
||||||
Apps
|
component={Link}
|
||||||
</Button>
|
/>
|
||||||
|
</Tabs>
|
||||||
|
<Button
|
||||||
|
href={`${baseUrl}/AppStream`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
size="large"
|
||||||
|
endIcon={<OpenInNew />}
|
||||||
|
>
|
||||||
|
Apps
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ flexGrow: 1, display: { xs: 'flex', md: 'none' } }}>
|
||||||
|
<IconButton size="large" onClick={handleOpenNavMenu} color="inherit">
|
||||||
|
<MenuIcon />
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
|
<Menu
|
||||||
|
id="menu-appbar"
|
||||||
|
anchorEl={anchorElNav}
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: 'bottom',
|
||||||
|
horizontal: 'left'
|
||||||
|
}}
|
||||||
|
keepMounted
|
||||||
|
transformOrigin={{
|
||||||
|
vertical: 'top',
|
||||||
|
horizontal: 'left'
|
||||||
|
}}
|
||||||
|
open={!!anchorElNav}
|
||||||
|
onClose={handleCloseNavMenu}
|
||||||
|
sx={{
|
||||||
|
display: { xs: 'block', md: 'none' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MenuItem sx={{ justifyContent: 'center' }}>
|
||||||
|
<Button
|
||||||
|
component={Link}
|
||||||
|
to="/"
|
||||||
|
onClick={handleCloseNavMenu}
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
Home
|
||||||
|
</Button>
|
||||||
|
</MenuItem>
|
||||||
|
|
||||||
|
<MenuItem sx={{ justifyContent: 'center' }}>
|
||||||
|
<Button
|
||||||
|
component={Link}
|
||||||
|
to="/SASjsStudio"
|
||||||
|
onClick={handleCloseNavMenu}
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
Studio
|
||||||
|
</Button>
|
||||||
|
</MenuItem>
|
||||||
|
|
||||||
|
<MenuItem sx={{ justifyContent: 'center' }}>
|
||||||
|
<Button
|
||||||
|
href={`${baseUrl}/AppStream`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
onClick={handleCloseNavMenu}
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
endIcon={<OpenInNew />}
|
||||||
|
>
|
||||||
|
Apps
|
||||||
|
</Button>
|
||||||
|
</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ display: { xs: 'flex', md: 'none' } }}>
|
||||||
|
<img
|
||||||
|
src="logo.png"
|
||||||
|
alt="logo"
|
||||||
|
style={{
|
||||||
|
width: '35px',
|
||||||
|
height: '35px',
|
||||||
|
marginTop: '2px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
marginRight: '25px'
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
setTabValue('/')
|
||||||
|
navigate('/')
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -110,11 +205,11 @@ const Header = (props: any) => {
|
|||||||
>
|
>
|
||||||
<Username
|
<Username
|
||||||
username={appContext.displayName || appContext.username}
|
username={appContext.displayName || appContext.username}
|
||||||
onClickHandler={handleMenu}
|
onClickHandler={handleOpenUserMenu}
|
||||||
/>
|
/>
|
||||||
<Menu
|
<Menu
|
||||||
id="menu-appbar"
|
id="menu-appbar"
|
||||||
anchorEl={anchorEl}
|
anchorEl={anchorElUser}
|
||||||
anchorOrigin={{
|
anchorOrigin={{
|
||||||
vertical: 'bottom',
|
vertical: 'bottom',
|
||||||
horizontal: 'center'
|
horizontal: 'center'
|
||||||
@@ -124,17 +219,30 @@ const Header = (props: any) => {
|
|||||||
vertical: 'top',
|
vertical: 'top',
|
||||||
horizontal: 'center'
|
horizontal: 'center'
|
||||||
}}
|
}}
|
||||||
open={!!anchorEl}
|
open={!!anchorElUser}
|
||||||
onClose={handleClose}
|
onClose={handleCloseUserMenu}
|
||||||
>
|
>
|
||||||
|
{appContext.loggedIn && (
|
||||||
|
<MenuItem
|
||||||
|
sx={{ justifyContent: 'center', display: { md: 'none' } }}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="h5"
|
||||||
|
sx={{ border: '1px solid black', padding: '5px' }}
|
||||||
|
>
|
||||||
|
{appContext.displayName || appContext.username}
|
||||||
|
</Typography>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
|
||||||
<MenuItem sx={{ justifyContent: 'center' }}>
|
<MenuItem sx={{ justifyContent: 'center' }}>
|
||||||
<Button
|
<Button
|
||||||
component={Link}
|
component={Link}
|
||||||
to="/SASjsSettings"
|
to="/SASjsSettings"
|
||||||
onClick={handleClose}
|
onClick={handleCloseUserMenu}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
startIcon={<SettingsIcon />}
|
startIcon={<Settings />}
|
||||||
>
|
>
|
||||||
Settings
|
Settings
|
||||||
</Button>
|
</Button>
|
||||||
@@ -147,7 +255,7 @@ const Header = (props: any) => {
|
|||||||
variant="contained"
|
variant="contained"
|
||||||
size="large"
|
size="large"
|
||||||
color="primary"
|
color="primary"
|
||||||
endIcon={<OpenInNewIcon />}
|
endIcon={<OpenInNew />}
|
||||||
>
|
>
|
||||||
Docs
|
Docs
|
||||||
</Button>
|
</Button>
|
||||||
@@ -160,16 +268,21 @@ const Header = (props: any) => {
|
|||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
size="large"
|
size="large"
|
||||||
endIcon={<OpenInNewIcon />}
|
endIcon={<OpenInNew />}
|
||||||
>
|
>
|
||||||
API
|
API
|
||||||
</Button>
|
</Button>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem onClick={handleLogout} sx={{ justifyContent: 'center' }}>
|
{appContext.loggedIn && (
|
||||||
<Button variant="contained" color="primary">
|
<MenuItem
|
||||||
Logout
|
onClick={handleLogout}
|
||||||
</Button>
|
sx={{ justifyContent: 'center' }}
|
||||||
</MenuItem>
|
>
|
||||||
|
<Button variant="contained" color="primary">
|
||||||
|
Logout
|
||||||
|
</Button>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
</Menu>
|
</Menu>
|
||||||
</div>
|
</div>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ const NameInputModal = ({
|
|||||||
const value = event.target.value
|
const value = event.target.value
|
||||||
|
|
||||||
const folderNameRegex = /[`!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?~]/
|
const folderNameRegex = /[`!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?~]/
|
||||||
const fileNameRegex = /[`!@#$%^&*()_+\-=[\]{};':"\\|,<>/?~]/
|
const fileNameRegex = /[`!@#$%^&*()+\-=[\]{};':"\\|,<>/?~]/
|
||||||
const fileNameExtensionRegex = /.(exe|sh|htaccess)$/i
|
const fileNameExtensionRegex = /.(exe|sh|htaccess)$/i
|
||||||
|
|
||||||
const specialChars = isFolder ? folderNameRegex : fileNameRegex
|
const specialChars = isFolder ? folderNameRegex : fileNameRegex
|
||||||
|
|||||||
@@ -20,7 +20,14 @@ const Username = (props: any) => {
|
|||||||
) : (
|
) : (
|
||||||
<AccountCircle></AccountCircle>
|
<AccountCircle></AccountCircle>
|
||||||
)}
|
)}
|
||||||
<Typography variant="h6" sx={{ color: 'white', padding: '0 8px' }}>
|
<Typography
|
||||||
|
variant="h6"
|
||||||
|
sx={{
|
||||||
|
color: 'white',
|
||||||
|
padding: '0 8px',
|
||||||
|
display: { xs: 'none', md: 'flex' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
{props.username}
|
{props.username}
|
||||||
</Typography>
|
</Typography>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|||||||
@@ -32,7 +32,13 @@ const BootstrapDialog = styled(Dialog)(({ theme }) => ({
|
|||||||
type AddPermissionModalProps = {
|
type AddPermissionModalProps = {
|
||||||
open: boolean
|
open: boolean
|
||||||
handleOpen: Dispatch<SetStateAction<boolean>>
|
handleOpen: Dispatch<SetStateAction<boolean>>
|
||||||
addPermission: (addPermissionPayload: RegisterPermissionPayload) => void
|
addPermission: (
|
||||||
|
permissions: RegisterPermissionPayload[],
|
||||||
|
permissionType: string,
|
||||||
|
principalType: string,
|
||||||
|
principal: string,
|
||||||
|
permissionSetting: string
|
||||||
|
) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const AddPermissionModal = ({
|
const AddPermissionModal = ({
|
||||||
@@ -40,10 +46,11 @@ const AddPermissionModal = ({
|
|||||||
handleOpen,
|
handleOpen,
|
||||||
addPermission
|
addPermission
|
||||||
}: AddPermissionModalProps) => {
|
}: AddPermissionModalProps) => {
|
||||||
const [URIs, setURIs] = useState<string[]>([])
|
const [paths, setPaths] = useState<string[]>([])
|
||||||
const [loadingURIs, setLoadingURIs] = useState(false)
|
const [loadingPaths, setLoadingPaths] = useState(false)
|
||||||
const [uri, setUri] = useState<string>()
|
const [selectedPaths, setSelectedPaths] = useState<string[]>([])
|
||||||
const [principalType, setPrincipalType] = useState('user')
|
const [permissionType, setPermissionType] = useState('Route')
|
||||||
|
const [principalType, setPrincipalType] = useState('Group')
|
||||||
const [userPrincipal, setUserPrincipal] = useState<UserResponse>()
|
const [userPrincipal, setUserPrincipal] = useState<UserResponse>()
|
||||||
const [groupPrincipal, setGroupPrincipal] = useState<GroupResponse>()
|
const [groupPrincipal, setGroupPrincipal] = useState<GroupResponse>()
|
||||||
const [permissionSetting, setPermissionSetting] = useState('Grant')
|
const [permissionSetting, setPermissionSetting] = useState('Grant')
|
||||||
@@ -52,29 +59,29 @@ const AddPermissionModal = ({
|
|||||||
const [groupPrincipals, setGroupPrincipals] = useState<GroupResponse[]>([])
|
const [groupPrincipals, setGroupPrincipals] = useState<GroupResponse[]>([])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLoadingURIs(true)
|
setLoadingPaths(true)
|
||||||
axios
|
axios
|
||||||
.get('/SASjsApi/info/authorizedRoutes')
|
.get('/SASjsApi/info/authorizedRoutes')
|
||||||
.then((res: any) => {
|
.then((res: any) => {
|
||||||
if (res.data) {
|
if (res.data) {
|
||||||
setURIs(res.data.URIs)
|
setPaths(res.data.paths)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.log(err)
|
console.log(err)
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setLoadingURIs(false)
|
setLoadingPaths(false)
|
||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLoadingPrincipals(true)
|
setLoadingPrincipals(true)
|
||||||
axios
|
axios
|
||||||
.get(`/SASjsApi/${principalType}`)
|
.get(`/SASjsApi/${principalType.toLowerCase()}`)
|
||||||
.then((res: any) => {
|
.then((res: any) => {
|
||||||
if (res.data) {
|
if (res.data) {
|
||||||
if (principalType === 'user') {
|
if (principalType.toLowerCase() === 'user') {
|
||||||
const users: UserResponse[] = res.data
|
const users: UserResponse[] = res.data
|
||||||
const nonAdminUsers = users.filter((user) => !user.isAdmin)
|
const nonAdminUsers = users.filter((user) => !user.isAdmin)
|
||||||
setUserPrincipals(nonAdminUsers)
|
setUserPrincipals(nonAdminUsers)
|
||||||
@@ -92,21 +99,40 @@ const AddPermissionModal = ({
|
|||||||
}, [principalType])
|
}, [principalType])
|
||||||
|
|
||||||
const handleAddPermission = () => {
|
const handleAddPermission = () => {
|
||||||
const addPermissionPayload: any = {
|
const permissions: RegisterPermissionPayload[] = []
|
||||||
uri,
|
|
||||||
setting: permissionSetting,
|
selectedPaths.forEach((path) => {
|
||||||
principalType
|
const addPermissionPayload: any = {
|
||||||
}
|
path,
|
||||||
if (principalType === 'user' && userPrincipal) {
|
type: permissionType,
|
||||||
addPermissionPayload.principalId = userPrincipal.id
|
setting: permissionSetting,
|
||||||
} else if (principalType === 'group' && groupPrincipal) {
|
principalType: principalType.toLowerCase(),
|
||||||
addPermissionPayload.principalId = groupPrincipal.groupId
|
principalId:
|
||||||
}
|
principalType.toLowerCase() === 'user'
|
||||||
addPermission(addPermissionPayload)
|
? userPrincipal?.id
|
||||||
|
: groupPrincipal?.groupId
|
||||||
|
}
|
||||||
|
|
||||||
|
permissions.push(addPermissionPayload)
|
||||||
|
})
|
||||||
|
|
||||||
|
const principal =
|
||||||
|
principalType.toLowerCase() === 'user'
|
||||||
|
? userPrincipal?.username
|
||||||
|
: groupPrincipal?.name
|
||||||
|
|
||||||
|
addPermission(
|
||||||
|
permissions,
|
||||||
|
permissionType,
|
||||||
|
principalType,
|
||||||
|
principal!,
|
||||||
|
permissionSetting
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const addButtonDisabled =
|
const addButtonDisabled =
|
||||||
!uri || (principalType === 'user' ? !userPrincipal : !groupPrincipal)
|
!selectedPaths.length ||
|
||||||
|
(principalType.toLowerCase() === 'user' ? !userPrincipal : !groupPrincipal)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BootstrapDialog onClose={() => handleOpen(false)} open={open}>
|
<BootstrapDialog onClose={() => handleOpen(false)} open={open}>
|
||||||
@@ -120,22 +146,37 @@ const AddPermissionModal = ({
|
|||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
options={URIs}
|
multiple
|
||||||
disableClearable
|
disableClearable
|
||||||
value={uri}
|
options={paths}
|
||||||
onChange={(event: any, newValue: string) => setUri(newValue)}
|
filterSelectedOptions
|
||||||
|
value={selectedPaths}
|
||||||
|
onChange={(event: any, newValue: string[]) => {
|
||||||
|
setSelectedPaths(newValue)
|
||||||
|
}}
|
||||||
|
renderInput={(params) => <TextField {...params} label="Paths" />}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Autocomplete
|
||||||
|
options={['Route']}
|
||||||
|
disableClearable
|
||||||
|
value={permissionType}
|
||||||
|
onChange={(event: any, newValue: string) =>
|
||||||
|
setPermissionType(newValue)
|
||||||
|
}
|
||||||
renderInput={(params) =>
|
renderInput={(params) =>
|
||||||
loadingURIs ? (
|
loadingPaths ? (
|
||||||
<CircularProgress />
|
<CircularProgress />
|
||||||
) : (
|
) : (
|
||||||
<TextField {...params} label="Principal" />
|
<TextField {...params} label="Permission Type" />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
options={['user', 'group']}
|
options={['Group', 'User']}
|
||||||
disableClearable
|
disableClearable
|
||||||
value={principalType}
|
value={principalType}
|
||||||
onChange={(event: any, newValue: string) =>
|
onChange={(event: any, newValue: string) =>
|
||||||
@@ -147,7 +188,7 @@ const AddPermissionModal = ({
|
|||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
{principalType === 'user' ? (
|
{principalType.toLowerCase() === 'user' ? (
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
options={userPrincipals}
|
options={userPrincipals}
|
||||||
getOptionLabel={(option) => option.displayName}
|
getOptionLabel={(option) => option.displayName}
|
||||||
|
|||||||
120
web/src/containers/Settings/addPermissionResponseModal.tsx
Normal file
120
web/src/containers/Settings/addPermissionResponseModal.tsx
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import { Typography, DialogContent } from '@mui/material'
|
||||||
|
|
||||||
|
import { BootstrapDialog } from '../../components/modal'
|
||||||
|
import { BootstrapDialogTitle } from '../../components/dialogTitle'
|
||||||
|
import { PermissionResponse } from '../../utils/types'
|
||||||
|
|
||||||
|
export interface PermissionResponsePayload {
|
||||||
|
permissionType: string
|
||||||
|
principalType: string
|
||||||
|
principal: string
|
||||||
|
permissionSetting: string
|
||||||
|
existingPermissions: PermissionResponse[]
|
||||||
|
newAddedPermissions: PermissionResponse[]
|
||||||
|
updatedPermissions: PermissionResponse[]
|
||||||
|
errorPaths: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
open: boolean
|
||||||
|
setOpen: React.Dispatch<React.SetStateAction<boolean>>
|
||||||
|
payload: PermissionResponsePayload
|
||||||
|
}
|
||||||
|
|
||||||
|
const PermissionResponseModal = ({ open, setOpen, payload }: Props) => {
|
||||||
|
const newAddedPermissionsLength = payload.newAddedPermissions.length
|
||||||
|
const updatedPermissionsLength = payload.updatedPermissions.length
|
||||||
|
const existingPermissionsLength = payload.existingPermissions.length
|
||||||
|
const appliedPermissionsLength =
|
||||||
|
newAddedPermissionsLength + updatedPermissionsLength
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<BootstrapDialog onClose={() => setOpen(false)} open={open}>
|
||||||
|
<BootstrapDialogTitle
|
||||||
|
id="permission-response-modal"
|
||||||
|
handleOpen={setOpen}
|
||||||
|
>
|
||||||
|
Permission Response
|
||||||
|
</BootstrapDialogTitle>
|
||||||
|
<DialogContent dividers>
|
||||||
|
<Typography sx={{ fontWeight: 'bold', marginBottom: '15px' }}>
|
||||||
|
{`${appliedPermissionsLength} "${payload.permissionSetting}", "${
|
||||||
|
payload.permissionType
|
||||||
|
}", "${payload.principalType}", "${payload.principal}" ${
|
||||||
|
appliedPermissionsLength > 1 ? 'Rules' : 'Rule'
|
||||||
|
}`}{' '}
|
||||||
|
Applied:
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{newAddedPermissionsLength > 0 && (
|
||||||
|
<>
|
||||||
|
<Typography>
|
||||||
|
{`${newAddedPermissionsLength} ${
|
||||||
|
newAddedPermissionsLength > 1 ? 'Rules' : 'Rule'
|
||||||
|
}`}{' '}
|
||||||
|
Added:
|
||||||
|
</Typography>
|
||||||
|
<ul>
|
||||||
|
{payload.newAddedPermissions.map((permission, index) => (
|
||||||
|
<li key={index}>{permission.path}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{updatedPermissionsLength > 0 && (
|
||||||
|
<>
|
||||||
|
<Typography>
|
||||||
|
{` ${updatedPermissionsLength} ${
|
||||||
|
updatedPermissionsLength > 1 ? 'Rules' : 'Rule'
|
||||||
|
}`}{' '}
|
||||||
|
Updated:
|
||||||
|
</Typography>
|
||||||
|
<ul>
|
||||||
|
{payload.updatedPermissions.map((permission, index) => (
|
||||||
|
<li key={index}>{permission.path}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{existingPermissionsLength > 0 && (
|
||||||
|
<>
|
||||||
|
<Typography>
|
||||||
|
{`${existingPermissionsLength} ${
|
||||||
|
existingPermissionsLength > 1 ? 'Rules' : 'Rule'
|
||||||
|
}`}{' '}
|
||||||
|
Unchanged:
|
||||||
|
</Typography>
|
||||||
|
<ul>
|
||||||
|
{payload.existingPermissions.map((permission, index) => (
|
||||||
|
<li key={index}>{permission.path}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{payload.errorPaths.length > 0 && (
|
||||||
|
<>
|
||||||
|
<Typography style={{ color: 'red', marginTop: '10px' }}>
|
||||||
|
Errors occurred for following paths:
|
||||||
|
</Typography>
|
||||||
|
<ul>
|
||||||
|
{payload.errorPaths.map((path, index) => (
|
||||||
|
<li key={index}>
|
||||||
|
<Typography>{path}</Typography>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</DialogContent>
|
||||||
|
</BootstrapDialog>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PermissionResponseModal
|
||||||
@@ -31,11 +31,20 @@ const Settings = () => {
|
|||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
flexDirection: { xs: 'column', md: 'row' },
|
||||||
marginTop: '65px'
|
marginTop: '65px'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TabContext value={value}>
|
<TabContext value={value}>
|
||||||
<Box component={Paper} sx={{ margin: '0 5px', height: '92vh' }}>
|
<Box
|
||||||
|
component={Paper}
|
||||||
|
sx={{
|
||||||
|
margin: '0 5px',
|
||||||
|
height: { md: '92vh' },
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
<TabList
|
<TabList
|
||||||
TabIndicatorProps={{
|
TabIndicatorProps={{
|
||||||
style: {
|
style: {
|
||||||
@@ -47,7 +56,7 @@ const Settings = () => {
|
|||||||
>
|
>
|
||||||
<StyledTab label="Profile" value="profile" />
|
<StyledTab label="Profile" value="profile" />
|
||||||
{appContext.mode === ModeType.Server && (
|
{appContext.mode === ModeType.Server && (
|
||||||
<StyledTab label="Uri Access" value="permission" />
|
<StyledTab label="Permissions" value="permission" />
|
||||||
)}
|
)}
|
||||||
</TabList>
|
</TabList>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ import { styled } from '@mui/material/styles'
|
|||||||
import Modal from '../../components/modal'
|
import Modal from '../../components/modal'
|
||||||
import PermissionFilterModal from './permissionFilterModal'
|
import PermissionFilterModal from './permissionFilterModal'
|
||||||
import AddPermissionModal from './addPermissionModal'
|
import AddPermissionModal from './addPermissionModal'
|
||||||
|
import PermissionResponseModal, {
|
||||||
|
PermissionResponsePayload
|
||||||
|
} from './addPermissionResponseModal'
|
||||||
import UpdatePermissionModal from './updatePermissionModal'
|
import UpdatePermissionModal from './updatePermissionModal'
|
||||||
import DeleteConfirmationModal from '../../components/deleteConfirmationModal'
|
import DeleteConfirmationModal from '../../components/deleteConfirmationModal'
|
||||||
import BootstrapSnackbar, { AlertSeverityType } from '../../components/snackbar'
|
import BootstrapSnackbar, { AlertSeverityType } from '../../components/snackbar'
|
||||||
@@ -36,12 +39,23 @@ import {
|
|||||||
PermissionResponse,
|
PermissionResponse,
|
||||||
RegisterPermissionPayload
|
RegisterPermissionPayload
|
||||||
} from '../../utils/types'
|
} from '../../utils/types'
|
||||||
|
import {
|
||||||
|
findExistingPermission,
|
||||||
|
findUpdatingPermission
|
||||||
|
} from '../../utils/helper'
|
||||||
|
|
||||||
import { AppContext } from '../../context/appContext'
|
import { AppContext } from '../../context/appContext'
|
||||||
|
|
||||||
const BootstrapTableCell = styled(TableCell)({
|
const BootstrapTableCell = styled(TableCell)({
|
||||||
textAlign: 'left'
|
textAlign: 'left'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const BootstrapGridItem = styled(Grid)({
|
||||||
|
'&.MuiGrid-item': {
|
||||||
|
maxWidth: '100%'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export enum PrincipalType {
|
export enum PrincipalType {
|
||||||
User = 'User',
|
User = 'User',
|
||||||
Group = 'Group'
|
Group = 'Group'
|
||||||
@@ -59,6 +73,20 @@ const Permission = () => {
|
|||||||
AlertSeverityType.Success
|
AlertSeverityType.Success
|
||||||
)
|
)
|
||||||
const [addPermissionModalOpen, setAddPermissionModalOpen] = useState(false)
|
const [addPermissionModalOpen, setAddPermissionModalOpen] = useState(false)
|
||||||
|
const [openPermissionResponseModal, setOpenPermissionResponseModal] =
|
||||||
|
useState(false)
|
||||||
|
const [permissionResponsePayload, setPermissionResponsePayload] =
|
||||||
|
useState<PermissionResponsePayload>({
|
||||||
|
permissionType: '',
|
||||||
|
principalType: '',
|
||||||
|
principal: '',
|
||||||
|
permissionSetting: '',
|
||||||
|
existingPermissions: [],
|
||||||
|
newAddedPermissions: [],
|
||||||
|
updatedPermissions: [],
|
||||||
|
errorPaths: []
|
||||||
|
})
|
||||||
|
|
||||||
const [updatePermissionModalOpen, setUpdatePermissionModalOpen] =
|
const [updatePermissionModalOpen, setUpdatePermissionModalOpen] =
|
||||||
useState(false)
|
useState(false)
|
||||||
const [deleteConfirmationModalOpen, setDeleteConfirmationModalOpen] =
|
const [deleteConfirmationModalOpen, setDeleteConfirmationModalOpen] =
|
||||||
@@ -68,7 +96,7 @@ const Permission = () => {
|
|||||||
const [selectedPermission, setSelectedPermission] =
|
const [selectedPermission, setSelectedPermission] =
|
||||||
useState<PermissionResponse>()
|
useState<PermissionResponse>()
|
||||||
const [filterModalOpen, setFilterModalOpen] = useState(false)
|
const [filterModalOpen, setFilterModalOpen] = useState(false)
|
||||||
const [uriFilter, setUriFilter] = useState<string[]>([])
|
const [pathFilter, setPathFilter] = useState<string[]>([])
|
||||||
const [principalFilter, setPrincipalFilter] = useState<string[]>([])
|
const [principalFilter, setPrincipalFilter] = useState<string[]>([])
|
||||||
const [principalTypeFilter, setPrincipalTypeFilter] = useState<
|
const [principalTypeFilter, setPrincipalTypeFilter] = useState<
|
||||||
PrincipalType[]
|
PrincipalType[]
|
||||||
@@ -111,8 +139,10 @@ const Permission = () => {
|
|||||||
setFilterModalOpen(false)
|
setFilterModalOpen(false)
|
||||||
|
|
||||||
const uriFilteredPermissions =
|
const uriFilteredPermissions =
|
||||||
uriFilter.length > 0
|
pathFilter.length > 0
|
||||||
? permissions.filter((permission) => uriFilter.includes(permission.uri))
|
? permissions.filter((permission) =>
|
||||||
|
pathFilter.includes(permission.path)
|
||||||
|
)
|
||||||
: permissions
|
: permissions
|
||||||
|
|
||||||
const principalFilteredPermissions =
|
const principalFilteredPermissions =
|
||||||
@@ -172,36 +202,84 @@ const Permission = () => {
|
|||||||
|
|
||||||
const resetFilter = () => {
|
const resetFilter = () => {
|
||||||
setFilterModalOpen(false)
|
setFilterModalOpen(false)
|
||||||
setUriFilter([])
|
setPathFilter([])
|
||||||
setPrincipalFilter([])
|
setPrincipalFilter([])
|
||||||
setSettingFilter([])
|
setSettingFilter([])
|
||||||
setFilteredPermissions([])
|
setFilteredPermissions([])
|
||||||
setFilterApplied(false)
|
setFilterApplied(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
const addPermission = (addPermissionPayload: RegisterPermissionPayload) => {
|
const addPermission = async (
|
||||||
|
permissionsToAdd: RegisterPermissionPayload[],
|
||||||
|
permissionType: string,
|
||||||
|
principalType: string,
|
||||||
|
principal: string,
|
||||||
|
permissionSetting: string
|
||||||
|
) => {
|
||||||
setAddPermissionModalOpen(false)
|
setAddPermissionModalOpen(false)
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
axios
|
|
||||||
.post('/SASjsApi/permission', addPermissionPayload)
|
const newAddedPermissions: PermissionResponse[] = []
|
||||||
.then((res: any) => {
|
const updatedPermissions: PermissionResponse[] = []
|
||||||
fetchPermissions()
|
const errorPaths: string[] = []
|
||||||
setSnackbarMessage('Permission added!')
|
|
||||||
setSnackbarSeverity(AlertSeverityType.Success)
|
const existingPermissions: PermissionResponse[] = []
|
||||||
setOpenSnackbar(true)
|
const updatingPermissions: PermissionResponse[] = []
|
||||||
})
|
const newPermissions: RegisterPermissionPayload[] = []
|
||||||
.catch((err) => {
|
|
||||||
setModalTitle('Abort')
|
permissionsToAdd.forEach((permission) => {
|
||||||
setModalPayload(
|
const existingPermission = findExistingPermission(permissions, permission)
|
||||||
typeof err.response.data === 'object'
|
if (existingPermission) {
|
||||||
? JSON.stringify(err.response.data)
|
existingPermissions.push(existingPermission)
|
||||||
: err.response.data
|
return
|
||||||
)
|
}
|
||||||
setOpenModal(true)
|
|
||||||
})
|
const updatingPermission = findUpdatingPermission(permissions, permission)
|
||||||
.finally(() => {
|
if (updatingPermission) {
|
||||||
setIsLoading(false)
|
updatingPermissions.push(updatingPermission)
|
||||||
})
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newPermissions.push(permission)
|
||||||
|
})
|
||||||
|
|
||||||
|
for (const permission of newPermissions) {
|
||||||
|
await axios
|
||||||
|
.post('/SASjsApi/permission', permission)
|
||||||
|
.then((res) => {
|
||||||
|
newAddedPermissions.push(res.data)
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
errorPaths.push(permission.path)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const permission of updatingPermissions) {
|
||||||
|
await axios
|
||||||
|
.patch(`/SASjsApi/permission/${permission.permissionId}`, {
|
||||||
|
setting: permission.setting === 'Grant' ? 'Deny' : 'Grant'
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
updatedPermissions.push(res.data)
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
errorPaths.push(permission.path)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchPermissions()
|
||||||
|
setIsLoading(false)
|
||||||
|
setPermissionResponsePayload({
|
||||||
|
permissionType,
|
||||||
|
principalType,
|
||||||
|
principal,
|
||||||
|
permissionSetting,
|
||||||
|
existingPermissions,
|
||||||
|
updatedPermissions,
|
||||||
|
newAddedPermissions,
|
||||||
|
errorPaths
|
||||||
|
})
|
||||||
|
setOpenPermissionResponseModal(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleUpdatePermissionClick = (permission: PermissionResponse) => {
|
const handleUpdatePermissionClick = (permission: PermissionResponse) => {
|
||||||
@@ -278,11 +356,11 @@ const Permission = () => {
|
|||||||
) : (
|
) : (
|
||||||
<Box className="permissions-page">
|
<Box className="permissions-page">
|
||||||
<Grid container direction="column" spacing={1}>
|
<Grid container direction="column" spacing={1}>
|
||||||
<Grid item xs={12}>
|
<BootstrapGridItem item xs={12}>
|
||||||
<Paper elevation={3} sx={{ display: 'flex' }}>
|
<Paper elevation={3} sx={{ display: 'flex' }}>
|
||||||
<Tooltip title="Filter Permissions">
|
<Tooltip title="Filter Permissions">
|
||||||
<IconButton>
|
<IconButton onClick={() => setFilterModalOpen(true)}>
|
||||||
<FilterListIcon onClick={() => setFilterModalOpen(true)} />
|
<FilterListIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{appContext.isAdmin && (
|
{appContext.isAdmin && (
|
||||||
@@ -297,14 +375,14 @@ const Permission = () => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</Paper>
|
</Paper>
|
||||||
</Grid>
|
</BootstrapGridItem>
|
||||||
<Grid item xs={12}>
|
<BootstrapGridItem item xs={12}>
|
||||||
<PermissionTable
|
<PermissionTable
|
||||||
permissions={filterApplied ? filteredPermissions : permissions}
|
permissions={filterApplied ? filteredPermissions : permissions}
|
||||||
handleUpdatePermissionClick={handleUpdatePermissionClick}
|
handleUpdatePermissionClick={handleUpdatePermissionClick}
|
||||||
handleDeletePermissionClick={handleDeletePermissionClick}
|
handleDeletePermissionClick={handleDeletePermissionClick}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</BootstrapGridItem>
|
||||||
</Grid>
|
</Grid>
|
||||||
<BootstrapSnackbar
|
<BootstrapSnackbar
|
||||||
open={openSnackbar}
|
open={openSnackbar}
|
||||||
@@ -322,8 +400,8 @@ const Permission = () => {
|
|||||||
open={filterModalOpen}
|
open={filterModalOpen}
|
||||||
handleOpen={setFilterModalOpen}
|
handleOpen={setFilterModalOpen}
|
||||||
permissions={permissions}
|
permissions={permissions}
|
||||||
uriFilter={uriFilter}
|
pathFilter={pathFilter}
|
||||||
setUriFilter={setUriFilter}
|
setPathFilter={setPathFilter}
|
||||||
principalFilter={principalFilter}
|
principalFilter={principalFilter}
|
||||||
setPrincipalFilter={setPrincipalFilter}
|
setPrincipalFilter={setPrincipalFilter}
|
||||||
principalTypeFilter={principalTypeFilter}
|
principalTypeFilter={principalTypeFilter}
|
||||||
@@ -338,6 +416,11 @@ const Permission = () => {
|
|||||||
handleOpen={setAddPermissionModalOpen}
|
handleOpen={setAddPermissionModalOpen}
|
||||||
addPermission={addPermission}
|
addPermission={addPermission}
|
||||||
/>
|
/>
|
||||||
|
<PermissionResponseModal
|
||||||
|
open={openPermissionResponseModal}
|
||||||
|
setOpen={setOpenPermissionResponseModal}
|
||||||
|
payload={permissionResponsePayload}
|
||||||
|
/>
|
||||||
<UpdatePermissionModal
|
<UpdatePermissionModal
|
||||||
open={updatePermissionModalOpen}
|
open={updatePermissionModalOpen}
|
||||||
handleOpen={setUpdatePermissionModalOpen}
|
handleOpen={setUpdatePermissionModalOpen}
|
||||||
@@ -374,9 +457,10 @@ const PermissionTable = ({
|
|||||||
<Table sx={{ minWidth: 650 }}>
|
<Table sx={{ minWidth: 650 }}>
|
||||||
<TableHead sx={{ background: 'rgb(0,0,0, 0.3)' }}>
|
<TableHead sx={{ background: 'rgb(0,0,0, 0.3)' }}>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<BootstrapTableCell>Uri</BootstrapTableCell>
|
<BootstrapTableCell>Path</BootstrapTableCell>
|
||||||
|
<BootstrapTableCell>Permission Type</BootstrapTableCell>
|
||||||
<BootstrapTableCell>Principal</BootstrapTableCell>
|
<BootstrapTableCell>Principal</BootstrapTableCell>
|
||||||
<BootstrapTableCell>Type</BootstrapTableCell>
|
<BootstrapTableCell>Principal Type</BootstrapTableCell>
|
||||||
<BootstrapTableCell>Setting</BootstrapTableCell>
|
<BootstrapTableCell>Setting</BootstrapTableCell>
|
||||||
{appContext.isAdmin && (
|
{appContext.isAdmin && (
|
||||||
<BootstrapTableCell>Action</BootstrapTableCell>
|
<BootstrapTableCell>Action</BootstrapTableCell>
|
||||||
@@ -386,7 +470,8 @@ const PermissionTable = ({
|
|||||||
<TableBody>
|
<TableBody>
|
||||||
{permissions.map((permission) => (
|
{permissions.map((permission) => (
|
||||||
<TableRow key={permission.permissionId}>
|
<TableRow key={permission.permissionId}>
|
||||||
<BootstrapTableCell>{permission.uri}</BootstrapTableCell>
|
<BootstrapTableCell>{permission.path}</BootstrapTableCell>
|
||||||
|
<BootstrapTableCell>{permission.type}</BootstrapTableCell>
|
||||||
<BootstrapTableCell>
|
<BootstrapTableCell>
|
||||||
{displayPrincipal(permission)}
|
{displayPrincipal(permission)}
|
||||||
</BootstrapTableCell>
|
</BootstrapTableCell>
|
||||||
@@ -474,8 +559,8 @@ const DisplayGroup = ({ group }: DisplayGroupProps) => {
|
|||||||
<Typography sx={{ p: 1 }} variant="h6" component="div">
|
<Typography sx={{ p: 1 }} variant="h6" component="div">
|
||||||
Group Members
|
Group Members
|
||||||
</Typography>
|
</Typography>
|
||||||
{group.users.map((user) => (
|
{group.users.map((user, index) => (
|
||||||
<Typography sx={{ p: 1 }} component="li">
|
<Typography key={index} sx={{ p: 1 }} component="li">
|
||||||
{user.username}
|
{user.username}
|
||||||
</Typography>
|
</Typography>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ type FilterModalProps = {
|
|||||||
open: boolean
|
open: boolean
|
||||||
handleOpen: Dispatch<SetStateAction<boolean>>
|
handleOpen: Dispatch<SetStateAction<boolean>>
|
||||||
permissions: PermissionResponse[]
|
permissions: PermissionResponse[]
|
||||||
uriFilter: string[]
|
pathFilter: string[]
|
||||||
setUriFilter: Dispatch<SetStateAction<string[]>>
|
setPathFilter: Dispatch<SetStateAction<string[]>>
|
||||||
principalFilter: string[]
|
principalFilter: string[]
|
||||||
setPrincipalFilter: Dispatch<SetStateAction<string[]>>
|
setPrincipalFilter: Dispatch<SetStateAction<string[]>>
|
||||||
principalTypeFilter: PrincipalType[]
|
principalTypeFilter: PrincipalType[]
|
||||||
@@ -43,8 +43,8 @@ const PermissionFilterModal = ({
|
|||||||
open,
|
open,
|
||||||
handleOpen,
|
handleOpen,
|
||||||
permissions,
|
permissions,
|
||||||
uriFilter,
|
pathFilter,
|
||||||
setUriFilter,
|
setPathFilter,
|
||||||
principalFilter,
|
principalFilter,
|
||||||
setPrincipalFilter,
|
setPrincipalFilter,
|
||||||
principalTypeFilter,
|
principalTypeFilter,
|
||||||
@@ -54,8 +54,8 @@ const PermissionFilterModal = ({
|
|||||||
applyFilter,
|
applyFilter,
|
||||||
resetFilter
|
resetFilter
|
||||||
}: FilterModalProps) => {
|
}: FilterModalProps) => {
|
||||||
const URIs = permissions
|
const paths = permissions
|
||||||
.map((permission) => permission.uri)
|
.map((permission) => permission.path)
|
||||||
.filter((uri, index, array) => array.indexOf(uri) === index)
|
.filter((uri, index, array) => array.indexOf(uri) === index)
|
||||||
|
|
||||||
// fetch all the principals from permissions array
|
// fetch all the principals from permissions array
|
||||||
@@ -86,13 +86,13 @@ const PermissionFilterModal = ({
|
|||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
multiple
|
multiple
|
||||||
options={URIs}
|
options={paths}
|
||||||
filterSelectedOptions
|
filterSelectedOptions
|
||||||
value={uriFilter}
|
value={pathFilter}
|
||||||
onChange={(event: any, newValue: string[]) => {
|
onChange={(event: any, newValue: string[]) => {
|
||||||
setUriFilter(newValue)
|
setPathFilter(newValue)
|
||||||
}}
|
}}
|
||||||
renderInput={(params) => <TextField {...params} label="URIs" />}
|
renderInput={(params) => <TextField {...params} label="Paths" />}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
|
|||||||
@@ -1,4 +1,12 @@
|
|||||||
import React, { useEffect, useRef, useState, useContext } from 'react'
|
import React, {
|
||||||
|
Dispatch,
|
||||||
|
SetStateAction,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
useContext,
|
||||||
|
useCallback
|
||||||
|
} from 'react'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -58,13 +66,19 @@ const StyledTab = styled(Tab)(() => ({
|
|||||||
type SASjsEditorProps = {
|
type SASjsEditorProps = {
|
||||||
selectedFilePath: string
|
selectedFilePath: string
|
||||||
setSelectedFilePath: (filePath: string, refreshSideBar?: boolean) => void
|
setSelectedFilePath: (filePath: string, refreshSideBar?: boolean) => void
|
||||||
|
tab: string
|
||||||
|
setTab: Dispatch<SetStateAction<string>>
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseUrl = window.location.origin
|
const baseUrl = window.location.origin
|
||||||
|
const SASJS_LOGS_SEPARATOR =
|
||||||
|
'SASJS_LOGS_SEPARATOR_163ee17b6ff24f028928972d80a26784'
|
||||||
|
|
||||||
const SASjsEditor = ({
|
const SASjsEditor = ({
|
||||||
selectedFilePath,
|
selectedFilePath,
|
||||||
setSelectedFilePath
|
setSelectedFilePath,
|
||||||
|
tab,
|
||||||
|
setTab
|
||||||
}: SASjsEditorProps) => {
|
}: SASjsEditorProps) => {
|
||||||
const appContext = useContext(AppContext)
|
const appContext = useContext(AppContext)
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
@@ -81,7 +95,6 @@ const SASjsEditor = ({
|
|||||||
const [log, setLog] = useState('')
|
const [log, setLog] = useState('')
|
||||||
const [ctrlPressed, setCtrlPressed] = useState(false)
|
const [ctrlPressed, setCtrlPressed] = useState(false)
|
||||||
const [webout, setWebout] = useState('')
|
const [webout, setWebout] = useState('')
|
||||||
const [tab, setTab] = useState('1')
|
|
||||||
const [runTimes, setRunTimes] = useState<string[]>([])
|
const [runTimes, setRunTimes] = useState<string[]>([])
|
||||||
const [selectedRunTime, setSelectedRunTime] = useState('')
|
const [selectedRunTime, setSelectedRunTime] = useState('')
|
||||||
const [selectedFileExtension, setSelectedFileExtension] = useState('')
|
const [selectedFileExtension, setSelectedFileExtension] = useState('')
|
||||||
@@ -127,6 +140,88 @@ const SASjsEditor = ({
|
|||||||
prevFileContent !== fileContent && !!selectedFilePath
|
prevFileContent !== fileContent && !!selectedFilePath
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const saveFile = useCallback(
|
||||||
|
(filePath?: string) => {
|
||||||
|
setIsLoading(true)
|
||||||
|
|
||||||
|
if (filePath) {
|
||||||
|
filePath = filePath.startsWith('/') ? filePath : `/${filePath}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = new FormData()
|
||||||
|
|
||||||
|
const stringBlob = new Blob([fileContent], { type: 'text/plain' })
|
||||||
|
formData.append('file', stringBlob)
|
||||||
|
formData.append('filePath', filePath ?? selectedFilePath)
|
||||||
|
|
||||||
|
const axiosPromise = filePath
|
||||||
|
? axios.post('/SASjsApi/drive/file', formData)
|
||||||
|
: axios.patch('/SASjsApi/drive/file', formData)
|
||||||
|
|
||||||
|
axiosPromise
|
||||||
|
.then(() => {
|
||||||
|
if (filePath && fileContent === prevFileContent) {
|
||||||
|
// when fileContent and prevFileContent is same,
|
||||||
|
// callback function in setPrevFileContent method is not called
|
||||||
|
// because behind the scene useEffect hook is being used
|
||||||
|
// for calling callback function, and it's only fired when the
|
||||||
|
// new value is not equal to old value.
|
||||||
|
// So, we'll have to explicitly update the selected file path
|
||||||
|
|
||||||
|
setSelectedFilePath(filePath, true)
|
||||||
|
} else {
|
||||||
|
setPrevFileContent(fileContent, () => {
|
||||||
|
if (filePath) {
|
||||||
|
setSelectedFilePath(filePath, true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
setSnackbarMessage('File saved!')
|
||||||
|
setSnackbarSeverity(AlertSeverityType.Success)
|
||||||
|
setOpenSnackbar(true)
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
setModalTitle('Abort')
|
||||||
|
setModalPayload(
|
||||||
|
typeof err.response.data === 'object'
|
||||||
|
? JSON.stringify(err.response.data)
|
||||||
|
: err.response.data
|
||||||
|
)
|
||||||
|
setOpenModal(true)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setIsLoading(false)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[
|
||||||
|
fileContent,
|
||||||
|
prevFileContent,
|
||||||
|
selectedFilePath,
|
||||||
|
setPrevFileContent,
|
||||||
|
setSelectedFilePath
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
editorRef.current.addAction({
|
||||||
|
// An unique identifier of the contributed action.
|
||||||
|
id: 'save-file',
|
||||||
|
|
||||||
|
// A label of the action that will be presented to the user.
|
||||||
|
label: 'Save',
|
||||||
|
|
||||||
|
// An optional array of keybindings for the action.
|
||||||
|
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS],
|
||||||
|
|
||||||
|
// Method that will be executed when the action is triggered.
|
||||||
|
// @param editor The editor instance is passed in as a convenience
|
||||||
|
run: () => {
|
||||||
|
if (!selectedFilePath) return setOpenFilePathInputModal(true)
|
||||||
|
if (prevFileContent !== fileContent) return saveFile()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [fileContent, prevFileContent, selectedFilePath, saveFile])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setRunTimes(Object.values(appContext.runTimes))
|
setRunTimes(Object.values(appContext.runTimes))
|
||||||
}, [appContext.runTimes])
|
}, [appContext.runTimes])
|
||||||
@@ -161,7 +256,7 @@ const SASjsEditor = ({
|
|||||||
}
|
}
|
||||||
setLog('')
|
setLog('')
|
||||||
setWebout('')
|
setWebout('')
|
||||||
setTab('1')
|
setTab('code')
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [selectedFilePath])
|
}, [selectedFilePath])
|
||||||
|
|
||||||
@@ -193,17 +288,13 @@ const SASjsEditor = ({
|
|||||||
axios
|
axios
|
||||||
.post(`/SASjsApi/code/execute`, { code, runTime: selectedRunTime })
|
.post(`/SASjsApi/code/execute`, { code, runTime: selectedRunTime })
|
||||||
.then((res: any) => {
|
.then((res: any) => {
|
||||||
const parsedLog = res?.data?.log
|
setWebout(res.data.split(SASJS_LOGS_SEPARATOR)[0] ?? '')
|
||||||
.map((logLine: any) => logLine.line)
|
setLog(res.data.split(SASJS_LOGS_SEPARATOR)[1] ?? '')
|
||||||
.join('\n')
|
setTab('log')
|
||||||
|
|
||||||
setLog(parsedLog)
|
|
||||||
|
|
||||||
setWebout(`${res.data?._webout}`)
|
|
||||||
setTab('2')
|
|
||||||
|
|
||||||
// Scroll to bottom of log
|
// Scroll to bottom of log
|
||||||
window.scrollTo(0, document.body.scrollHeight)
|
const logElement = document.getElementById('log')
|
||||||
|
if (logElement) logElement.scrollTop = logElement.scrollHeight
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
setModalTitle('Abort')
|
setModalTitle('Abort')
|
||||||
@@ -241,59 +332,6 @@ const SASjsEditor = ({
|
|||||||
saveFile(filePath)
|
saveFile(filePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveFile = (filePath?: string) => {
|
|
||||||
setIsLoading(true)
|
|
||||||
|
|
||||||
if (filePath) {
|
|
||||||
filePath = filePath.startsWith('/') ? filePath : `/${filePath}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const formData = new FormData()
|
|
||||||
|
|
||||||
const stringBlob = new Blob([fileContent], { type: 'text/plain' })
|
|
||||||
formData.append('file', stringBlob, 'filename.sas')
|
|
||||||
formData.append('filePath', filePath ?? selectedFilePath)
|
|
||||||
|
|
||||||
const axiosPromise = filePath
|
|
||||||
? axios.post('/SASjsApi/drive/file', formData)
|
|
||||||
: axios.patch('/SASjsApi/drive/file', formData)
|
|
||||||
|
|
||||||
axiosPromise
|
|
||||||
.then(() => {
|
|
||||||
if (filePath && fileContent === prevFileContent) {
|
|
||||||
// when fileContent and prevFileContent is same,
|
|
||||||
// callback function in setPrevFileContent method is not called
|
|
||||||
// because behind the scene useEffect hook is being used
|
|
||||||
// for calling callback function, and it's only fired when the
|
|
||||||
// new value is not equal to old value.
|
|
||||||
// So, we'll have to explicitly update the selected file path
|
|
||||||
|
|
||||||
setSelectedFilePath(filePath, true)
|
|
||||||
} else {
|
|
||||||
setPrevFileContent(fileContent, () => {
|
|
||||||
if (filePath) {
|
|
||||||
setSelectedFilePath(filePath, true)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
setSnackbarMessage('File saved!')
|
|
||||||
setSnackbarSeverity(AlertSeverityType.Success)
|
|
||||||
setOpenSnackbar(true)
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
setModalTitle('Abort')
|
|
||||||
setModalPayload(
|
|
||||||
typeof err.response.data === 'object'
|
|
||||||
? JSON.stringify(err.response.data)
|
|
||||||
: err.response.data
|
|
||||||
)
|
|
||||||
setOpenModal(true)
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setIsLoading(false)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ width: '100%', typography: 'body1', marginTop: '50px' }}>
|
<Box sx={{ width: '100%', typography: 'body1', marginTop: '50px' }}>
|
||||||
<Backdrop
|
<Backdrop
|
||||||
@@ -353,29 +391,24 @@ const SASjsEditor = ({
|
|||||||
sx={{
|
sx={{
|
||||||
borderBottom: 1,
|
borderBottom: 1,
|
||||||
borderColor: 'divider',
|
borderColor: 'divider',
|
||||||
position: 'fixed',
|
background: 'white'
|
||||||
background: 'white',
|
|
||||||
width: '85%'
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TabList onChange={handleTabChange} centered>
|
<TabList onChange={handleTabChange} centered>
|
||||||
<StyledTab label="Code" value="1" />
|
<StyledTab label="Code" value="code" />
|
||||||
<StyledTab label="Log" value="2" />
|
<StyledTab label="Log" value="log" />
|
||||||
<StyledTab
|
<StyledTab
|
||||||
label={
|
label={
|
||||||
<Tooltip title="Displays content from the _webout fileref">
|
<Tooltip title="Displays content from the _webout fileref">
|
||||||
<Typography>Webout</Typography>
|
<Typography>Webout</Typography>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
}
|
}
|
||||||
value="3"
|
value="webout"
|
||||||
/>
|
/>
|
||||||
</TabList>
|
</TabList>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<StyledTabPanel
|
<StyledTabPanel sx={{ paddingBottom: 0 }} value="code">
|
||||||
sx={{ paddingBottom: 0, marginTop: '45px' }}
|
|
||||||
value="1"
|
|
||||||
>
|
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
|
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
|
||||||
<RunMenu
|
<RunMenu
|
||||||
fileContent={fileContent}
|
fileContent={fileContent}
|
||||||
@@ -441,14 +474,16 @@ const SASjsEditor = ({
|
|||||||
</p>
|
</p>
|
||||||
</Paper>
|
</Paper>
|
||||||
</StyledTabPanel>
|
</StyledTabPanel>
|
||||||
<StyledTabPanel value="2">
|
<StyledTabPanel value="log">
|
||||||
<div style={{ marginTop: '50px' }}>
|
<div>
|
||||||
<h2>SAS Log</h2>
|
<h2>Log</h2>
|
||||||
<pre>{log}</pre>
|
<pre id="log" style={{ overflow: 'auto', height: '75vh' }}>
|
||||||
|
{log}
|
||||||
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
</StyledTabPanel>
|
</StyledTabPanel>
|
||||||
<StyledTabPanel value="3">
|
<StyledTabPanel value="webout">
|
||||||
<div style={{ marginTop: '50px' }}>
|
<div>
|
||||||
<pre>{webout}</pre>
|
<pre>{webout}</pre>
|
||||||
</div>
|
</div>
|
||||||
</StyledTabPanel>
|
</StyledTabPanel>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ const Studio = () => {
|
|||||||
const [searchParams, setSearchParams] = useSearchParams()
|
const [searchParams, setSearchParams] = useSearchParams()
|
||||||
const [selectedFilePath, setSelectedFilePath] = useState('')
|
const [selectedFilePath, setSelectedFilePath] = useState('')
|
||||||
const [directoryData, setDirectoryData] = useState<TreeNode | null>(null)
|
const [directoryData, setDirectoryData] = useState<TreeNode | null>(null)
|
||||||
|
const [tab, setTab] = useState('code')
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedFilePath(searchParams.get('filePath') ?? '')
|
setSelectedFilePath(searchParams.get('filePath') ?? '')
|
||||||
@@ -83,16 +84,20 @@ const Studio = () => {
|
|||||||
return (
|
return (
|
||||||
<Box sx={{ display: 'flex' }}>
|
<Box sx={{ display: 'flex' }}>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<SideBar
|
{tab === 'code' && (
|
||||||
selectedFilePath={selectedFilePath}
|
<SideBar
|
||||||
directoryData={directoryData}
|
selectedFilePath={selectedFilePath}
|
||||||
handleSelect={handleSelect}
|
directoryData={directoryData}
|
||||||
removeFileFromTree={removeFileFromTree}
|
handleSelect={handleSelect}
|
||||||
refreshSideBar={fetchDirectoryData}
|
removeFileFromTree={removeFileFromTree}
|
||||||
/>
|
refreshSideBar={fetchDirectoryData}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<SASjsEditor
|
<SASjsEditor
|
||||||
selectedFilePath={selectedFilePath}
|
selectedFilePath={selectedFilePath}
|
||||||
setSelectedFilePath={handleSelect}
|
setSelectedFilePath={handleSelect}
|
||||||
|
tab={tab}
|
||||||
|
setTab={setTab}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
import React, { useState, useMemo } from 'react'
|
import React, { useState, useMemo } from 'react'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { Backdrop, Box, CircularProgress, Drawer, Toolbar } from '@mui/material'
|
import {
|
||||||
|
Backdrop,
|
||||||
|
Box,
|
||||||
|
Paper,
|
||||||
|
CircularProgress,
|
||||||
|
Drawer,
|
||||||
|
Toolbar,
|
||||||
|
IconButton
|
||||||
|
} from '@mui/material'
|
||||||
|
import { FolderOpen } from '@mui/icons-material'
|
||||||
|
|
||||||
import TreeView from '../../components/tree'
|
import TreeView from '../../components/tree'
|
||||||
import BootstrapSnackbar, { AlertSeverityType } from '../../components/snackbar'
|
import BootstrapSnackbar, { AlertSeverityType } from '../../components/snackbar'
|
||||||
@@ -33,6 +42,17 @@ const SideBar = ({
|
|||||||
const [snackbarSeverity, setSnackbarSeverity] = useState<AlertSeverityType>(
|
const [snackbarSeverity, setSnackbarSeverity] = useState<AlertSeverityType>(
|
||||||
AlertSeverityType.Success
|
AlertSeverityType.Success
|
||||||
)
|
)
|
||||||
|
const [mobileOpen, setMobileOpen] = React.useState(false)
|
||||||
|
|
||||||
|
const handleDrawerToggle = () => {
|
||||||
|
setMobileOpen(!mobileOpen)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFileSelect = (filePath: string) => {
|
||||||
|
setMobileOpen(false)
|
||||||
|
handleSelect(filePath)
|
||||||
|
}
|
||||||
|
|
||||||
const defaultExpanded = useMemo(() => {
|
const defaultExpanded = useMemo(() => {
|
||||||
const splittedPath = selectedFilePath.split('/')
|
const splittedPath = selectedFilePath.split('/')
|
||||||
const arr = ['']
|
const arr = ['']
|
||||||
@@ -147,15 +167,8 @@ const SideBar = ({
|
|||||||
.finally(() => setIsLoading(false))
|
.finally(() => setIsLoading(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const drawer = (
|
||||||
<Drawer
|
<div>
|
||||||
variant="permanent"
|
|
||||||
sx={{
|
|
||||||
width: drawerWidth,
|
|
||||||
flexShrink: 0,
|
|
||||||
[`& .MuiDrawer-paper`]: { width: drawerWidth, boxSizing: 'border-box' }
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Backdrop
|
<Backdrop
|
||||||
sx={{ color: '#fff', zIndex: (theme) => theme.zIndex.drawer + 1 }}
|
sx={{ color: '#fff', zIndex: (theme) => theme.zIndex.drawer + 1 }}
|
||||||
open={isLoading}
|
open={isLoading}
|
||||||
@@ -168,7 +181,7 @@ const SideBar = ({
|
|||||||
<TreeView
|
<TreeView
|
||||||
node={directoryData}
|
node={directoryData}
|
||||||
selectedFilePath={selectedFilePath}
|
selectedFilePath={selectedFilePath}
|
||||||
handleSelect={handleSelect}
|
handleSelect={handleFileSelect}
|
||||||
deleteNode={deleteNode}
|
deleteNode={deleteNode}
|
||||||
addFile={addFile}
|
addFile={addFile}
|
||||||
addFolder={addFolder}
|
addFolder={addFolder}
|
||||||
@@ -189,7 +202,65 @@ const SideBar = ({
|
|||||||
title={modalTitle}
|
title={modalTitle}
|
||||||
payload={modalPayload}
|
payload={modalPayload}
|
||||||
/>
|
/>
|
||||||
</Drawer>
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box
|
||||||
|
component={Paper}
|
||||||
|
sx={{
|
||||||
|
margin: '5px',
|
||||||
|
height: '97vh',
|
||||||
|
paddingTop: '45px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'flex-start'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
color="inherit"
|
||||||
|
size="large"
|
||||||
|
aria-label="open drawer"
|
||||||
|
edge="start"
|
||||||
|
onClick={handleDrawerToggle}
|
||||||
|
sx={{ left: '5px', display: { md: 'none' } }}
|
||||||
|
>
|
||||||
|
<FolderOpen />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
<Drawer
|
||||||
|
variant="temporary"
|
||||||
|
open={mobileOpen}
|
||||||
|
onClose={handleDrawerToggle}
|
||||||
|
ModalProps={{
|
||||||
|
keepMounted: true // Better open performance on mobile.
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
display: { xs: 'block', md: 'none' },
|
||||||
|
flexShrink: 0,
|
||||||
|
[`& .MuiDrawer-paper`]: {
|
||||||
|
width: 240,
|
||||||
|
boxSizing: 'border-box'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{drawer}
|
||||||
|
</Drawer>
|
||||||
|
<Drawer
|
||||||
|
variant="permanent"
|
||||||
|
sx={{
|
||||||
|
display: { xs: 'none', md: 'block' },
|
||||||
|
width: drawerWidth,
|
||||||
|
flexShrink: 0,
|
||||||
|
[`& .MuiDrawer-paper`]: {
|
||||||
|
width: drawerWidth,
|
||||||
|
boxSizing: 'border-box'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{drawer}
|
||||||
|
</Drawer>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,7 +80,18 @@ const AppContextProvider = (props: { children: ReactNode }) => {
|
|||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setLoggedIn(false)
|
setLoggedIn(false)
|
||||||
axios.get('/') // get CSRF TOKEN
|
// get CSRF TOKEN and set cookie
|
||||||
|
axios
|
||||||
|
.get('/')
|
||||||
|
.then((res) => res.data)
|
||||||
|
.then((data: string) => {
|
||||||
|
const result =
|
||||||
|
/<script>document.cookie = '(XSRF-TOKEN=.*; Max-Age=86400; SameSite=Strict; Path=\/;)'<\/script>/.exec(
|
||||||
|
data
|
||||||
|
)?.[1]
|
||||||
|
|
||||||
|
if (result) document.cookie = result
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
axios
|
axios
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ code {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
margin-top: 50px;
|
margin: 50px 10px 0 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
59
web/src/utils/helper.ts
Normal file
59
web/src/utils/helper.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { PermissionResponse, RegisterPermissionPayload } from './types'
|
||||||
|
|
||||||
|
export const findExistingPermission = (
|
||||||
|
existingPermissions: PermissionResponse[],
|
||||||
|
newPermission: RegisterPermissionPayload
|
||||||
|
) => {
|
||||||
|
for (const permission of existingPermissions) {
|
||||||
|
if (
|
||||||
|
permission.user?.id === newPermission.principalId &&
|
||||||
|
hasSameCombination(permission, newPermission)
|
||||||
|
)
|
||||||
|
return permission
|
||||||
|
|
||||||
|
if (
|
||||||
|
permission.group?.groupId === newPermission.principalId &&
|
||||||
|
hasSameCombination(permission, newPermission)
|
||||||
|
)
|
||||||
|
return permission
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export const findUpdatingPermission = (
|
||||||
|
existingPermissions: PermissionResponse[],
|
||||||
|
newPermission: RegisterPermissionPayload
|
||||||
|
) => {
|
||||||
|
for (const permission of existingPermissions) {
|
||||||
|
if (
|
||||||
|
permission.user?.id === newPermission.principalId &&
|
||||||
|
hasDifferentSetting(permission, newPermission)
|
||||||
|
)
|
||||||
|
return permission
|
||||||
|
|
||||||
|
if (
|
||||||
|
permission.group?.groupId === newPermission.principalId &&
|
||||||
|
hasDifferentSetting(permission, newPermission)
|
||||||
|
)
|
||||||
|
return permission
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasSameCombination = (
|
||||||
|
existingPermission: PermissionResponse,
|
||||||
|
newPermission: RegisterPermissionPayload
|
||||||
|
) =>
|
||||||
|
existingPermission.path === newPermission.path &&
|
||||||
|
existingPermission.type === newPermission.type &&
|
||||||
|
existingPermission.setting === newPermission.setting
|
||||||
|
|
||||||
|
const hasDifferentSetting = (
|
||||||
|
existingPermission: PermissionResponse,
|
||||||
|
newPermission: RegisterPermissionPayload
|
||||||
|
) =>
|
||||||
|
existingPermission.path === newPermission.path &&
|
||||||
|
existingPermission.type === newPermission.type &&
|
||||||
|
existingPermission.setting !== newPermission.setting
|
||||||
@@ -18,14 +18,16 @@ export interface GroupDetailsResponse extends GroupResponse {
|
|||||||
|
|
||||||
export interface PermissionResponse {
|
export interface PermissionResponse {
|
||||||
permissionId: number
|
permissionId: number
|
||||||
uri: string
|
path: string
|
||||||
|
type: string
|
||||||
setting: string
|
setting: string
|
||||||
user?: UserResponse
|
user?: UserResponse
|
||||||
group?: GroupDetailsResponse
|
group?: GroupDetailsResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RegisterPermissionPayload {
|
export interface RegisterPermissionPayload {
|
||||||
uri: string
|
path: string
|
||||||
|
type: string
|
||||||
setting: string
|
setting: string
|
||||||
principalType: string
|
principalType: string
|
||||||
principalId: number
|
principalId: number
|
||||||
|
|||||||
Reference in New Issue
Block a user