1
0
mirror of https://github.com/sasjs/server.git synced 2025-12-10 19:34:34 +00:00

Compare commits

..

41 Commits

Author SHA1 Message Date
semantic-release-bot
abce135da2 chore(release): 0.15.2 [skip ci]
## [0.15.2](https://github.com/sasjs/server/compare/v0.15.1...v0.15.2) (2022-08-10)

### Bug Fixes

* remove vulnerabitities ([f27ac51](f27ac51fc4))
2022-08-10 11:28:07 +00:00
Allan Bowe
a6c014946a Merge pull request #252 from sasjs/fix-vulnerabilities
fix: remove vulnerabitities
2022-08-10 12:23:23 +01:00
f27ac51fc4 fix: remove vulnerabitities 2022-08-10 16:10:37 +05:00
semantic-release-bot
cb5be1be21 chore(release): 0.15.1 [skip ci]
## [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](d99fdd1ec7))
2022-08-10 10:34:36 +00:00
Allan Bowe
d90fa9e5dd Merge pull request #251 from sasjs/issue-250
fix(web): fix UI responsiveness
2022-08-10 11:29:41 +01:00
d99fdd1ec7 fix(web): fix UI responsiveness 2022-08-10 15:18:05 +05:00
semantic-release-bot
399b5edad0 chore(release): 0.15.0 [skip ci]
# [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](e215958b8b))
* improve mobile view for studio page ([c67d3ee](c67d3ee2f1))
* improve responsiveness for mobile view ([6ef40b9](6ef40b954a))
* improve user experience for adding permissions ([7a162ed](7a162eda8f))
* show logout button only when user is logged in ([9227cd4](9227cd449d))

### Features

* add multiple permission for same combination of type and principal at once ([754704b](754704bca8))
2022-08-05 09:59:19 +00:00
Allan Bowe
1dbc12e96b Merge pull request #249 from sasjs/issue-225
feat: add multiple permission for same combination of type and principal at once
2022-08-05 10:55:32 +01:00
e215958b8b fix: after selecting file in sidebar collapse sidebar in mobile view 2022-08-05 14:18:59 +05:00
9227cd449d fix: show logout button only when user is logged in 2022-08-05 01:22:27 +05:00
c67d3ee2f1 fix: improve mobile view for studio page 2022-08-05 01:10:15 +05:00
6ef40b954a fix: improve responsiveness for mobile view 2022-08-04 22:57:21 +05:00
semantic-release-bot
0d913baff1 chore(release): 0.14.1 [skip ci]
## [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](87c03c5f8d))
* **cookie:** XSRF cookie is removed and passed token in head section ([77f8d30](77f8d30baf))
* **env:** check added for not providing WHITELIST ([5966016](5966016853))
* **web:** show login on logged-out state ([f7fcc77](f7fcc7741a))
2022-08-04 12:10:31 +00:00
Allan Bowe
3671736c3d Merge pull request #248 from sasjs/cookies-management
fix(cookie): XSRF cookie is removed and passed token in head section
2022-08-04 13:06:30 +01:00
34cd84d8a9 chore: improve interface for add permission response 2022-08-04 16:34:15 +05:00
Saad Jutt
f7fcc7741a fix(web): show login on logged-out state 2022-08-04 05:39:28 +05:00
Saad Jutt
18052fdbf6 test: fixed failed specs 2022-08-04 04:01:51 +05:00
Saad Jutt
5966016853 fix(env): check added for not providing WHITELIST 2022-08-04 03:32:04 +05:00
Saad Jutt
87c03c5f8d fix(apps): App Stream logo fix 2022-08-04 03:03:27 +05:00
7a162eda8f fix: improve user experience for adding permissions 2022-08-04 02:51:59 +05:00
754704bca8 feat: add multiple permission for same combination of type and principal at once 2022-08-03 23:26:31 +05:00
Saad Jutt
77f8d30baf fix(cookie): XSRF cookie is removed and passed token in head section 2022-08-03 03:38:11 +05:00
semantic-release-bot
78bea7c154 chore(release): 0.14.0 [skip ci]
# [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](d3a516c36e))
* call jwt.verify in synchronous way ([254bc07](254bc07da7))

### Features

* add public group to DB on seed ([c3e3bef](c3e3befc17))
* bypass authentication when route is enabled for public group ([68515f9](68515f95a6))
2022-08-02 19:08:38 +00:00
Saad Jutt
9c3b155c12 Merge pull request #246 from sasjs/issue-240
feat: bypass authentication when route is enabled for public group
2022-08-03 00:03:43 +05:00
Allan Bowe
98e501334f Update seedDB.ts 2022-08-02 19:33:16 +01:00
Allan Bowe
bbfd53e79e Update group.spec.ts 2022-08-02 19:32:44 +01:00
254bc07da7 fix: call jwt.verify in synchronous way 2022-08-02 23:05:42 +05:00
f978814ca7 chore: code refactor 2022-08-02 22:16:41 +05:00
68515f95a6 feat: bypass authentication when route is enabled for public group 2022-08-02 18:06:33 +05:00
d3a516c36e fix: add restriction on add/remove user to public group 2022-08-02 18:05:28 +05:00
c3e3befc17 feat: add public group to DB on seed 2022-08-02 18:04:00 +05:00
semantic-release-bot
275de9478e chore(release): 0.13.3 [skip ci]
## [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](8a3054e19a))
* update schema of Permission ([5d5a9d3](5d5a9d3788))
2022-08-02 12:01:53 +00:00
Allan Bowe
1a3ef62cb2 Merge pull request #243 from sasjs/issue-241
fix: show non-admin user his own permissions only
2022-08-02 12:57:57 +01:00
semantic-release-bot
9eb5f3ca4d chore(release): 0.13.2 [skip ci]
## [0.13.2](https://github.com/sasjs/server/compare/v0.13.1...v0.13.2) (2022-08-01)

### Bug Fixes

* adding ls=max to reduce log size and improve readability ([916947d](916947dffa))
2022-08-01 22:42:31 +00:00
Allan Bowe
916947dffa fix: adding ls=max to reduce log size and improve readability 2022-08-01 22:38:31 +00:00
79b7827b7c chore: update tabs label in setting page 2022-08-01 23:01:05 +05:00
37e1aa9b61 chore: spec fixed 2022-08-01 22:54:31 +05:00
7e504008b7 chore: quick fix 2022-08-01 22:50:18 +05:00
5d5a9d3788 fix: update schema of Permission 2022-08-01 21:33:10 +05:00
b7dff341f0 chore: fix specs 2022-07-30 00:18:02 +05:00
8a3054e19a fix: show non-admin user his own permissions only 2022-07-30 00:01:15 +05:00
43 changed files with 1798 additions and 1071 deletions

View File

@@ -1,3 +1,72 @@
## [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)
### Bug Fixes
* adding ls=max to reduce log size and improve readability ([916947d](https://github.com/sasjs/server/commit/916947dffacd902ff23ac3e899d1bf5ab6238b75))
## [0.13.1](https://github.com/sasjs/server/compare/v0.13.0...v0.13.1) (2022-07-31)

503
api/package-lock.json generated
View File

@@ -23,7 +23,7 @@
"mongoose": "^6.0.12",
"mongoose-sequence": "^5.3.1",
"morgan": "^1.10.0",
"multer": "^1.4.3",
"multer": "^1.4.5-lts.1",
"rotating-file-stream": "^3.0.4",
"swagger-ui-express": "4.3.0",
"unzipper": "^0.10.11",
@@ -2184,9 +2184,9 @@
"integrity": "sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q=="
},
"node_modules/@types/whatwg-url": {
"version": "8.2.1",
"resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.1.tgz",
"integrity": "sha512-2YubE1sjj5ifxievI5Ge1sckb9k/Er66HyR2c+3+I6VDUUg1TLPdYYTEbQ+DjRkS4nTxMJhgWfSfMRD2sl2EYQ==",
"version": "8.2.2",
"resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz",
"integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==",
"dependencies": {
"@types/node": "*",
"@types/webidl-conversions": "*"
@@ -2834,9 +2834,9 @@
}
},
"node_modules/bson": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/bson/-/bson-4.5.4.tgz",
"integrity": "sha512-wIt0bPACnx8Ju9r6IsS2wVtGDHBr9Dxb+U29A1YED2pu8XOhS8aKjOnLZ8sxyXkPwanoK7iWWVhS1+coxde6xA==",
"version": "4.6.5",
"resolved": "https://registry.npmjs.org/bson/-/bson-4.6.5.tgz",
"integrity": "sha512-uqrgcjyOaZsHfz7ea8zLRCLe1u+QGUSzMZmvXqO24CDW7DWoW1qiN9folSwa7hSneTSgM2ykDIzF5kcQQ8cwNw==",
"dependencies": {
"buffer": "^5.6.0"
},
@@ -2903,38 +2903,16 @@
}
},
"node_modules/busboy": {
"version": "0.2.14",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz",
"integrity": "sha512-InWFDomvlkEj+xWLBfU3AvnbVYqeTWmQopiW0tWWEy5yehYm2YkGEc59sUmw/4ty5Zj/b0WHGs1LgecuBSBGrg==",
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
"integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
"dependencies": {
"dicer": "0.2.5",
"readable-stream": "1.1.x"
"streamsearch": "^1.1.0"
},
"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": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
@@ -3566,39 +3544,6 @@
"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": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
@@ -4946,6 +4891,11 @@
"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": {
"version": "1.9.1",
"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=="
},
"node_modules/kareem": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz",
"integrity": "sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ=="
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.4.1.tgz",
"integrity": "sha512-aJ9opVoXroQUPfovYP5kaj2lM7Jn02Gw13bL0lg9v0V7SaUc0qavPs0Eue7d2DcC3NjqI6QAUElXNsuZSeM+EA=="
},
"node_modules/kleur": {
"version": "3.0.3",
@@ -7093,13 +7043,14 @@
"dev": true
},
"node_modules/mongodb": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.1.4.tgz",
"integrity": "sha512-Cv/sk8on/tpvvqbEvR1h03mdyNdyvvO+WhtFlL4jrZ+DSsN/oSQHVqmJQI/sBCqqbOArFcYCAYDfyzqFwV4GSQ==",
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.8.1.tgz",
"integrity": "sha512-/NyiM3Ox9AwP5zrfT9TXjRKDJbXlLaUDQ9Rg//2lbg8D2A8GXV0VidYYnA/gfdK6uwbnL4FnAflH7FbGw3TS7w==",
"dependencies": {
"bson": "^4.5.4",
"bson": "^4.6.5",
"denque": "^2.0.1",
"mongodb-connection-string-url": "^2.1.0"
"mongodb-connection-string-url": "^2.5.2",
"socks": "^2.6.2"
},
"engines": {
"node": ">=12.9.0"
@@ -7109,21 +7060,40 @@
}
},
"node_modules/mongodb-connection-string-url": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.1.0.tgz",
"integrity": "sha512-Qf9Zw7KGiRljWvMrrUFDdVqo46KIEiDuCzvEN97rh/PcKzk2bd6n9KuzEwBwW9xo5glwx69y1mI6s+jFUD/aIQ==",
"version": "2.5.3",
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.5.3.tgz",
"integrity": "sha512-f+/WsED+xF4B74l3k9V/XkTVj5/fxFH2o5ToKXd8Iyi5UhM+sO9u0Ape17Mvl/GkZaFtM0HQnzAG5OTmhKw+tQ==",
"dependencies": {
"@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": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-9.1.0.tgz",
"integrity": "sha512-CQ0UcrPHyomtlOCot1TL77WyMIm/bCwrJ2D6AOKGwEczU9EpyoqAokfqrf/MioU9kHcMsmJZcg1egXix2KYEsA==",
"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==",
"dependencies": {
"tr46": "^2.1.0",
"webidl-conversions": "^6.1.0"
"tr46": "^3.0.0",
"webidl-conversions": "^7.0.0"
},
"engines": {
"node": ">=12"
@@ -7221,19 +7191,17 @@
}
},
"node_modules/mongoose": {
"version": "6.0.12",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.0.12.tgz",
"integrity": "sha512-BvsZk7zEEhb1AgQFLtxN9C+7qgy5edRuA3ZDDwHU+kHG/HM44vI6FdKV5m6HVdAUeCHHQTiVv+YQh8BRsToSHw==",
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.5.2.tgz",
"integrity": "sha512-3CFDrSLtK2qjM1pZeZpLTUyqPRkc11Iuh74ZrwS4IwEJ3K2PqGnmyPLw7ex4Kzu37ujIMp3MAuiBlUjfrcb6hw==",
"dependencies": {
"bson": "^4.2.2",
"kareem": "2.3.2",
"mongodb": "4.1.3",
"mpath": "0.8.4",
"mquery": "4.0.0",
"ms": "2.1.2",
"regexp-clone": "1.0.0",
"sift": "13.5.2",
"sliced": "1.0.1"
"bson": "^4.6.5",
"kareem": "2.4.1",
"mongodb": "4.8.1",
"mpath": "0.9.0",
"mquery": "4.0.3",
"ms": "2.1.3",
"sift": "16.0.0"
},
"engines": {
"node": ">=12.0.0"
@@ -7255,26 +7223,10 @@
"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": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/morgan": {
"version": "1.10.0",
@@ -7300,30 +7252,28 @@
}
},
"node_modules/mpath": {
"version": "0.8.4",
"resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.4.tgz",
"integrity": "sha512-DTxNZomBcTWlrMW76jy1wvV37X/cNNxPW1y2Jzd4DZkAaC5ZGsm8bfGfNOthcDuRJujXLqiuS6o3Tpy0JEoh7g==",
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
"integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==",
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/mquery": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.0.tgz",
"integrity": "sha512-nGjm89lHja+T/b8cybAby6H0YgA4qYC/lx6UlwvHGqvTq8bDaNeCwl1sY8uRELrNbVWJzIihxVd+vphGGn1vBw==",
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.3.tgz",
"integrity": "sha512-J5heI+P08I6VJ2Ky3+33IpCdAvlYGTSUjwTPxkAr8i8EoduPMBX2OY/wa3IKZIQl7MU4SbFk8ndgSKyB/cl1zA==",
"dependencies": {
"debug": "4.x",
"regexp-clone": "^1.0.0",
"sliced": "1.0.1"
"debug": "4.x"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/mquery/node_modules/debug": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
"integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dependencies": {
"ms": "2.1.2"
},
@@ -7347,22 +7297,20 @@
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"node_modules/multer": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/multer/-/multer-1.4.4.tgz",
"integrity": "sha512-2wY2+xD4udX612aMqMcB8Ws2Voq6NIUPEtD1be6m411T4uDH/VtL9i//xvcyFlTVfRdaBsk7hV5tgrGQqhuBiw==",
"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.",
"version": "1.4.5-lts.1",
"resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz",
"integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==",
"dependencies": {
"append-field": "^1.0.0",
"busboy": "^0.2.11",
"busboy": "^1.0.0",
"concat-stream": "^1.5.2",
"mkdirp": "^0.5.4",
"object-assign": "^4.1.1",
"on-finished": "^2.3.0",
"type-is": "^1.6.4",
"xtend": "^4.0.0"
},
"engines": {
"node": ">= 0.10.0"
"node": ">= 6.0.0"
}
},
"node_modules/multer/node_modules/mkdirp": {
@@ -8353,11 +8301,6 @@
"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": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -8606,9 +8549,9 @@
}
},
"node_modules/sift": {
"version": "13.5.2",
"resolved": "https://registry.npmjs.org/sift/-/sift-13.5.2.tgz",
"integrity": "sha512-+gxdEOMA2J+AI+fVsCqeNn7Tgx3M9ZN9jdi95939l1IJ8cZsqS8sqpJyOkic2SJk+1+98Uwryt/gL6XDaV+UZA=="
"version": "16.0.0",
"resolved": "https://registry.npmjs.org/sift/-/sift-16.0.0.tgz",
"integrity": "sha512-ILTjdP2Mv9V1kIxWMXeMTIRbOBrqKc4JAXmFMnFq3fKeyQ2Qwa3Dw1ubcye3vR+Y6ofA0b9gNDr/y2t6eUeIzQ=="
},
"node_modules/signal-exit": {
"version": "3.0.3",
@@ -8706,10 +8649,27 @@
"node": ">=8"
}
},
"node_modules/sliced": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz",
"integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E="
"node_modules/smart-buffer": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
"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": {
"version": "0.6.1",
@@ -8808,11 +8768,11 @@
}
},
"node_modules/streamsearch": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz",
"integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
"integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
"engines": {
"node": ">=0.8.0"
"node": ">=10.0.0"
}
},
"node_modules/string_decoder": {
@@ -9296,6 +9256,7 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz",
"integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==",
"dev": true,
"dependencies": {
"punycode": "^2.1.1"
},
@@ -9719,6 +9680,7 @@
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz",
"integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==",
"dev": true,
"engines": {
"node": ">=10.4"
}
@@ -11724,9 +11686,9 @@
"integrity": "sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q=="
},
"@types/whatwg-url": {
"version": "8.2.1",
"resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.1.tgz",
"integrity": "sha512-2YubE1sjj5ifxievI5Ge1sckb9k/Er66HyR2c+3+I6VDUUg1TLPdYYTEbQ+DjRkS4nTxMJhgWfSfMRD2sl2EYQ==",
"version": "8.2.2",
"resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz",
"integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==",
"requires": {
"@types/node": "*",
"@types/webidl-conversions": "*"
@@ -12240,9 +12202,9 @@
}
},
"bson": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/bson/-/bson-4.5.4.tgz",
"integrity": "sha512-wIt0bPACnx8Ju9r6IsS2wVtGDHBr9Dxb+U29A1YED2pu8XOhS8aKjOnLZ8sxyXkPwanoK7iWWVhS1+coxde6xA==",
"version": "4.6.5",
"resolved": "https://registry.npmjs.org/bson/-/bson-4.6.5.tgz",
"integrity": "sha512-uqrgcjyOaZsHfz7ea8zLRCLe1u+QGUSzMZmvXqO24CDW7DWoW1qiN9folSwa7hSneTSgM2ykDIzF5kcQQ8cwNw==",
"requires": {
"buffer": "^5.6.0"
}
@@ -12283,35 +12245,11 @@
"integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ=="
},
"busboy": {
"version": "0.2.14",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz",
"integrity": "sha512-InWFDomvlkEj+xWLBfU3AvnbVYqeTWmQopiW0tWWEy5yehYm2YkGEc59sUmw/4ty5Zj/b0WHGs1LgecuBSBGrg==",
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
"integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
"requires": {
"dicer": "0.2.5",
"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="
}
"streamsearch": "^1.1.0"
}
},
"bytes": {
@@ -12813,38 +12751,6 @@
"integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==",
"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": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
@@ -13868,6 +13774,11 @@
"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": {
"version": "1.9.1",
"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=="
},
"kareem": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz",
"integrity": "sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ=="
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.4.1.tgz",
"integrity": "sha512-aJ9opVoXroQUPfovYP5kaj2lM7Jn02Gw13bL0lg9v0V7SaUc0qavPs0Eue7d2DcC3NjqI6QAUElXNsuZSeM+EA=="
},
"kleur": {
"version": "3.0.3",
@@ -15486,32 +15397,46 @@
"dev": true
},
"mongodb": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.1.4.tgz",
"integrity": "sha512-Cv/sk8on/tpvvqbEvR1h03mdyNdyvvO+WhtFlL4jrZ+DSsN/oSQHVqmJQI/sBCqqbOArFcYCAYDfyzqFwV4GSQ==",
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.8.1.tgz",
"integrity": "sha512-/NyiM3Ox9AwP5zrfT9TXjRKDJbXlLaUDQ9Rg//2lbg8D2A8GXV0VidYYnA/gfdK6uwbnL4FnAflH7FbGw3TS7w==",
"requires": {
"bson": "^4.5.4",
"bson": "^4.6.5",
"denque": "^2.0.1",
"mongodb-connection-string-url": "^2.1.0",
"saslprep": "^1.0.3"
"mongodb-connection-string-url": "^2.5.2",
"saslprep": "^1.0.3",
"socks": "^2.6.2"
}
},
"mongodb-connection-string-url": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.1.0.tgz",
"integrity": "sha512-Qf9Zw7KGiRljWvMrrUFDdVqo46KIEiDuCzvEN97rh/PcKzk2bd6n9KuzEwBwW9xo5glwx69y1mI6s+jFUD/aIQ==",
"version": "2.5.3",
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.5.3.tgz",
"integrity": "sha512-f+/WsED+xF4B74l3k9V/XkTVj5/fxFH2o5ToKXd8Iyi5UhM+sO9u0Ape17Mvl/GkZaFtM0HQnzAG5OTmhKw+tQ==",
"requires": {
"@types/whatwg-url": "^8.2.1",
"whatwg-url": "^9.1.0"
"whatwg-url": "^11.0.0"
},
"dependencies": {
"whatwg-url": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-9.1.0.tgz",
"integrity": "sha512-CQ0UcrPHyomtlOCot1TL77WyMIm/bCwrJ2D6AOKGwEczU9EpyoqAokfqrf/MioU9kHcMsmJZcg1egXix2KYEsA==",
"tr46": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
"integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
"requires": {
"tr46": "^2.1.0",
"webidl-conversions": "^6.1.0"
"punycode": "^2.1.1"
}
},
"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": {
"version": "6.0.12",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.0.12.tgz",
"integrity": "sha512-BvsZk7zEEhb1AgQFLtxN9C+7qgy5edRuA3ZDDwHU+kHG/HM44vI6FdKV5m6HVdAUeCHHQTiVv+YQh8BRsToSHw==",
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.5.2.tgz",
"integrity": "sha512-3CFDrSLtK2qjM1pZeZpLTUyqPRkc11Iuh74ZrwS4IwEJ3K2PqGnmyPLw7ex4Kzu37ujIMp3MAuiBlUjfrcb6hw==",
"requires": {
"bson": "^4.2.2",
"kareem": "2.3.2",
"mongodb": "4.1.3",
"mpath": "0.8.4",
"mquery": "4.0.0",
"ms": "2.1.2",
"regexp-clone": "1.0.0",
"sift": "13.5.2",
"sliced": "1.0.1"
"bson": "^4.6.5",
"kareem": "2.4.1",
"mongodb": "4.8.1",
"mpath": "0.9.0",
"mquery": "4.0.3",
"ms": "2.1.3",
"sift": "16.0.0"
},
"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": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
}
}
},
@@ -15645,24 +15557,22 @@
}
},
"mpath": {
"version": "0.8.4",
"resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.4.tgz",
"integrity": "sha512-DTxNZomBcTWlrMW76jy1wvV37X/cNNxPW1y2Jzd4DZkAaC5ZGsm8bfGfNOthcDuRJujXLqiuS6o3Tpy0JEoh7g=="
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
"integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew=="
},
"mquery": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.0.tgz",
"integrity": "sha512-nGjm89lHja+T/b8cybAby6H0YgA4qYC/lx6UlwvHGqvTq8bDaNeCwl1sY8uRELrNbVWJzIihxVd+vphGGn1vBw==",
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.3.tgz",
"integrity": "sha512-J5heI+P08I6VJ2Ky3+33IpCdAvlYGTSUjwTPxkAr8i8EoduPMBX2OY/wa3IKZIQl7MU4SbFk8ndgSKyB/cl1zA==",
"requires": {
"debug": "4.x",
"regexp-clone": "^1.0.0",
"sliced": "1.0.1"
"debug": "4.x"
},
"dependencies": {
"debug": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
"integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"requires": {
"ms": "2.1.2"
}
@@ -15680,16 +15590,15 @@
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"multer": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/multer/-/multer-1.4.4.tgz",
"integrity": "sha512-2wY2+xD4udX612aMqMcB8Ws2Voq6NIUPEtD1be6m411T4uDH/VtL9i//xvcyFlTVfRdaBsk7hV5tgrGQqhuBiw==",
"version": "1.4.5-lts.1",
"resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz",
"integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==",
"requires": {
"append-field": "^1.0.0",
"busboy": "^0.2.11",
"busboy": "^1.0.0",
"concat-stream": "^1.5.2",
"mkdirp": "^0.5.4",
"object-assign": "^4.1.1",
"on-finished": "^2.3.0",
"type-is": "^1.6.4",
"xtend": "^4.0.0"
},
@@ -16416,11 +16325,6 @@
"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": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -16605,9 +16509,9 @@
}
},
"sift": {
"version": "13.5.2",
"resolved": "https://registry.npmjs.org/sift/-/sift-13.5.2.tgz",
"integrity": "sha512-+gxdEOMA2J+AI+fVsCqeNn7Tgx3M9ZN9jdi95939l1IJ8cZsqS8sqpJyOkic2SJk+1+98Uwryt/gL6XDaV+UZA=="
"version": "16.0.0",
"resolved": "https://registry.npmjs.org/sift/-/sift-16.0.0.tgz",
"integrity": "sha512-ILTjdP2Mv9V1kIxWMXeMTIRbOBrqKc4JAXmFMnFq3fKeyQ2Qwa3Dw1ubcye3vR+Y6ofA0b9gNDr/y2t6eUeIzQ=="
},
"signal-exit": {
"version": "3.0.3",
@@ -16677,10 +16581,19 @@
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
"dev": true
},
"sliced": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz",
"integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E="
"smart-buffer": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
"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": {
"version": "0.6.1",
@@ -16771,9 +16684,9 @@
}
},
"streamsearch": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz",
"integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo="
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
"integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="
},
"string_decoder": {
"version": "1.3.0",
@@ -17140,6 +17053,7 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz",
"integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==",
"dev": true,
"requires": {
"punycode": "^2.1.1"
}
@@ -17456,7 +17370,8 @@
"webidl-conversions": {
"version": "6.1.0",
"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": {
"version": "1.0.5",

View File

@@ -62,7 +62,7 @@
"mongoose": "^6.0.12",
"mongoose-sequence": "^5.3.1",
"morgan": "^1.10.0",
"multer": "^1.4.3",
"multer": "^1.4.5-lts.1",
"rotating-file-stream": "^3.0.4",
"swagger-ui-express": "4.3.0",
"unzipper": "^0.10.11",

View File

@@ -470,12 +470,89 @@ components:
additionalProperties: false
AuthorizedRoutesResponse:
properties:
URIs:
paths:
items:
type: string
type: array
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
additionalProperties: false
ExecuteReturnJsonPayload:
@@ -521,71 +598,6 @@ components:
- clientId
type: object
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:
bearerAuth:
type: http
@@ -1598,12 +1610,165 @@ paths:
$ref: '#/components/schemas/AuthorizedRoutesResponse'
examples:
'Example 1':
value: { URIs: [/AppStream, /SASjsApi/stp/execute] }
summary: 'Get authorized routes.'
value: { paths: [/AppStream, /SASjsApi/stp/execute] }
summary: 'Get the list of available routes to which permissions can be applied. Used to populate the dialog in the URI Permissions feature.'
tags:
- Info
security: []
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:
get:
operationId: Session
@@ -1788,154 +1953,6 @@ paths:
- Web
security: []
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:
- url: /
tags:

View File

@@ -11,7 +11,7 @@ import { apiRoot, sysInitCompiledPath } from '../src/utils/file'
const macroCorePath = path.join(apiRoot, 'node_modules', '@sasjs', 'core')
const compiledSystemInit = async (systemInit: string) =>
'options ps=max;\n' +
'options ls=max ps=max;\n' +
(await loadDependenciesFile({
fileContent: systemInit,
type: SASJsFileType.job,

View File

@@ -1,6 +1,6 @@
import path from 'path'
import express, { ErrorRequestHandler } from 'express'
import csrf from 'csurf'
import csrf, { CookieOptions } from 'csurf'
import cookieParser from 'cookie-parser'
import dotenv from 'dotenv'
@@ -32,9 +32,10 @@ const app = express()
const { PROTOCOL } = process.env
export const cookieOptions = {
export const cookieOptions: CookieOptions = {
secure: PROTOCOL === ProtocolType.HTTPS,
httpOnly: true,
sameSite: PROTOCOL === ProtocolType.HTTPS ? 'none' : undefined,
maxAge: 24 * 60 * 60 * 1000 // 24 hours
}

View File

@@ -10,7 +10,7 @@ import {
Body
} from 'tsoa'
import Group, { GroupPayload } from '../model/Group'
import Group, { GroupPayload, PUBLIC_GROUP_NAME } from '../model/Group'
import User from '../model/User'
import { UserResponse } from './user'
@@ -241,6 +241,13 @@ const updateUsersListInGroup = async (
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 })
if (!user)
throw {

View File

@@ -1,7 +1,7 @@
import { Route, Tags, Example, Get } from 'tsoa'
import { getAuthorizedRoutes } from '../utils'
export interface AuthorizedRoutesResponse {
URIs: string[]
paths: string[]
}
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>({
URIs: ['/AppStream', '/SASjsApi/stp/execute']
paths: ['/AppStream', '/SASjsApi/stp/execute']
})
@Get('/authorizedRoutes')
public authorizedRoutes(): AuthorizedRoutesResponse {
const response = {
URIs: getAuthorizedRoutes()
paths: getAuthorizedRoutes()
}
return response
}

View File

@@ -1,3 +1,4 @@
import express from 'express'
import {
Security,
Route,
@@ -8,7 +9,8 @@ import {
Post,
Patch,
Delete,
Body
Body,
Request
} from 'tsoa'
import Permission from '../model/Permission'
@@ -17,12 +19,16 @@ import Group from '../model/Group'
import { UserResponse } from './user'
import { GroupDetailsResponse } from './group'
export enum PermissionType {
route = 'Route'
}
export enum PrincipalType {
user = 'user',
group = 'group'
}
export enum PermissionSetting {
export enum PermissionSettingForRoute {
grant = 'Grant',
deny = 'Deny'
}
@@ -32,12 +38,17 @@ interface RegisterPermissionPayload {
* Name of affected resource
* @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
* @example "Grant"
*/
setting: PermissionSetting
setting: PermissionSettingForRoute
/**
* Indicates the type of principal
* @example "user"
@@ -55,12 +66,13 @@ interface UpdatePermissionPayload {
* The indication of whether (and to what extent) access is provided
* @example "Grant"
*/
setting: PermissionSetting
setting: PermissionSettingForRoute
}
export interface PermissionDetailsResponse {
permissionId: number
uri: string
path: string
type: string
setting: string
user?: UserResponse
group?: GroupDetailsResponse
@@ -71,13 +83,17 @@ export interface PermissionDetailsResponse {
@Tags('Permission')
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[]>([
{
permissionId: 123,
uri: '/SASjsApi/code/execute',
path: '/SASjsApi/code/execute',
type: 'Route',
setting: 'Grant',
user: {
id: 1,
@@ -88,7 +104,8 @@ export class PermissionController {
},
{
permissionId: 124,
uri: '/SASjsApi/code/execute',
path: '/SASjsApi/code/execute',
type: 'Route',
setting: 'Grant',
group: {
groupId: 1,
@@ -100,8 +117,10 @@ export class PermissionController {
}
])
@Get('/')
public async getAllPermissions(): Promise<PermissionDetailsResponse[]> {
return getAllPermissions()
public async getAllPermissions(
@Request() request: express.Request
): Promise<PermissionDetailsResponse[]> {
return getAllPermissions(request)
}
/**
@@ -110,7 +129,8 @@ export class PermissionController {
*/
@Example<PermissionDetailsResponse>({
permissionId: 123,
uri: '/SASjsApi/code/execute',
path: '/SASjsApi/code/execute',
type: 'Route',
setting: 'Grant',
user: {
id: 1,
@@ -133,7 +153,8 @@ export class PermissionController {
*/
@Example<PermissionDetailsResponse>({
permissionId: 123,
uri: '/SASjsApi/code/execute',
path: '/SASjsApi/code/execute',
type: 'Route',
setting: 'Grant',
user: {
id: 1,
@@ -161,33 +182,43 @@ export class PermissionController {
}
}
const getAllPermissions = async (): Promise<PermissionDetailsResponse[]> =>
(await Permission.find({})
.select({
_id: 0,
permissionId: 1,
uri: 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 }
const getAllPermissions = async (
req: express.Request
): Promise<PermissionDetailsResponse[]> => {
const { user } = req
if (user?.isAdmin) return await Permission.get({})
else {
const permissions: PermissionDetailsResponse[] = []
const dbUser = await User.findOne({ id: user?.userId })
if (!dbUser)
throw {
code: 404,
status: 'Not Found',
message: 'User not found.'
}
permissions.push(...(await Permission.get({ user: dbUser._id })))
for (const group of dbUser.groups) {
permissions.push(...(await Permission.get({ group })))
}
return permissions
}
}
})) as unknown as PermissionDetailsResponse[]
const createPermission = async ({
uri,
path,
type,
setting,
principalType,
principalId
}: RegisterPermissionPayload): Promise<PermissionDetailsResponse> => {
const permission = new Permission({
uri,
path,
type,
setting
})
@@ -212,7 +243,8 @@ const createPermission = async ({
}
const alreadyExists = await Permission.findOne({
uri,
path,
type,
user: userInDB._id
})
@@ -220,7 +252,8 @@ const createPermission = async ({
throw {
code: 409,
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
@@ -243,14 +276,16 @@ const createPermission = async ({
}
const alreadyExists = await Permission.findOne({
uri,
path,
type,
group: groupInDB._id
})
if (alreadyExists)
throw {
code: 409,
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
@@ -280,7 +315,8 @@ const createPermission = async ({
return {
permissionId: savedPermission.permissionId,
uri: savedPermission.uri,
path: savedPermission.path,
type: savedPermission.type,
setting: savedPermission.setting,
user,
group
@@ -301,7 +337,8 @@ const updatePermission = async (
.select({
_id: 0,
permissionId: 1,
uri: 1,
path: 1,
type: 1,
setting: 1
})
.populate({ path: 'user', select: 'id username displayName isAdmin -_id' })

View File

@@ -5,7 +5,9 @@ import {
fetchLatestAutoExec,
ModeType,
verifyTokenInDB,
isAuthorizingRoute
isAuthorizingRoute,
isPublicRoute,
publicUser
} from '../utils'
import { desktopUser } from './desktop'
import { authorize } from './authorize'
@@ -41,7 +43,7 @@ export const authenticateAccessToken: RequestHandler = async (
return res.sendStatus(401)
}
authenticateToken(
await authenticateToken(
req,
res,
nextFunction,
@@ -50,8 +52,12 @@ export const authenticateAccessToken: RequestHandler = async (
)
}
export const authenticateRefreshToken: RequestHandler = (req, res, next) => {
authenticateToken(
export const authenticateRefreshToken: RequestHandler = async (
req,
res,
next
) => {
await authenticateToken(
req,
res,
next,
@@ -60,7 +66,7 @@ export const authenticateRefreshToken: RequestHandler = (req, res, next) => {
)
}
const authenticateToken = (
const authenticateToken = async (
req: Request,
res: Response,
next: NextFunction,
@@ -83,12 +89,12 @@ const authenticateToken = (
const authHeader = req.headers['authorization']
const token = authHeader?.split(' ')[1]
if (!token) return res.sendStatus(401)
jwt.verify(token, key, async (err: any, data: any) => {
if (err) return res.sendStatus(401)
try {
if (!token) throw 'Unauthorized'
const data: any = jwt.verify(token, key)
// verify this valid token's entry in DB
const user = await verifyTokenInDB(
data?.userId,
data?.clientId,
@@ -101,8 +107,16 @@ const authenticateToken = (
req.user = user
if (tokenType === 'accessToken') req.accessToken = token
return next()
} else return res.sendStatus(401)
} else throw 'Unauthorized'
}
throw 'Unauthorized'
} catch (error) {
if (await isPublicRoute(req)) {
req.user = publicUser
return next()
}
res.sendStatus(401)
}
return res.sendStatus(401)
})
}

View File

@@ -1,8 +1,11 @@
import { RequestHandler } from 'express'
import User from '../model/User'
import Permission from '../model/Permission'
import { PermissionSetting } from '../controllers/permission'
import { getUri } from '../utils'
import {
PermissionSettingForRoute,
PermissionType
} from '../controllers/permission'
import { getPath, isPublicRoute } from '../utils'
export const authorize: RequestHandler = async (req, res, next) => {
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
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 })
if (!dbUser) return res.sendStatus(401)
const uri = getUri(req)
const path = getPath(req)
// 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.setting === PermissionSetting.grant) return next()
if (permission.setting === PermissionSettingForRoute.grant) return next()
else return res.sendStatus(401)
}
// find permission w.r.t user's groups
for (const group of dbUser.groups) {
const groupPermission = await Permission.findOne({ uri, group })
if (groupPermission?.setting === PermissionSetting.grant) return next()
const groupPermission = await Permission.findOne({
path,
type: PermissionType.route,
group
})
if (groupPermission?.setting === PermissionSettingForRoute.grant)
return next()
}
return res.sendStatus(401)
}

View File

@@ -3,6 +3,8 @@ import { GroupDetailsResponse } from '../controllers'
import User, { IUser } from './User'
const AutoIncrement = require('mongoose-sequence')(mongoose)
export const PUBLIC_GROUP_NAME = 'Public'
export interface GroupPayload {
/**
* Name of the group

View File

@@ -1,8 +1,15 @@
import mongoose, { Schema, model, Document, Model } from '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 {
uri: string
path: string
type: string
setting: string
permissionId: number
user: Schema.Types.ObjectId
@@ -11,10 +18,16 @@ interface IPermissionDocument extends Document {
interface IPermission extends IPermissionDocument {}
interface IPermissionModel extends Model<IPermission> {}
interface IPermissionModel extends Model<IPermission> {
get(getBy: GetPermissionBy): Promise<PermissionDetailsResponse[]>
}
const permissionSchema = new Schema<IPermissionDocument>({
uri: {
path: {
type: String,
required: true
},
type: {
type: String,
required: true
},
@@ -28,6 +41,30 @@ const permissionSchema = new Schema<IPermissionDocument>({
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<
IPermission,
IPermissionModel

View File

@@ -11,7 +11,7 @@ const controller = new PermissionController()
permissionRouter.get('/', async (req, res) => {
try {
const response = await controller.getAllPermissions()
const response = await controller.getAllPermissions(req)
res.send(response)
} catch (err: any) {
const statusCode = err.code

View File

@@ -32,7 +32,8 @@ import appPromise from '../../../app'
import {
UserController,
PermissionController,
PermissionSetting,
PermissionType,
PermissionSettingForRoute,
PrincipalType
} from '../../../controllers/'
import { getTreeExample } from '../../../controllers/internal'
@@ -48,6 +49,12 @@ const user = {
isActive: true
}
const permission = {
type: PermissionType.route,
principalType: PrincipalType.user,
setting: PermissionSettingForRoute.grant
}
describe('drive', () => {
let app: Express
let con: Mongoose
@@ -66,34 +73,29 @@ describe('drive', () => {
const dbUser = await controller.createUser(user)
accessToken = await generateAndSaveToken(dbUser.id)
await permissionController.createPermission({
uri: '/SASjsApi/drive/deploy',
principalType: PrincipalType.user,
principalId: dbUser.id,
setting: PermissionSetting.grant
...permission,
path: '/SASjsApi/drive/deploy',
principalId: dbUser.id
})
await permissionController.createPermission({
uri: '/SASjsApi/drive/deploy/upload',
principalType: PrincipalType.user,
principalId: dbUser.id,
setting: PermissionSetting.grant
...permission,
path: '/SASjsApi/drive/deploy/upload',
principalId: dbUser.id
})
await permissionController.createPermission({
uri: '/SASjsApi/drive/file',
principalType: PrincipalType.user,
principalId: dbUser.id,
setting: PermissionSetting.grant
...permission,
path: '/SASjsApi/drive/file',
principalId: dbUser.id
})
await permissionController.createPermission({
uri: '/SASjsApi/drive/folder',
principalType: PrincipalType.user,
principalId: dbUser.id,
setting: PermissionSetting.grant
...permission,
path: '/SASjsApi/drive/folder',
principalId: dbUser.id
})
await permissionController.createPermission({
uri: '/SASjsApi/drive/rename',
principalType: PrincipalType.user,
principalId: dbUser.id,
setting: PermissionSetting.grant
...permission,
path: '/SASjsApi/drive/rename',
principalId: dbUser.id
})
})

View File

@@ -5,6 +5,7 @@ import request from 'supertest'
import appPromise from '../../../app'
import { UserController, GroupController } from '../../../controllers/'
import { generateAccessToken, saveTokensInDB } from '../../../utils'
import { PUBLIC_GROUP_NAME } from '../../../model/Group'
const clientId = 'someclientID'
const adminUser = {
@@ -27,6 +28,12 @@ const group = {
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 groupController = new GroupController()
@@ -535,6 +542,24 @@ describe('group', () => {
expect(res.text).toEqual('User not found.')
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', () => {

View File

@@ -7,10 +7,10 @@ import {
DriveController,
UserController,
GroupController,
ClientController,
PermissionController,
PrincipalType,
PermissionSetting
PermissionType,
PermissionSettingForRoute
} from '../../../controllers/'
import {
UserDetailsResponse,
@@ -56,10 +56,10 @@ const user = {
}
const permission = {
uri: '/SASjsApi/code/execute',
setting: PermissionSetting.grant,
principalType: PrincipalType.user,
principalId: 123
path: '/SASjsApi/code/execute',
type: PermissionType.route,
setting: PermissionSettingForRoute.grant,
principalType: PrincipalType.user
}
const group = {
@@ -69,7 +69,6 @@ const group = {
const userController = new UserController()
const groupController = new GroupController()
const clientController = new ClientController()
const permissionController = new PermissionController()
describe('permission', () => {
@@ -108,7 +107,8 @@ describe('permission', () => {
.expect(200)
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.user).toBeTruthy()
})
@@ -127,7 +127,8 @@ describe('permission', () => {
.expect(200)
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.group).toBeTruthy()
})
@@ -142,53 +143,74 @@ describe('permission', () => {
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)
await permissionController.createPermission({
uri: '/SASjsApi/permission',
principalType: PrincipalType.user,
principalId: dbUser.id,
setting: PermissionSetting.grant
})
const res = await request(app)
.post('/SASjsApi/permission')
.auth(accessToken, { type: 'bearer' })
.send()
.send(permission)
.expect(401)
expect(res.text).toEqual('Admin account required')
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)
.post('/SASjsApi/permission')
.auth(adminAccessToken, { type: 'bearer' })
.send({
...permission,
uri: undefined
path: undefined
})
.expect(400)
expect(res.text).toEqual(`"uri" is required`)
expect(res.text).toEqual(`"path" is required`)
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)
.post('/SASjsApi/permission')
.auth(adminAccessToken, { type: 'bearer' })
.send({
...permission,
uri: '/some/random/api/endpoint'
path: '/some/random/api/endpoint'
})
.expect(400)
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 () => {
const res = await request(app)
.post('/SASjsApi/permission')
@@ -203,6 +225,20 @@ describe('permission', () => {
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 () => {
const res = await request(app)
.post('/SASjsApi/permission')
@@ -217,20 +253,6 @@ describe('permission', () => {
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 () => {
const res = await request(app)
.post('/SASjsApi/permission')
@@ -245,17 +267,17 @@ describe('permission', () => {
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)
.post('/SASjsApi/permission')
.auth(adminAccessToken, { type: 'bearer' })
.send({
...permission,
setting: 'invalid'
principalId: undefined
})
.expect(400)
expect(res.text).toEqual('"setting" must be one of [Grant, Deny]')
expect(res.text).toEqual(`"principalId" is required`)
expect(res.body).toEqual({})
})
@@ -313,7 +335,8 @@ describe('permission', () => {
.auth(adminAccessToken, { type: 'bearer' })
.send({
...permission,
principalType: 'group'
principalType: 'group',
principalId: 123
})
.expect(404)
@@ -334,7 +357,7 @@ describe('permission', () => {
.expect(409)
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({})
})
@@ -357,7 +380,7 @@ describe('permission', () => {
const res = await request(app)
.patch(`/SASjsApi/permission/${dbPermission?.permissionId}`)
.auth(adminAccessToken, { type: 'bearer' })
.send({ setting: 'Deny' })
.send({ setting: PermissionSettingForRoute.deny })
.expect(200)
expect(res.body.setting).toEqual('Deny')
@@ -366,7 +389,7 @@ describe('permission', () => {
it('should respond with Unauthorized if access token is not present', async () => {
const res = await request(app)
.patch(`/SASjsApi/permission/${dbPermission?.permissionId}`)
.send(permission)
.send()
.expect(401)
expect(res.text).toEqual('Unauthorized')
@@ -400,12 +423,11 @@ describe('permission', () => {
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)
.post('/SASjsApi/permission')
.patch(`/SASjsApi/permission/${dbPermission?.permissionId}`)
.auth(adminAccessToken, { type: 'bearer' })
.send({
...permission,
setting: 'invalid'
})
.expect(400)
@@ -414,12 +436,12 @@ describe('permission', () => {
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)
.patch('/SASjsApi/permission/123')
.auth(adminAccessToken, { type: 'bearer' })
.send({
setting: PermissionSetting.deny
setting: PermissionSettingForRoute.deny
})
.expect(404)
@@ -458,12 +480,12 @@ describe('permission', () => {
beforeAll(async () => {
await permissionController.createPermission({
...permission,
uri: '/test-1',
path: '/test-1',
principalId: dbUser.id
})
await permissionController.createPermission({
...permission,
uri: '/test-2',
path: '/test-2',
principalId: dbUser.id
})
})
@@ -478,34 +500,37 @@ describe('permission', () => {
expect(res.body).toHaveLength(2)
})
it('should give a list of all permissions when user is not admin', async () => {
const dbUser = await userController.createUser({
it(`should give a list of user's own permissions when user is not admin`, async () => {
const nonAdminUser = await userController.createUser({
...user,
username: 'get' + user.username
})
const accessToken = await generateAndSaveToken(dbUser.id)
const accessToken = await generateAndSaveToken(nonAdminUser.id)
await permissionController.createPermission({
uri: '/SASjsApi/permission',
path: '/test-1',
type: PermissionType.route,
principalType: PrincipalType.user,
principalId: dbUser.id,
setting: PermissionSetting.grant
principalId: nonAdminUser.id,
setting: PermissionSettingForRoute.grant
})
const permissionCount = 1
const res = await request(app)
.get('/SASjsApi/permission/')
.auth(accessToken, { type: 'bearer' })
.send()
.expect(200)
expect(res.body).toHaveLength(3)
expect(res.body).toHaveLength(permissionCount)
})
})
describe.only('verify', () => {
describe('verify', () => {
beforeAll(async () => {
await permissionController.createPermission({
...permission,
uri: '/SASjsApi/drive/deploy',
path: '/SASjsApi/drive/deploy',
principalId: dbUser.id
})
})

View File

@@ -7,7 +7,8 @@ import appPromise from '../../../app'
import {
UserController,
PermissionController,
PermissionSetting,
PermissionType,
PermissionSettingForRoute,
PrincipalType
} from '../../../controllers/'
import {
@@ -56,10 +57,11 @@ describe('stp', () => {
const dbUser = await userController.createUser(user)
accessToken = await generateAndSaveToken(dbUser.id)
await permissionController.createPermission({
uri: '/SASjsApi/stp/execute',
path: '/SASjsApi/stp/execute',
type: PermissionType.route,
principalType: PrincipalType.user,
principalId: dbUser.id,
setting: PermissionSetting.grant
setting: PermissionSettingForRoute.grant
})
})

View File

@@ -39,11 +39,10 @@ describe('web', () => {
describe('home', () => {
it('should respond with CSRF Token', async () => {
await request(app)
.get('/')
.expect(
'set-cookie',
/_csrf=.*; Max-Age=86400000; Path=\/; HttpOnly,XSRF-TOKEN=.*; Path=\//
const res = await request(app).get('/').expect(200)
expect(res.text).toMatch(
/<script>document.cookie = '(XSRF-TOKEN=.*; Max-Age=86400; SameSite=Strict; Path=\/;)'<\/script>/
)
})
})
@@ -154,10 +153,10 @@ describe('web', () => {
const getCSRF = async (app: Express) => {
// 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 csrfToken = extractCSRF(cookies)
const csrfToken = extractCSRF(text)
return { csrfToken, cookies }
}
@@ -177,7 +176,7 @@ const performLogin = async (
return { cookies: newCookies }
}
const extractCSRF = (cookies: string) =>
/_csrf=(.*); Max-Age=86400000; Path=\/; HttpOnly,XSRF-TOKEN=(.*); Path=\//.exec(
cookies
)![2]
const extractCSRF = (text: string) =>
/<script>document.cookie = 'XSRF-TOKEN=(.*); Max-Age=86400; SameSite=Strict; Path=\/;'<\/script>/.exec(
text
)![1]

View File

@@ -26,6 +26,7 @@ export const style = `<style>
}
.app-container .app img{
width: 100%;
height: calc(100% - 30px);
margin-bottom: 10px;
border-radius: 10px;
}

View File

@@ -11,11 +11,15 @@ webRouter.get('/', async (req, res) => {
try {
response = await controller.home()
} catch (_) {
response = 'Web Build is not present'
response = '<html><head></head><body>Web Build is not present</body></html>'
} 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)
}
})

View File

@@ -9,8 +9,7 @@ const StaticAuthorizedRoutes = [
'/SASjsApi/drive/file',
'/SASjsApi/drive/folder',
'/SASjsApi/drive/fileTree',
'/SASjsApi/drive/rename',
'/SASjsApi/permission'
'/SASjsApi/drive/rename'
]
export const getAuthorizedRoutes = () => {
@@ -19,7 +18,7 @@ export const getAuthorizedRoutes = () => {
return [...StaticAuthorizedRoutes, ...streamingAppsRoutes]
}
export const getUri = (req: Request) => {
export const getPath = (req: Request) => {
const { baseUrl, path: reqPath } = req
if (baseUrl === '/AppStream') {
@@ -33,4 +32,4 @@ export const getUri = (req: Request) => {
}
export const isAuthorizingRoute = (req: Request): boolean =>
getAuthorizedRoutes().includes(getUri(req))
getAuthorizedRoutes().includes(getPath(req))

View File

@@ -16,6 +16,7 @@ export * from './getRunTimeAndFilePath'
export * from './getServerUrl'
export * from './instantiateLogger'
export * from './isDebugOn'
export * from './isPublicRoute'
export * from './zipped'
export * from './parseLogToArray'
export * from './removeTokensInDB'

View 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
}

View File

@@ -1,5 +1,5 @@
import Client from '../model/Client'
import Group from '../model/Group'
import Group, { PUBLIC_GROUP_NAME } from '../model/Group'
import User from '../model/User'
import Configuration, { ConfigurationType } from '../model/Configuration'
@@ -31,6 +31,15 @@ export const seedDB = async (): Promise<ConfigurationType> => {
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
let usernameExist = await User.findOne({ username: ADMIN_USER.username })
if (!usernameExist) {
@@ -68,6 +77,13 @@ const GROUP = {
name: 'AllUsers',
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 = {
clientId: 'clientID1',
clientSecret: 'clientSecret'

View File

@@ -1,5 +1,9 @@
import Joi from 'joi'
import { PermissionSetting, PrincipalType } from '../controllers/permission'
import {
PermissionType,
PermissionSettingForRoute,
PrincipalType
} from '../controllers/permission'
import { getAuthorizedRoutes } from './getAuthorizedRoutes'
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 =>
Joi.object({
uri: Joi.string()
path: Joi.string()
.required()
.valid(...getAuthorizedRoutes()),
type: Joi.string()
.required()
.valid(...Object.values(PermissionType)),
setting: Joi.string()
.required()
.valid(...Object.values(PermissionSetting)),
.valid(...Object.values(PermissionSettingForRoute)),
principalType: Joi.string()
.required()
.valid(...Object.values(PrincipalType)),
@@ -105,7 +112,7 @@ export const updatePermissionValidation = (data: any): Joi.ValidationResult =>
Joi.object({
setting: Joi.string()
.required()
.valid(...Object.values(PermissionSetting))
.valid(...Object.values(PermissionSettingForRoute))
}).validate(data)
export const deployValidation = (data: any): Joi.ValidationResult =>

View File

@@ -125,8 +125,27 @@ const verifyCORS = (): string[] => {
if (CORS) {
const corsTypes = Object.values(CorsType)
if (!corsTypes.includes(CORS as CorsType))
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 {
const { MODE } = process.env
process.env.CORS =

331
web/package-lock.json generated
View File

@@ -2284,6 +2284,58 @@
"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": {
"version": "5.0.0-alpha.54",
"resolved": "https://registry.npmjs.org/@mui/core/-/core-5.0.0-alpha.54.tgz",
@@ -3937,11 +3989,9 @@
}
},
"node_modules/acorn": {
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
"integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
"dev": true,
"peer": true,
"version": "8.8.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz",
"integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==",
"bin": {
"acorn": "bin/acorn"
},
@@ -6522,18 +6572,6 @@
"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": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz",
@@ -7197,20 +7235,6 @@
"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": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
@@ -7220,46 +7244,6 @@
"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": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz",
@@ -10287,6 +10271,28 @@
"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": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@@ -11124,17 +11130,6 @@
"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": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz",
@@ -11143,11 +11138,6 @@
"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": {
"version": "4.0.0",
"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"
}
},
"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": {
"version": "5.3.1",
"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": {
"version": "3.2.3",
"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": {
"version": "5.0.0-alpha.54",
"resolved": "https://registry.npmjs.org/@mui/core/-/core-5.0.0-alpha.54.tgz",
@@ -14110,11 +14111,9 @@
}
},
"acorn": {
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
"integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
"dev": true,
"peer": true
"version": "8.8.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz",
"integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w=="
},
"acorn-jsx": {
"version": "5.3.2",
@@ -16061,12 +16060,6 @@
"eslint-visitor-keys": "^3.3.0"
},
"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": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz",
@@ -16585,44 +16578,11 @@
"terser": "^5.10.0"
},
"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": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
"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",
"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": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@@ -19259,22 +19237,12 @@
"webpack-sources": "^3.2.2"
},
"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": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz",
"integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==",
"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": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -19321,23 +19289,6 @@
"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": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.1.tgz",

View File

@@ -22,7 +22,7 @@ function App() {
<HashRouter>
<Header />
<Routes>
<Route path="/" element={<Login />} />
<Route path="*" element={<Login />} />
</Routes>
</HashRouter>
</ThemeProvider>

View File

@@ -2,16 +2,18 @@ import React, { useState, useEffect, useContext } from 'react'
import { Link, useNavigate, useLocation } from 'react-router-dom'
import {
Box,
AppBar,
Toolbar,
Tabs,
Tab,
Button,
Menu,
MenuItem
MenuItem,
IconButton,
Typography
} from '@mui/material'
import OpenInNewIcon from '@mui/icons-material/OpenInNew'
import SettingsIcon from '@mui/icons-material/Settings'
import { OpenInNew, Settings, Menu as MenuIcon } from '@mui/icons-material'
import Username from './username'
import { AppContext } from '../context/appContext'
@@ -30,31 +32,38 @@ const Header = (props: any) => {
const [tabValue, setTabValue] = useState(
validTabs.includes(pathname) ? pathname : '/'
)
const [anchorEl, setAnchorEl] = useState<
(EventTarget & HTMLButtonElement) | null
>(null)
const [anchorElNav, setAnchorElNav] = React.useState<null | HTMLElement>(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(() => {
setTabValue(validTabs.includes(pathname) ? pathname : '/')
}, [pathname])
const handleMenu = (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => {
setAnchorEl(event.currentTarget)
}
const handleClose = () => {
setAnchorEl(null)
}
const handleTabChange = (event: React.SyntheticEvent, value: string) => {
setTabValue(value)
}
const handleLogout = () => {
if (appContext.logout) {
handleClose()
handleCloseUserMenu()
appContext.logout()
}
}
@@ -64,11 +73,14 @@ const Header = (props: any) => {
sx={{ zIndex: (theme) => theme.zIndex.drawer + 1 }}
>
<Toolbar variant="dense">
<Box sx={{ display: { xs: 'none', md: 'flex' } }}>
<img
src="logo.png"
alt="logo"
style={{
width: '35px',
height: '35px',
marginTop: '9px',
cursor: 'pointer',
marginRight: '25px'
}}
@@ -97,10 +109,93 @@ const Header = (props: any) => {
variant="contained"
color="primary"
size="large"
endIcon={<OpenInNewIcon />}
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
style={{
display: 'flex',
@@ -110,11 +205,11 @@ const Header = (props: any) => {
>
<Username
username={appContext.displayName || appContext.username}
onClickHandler={handleMenu}
onClickHandler={handleOpenUserMenu}
/>
<Menu
id="menu-appbar"
anchorEl={anchorEl}
anchorEl={anchorElUser}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center'
@@ -124,17 +219,30 @@ const Header = (props: any) => {
vertical: 'top',
horizontal: 'center'
}}
open={!!anchorEl}
onClose={handleClose}
open={!!anchorElUser}
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' }}>
<Button
component={Link}
to="/SASjsSettings"
onClick={handleClose}
onClick={handleCloseUserMenu}
variant="contained"
color="primary"
startIcon={<SettingsIcon />}
startIcon={<Settings />}
>
Settings
</Button>
@@ -147,7 +255,7 @@ const Header = (props: any) => {
variant="contained"
size="large"
color="primary"
endIcon={<OpenInNewIcon />}
endIcon={<OpenInNew />}
>
Docs
</Button>
@@ -160,16 +268,21 @@ const Header = (props: any) => {
variant="contained"
color="primary"
size="large"
endIcon={<OpenInNewIcon />}
endIcon={<OpenInNew />}
>
API
</Button>
</MenuItem>
<MenuItem onClick={handleLogout} sx={{ justifyContent: 'center' }}>
{appContext.loggedIn && (
<MenuItem
onClick={handleLogout}
sx={{ justifyContent: 'center' }}
>
<Button variant="contained" color="primary">
Logout
</Button>
</MenuItem>
)}
</Menu>
</div>
</Toolbar>

View File

@@ -20,7 +20,14 @@ const Username = (props: any) => {
) : (
<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}
</Typography>
</IconButton>

View File

@@ -32,7 +32,13 @@ const BootstrapDialog = styled(Dialog)(({ theme }) => ({
type AddPermissionModalProps = {
open: boolean
handleOpen: Dispatch<SetStateAction<boolean>>
addPermission: (addPermissionPayload: RegisterPermissionPayload) => void
addPermission: (
permissions: RegisterPermissionPayload[],
permissionType: string,
principalType: string,
principal: string,
permissionSetting: string
) => void
}
const AddPermissionModal = ({
@@ -40,10 +46,11 @@ const AddPermissionModal = ({
handleOpen,
addPermission
}: AddPermissionModalProps) => {
const [URIs, setURIs] = useState<string[]>([])
const [loadingURIs, setLoadingURIs] = useState(false)
const [uri, setUri] = useState<string>()
const [principalType, setPrincipalType] = useState('user')
const [paths, setPaths] = useState<string[]>([])
const [loadingPaths, setLoadingPaths] = useState(false)
const [selectedPaths, setSelectedPaths] = useState<string[]>([])
const [permissionType, setPermissionType] = useState('Route')
const [principalType, setPrincipalType] = useState('Group')
const [userPrincipal, setUserPrincipal] = useState<UserResponse>()
const [groupPrincipal, setGroupPrincipal] = useState<GroupResponse>()
const [permissionSetting, setPermissionSetting] = useState('Grant')
@@ -52,29 +59,29 @@ const AddPermissionModal = ({
const [groupPrincipals, setGroupPrincipals] = useState<GroupResponse[]>([])
useEffect(() => {
setLoadingURIs(true)
setLoadingPaths(true)
axios
.get('/SASjsApi/info/authorizedRoutes')
.then((res: any) => {
if (res.data) {
setURIs(res.data.URIs)
setPaths(res.data.paths)
}
})
.catch((err) => {
console.log(err)
})
.finally(() => {
setLoadingURIs(false)
setLoadingPaths(false)
})
}, [])
useEffect(() => {
setLoadingPrincipals(true)
axios
.get(`/SASjsApi/${principalType}`)
.get(`/SASjsApi/${principalType.toLowerCase()}`)
.then((res: any) => {
if (res.data) {
if (principalType === 'user') {
if (principalType.toLowerCase() === 'user') {
const users: UserResponse[] = res.data
const nonAdminUsers = users.filter((user) => !user.isAdmin)
setUserPrincipals(nonAdminUsers)
@@ -92,21 +99,40 @@ const AddPermissionModal = ({
}, [principalType])
const handleAddPermission = () => {
const permissions: RegisterPermissionPayload[] = []
selectedPaths.forEach((path) => {
const addPermissionPayload: any = {
uri,
path,
type: permissionType,
setting: permissionSetting,
principalType
principalType: principalType.toLowerCase(),
principalId:
principalType.toLowerCase() === 'user'
? userPrincipal?.id
: groupPrincipal?.groupId
}
if (principalType === 'user' && userPrincipal) {
addPermissionPayload.principalId = userPrincipal.id
} else if (principalType === 'group' && groupPrincipal) {
addPermissionPayload.principalId = groupPrincipal.groupId
}
addPermission(addPermissionPayload)
permissions.push(addPermissionPayload)
})
const principal =
principalType.toLowerCase() === 'user'
? userPrincipal?.username
: groupPrincipal?.name
addPermission(
permissions,
permissionType,
principalType,
principal!,
permissionSetting
)
}
const addButtonDisabled =
!uri || (principalType === 'user' ? !userPrincipal : !groupPrincipal)
!selectedPaths.length ||
(principalType.toLowerCase() === 'user' ? !userPrincipal : !groupPrincipal)
return (
<BootstrapDialog onClose={() => handleOpen(false)} open={open}>
@@ -120,22 +146,37 @@ const AddPermissionModal = ({
<Grid container spacing={2}>
<Grid item xs={12}>
<Autocomplete
options={URIs}
multiple
disableClearable
value={uri}
onChange={(event: any, newValue: string) => setUri(newValue)}
options={paths}
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) =>
loadingURIs ? (
loadingPaths ? (
<CircularProgress />
) : (
<TextField {...params} label="Principal" />
<TextField {...params} label="Permission Type" />
)
}
/>
</Grid>
<Grid item xs={12}>
<Autocomplete
options={['user', 'group']}
options={['Group', 'User']}
disableClearable
value={principalType}
onChange={(event: any, newValue: string) =>
@@ -147,7 +188,7 @@ const AddPermissionModal = ({
/>
</Grid>
<Grid item xs={12}>
{principalType === 'user' ? (
{principalType.toLowerCase() === 'user' ? (
<Autocomplete
options={userPrincipals}
getOptionLabel={(option) => option.displayName}

View 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

View File

@@ -31,11 +31,20 @@ const Settings = () => {
<Box
sx={{
display: 'flex',
flexDirection: { xs: 'column', md: 'row' },
marginTop: '65px'
}}
>
<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
TabIndicatorProps={{
style: {
@@ -47,7 +56,7 @@ const Settings = () => {
>
<StyledTab label="Profile" value="profile" />
{appContext.mode === ModeType.Server && (
<StyledTab label="Uri Access" value="permission" />
<StyledTab label="Permissions" value="permission" />
)}
</TabList>
</Box>

View File

@@ -27,6 +27,9 @@ import { styled } from '@mui/material/styles'
import Modal from '../../components/modal'
import PermissionFilterModal from './permissionFilterModal'
import AddPermissionModal from './addPermissionModal'
import PermissionResponseModal, {
PermissionResponsePayload
} from './addPermissionResponseModal'
import UpdatePermissionModal from './updatePermissionModal'
import DeleteConfirmationModal from '../../components/deleteConfirmationModal'
import BootstrapSnackbar, { AlertSeverityType } from '../../components/snackbar'
@@ -36,12 +39,23 @@ import {
PermissionResponse,
RegisterPermissionPayload
} from '../../utils/types'
import {
findExistingPermission,
findUpdatingPermission
} from '../../utils/helper'
import { AppContext } from '../../context/appContext'
const BootstrapTableCell = styled(TableCell)({
textAlign: 'left'
})
const BootstrapGridItem = styled(Grid)({
'&.MuiGrid-item': {
maxWidth: '100%'
}
})
export enum PrincipalType {
User = 'User',
Group = 'Group'
@@ -59,6 +73,20 @@ const Permission = () => {
AlertSeverityType.Success
)
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] =
useState(false)
const [deleteConfirmationModalOpen, setDeleteConfirmationModalOpen] =
@@ -68,7 +96,7 @@ const Permission = () => {
const [selectedPermission, setSelectedPermission] =
useState<PermissionResponse>()
const [filterModalOpen, setFilterModalOpen] = useState(false)
const [uriFilter, setUriFilter] = useState<string[]>([])
const [pathFilter, setPathFilter] = useState<string[]>([])
const [principalFilter, setPrincipalFilter] = useState<string[]>([])
const [principalTypeFilter, setPrincipalTypeFilter] = useState<
PrincipalType[]
@@ -111,8 +139,10 @@ const Permission = () => {
setFilterModalOpen(false)
const uriFilteredPermissions =
uriFilter.length > 0
? permissions.filter((permission) => uriFilter.includes(permission.uri))
pathFilter.length > 0
? permissions.filter((permission) =>
pathFilter.includes(permission.path)
)
: permissions
const principalFilteredPermissions =
@@ -172,36 +202,84 @@ const Permission = () => {
const resetFilter = () => {
setFilterModalOpen(false)
setUriFilter([])
setPathFilter([])
setPrincipalFilter([])
setSettingFilter([])
setFilteredPermissions([])
setFilterApplied(false)
}
const addPermission = (addPermissionPayload: RegisterPermissionPayload) => {
const addPermission = async (
permissionsToAdd: RegisterPermissionPayload[],
permissionType: string,
principalType: string,
principal: string,
permissionSetting: string
) => {
setAddPermissionModalOpen(false)
setIsLoading(true)
axios
.post('/SASjsApi/permission', addPermissionPayload)
.then((res: any) => {
const newAddedPermissions: PermissionResponse[] = []
const updatedPermissions: PermissionResponse[] = []
const errorPaths: string[] = []
const existingPermissions: PermissionResponse[] = []
const updatingPermissions: PermissionResponse[] = []
const newPermissions: RegisterPermissionPayload[] = []
permissionsToAdd.forEach((permission) => {
const existingPermission = findExistingPermission(permissions, permission)
if (existingPermission) {
existingPermissions.push(existingPermission)
return
}
const updatingPermission = findUpdatingPermission(permissions, permission)
if (updatingPermission) {
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()
setSnackbarMessage('Permission added!')
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)
setPermissionResponsePayload({
permissionType,
principalType,
principal,
permissionSetting,
existingPermissions,
updatedPermissions,
newAddedPermissions,
errorPaths
})
setOpenPermissionResponseModal(true)
}
const handleUpdatePermissionClick = (permission: PermissionResponse) => {
@@ -278,11 +356,11 @@ const Permission = () => {
) : (
<Box className="permissions-page">
<Grid container direction="column" spacing={1}>
<Grid item xs={12}>
<BootstrapGridItem item xs={12}>
<Paper elevation={3} sx={{ display: 'flex' }}>
<Tooltip title="Filter Permissions">
<IconButton>
<FilterListIcon onClick={() => setFilterModalOpen(true)} />
<IconButton onClick={() => setFilterModalOpen(true)}>
<FilterListIcon />
</IconButton>
</Tooltip>
{appContext.isAdmin && (
@@ -297,14 +375,14 @@ const Permission = () => {
</Tooltip>
)}
</Paper>
</Grid>
<Grid item xs={12}>
</BootstrapGridItem>
<BootstrapGridItem item xs={12}>
<PermissionTable
permissions={filterApplied ? filteredPermissions : permissions}
handleUpdatePermissionClick={handleUpdatePermissionClick}
handleDeletePermissionClick={handleDeletePermissionClick}
/>
</Grid>
</BootstrapGridItem>
</Grid>
<BootstrapSnackbar
open={openSnackbar}
@@ -322,8 +400,8 @@ const Permission = () => {
open={filterModalOpen}
handleOpen={setFilterModalOpen}
permissions={permissions}
uriFilter={uriFilter}
setUriFilter={setUriFilter}
pathFilter={pathFilter}
setPathFilter={setPathFilter}
principalFilter={principalFilter}
setPrincipalFilter={setPrincipalFilter}
principalTypeFilter={principalTypeFilter}
@@ -338,6 +416,11 @@ const Permission = () => {
handleOpen={setAddPermissionModalOpen}
addPermission={addPermission}
/>
<PermissionResponseModal
open={openPermissionResponseModal}
setOpen={setOpenPermissionResponseModal}
payload={permissionResponsePayload}
/>
<UpdatePermissionModal
open={updatePermissionModalOpen}
handleOpen={setUpdatePermissionModalOpen}
@@ -374,9 +457,10 @@ const PermissionTable = ({
<Table sx={{ minWidth: 650 }}>
<TableHead sx={{ background: 'rgb(0,0,0, 0.3)' }}>
<TableRow>
<BootstrapTableCell>Uri</BootstrapTableCell>
<BootstrapTableCell>Path</BootstrapTableCell>
<BootstrapTableCell>Permission Type</BootstrapTableCell>
<BootstrapTableCell>Principal</BootstrapTableCell>
<BootstrapTableCell>Type</BootstrapTableCell>
<BootstrapTableCell>Principal Type</BootstrapTableCell>
<BootstrapTableCell>Setting</BootstrapTableCell>
{appContext.isAdmin && (
<BootstrapTableCell>Action</BootstrapTableCell>
@@ -386,7 +470,8 @@ const PermissionTable = ({
<TableBody>
{permissions.map((permission) => (
<TableRow key={permission.permissionId}>
<BootstrapTableCell>{permission.uri}</BootstrapTableCell>
<BootstrapTableCell>{permission.path}</BootstrapTableCell>
<BootstrapTableCell>{permission.type}</BootstrapTableCell>
<BootstrapTableCell>
{displayPrincipal(permission)}
</BootstrapTableCell>
@@ -474,8 +559,8 @@ const DisplayGroup = ({ group }: DisplayGroupProps) => {
<Typography sx={{ p: 1 }} variant="h6" component="div">
Group Members
</Typography>
{group.users.map((user) => (
<Typography sx={{ p: 1 }} component="li">
{group.users.map((user, index) => (
<Typography key={index} sx={{ p: 1 }} component="li">
{user.username}
</Typography>
))}

View File

@@ -27,8 +27,8 @@ type FilterModalProps = {
open: boolean
handleOpen: Dispatch<SetStateAction<boolean>>
permissions: PermissionResponse[]
uriFilter: string[]
setUriFilter: Dispatch<SetStateAction<string[]>>
pathFilter: string[]
setPathFilter: Dispatch<SetStateAction<string[]>>
principalFilter: string[]
setPrincipalFilter: Dispatch<SetStateAction<string[]>>
principalTypeFilter: PrincipalType[]
@@ -43,8 +43,8 @@ const PermissionFilterModal = ({
open,
handleOpen,
permissions,
uriFilter,
setUriFilter,
pathFilter,
setPathFilter,
principalFilter,
setPrincipalFilter,
principalTypeFilter,
@@ -54,8 +54,8 @@ const PermissionFilterModal = ({
applyFilter,
resetFilter
}: FilterModalProps) => {
const URIs = permissions
.map((permission) => permission.uri)
const paths = permissions
.map((permission) => permission.path)
.filter((uri, index, array) => array.indexOf(uri) === index)
// fetch all the principals from permissions array
@@ -86,13 +86,13 @@ const PermissionFilterModal = ({
<Grid item xs={12}>
<Autocomplete
multiple
options={URIs}
options={paths}
filterSelectedOptions
value={uriFilter}
value={pathFilter}
onChange={(event: any, newValue: string[]) => {
setUriFilter(newValue)
setPathFilter(newValue)
}}
renderInput={(params) => <TextField {...params} label="URIs" />}
renderInput={(params) => <TextField {...params} label="Paths" />}
/>
</Grid>
<Grid item xs={12}>

View File

@@ -1,4 +1,11 @@
import React, { useEffect, useRef, useState, useContext } from 'react'
import React, {
Dispatch,
SetStateAction,
useEffect,
useRef,
useState,
useContext
} from 'react'
import axios from 'axios'
import {
@@ -58,13 +65,17 @@ const StyledTab = styled(Tab)(() => ({
type SASjsEditorProps = {
selectedFilePath: string
setSelectedFilePath: (filePath: string, refreshSideBar?: boolean) => void
tab: string
setTab: Dispatch<SetStateAction<string>>
}
const baseUrl = window.location.origin
const SASjsEditor = ({
selectedFilePath,
setSelectedFilePath
setSelectedFilePath,
tab,
setTab
}: SASjsEditorProps) => {
const appContext = useContext(AppContext)
const [isLoading, setIsLoading] = useState(false)
@@ -81,7 +92,6 @@ const SASjsEditor = ({
const [log, setLog] = useState('')
const [ctrlPressed, setCtrlPressed] = useState(false)
const [webout, setWebout] = useState('')
const [tab, setTab] = useState('1')
const [runTimes, setRunTimes] = useState<string[]>([])
const [selectedRunTime, setSelectedRunTime] = useState('')
const [selectedFileExtension, setSelectedFileExtension] = useState('')
@@ -161,7 +171,7 @@ const SASjsEditor = ({
}
setLog('')
setWebout('')
setTab('1')
setTab('code')
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedFilePath])
@@ -200,10 +210,11 @@ const SASjsEditor = ({
setLog(parsedLog)
setWebout(`${res.data?._webout}`)
setTab('2')
setTab('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) => {
setModalTitle('Abort')
@@ -353,29 +364,24 @@ const SASjsEditor = ({
sx={{
borderBottom: 1,
borderColor: 'divider',
position: 'fixed',
background: 'white',
width: '85%'
background: 'white'
}}
>
<TabList onChange={handleTabChange} centered>
<StyledTab label="Code" value="1" />
<StyledTab label="Log" value="2" />
<StyledTab label="Code" value="code" />
<StyledTab label="Log" value="log" />
<StyledTab
label={
<Tooltip title="Displays content from the _webout fileref">
<Typography>Webout</Typography>
</Tooltip>
}
value="3"
value="webout"
/>
</TabList>
</Box>
<StyledTabPanel
sx={{ paddingBottom: 0, marginTop: '45px' }}
value="1"
>
<StyledTabPanel sx={{ paddingBottom: 0 }} value="code">
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
<RunMenu
fileContent={fileContent}
@@ -441,14 +447,16 @@ const SASjsEditor = ({
</p>
</Paper>
</StyledTabPanel>
<StyledTabPanel value="2">
<div style={{ marginTop: '50px' }}>
<h2>SAS Log</h2>
<pre>{log}</pre>
<StyledTabPanel value="log">
<div>
<h2>Log</h2>
<pre id="log" style={{ overflow: 'auto', height: '75vh' }}>
{log}
</pre>
</div>
</StyledTabPanel>
<StyledTabPanel value="3">
<div style={{ marginTop: '50px' }}>
<StyledTabPanel value="webout">
<div>
<pre>{webout}</pre>
</div>
</StyledTabPanel>

View File

@@ -14,6 +14,7 @@ const Studio = () => {
const [searchParams, setSearchParams] = useSearchParams()
const [selectedFilePath, setSelectedFilePath] = useState('')
const [directoryData, setDirectoryData] = useState<TreeNode | null>(null)
const [tab, setTab] = useState('code')
useEffect(() => {
setSelectedFilePath(searchParams.get('filePath') ?? '')
@@ -83,6 +84,7 @@ const Studio = () => {
return (
<Box sx={{ display: 'flex' }}>
<CssBaseline />
{tab === 'code' && (
<SideBar
selectedFilePath={selectedFilePath}
directoryData={directoryData}
@@ -90,9 +92,12 @@ const Studio = () => {
removeFileFromTree={removeFileFromTree}
refreshSideBar={fetchDirectoryData}
/>
)}
<SASjsEditor
selectedFilePath={selectedFilePath}
setSelectedFilePath={handleSelect}
tab={tab}
setTab={setTab}
/>
</Box>
)

View File

@@ -1,6 +1,15 @@
import React, { useState, useMemo } from 'react'
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 BootstrapSnackbar, { AlertSeverityType } from '../../components/snackbar'
@@ -33,6 +42,17 @@ const SideBar = ({
const [snackbarSeverity, setSnackbarSeverity] = useState<AlertSeverityType>(
AlertSeverityType.Success
)
const [mobileOpen, setMobileOpen] = React.useState(false)
const handleDrawerToggle = () => {
setMobileOpen(!mobileOpen)
}
const handleFileSelect = (filePath: string) => {
setMobileOpen(false)
handleSelect(filePath)
}
const defaultExpanded = useMemo(() => {
const splittedPath = selectedFilePath.split('/')
const arr = ['']
@@ -147,15 +167,8 @@ const SideBar = ({
.finally(() => setIsLoading(false))
}
return (
<Drawer
variant="permanent"
sx={{
width: drawerWidth,
flexShrink: 0,
[`& .MuiDrawer-paper`]: { width: drawerWidth, boxSizing: 'border-box' }
}}
>
const drawer = (
<div>
<Backdrop
sx={{ color: '#fff', zIndex: (theme) => theme.zIndex.drawer + 1 }}
open={isLoading}
@@ -168,7 +181,7 @@ const SideBar = ({
<TreeView
node={directoryData}
selectedFilePath={selectedFilePath}
handleSelect={handleSelect}
handleSelect={handleFileSelect}
deleteNode={deleteNode}
addFile={addFile}
addFolder={addFolder}
@@ -189,7 +202,65 @@ const SideBar = ({
title={modalTitle}
payload={modalPayload}
/>
</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>
</>
)
}

View File

@@ -80,7 +80,18 @@ const AppContextProvider = (props: { children: ReactNode }) => {
})
.catch(() => {
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

View File

@@ -13,7 +13,7 @@ code {
}
.main {
margin-top: 50px;
margin: 50px 10px 0 10px;
display: flex;
flex-direction: column;
align-items: center;

59
web/src/utils/helper.ts Normal file
View 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

View File

@@ -18,14 +18,16 @@ export interface GroupDetailsResponse extends GroupResponse {
export interface PermissionResponse {
permissionId: number
uri: string
path: string
type: string
setting: string
user?: UserResponse
group?: GroupDetailsResponse
}
export interface RegisterPermissionPayload {
uri: string
path: string
type: string
setting: string
principalType: string
principalId: number