mirror of
https://github.com/sasjs/server.git
synced 2025-12-12 20:04:36 +00:00
Compare commits
3 Commits
issue-361
...
domain-che
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ed10109c1 | ||
|
|
ef9cca575f | ||
|
|
efbfd3f392 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,6 +5,8 @@ node_modules/
|
|||||||
.env*
|
.env*
|
||||||
sas/
|
sas/
|
||||||
sasjs_root/
|
sasjs_root/
|
||||||
|
api/mocks/custom/*
|
||||||
|
!api/mocks/custom/.keep
|
||||||
tmp/
|
tmp/
|
||||||
build/
|
build/
|
||||||
sasjsbuild/
|
sasjsbuild/
|
||||||
|
|||||||
300
CHANGELOG.md
300
CHANGELOG.md
@@ -1,303 +1,3 @@
|
|||||||
## [0.34.2](https://github.com/sasjs/server/compare/v0.34.1...v0.34.2) (2023-05-01)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* use custom logic for handling sequence ids ([dba53de](https://github.com/sasjs/server/commit/dba53de64664c9d8a40fe69de6281c53d1c73641))
|
|
||||||
|
|
||||||
## [0.34.1](https://github.com/sasjs/server/compare/v0.34.0...v0.34.1) (2023-04-28)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **css:** fixed css loading ([9c5acd6](https://github.com/sasjs/server/commit/9c5acd6de32afdbc186f79ae5b35375dda2e49b0))
|
|
||||||
* **log:** fixed chunk collapsing ([64b156f](https://github.com/sasjs/server/commit/64b156f7627969b7f13022726f984fbbfe1a33ef))
|
|
||||||
|
|
||||||
# [0.34.0](https://github.com/sasjs/server/compare/v0.33.3...v0.34.0) (2023-04-28)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **log:** fixed checks for errors and warnings ([02e2b06](https://github.com/sasjs/server/commit/02e2b060f9bedf4806f45f5205fd87bfa2ecae90))
|
|
||||||
* **log:** fixed default runtime ([e04300a](https://github.com/sasjs/server/commit/e04300ad2ac237be7b28a6332fa87a3bcf761c7b))
|
|
||||||
* **log:** fixed parsing log for different runtime ([3b1e4a1](https://github.com/sasjs/server/commit/3b1e4a128b1f22ff6f3069f5aaada6bfb1b40d12))
|
|
||||||
* **log:** fixed scrolling issue ([56a522c](https://github.com/sasjs/server/commit/56a522c07c6f6d4c26c6d3b7cd6e9ef7007067a9))
|
|
||||||
* **log:** fixed single chunk display ([8254b78](https://github.com/sasjs/server/commit/8254b789555cb8bbb169f52b754b4ce24e876dd2))
|
|
||||||
* **log:** fixed single chunk scrolling ([57b7f95](https://github.com/sasjs/server/commit/57b7f954a17936f39aa9b757998b5b25e9442601))
|
|
||||||
* **log:** fixed switching runtime ([c7a7399](https://github.com/sasjs/server/commit/c7a73991a7aa25d0c75d0c00e712bdc78769300b))
|
|
||||||
* **log:** fixing switching from SAS to other runtime ([c72ecc7](https://github.com/sasjs/server/commit/c72ecc7e5943af9536ee31cfa85398e016d5354f))
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* **log:** added download chunk and entire log ([a38a9f9](https://github.com/sasjs/server/commit/a38a9f9c3dfe36bd55d32024c166147318216995))
|
|
||||||
* **log:** added logComponent and LogTabWithIcons ([3a887de](https://github.com/sasjs/server/commit/3a887dec55371b6a00b92291bb681e4cccb770c0))
|
|
||||||
* **log:** added parseErrorsAndWarnings utility ([7c1c1e2](https://github.com/sasjs/server/commit/7c1c1e241002313c10f94dd61702584b9f148010))
|
|
||||||
* **log:** added time to downloaded log name ([3848bb0](https://github.com/sasjs/server/commit/3848bb0added69ca81a5c9419ea414bdd1c294bb))
|
|
||||||
* **log:** put download log icon into log tab ([777b3a5](https://github.com/sasjs/server/commit/777b3a55be1ecf5b05bf755ce8b14735496509e1))
|
|
||||||
* **log:** split large log into chunks ([75f5a3c](https://github.com/sasjs/server/commit/75f5a3c0b39665bef8b83dc7e1e8b3e5f23fc303))
|
|
||||||
* **log:** use improved log for SAS run time only ([7b12591](https://github.com/sasjs/server/commit/7b12591595cdd5144d9311ffa06a80c5dab79364))
|
|
||||||
|
|
||||||
## [0.33.3](https://github.com/sasjs/server/compare/v0.33.2...v0.33.3) (2023-04-27)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* use RateLimiterMemory instead of RateLimiterMongo ([6a520f5](https://github.com/sasjs/server/commit/6a520f5b26a3e2ed6345721b30ff4e3d9bfa903d))
|
|
||||||
|
|
||||||
## [0.33.2](https://github.com/sasjs/server/compare/v0.33.1...v0.33.2) (2023-04-24)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* removing print redirection pending full [#274](https://github.com/sasjs/server/issues/274) fix ([d49ea47](https://github.com/sasjs/server/commit/d49ea47bd7a2add42bdb9a717082201f29e16597))
|
|
||||||
|
|
||||||
## [0.33.1](https://github.com/sasjs/server/compare/v0.33.0...v0.33.1) (2023-04-20)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* applying nologo only for sas.exe ([b4436ba](https://github.com/sasjs/server/commit/b4436bad0d24d5b5a402272632db1739b1018c90)), closes [#352](https://github.com/sasjs/server/issues/352)
|
|
||||||
|
|
||||||
# [0.33.0](https://github.com/sasjs/server/compare/v0.32.0...v0.33.0) (2023-04-05)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* option to reset admin password on startup ([eda8e56](https://github.com/sasjs/server/commit/eda8e56bb0ea20fdaacabbbe7dcf1e3ea7bd215a))
|
|
||||||
|
|
||||||
# [0.32.0](https://github.com/sasjs/server/compare/v0.31.0...v0.32.0) (2023-04-05)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* add an api endpoint for admin to get list of client ids ([6ffaa7e](https://github.com/sasjs/server/commit/6ffaa7e9e2a62c083bb9fcc3398dcbed10cebdb1))
|
|
||||||
|
|
||||||
# [0.31.0](https://github.com/sasjs/server/compare/v0.30.3...v0.31.0) (2023-03-30)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* prevent brute force attack by rate limiting login endpoint ([a82cabb](https://github.com/sasjs/server/commit/a82cabb00134c79c5ee77afd1b1628a1f768e050))
|
|
||||||
|
|
||||||
## [0.30.3](https://github.com/sasjs/server/compare/v0.30.2...v0.30.3) (2023-03-07)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* add location.pathname to location.origin conditionally ([edab51c](https://github.com/sasjs/server/commit/edab51c51997f17553e037dc7c2b5e5fa6ea8ffe))
|
|
||||||
|
|
||||||
## [0.30.2](https://github.com/sasjs/server/compare/v0.30.1...v0.30.2) (2023-03-07)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **web:** add path to base in launch program url ([2c31922](https://github.com/sasjs/server/commit/2c31922f58a8aa20d7fa6bfc95b53a350f90c798))
|
|
||||||
|
|
||||||
## [0.30.1](https://github.com/sasjs/server/compare/v0.30.0...v0.30.1) (2023-03-01)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **web:** add proper base url in axios.defaults ([5e3ce8a](https://github.com/sasjs/server/commit/5e3ce8a98f1825e14c1d26d8da0c9821beeff7b3))
|
|
||||||
|
|
||||||
# [0.30.0](https://github.com/sasjs/server/compare/v0.29.0...v0.30.0) (2023-02-28)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* lint + remove default settings ([3de59ac](https://github.com/sasjs/server/commit/3de59ac4f8e3d95cad31f09e6963bd04c4811f26))
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* add new env config DB_TYPE ([158f044](https://github.com/sasjs/server/commit/158f044363abf2576c8248f0ca9da4bc9cb7e9d8))
|
|
||||||
|
|
||||||
# [0.29.0](https://github.com/sasjs/server/compare/v0.28.7...v0.29.0) (2023-02-06)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* Add /SASjsApi endpoint in permissions ([b3402ea](https://github.com/sasjs/server/commit/b3402ea80afb8802eee8b8b6cbbbcc29903424bc))
|
|
||||||
|
|
||||||
## [0.28.7](https://github.com/sasjs/server/compare/v0.28.6...v0.28.7) (2023-02-03)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* add user to all users group on user creation ([2bae52e](https://github.com/sasjs/server/commit/2bae52e307327d7ee4a94b19d843abdc0ccec9d1))
|
|
||||||
|
|
||||||
## [0.28.6](https://github.com/sasjs/server/compare/v0.28.5...v0.28.6) (2023-01-26)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* show loading spinner on login screen while request is in process ([69f2576](https://github.com/sasjs/server/commit/69f2576ee6d3d7b7f3325922a88656d511e3ac88))
|
|
||||||
|
|
||||||
## [0.28.5](https://github.com/sasjs/server/compare/v0.28.4...v0.28.5) (2023-01-01)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* adding NOPRNGETLIST system option for faster startup ([96eca3a](https://github.com/sasjs/server/commit/96eca3a35dce4521150257ee019beb4488c8a08f))
|
|
||||||
|
|
||||||
## [0.28.4](https://github.com/sasjs/server/compare/v0.28.3...v0.28.4) (2022-12-07)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* replace main class with container class ([71c429b](https://github.com/sasjs/server/commit/71c429b093b91e2444ae75d946579dccc2e48636))
|
|
||||||
|
|
||||||
## [0.28.3](https://github.com/sasjs/server/compare/v0.28.2...v0.28.3) (2022-12-06)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* stringify json file ([1192583](https://github.com/sasjs/server/commit/1192583843d7efd1a6ab6943207f394c3ae966be))
|
|
||||||
|
|
||||||
## [0.28.2](https://github.com/sasjs/server/compare/v0.28.1...v0.28.2) (2022-12-05)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* execute child process asyncronously ([23c997b](https://github.com/sasjs/server/commit/23c997b3beabeb6b733ae893031d2f1a48f28ad2))
|
|
||||||
* JS / Python / R session folders should be NEW folders, not existing SAS folders ([39ba995](https://github.com/sasjs/server/commit/39ba995355daa24bb7ab22720f8fc57d2dc85f40))
|
|
||||||
|
|
||||||
## [0.28.1](https://github.com/sasjs/server/compare/v0.28.0...v0.28.1) (2022-11-28)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* update the content type header after the program has been executed ([4dcee4b](https://github.com/sasjs/server/commit/4dcee4b3c3950d402220b8f451c50ad98a317d83))
|
|
||||||
|
|
||||||
# [0.28.0](https://github.com/sasjs/server/compare/v0.27.0...v0.28.0) (2022-11-28)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* update the response header of request to stp/execute routes ([112431a](https://github.com/sasjs/server/commit/112431a1b7461989c04100418d67d975a2a8f354))
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* **api:** add the api endpoint for updating user password ([4581f32](https://github.com/sasjs/server/commit/4581f325344eb68c5df5a28492f132312f15bb5c))
|
|
||||||
* ask for updated password on first login ([1d48f88](https://github.com/sasjs/server/commit/1d48f8856b1fbbf3ef868914558333190e04981f))
|
|
||||||
* **web:** add the UI for updating user password ([8b8c43c](https://github.com/sasjs/server/commit/8b8c43c21bde5379825c5ec44ecd81a92425f605))
|
|
||||||
|
|
||||||
# [0.27.0](https://github.com/sasjs/server/compare/v0.26.2...v0.27.0) (2022-11-17)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* on startup add webout.sas file in sasautos folder ([200f6c5](https://github.com/sasjs/server/commit/200f6c596a6e732d799ed408f1f0fd92f216ba58))
|
|
||||||
|
|
||||||
## [0.26.2](https://github.com/sasjs/server/compare/v0.26.1...v0.26.2) (2022-11-15)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* comments ([7ae862c](https://github.com/sasjs/server/commit/7ae862c5ce720e9483d4728f4295dede4f849436))
|
|
||||||
|
|
||||||
## [0.26.1](https://github.com/sasjs/server/compare/v0.26.0...v0.26.1) (2022-11-15)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* change the expiration of access/refresh tokens from days to seconds ([bb05493](https://github.com/sasjs/server/commit/bb054938c5bd0535ae6b9da93ba0b14f9b80ddcd))
|
|
||||||
|
|
||||||
# [0.26.0](https://github.com/sasjs/server/compare/v0.25.1...v0.26.0) (2022-11-13)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **web:** dispose monaco editor actions in return of useEffect ([acc25cb](https://github.com/sasjs/server/commit/acc25cbd686952d3f1c65e57aefcebe1cb859cc7))
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* make access token duration configurable when creating client/secret ([2413c05](https://github.com/sasjs/server/commit/2413c05fea3960f7e5c3c8b7b2f85d61314f08db))
|
|
||||||
* make refresh token duration configurable ([abd5c64](https://github.com/sasjs/server/commit/abd5c64b4a726e3f17594a98111b6aa269b71fee))
|
|
||||||
|
|
||||||
## [0.25.1](https://github.com/sasjs/server/compare/v0.25.0...v0.25.1) (2022-11-07)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **web:** use mui treeView instead of custom implementation ([c51b504](https://github.com/sasjs/server/commit/c51b50428f32608bc46438e9d7964429b2d595da))
|
|
||||||
|
|
||||||
# [0.25.0](https://github.com/sasjs/server/compare/v0.24.0...v0.25.0) (2022-11-02)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* Enable DRIVE_LOCATION setting for deploying multiple instances of SASjs Server ([1c9d167](https://github.com/sasjs/server/commit/1c9d167f86bbbb108b96e9bc30efaf8de65d82ff))
|
|
||||||
|
|
||||||
# [0.24.0](https://github.com/sasjs/server/compare/v0.23.4...v0.24.0) (2022-10-28)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* cli mock testing ([6434123](https://github.com/sasjs/server/commit/643412340162e854f31fba2f162d83b7ab1751d8))
|
|
||||||
* mocking sas9 responses with JS STP ([36be3a7](https://github.com/sasjs/server/commit/36be3a7d5e7df79f9a1f3f00c3661b925f462383))
|
|
||||||
|
|
||||||
## [0.23.4](https://github.com/sasjs/server/compare/v0.23.3...v0.23.4) (2022-10-11)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* add action to editor ref for running code ([2412622](https://github.com/sasjs/server/commit/2412622367eb46c40f388e988ae4606a7ec239b2))
|
|
||||||
|
|
||||||
## [0.23.3](https://github.com/sasjs/server/compare/v0.23.2...v0.23.3) (2022-10-09)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* added domain for session cookies ([94072c3](https://github.com/sasjs/server/commit/94072c3d24a4d0d4c97900dc31bfbf1c9d2559b7))
|
|
||||||
|
|
||||||
## [0.23.2](https://github.com/sasjs/server/compare/v0.23.1...v0.23.2) (2022-10-06)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* bump in correct place ([14731e8](https://github.com/sasjs/server/commit/14731e8824fa9f3d1daf89fd62f9916d5e3fcae4))
|
|
||||||
* bumping sasjs/score ([258cc35](https://github.com/sasjs/server/commit/258cc35f14cf50f2160f607000c60de27593fd79))
|
|
||||||
* reverting commit ([fda0e0b](https://github.com/sasjs/server/commit/fda0e0b57d56e3b5231e626a8d933343ac0c5cdc))
|
|
||||||
|
|
||||||
## [0.23.1](https://github.com/sasjs/server/compare/v0.23.0...v0.23.1) (2022-10-04)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* ldap issues ([4d64420](https://github.com/sasjs/server/commit/4d64420c45424134b4d2014a2d5dd6e846ed03b3))
|
|
||||||
|
|
||||||
# [0.23.0](https://github.com/sasjs/server/compare/v0.22.1...v0.23.0) (2022-10-03)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* Enable SAS_PACKAGES in SASjs Server ([424f0fc](https://github.com/sasjs/server/commit/424f0fc1faec765eb7a14619584e649454105b70))
|
|
||||||
|
|
||||||
## [0.22.1](https://github.com/sasjs/server/compare/v0.22.0...v0.22.1) (2022-10-03)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* spelling issues ([3bb0597](https://github.com/sasjs/server/commit/3bb05974d216d69368f4498eb9f309bce7d97fd8))
|
|
||||||
|
|
||||||
# [0.22.0](https://github.com/sasjs/server/compare/v0.21.7...v0.22.0) (2022-10-03)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* do not throw error on deleting group when it is created by an external auth provider ([68f0c5c](https://github.com/sasjs/server/commit/68f0c5c5884431e7e8f586dccf98132abebb193e))
|
|
||||||
* no need to restrict api endpoints when ldap auth is applied ([a142660](https://github.com/sasjs/server/commit/a14266077d3541c7a33b7635efa4208335e73519))
|
|
||||||
* remove authProvider attribute from user and group payload interface ([bbd7786](https://github.com/sasjs/server/commit/bbd7786c6ce13b374d896a45c23255b8fa3e8bd2))
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* implemented LDAP authentication ([f915c51](https://github.com/sasjs/server/commit/f915c51b077a2b8c4099727355ed914ecd6364bd))
|
|
||||||
|
|
||||||
## [0.21.7](https://github.com/sasjs/server/compare/v0.21.6...v0.21.7) (2022-09-30)
|
## [0.21.7](https://github.com/sasjs/server/compare/v0.21.6...v0.21.7) (2022-09-30)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
50
README.md
50
README.md
@@ -93,10 +93,6 @@ R_PATH=/usr/bin/Rscript
|
|||||||
SASJS_ROOT=./sasjs_root
|
SASJS_ROOT=./sasjs_root
|
||||||
|
|
||||||
|
|
||||||
# This location is for files, sasjs packages and appStreamConfig.json
|
|
||||||
DRIVE_LOCATION=./sasjs_root/drive
|
|
||||||
|
|
||||||
|
|
||||||
# options: [http|https] default: http
|
# options: [http|https] default: http
|
||||||
PROTOCOL=
|
PROTOCOL=
|
||||||
|
|
||||||
@@ -107,11 +103,6 @@ PORT=
|
|||||||
# If not present, mocking function is disabled
|
# If not present, mocking function is disabled
|
||||||
MOCK_SERVERTYPE=
|
MOCK_SERVERTYPE=
|
||||||
|
|
||||||
# default: /api/mocks
|
|
||||||
# Path to mocking folder, for generic responses, it's sub directories should be: sas9, viya, sasjs
|
|
||||||
# Server will automatically use subdirectory accordingly
|
|
||||||
STATIC_MOCK_LOCATION=
|
|
||||||
|
|
||||||
#
|
#
|
||||||
## Additional SAS Options
|
## Additional SAS Options
|
||||||
#
|
#
|
||||||
@@ -134,22 +125,9 @@ PRIVATE_KEY=privkey.pem (required)
|
|||||||
CERT_CHAIN=certificate.pem (required)
|
CERT_CHAIN=certificate.pem (required)
|
||||||
CA_ROOT=fullchain.pem (optional)
|
CA_ROOT=fullchain.pem (optional)
|
||||||
|
|
||||||
## ENV variables required for MODE: `server`
|
# ENV variables required for MODE: `server`
|
||||||
DB_CONNECT=mongodb+srv://<DB_USERNAME>:<DB_PASSWORD>@<CLUSTER>/<DB_NAME>?retryWrites=true&w=majority
|
DB_CONNECT=mongodb+srv://<DB_USERNAME>:<DB_PASSWORD>@<CLUSTER>/<DB_NAME>?retryWrites=true&w=majority
|
||||||
|
|
||||||
# options: [mongodb|cosmos_mongodb] default: mongodb
|
|
||||||
DB_TYPE=
|
|
||||||
|
|
||||||
# AUTH_PROVIDERS options: [ldap] default: ``
|
|
||||||
AUTH_PROVIDERS=
|
|
||||||
|
|
||||||
## ENV variables required for AUTH_MECHANISM: `ldap`
|
|
||||||
LDAP_URL= <LDAP_SERVER_URL>
|
|
||||||
LDAP_BIND_DN= <cn=admin,ou=system,dc=cloudron>
|
|
||||||
LDAP_BIND_PASSWORD = <password>
|
|
||||||
LDAP_USERS_BASE_DN = <ou=users,dc=cloudron>
|
|
||||||
LDAP_GROUPS_BASE_DN = <ou=groups,dc=cloudron>
|
|
||||||
|
|
||||||
# options: [disable|enable] default: `disable` for `server` & `enable` for `desktop`
|
# options: [disable|enable] default: `disable` for `server` & `enable` for `desktop`
|
||||||
# If enabled, be sure to also configure the WHITELIST of third party servers.
|
# If enabled, be sure to also configure the WHITELIST of third party servers.
|
||||||
CORS=
|
CORS=
|
||||||
@@ -175,32 +153,6 @@ HELMET_COEP=
|
|||||||
# }
|
# }
|
||||||
HELMET_CSP_CONFIG_PATH=./csp.config.json
|
HELMET_CSP_CONFIG_PATH=./csp.config.json
|
||||||
|
|
||||||
# To prevent brute force attack on login route we have implemented rate limiter
|
|
||||||
# Only valid for MODE: server
|
|
||||||
# Following are configurable env variable rate limiter
|
|
||||||
|
|
||||||
# After this, access is blocked for 1 day
|
|
||||||
MAX_WRONG_ATTEMPTS_BY_IP_PER_DAY = <number> default: 100;
|
|
||||||
|
|
||||||
|
|
||||||
# After this, access is blocked for an hour
|
|
||||||
# Store number for 24 days since first fail
|
|
||||||
# Once a successful login is attempted, it resets
|
|
||||||
MAX_CONSECUTIVE_FAILS_BY_USERNAME_AND_IP = <number> default: 10;
|
|
||||||
|
|
||||||
# Name of the admin user that will be created on startup if not exists already
|
|
||||||
# Default is `secretuser`
|
|
||||||
ADMIN_USERNAME=secretuser
|
|
||||||
|
|
||||||
# Temporary password for the ADMIN_USERNAME, which is in place until the first login
|
|
||||||
# Default is `secretpassword`
|
|
||||||
ADMIN_PASSWORD_INITIAL=secretpassword
|
|
||||||
|
|
||||||
# Specify whether app has to reset the ADMIN_USERNAME's password or not
|
|
||||||
# Default is NO. Possible options are YES and NO
|
|
||||||
# If ADMIN_PASSWORD_RESET is YES then the ADMIN_USERNAME will be prompted to change the password from ADMIN_PASSWORD_INITIAL on their next login. This will repeat on every server restart, unless the option is removed / set to NO.
|
|
||||||
ADMIN_PASSWORD_RESET=NO
|
|
||||||
|
|
||||||
# LOG_FORMAT_MORGAN options: [combined|common|dev|short|tiny] default: `common`
|
# LOG_FORMAT_MORGAN options: [combined|common|dev|short|tiny] default: `common`
|
||||||
# Docs: https://www.npmjs.com/package/morgan#predefined-formats
|
# Docs: https://www.npmjs.com/package/morgan#predefined-formats
|
||||||
LOG_FORMAT_MORGAN=
|
LOG_FORMAT_MORGAN=
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
MODE=[desktop|server] default considered as desktop
|
MODE=[desktop|server] default considered as desktop
|
||||||
CORS=[disable|enable] default considered as disable for server MODE & enable for desktop MODE
|
CORS=[disable|enable] default considered as disable for server MODE & enable for desktop MODE
|
||||||
ALLOWED_DOMAIN=<just domain e.g. example.com >
|
ALLOWED_DOMAINS=<space separated urls, each starting with protocol `http` or `https`>
|
||||||
WHITELIST=<space separated urls, each starting with protocol `http` or `https`>
|
WHITELIST=<space separated urls, each starting with protocol `http` or `https`>
|
||||||
|
|
||||||
PROTOCOL=[http|https] default considered as http
|
PROTOCOL=[http|https] default considered as http
|
||||||
@@ -14,25 +14,6 @@ HELMET_CSP_CONFIG_PATH=./csp.config.json if omitted HELMET default will be used
|
|||||||
HELMET_COEP=[true|false] if omitted HELMET default will be used
|
HELMET_COEP=[true|false] if omitted HELMET default will be used
|
||||||
|
|
||||||
DB_CONNECT=mongodb+srv://<DB_USERNAME>:<DB_PASSWORD>@<CLUSTER>/<DB_NAME>?retryWrites=true&w=majority
|
DB_CONNECT=mongodb+srv://<DB_USERNAME>:<DB_PASSWORD>@<CLUSTER>/<DB_NAME>?retryWrites=true&w=majority
|
||||||
DB_TYPE=[mongodb|cosmos_mongodb] default considered as mongodb
|
|
||||||
|
|
||||||
AUTH_PROVIDERS=[ldap]
|
|
||||||
|
|
||||||
LDAP_URL= <LDAP_SERVER_URL>
|
|
||||||
LDAP_BIND_DN= <cn=admin,ou=system,dc=cloudron>
|
|
||||||
LDAP_BIND_PASSWORD = <password>
|
|
||||||
LDAP_USERS_BASE_DN = <ou=users,dc=cloudron>
|
|
||||||
LDAP_GROUPS_BASE_DN = <ou=groups,dc=cloudron>
|
|
||||||
|
|
||||||
#default value is 100
|
|
||||||
MAX_WRONG_ATTEMPTS_BY_IP_PER_DAY=100
|
|
||||||
|
|
||||||
#default value is 10
|
|
||||||
MAX_CONSECUTIVE_FAILS_BY_USERNAME_AND_IP=10
|
|
||||||
|
|
||||||
ADMIN_USERNAME=secretuser
|
|
||||||
ADMIN_PASSWORD_INITIAL=secretpassword
|
|
||||||
ADMIN_PASSWORD_RESET=NO
|
|
||||||
|
|
||||||
RUN_TIMES=[sas,js,py | js,py | sas | sas,js] default considered as sas
|
RUN_TIMES=[sas,js,py | js,py | sas | sas,js] default considered as sas
|
||||||
SAS_PATH=/opt/sas/sas9/SASHome/SASFoundation/9.4/sas
|
SAS_PATH=/opt/sas/sas9/SASHome/SASFoundation/9.4/sas
|
||||||
@@ -41,7 +22,6 @@ PYTHON_PATH=/usr/bin/python
|
|||||||
R_PATH=/usr/bin/Rscript
|
R_PATH=/usr/bin/Rscript
|
||||||
|
|
||||||
SASJS_ROOT=./sasjs_root
|
SASJS_ROOT=./sasjs_root
|
||||||
DRIVE_LOCATION=./sasjs_root/drive
|
|
||||||
|
|
||||||
LOG_FORMAT_MORGAN=common
|
LOG_FORMAT_MORGAN=common
|
||||||
LOG_LOCATION=./sasjs_root/logs
|
LOG_LOCATION=./sasjs_root/logs
|
||||||
0
api/mocks/custom/.keep
Normal file
0
api/mocks/custom/.keep
Normal file
@@ -9,7 +9,7 @@
|
|||||||
<div class="content">
|
<div class="content">
|
||||||
<form id="credentials" class="minimal" action="/SASLogon/login?service=http%3A%2F%2Flocalhost:5004%2FSASStoredProcess%2Fj_spring_cas_security_check" method="post">
|
<form id="credentials" class="minimal" action="/SASLogon/login?service=http%3A%2F%2Flocalhost:5004%2FSASStoredProcess%2Fj_spring_cas_security_check" method="post">
|
||||||
<!--form container-->
|
<!--form container-->
|
||||||
<input type="hidden" name="lt" value="validtoken" aria-hidden="true" />
|
<input type="hidden" name="lt" value="LT-8-WGkt9EXwICBihaVbxGc92opjufTK1D" aria-hidden="true" />
|
||||||
<input type="hidden" name="execution" value="e2s1" aria-hidden="true" />
|
<input type="hidden" name="execution" value="e2s1" aria-hidden="true" />
|
||||||
<input type="hidden" name="_eventId" value="submit" aria-hidden="true" />
|
<input type="hidden" name="_eventId" value="submit" aria-hidden="true" />
|
||||||
|
|
||||||
3794
api/package-lock.json
generated
3794
api/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@
|
|||||||
"description": "Api of SASjs server",
|
"description": "Api of SASjs server",
|
||||||
"main": "./src/server.ts",
|
"main": "./src/server.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"initial": "npm run swagger && npm run compileSysInit && npm run copySASjsCore && npm run downloadMacros",
|
"initial": "npm run swagger && npm run compileSysInit && npm run copySASjsCore",
|
||||||
"prestart": "npm run initial",
|
"prestart": "npm run initial",
|
||||||
"prebuild": "npm run initial",
|
"prebuild": "npm run initial",
|
||||||
"start": "NODE_ENV=development nodemon ./src/server.ts",
|
"start": "NODE_ENV=development nodemon ./src/server.ts",
|
||||||
@@ -17,21 +17,20 @@
|
|||||||
"lint:fix": "npx prettier --write \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\"",
|
"lint:fix": "npx prettier --write \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\"",
|
||||||
"lint": "npx prettier --check \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\"",
|
"lint": "npx prettier --check \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\"",
|
||||||
"exe": "npm run build && pkg .",
|
"exe": "npm run build && pkg .",
|
||||||
"copy:files": "npm run public:copy && npm run sasjsbuild:copy && npm run sas:copy && npm run web:copy",
|
"copy:files": "npm run public:copy && npm run sasjsbuild:copy && npm run sasjscore:copy && npm run web:copy",
|
||||||
"public:copy": "cp -r ./public/ ./build/public/",
|
"public:copy": "cp -r ./public/ ./build/public/",
|
||||||
"sasjsbuild:copy": "cp -r ./sasjsbuild/ ./build/sasjsbuild/",
|
"sasjsbuild:copy": "cp -r ./sasjsbuild/ ./build/sasjsbuild/",
|
||||||
"sas:copy": "cp -r ./sas/ ./build/sas/",
|
"sasjscore:copy": "cp -r ./sasjscore/ ./build/sasjscore/",
|
||||||
"web:copy": "rimraf web && mkdir web && cp -r ../web/build/ ./web/build/",
|
"web:copy": "rimraf web && mkdir web && cp -r ../web/build/ ./web/build/",
|
||||||
"compileSysInit": "ts-node ./scripts/compileSysInit.ts",
|
"compileSysInit": "ts-node ./scripts/compileSysInit.ts",
|
||||||
"copySASjsCore": "ts-node ./scripts/copySASjsCore.ts",
|
"copySASjsCore": "ts-node ./scripts/copySASjsCore.ts"
|
||||||
"downloadMacros": "ts-node ./scripts/downloadMacros.ts"
|
|
||||||
},
|
},
|
||||||
"bin": "./build/src/server.js",
|
"bin": "./build/src/server.js",
|
||||||
"pkg": {
|
"pkg": {
|
||||||
"assets": [
|
"assets": [
|
||||||
"./build/public/**/*",
|
"./build/public/**/*",
|
||||||
"./build/sasjsbuild/**/*",
|
"./build/sasjsbuild/**/*",
|
||||||
"./build/sas/**/*",
|
"./build/sasjscore/**/*",
|
||||||
"./web/build/**/*"
|
"./web/build/**/*"
|
||||||
],
|
],
|
||||||
"targets": [
|
"targets": [
|
||||||
@@ -48,8 +47,8 @@
|
|||||||
},
|
},
|
||||||
"author": "4GL Ltd",
|
"author": "4GL Ltd",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sasjs/core": "^4.40.1",
|
"@sasjs/core": "^4.31.3",
|
||||||
"@sasjs/utils": "3.2.0",
|
"@sasjs/utils": "2.48.1",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"connect-mongo": "^4.6.0",
|
"connect-mongo": "^4.6.0",
|
||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
@@ -59,11 +58,10 @@
|
|||||||
"helmet": "^5.0.2",
|
"helmet": "^5.0.2",
|
||||||
"joi": "^17.4.2",
|
"joi": "^17.4.2",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"ldapjs": "2.3.3",
|
|
||||||
"mongoose": "^6.0.12",
|
"mongoose": "^6.0.12",
|
||||||
|
"mongoose-sequence": "^5.3.1",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"multer": "^1.4.5-lts.1",
|
"multer": "^1.4.5-lts.1",
|
||||||
"rate-limiter-flexible": "2.4.1",
|
|
||||||
"rotating-file-stream": "^3.0.4",
|
"rotating-file-stream": "^3.0.4",
|
||||||
"swagger-ui-express": "4.3.0",
|
"swagger-ui-express": "4.3.0",
|
||||||
"unzipper": "^0.10.11",
|
"unzipper": "^0.10.11",
|
||||||
@@ -78,7 +76,7 @@
|
|||||||
"@types/express-session": "^1.17.4",
|
"@types/express-session": "^1.17.4",
|
||||||
"@types/jest": "^26.0.24",
|
"@types/jest": "^26.0.24",
|
||||||
"@types/jsonwebtoken": "^8.5.5",
|
"@types/jsonwebtoken": "^8.5.5",
|
||||||
"@types/ldapjs": "^2.2.4",
|
"@types/mongoose-sequence": "^3.0.6",
|
||||||
"@types/morgan": "^1.9.3",
|
"@types/morgan": "^1.9.3",
|
||||||
"@types/multer": "^1.4.7",
|
"@types/multer": "^1.4.7",
|
||||||
"@types/node": "^15.12.2",
|
"@types/node": "^15.12.2",
|
||||||
@@ -86,13 +84,11 @@
|
|||||||
"@types/swagger-ui-express": "^4.1.3",
|
"@types/swagger-ui-express": "^4.1.3",
|
||||||
"@types/unzipper": "^0.10.5",
|
"@types/unzipper": "^0.10.5",
|
||||||
"adm-zip": "^0.5.9",
|
"adm-zip": "^0.5.9",
|
||||||
"axios": "0.27.2",
|
|
||||||
"csrf": "^3.1.0",
|
"csrf": "^3.1.0",
|
||||||
"dotenv": "^10.0.0",
|
"dotenv": "^10.0.0",
|
||||||
"http-headers-validation": "^0.0.1",
|
"http-headers-validation": "^0.0.1",
|
||||||
"jest": "^27.0.6",
|
"jest": "^27.0.6",
|
||||||
"mongodb-memory-server": "8.11.4",
|
"mongodb-memory-server": "^8.0.0",
|
||||||
"nodejs-file-downloader": "4.10.2",
|
|
||||||
"nodemon": "^2.0.7",
|
"nodemon": "^2.0.7",
|
||||||
"pkg": "5.6.0",
|
"pkg": "5.6.0",
|
||||||
"prettier": "^2.3.1",
|
"prettier": "^2.3.1",
|
||||||
|
|||||||
@@ -40,27 +40,13 @@ components:
|
|||||||
clientId:
|
clientId:
|
||||||
type: string
|
type: string
|
||||||
userId:
|
userId:
|
||||||
type: string
|
type: number
|
||||||
|
format: double
|
||||||
required:
|
required:
|
||||||
- clientId
|
- clientId
|
||||||
- userId
|
- userId
|
||||||
type: object
|
type: object
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
UpdatePasswordPayload:
|
|
||||||
properties:
|
|
||||||
currentPassword:
|
|
||||||
type: string
|
|
||||||
description: 'Current Password'
|
|
||||||
example: currentPasswordString
|
|
||||||
newPassword:
|
|
||||||
type: string
|
|
||||||
description: 'New Password'
|
|
||||||
example: newPassword
|
|
||||||
required:
|
|
||||||
- currentPassword
|
|
||||||
- newPassword
|
|
||||||
type: object
|
|
||||||
additionalProperties: false
|
|
||||||
ClientPayload:
|
ClientPayload:
|
||||||
properties:
|
properties:
|
||||||
clientId:
|
clientId:
|
||||||
@@ -71,16 +57,6 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
description: 'Client Secret'
|
description: 'Client Secret'
|
||||||
example: someRandomCryptoString
|
example: someRandomCryptoString
|
||||||
accessTokenExpiration:
|
|
||||||
type: number
|
|
||||||
format: double
|
|
||||||
description: 'Number of seconds after which access token will expire. Default is 86400 (1 day)'
|
|
||||||
example: 86400
|
|
||||||
refreshTokenExpiration:
|
|
||||||
type: number
|
|
||||||
format: double
|
|
||||||
description: 'Number of seconds after which access token will expire. Default is 2592000 (30 days)'
|
|
||||||
example: 2592000
|
|
||||||
required:
|
required:
|
||||||
- clientId
|
- clientId
|
||||||
- clientSecret
|
- clientSecret
|
||||||
@@ -284,8 +260,9 @@ components:
|
|||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
UserResponse:
|
UserResponse:
|
||||||
properties:
|
properties:
|
||||||
uid:
|
id:
|
||||||
type: string
|
type: number
|
||||||
|
format: double
|
||||||
username:
|
username:
|
||||||
type: string
|
type: string
|
||||||
displayName:
|
displayName:
|
||||||
@@ -293,7 +270,7 @@ components:
|
|||||||
isAdmin:
|
isAdmin:
|
||||||
type: boolean
|
type: boolean
|
||||||
required:
|
required:
|
||||||
- uid
|
- id
|
||||||
- username
|
- username
|
||||||
- displayName
|
- displayName
|
||||||
- isAdmin
|
- isAdmin
|
||||||
@@ -301,30 +278,32 @@ components:
|
|||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
GroupResponse:
|
GroupResponse:
|
||||||
properties:
|
properties:
|
||||||
uid:
|
groupId:
|
||||||
type: string
|
type: number
|
||||||
|
format: double
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
description:
|
description:
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
- uid
|
- groupId
|
||||||
- name
|
- name
|
||||||
- description
|
- description
|
||||||
type: object
|
type: object
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
UserDetailsResponse:
|
UserDetailsResponse:
|
||||||
properties:
|
properties:
|
||||||
uid:
|
id:
|
||||||
|
type: number
|
||||||
|
format: double
|
||||||
|
displayName:
|
||||||
type: string
|
type: string
|
||||||
username:
|
username:
|
||||||
type: string
|
type: string
|
||||||
displayName:
|
|
||||||
type: string
|
|
||||||
isAdmin:
|
|
||||||
type: boolean
|
|
||||||
isActive:
|
isActive:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
isAdmin:
|
||||||
|
type: boolean
|
||||||
autoExec:
|
autoExec:
|
||||||
type: string
|
type: string
|
||||||
groups:
|
groups:
|
||||||
@@ -332,11 +311,11 @@ components:
|
|||||||
$ref: '#/components/schemas/GroupResponse'
|
$ref: '#/components/schemas/GroupResponse'
|
||||||
type: array
|
type: array
|
||||||
required:
|
required:
|
||||||
- uid
|
- id
|
||||||
- username
|
|
||||||
- displayName
|
- displayName
|
||||||
- isAdmin
|
- username
|
||||||
- isActive
|
- isActive
|
||||||
|
- isAdmin
|
||||||
type: object
|
type: object
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
UserPayload:
|
UserPayload:
|
||||||
@@ -372,8 +351,9 @@ components:
|
|||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
GroupDetailsResponse:
|
GroupDetailsResponse:
|
||||||
properties:
|
properties:
|
||||||
uid:
|
groupId:
|
||||||
type: string
|
type: number
|
||||||
|
format: double
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
description:
|
description:
|
||||||
@@ -385,7 +365,7 @@ components:
|
|||||||
$ref: '#/components/schemas/UserResponse'
|
$ref: '#/components/schemas/UserResponse'
|
||||||
type: array
|
type: array
|
||||||
required:
|
required:
|
||||||
- uid
|
- groupId
|
||||||
- name
|
- name
|
||||||
- description
|
- description
|
||||||
- isActive
|
- isActive
|
||||||
@@ -454,8 +434,9 @@ components:
|
|||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
PermissionDetailsResponse:
|
PermissionDetailsResponse:
|
||||||
properties:
|
properties:
|
||||||
uid:
|
permissionId:
|
||||||
type: string
|
type: number
|
||||||
|
format: double
|
||||||
path:
|
path:
|
||||||
type: string
|
type: string
|
||||||
type:
|
type:
|
||||||
@@ -467,7 +448,7 @@ components:
|
|||||||
group:
|
group:
|
||||||
$ref: '#/components/schemas/GroupDetailsResponse'
|
$ref: '#/components/schemas/GroupDetailsResponse'
|
||||||
required:
|
required:
|
||||||
- uid
|
- permissionId
|
||||||
- path
|
- path
|
||||||
- type
|
- type
|
||||||
- setting
|
- setting
|
||||||
@@ -506,8 +487,10 @@ components:
|
|||||||
description: 'Indicates the type of principal'
|
description: 'Indicates the type of principal'
|
||||||
example: user
|
example: user
|
||||||
principalId:
|
principalId:
|
||||||
type: string
|
type: number
|
||||||
|
format: double
|
||||||
description: 'The id of user or group to which a rule is assigned.'
|
description: 'The id of user or group to which a rule is assigned.'
|
||||||
|
example: 123
|
||||||
required:
|
required:
|
||||||
- path
|
- path
|
||||||
- type
|
- type
|
||||||
@@ -526,39 +509,6 @@ components:
|
|||||||
- setting
|
- setting
|
||||||
type: object
|
type: object
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
Pick_UserResponse.Exclude_keyofUserResponse.uid__:
|
|
||||||
properties:
|
|
||||||
username:
|
|
||||||
type: string
|
|
||||||
displayName:
|
|
||||||
type: string
|
|
||||||
isAdmin:
|
|
||||||
type: boolean
|
|
||||||
required:
|
|
||||||
- username
|
|
||||||
- displayName
|
|
||||||
- isAdmin
|
|
||||||
type: object
|
|
||||||
description: 'From T, pick a set of properties whose keys are in the union K'
|
|
||||||
SessionResponse:
|
|
||||||
properties:
|
|
||||||
username:
|
|
||||||
type: string
|
|
||||||
displayName:
|
|
||||||
type: string
|
|
||||||
isAdmin:
|
|
||||||
type: boolean
|
|
||||||
id:
|
|
||||||
type: string
|
|
||||||
needsToUpdatePassword:
|
|
||||||
type: boolean
|
|
||||||
required:
|
|
||||||
- username
|
|
||||||
- displayName
|
|
||||||
- isAdmin
|
|
||||||
- id
|
|
||||||
type: object
|
|
||||||
additionalProperties: false
|
|
||||||
ExecutePostRequestPayload:
|
ExecutePostRequestPayload:
|
||||||
properties:
|
properties:
|
||||||
_program:
|
_program:
|
||||||
@@ -672,70 +622,6 @@ paths:
|
|||||||
-
|
-
|
||||||
bearerAuth: []
|
bearerAuth: []
|
||||||
parameters: []
|
parameters: []
|
||||||
/SASjsApi/auth/updatePassword:
|
|
||||||
patch:
|
|
||||||
operationId: UpdatePassword
|
|
||||||
responses:
|
|
||||||
'204':
|
|
||||||
description: 'No content'
|
|
||||||
summary: 'Update user''s password.'
|
|
||||||
tags:
|
|
||||||
- Auth
|
|
||||||
security:
|
|
||||||
-
|
|
||||||
bearerAuth: []
|
|
||||||
parameters: []
|
|
||||||
requestBody:
|
|
||||||
required: true
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/UpdatePasswordPayload'
|
|
||||||
/SASjsApi/authConfig:
|
|
||||||
get:
|
|
||||||
operationId: GetDetail
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Ok
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema: {}
|
|
||||||
examples:
|
|
||||||
'Example 1':
|
|
||||||
value: {ldap: {LDAP_URL: 'ldaps://my.ldap.server:636', LDAP_BIND_DN: 'cn=admin,ou=system,dc=cloudron', LDAP_BIND_PASSWORD: secret, LDAP_USERS_BASE_DN: 'ou=users,dc=cloudron', LDAP_GROUPS_BASE_DN: 'ou=groups,dc=cloudron'}}
|
|
||||||
summary: 'Gives the detail of Auth Mechanism.'
|
|
||||||
tags:
|
|
||||||
- Auth_Config
|
|
||||||
security:
|
|
||||||
-
|
|
||||||
bearerAuth: []
|
|
||||||
parameters: []
|
|
||||||
/SASjsApi/authConfig/synchroniseWithLDAP:
|
|
||||||
post:
|
|
||||||
operationId: SynchroniseWithLDAP
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Ok
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
properties:
|
|
||||||
groupCount: {type: number, format: double}
|
|
||||||
userCount: {type: number, format: double}
|
|
||||||
required:
|
|
||||||
- groupCount
|
|
||||||
- userCount
|
|
||||||
type: object
|
|
||||||
examples:
|
|
||||||
'Example 1':
|
|
||||||
value: {users: 5, groups: 3}
|
|
||||||
summary: 'Synchronises LDAP users and groups with internal DB and returns the count of imported users and groups.'
|
|
||||||
tags:
|
|
||||||
- Auth_Config
|
|
||||||
security:
|
|
||||||
-
|
|
||||||
bearerAuth: []
|
|
||||||
parameters: []
|
|
||||||
/SASjsApi/client:
|
/SASjsApi/client:
|
||||||
post:
|
post:
|
||||||
operationId: CreateClient
|
operationId: CreateClient
|
||||||
@@ -748,8 +634,8 @@ paths:
|
|||||||
$ref: '#/components/schemas/ClientPayload'
|
$ref: '#/components/schemas/ClientPayload'
|
||||||
examples:
|
examples:
|
||||||
'Example 1':
|
'Example 1':
|
||||||
value: {clientId: someFormattedClientID1234, clientSecret: someRandomCryptoString, accessTokenExpiration: 86400}
|
value: {clientId: someFormattedClientID1234, clientSecret: someRandomCryptoString}
|
||||||
summary: "Admin only task. Create client with the following attributes:\nClientId,\nClientSecret,\naccessTokenExpiration (optional),\nrefreshTokenExpiration (optional)"
|
summary: 'Create client with the following attributes: ClientId, ClientSecret. Admin only task.'
|
||||||
tags:
|
tags:
|
||||||
- Client
|
- Client
|
||||||
security:
|
security:
|
||||||
@@ -762,27 +648,6 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/ClientPayload'
|
$ref: '#/components/schemas/ClientPayload'
|
||||||
get:
|
|
||||||
operationId: GetAllClients
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Ok
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
items:
|
|
||||||
$ref: '#/components/schemas/ClientPayload'
|
|
||||||
type: array
|
|
||||||
examples:
|
|
||||||
'Example 1':
|
|
||||||
value: [{clientId: someClientID1234, clientSecret: someRandomCryptoString, accessTokenExpiration: 86400}, {clientId: someOtherClientID, clientSecret: someOtherRandomCryptoString, accessTokenExpiration: 86400}]
|
|
||||||
summary: 'Admin only task. Returns the list of all the clients'
|
|
||||||
tags:
|
|
||||||
- Client
|
|
||||||
security:
|
|
||||||
-
|
|
||||||
bearerAuth: []
|
|
||||||
parameters: []
|
|
||||||
/SASjsApi/code/execute:
|
/SASjsApi/code/execute:
|
||||||
post:
|
post:
|
||||||
operationId: ExecuteCode
|
operationId: ExecuteCode
|
||||||
@@ -1210,7 +1075,7 @@ paths:
|
|||||||
type: array
|
type: array
|
||||||
examples:
|
examples:
|
||||||
'Example 1':
|
'Example 1':
|
||||||
value: [{uid: userIdString, username: johnusername, displayName: John, isAdmin: false}, {uid: anotherUserIdString, username: starkusername, displayName: Stark, isAdmin: true}]
|
value: [{id: 123, username: johnusername, displayName: John, isAdmin: false}, {id: 456, username: starkusername, displayName: Stark, isAdmin: true}]
|
||||||
summary: 'Get list of all users (username, displayname). All users can request this.'
|
summary: 'Get list of all users (username, displayname). All users can request this.'
|
||||||
tags:
|
tags:
|
||||||
- User
|
- User
|
||||||
@@ -1229,7 +1094,7 @@ paths:
|
|||||||
$ref: '#/components/schemas/UserDetailsResponse'
|
$ref: '#/components/schemas/UserDetailsResponse'
|
||||||
examples:
|
examples:
|
||||||
'Example 1':
|
'Example 1':
|
||||||
value: {uid: userIdString, displayName: 'John Snow', username: johnSnow01, isAdmin: false, isActive: true}
|
value: {id: 1234, displayName: 'John Snow', username: johnSnow01, isAdmin: false, isActive: true}
|
||||||
summary: 'Create user with the following attributes: UserId, UserName, Password, isAdmin, isActive. Admin only task.'
|
summary: 'Create user with the following attributes: UserId, UserName, Password, isAdmin, isActive. Admin only task.'
|
||||||
tags:
|
tags:
|
||||||
- User
|
- User
|
||||||
@@ -1280,7 +1145,7 @@ paths:
|
|||||||
$ref: '#/components/schemas/UserDetailsResponse'
|
$ref: '#/components/schemas/UserDetailsResponse'
|
||||||
examples:
|
examples:
|
||||||
'Example 1':
|
'Example 1':
|
||||||
value: {uid: userIdString, displayName: 'John Snow', username: johnSnow01, isAdmin: false, isActive: true}
|
value: {id: 1234, displayName: 'John Snow', username: johnSnow01, isAdmin: false, isActive: true}
|
||||||
summary: 'Update user properties - such as displayName. Can be performed either by admins, or the user in question.'
|
summary: 'Update user properties - such as displayName. Can be performed either by admins, or the user in question.'
|
||||||
tags:
|
tags:
|
||||||
- User
|
- User
|
||||||
@@ -1331,7 +1196,7 @@ paths:
|
|||||||
password:
|
password:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
'/SASjsApi/user/{uid}':
|
'/SASjsApi/user/{userId}':
|
||||||
get:
|
get:
|
||||||
operationId: GetUser
|
operationId: GetUser
|
||||||
responses:
|
responses:
|
||||||
@@ -1350,12 +1215,14 @@ paths:
|
|||||||
bearerAuth: []
|
bearerAuth: []
|
||||||
parameters:
|
parameters:
|
||||||
-
|
-
|
||||||
|
description: 'The user''s identifier'
|
||||||
in: path
|
in: path
|
||||||
name: uid
|
name: userId
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
type: string
|
format: double
|
||||||
'/SASjsApi/user/{userId}':
|
type: number
|
||||||
|
example: 1234
|
||||||
patch:
|
patch:
|
||||||
operationId: UpdateUser
|
operationId: UpdateUser
|
||||||
responses:
|
responses:
|
||||||
@@ -1367,7 +1234,7 @@ paths:
|
|||||||
$ref: '#/components/schemas/UserDetailsResponse'
|
$ref: '#/components/schemas/UserDetailsResponse'
|
||||||
examples:
|
examples:
|
||||||
'Example 1':
|
'Example 1':
|
||||||
value: {uid: userIdString, displayName: 'John Snow', username: johnSnow01, isAdmin: false, isActive: true}
|
value: {id: 1234, displayName: 'John Snow', username: johnSnow01, isAdmin: false, isActive: true}
|
||||||
summary: 'Update user properties - such as displayName. Can be performed either by admins, or the user in question.'
|
summary: 'Update user properties - such as displayName. Can be performed either by admins, or the user in question.'
|
||||||
tags:
|
tags:
|
||||||
- User
|
- User
|
||||||
@@ -1381,7 +1248,8 @@ paths:
|
|||||||
name: userId
|
name: userId
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
type: string
|
format: double
|
||||||
|
type: number
|
||||||
example: '1234'
|
example: '1234'
|
||||||
requestBody:
|
requestBody:
|
||||||
required: true
|
required: true
|
||||||
@@ -1407,7 +1275,8 @@ paths:
|
|||||||
name: userId
|
name: userId
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
type: string
|
format: double
|
||||||
|
type: number
|
||||||
example: 1234
|
example: 1234
|
||||||
requestBody:
|
requestBody:
|
||||||
required: true
|
required: true
|
||||||
@@ -1432,7 +1301,7 @@ paths:
|
|||||||
type: array
|
type: array
|
||||||
examples:
|
examples:
|
||||||
'Example 1':
|
'Example 1':
|
||||||
value: [{uid: groupIdString, name: DCGroup, description: 'This group represents Data Controller Users'}]
|
value: [{groupId: 123, name: DCGroup, description: 'This group represents Data Controller Users'}]
|
||||||
summary: 'Get list of all groups (groupName and groupDescription). All users can request this.'
|
summary: 'Get list of all groups (groupName and groupDescription). All users can request this.'
|
||||||
tags:
|
tags:
|
||||||
- Group
|
- Group
|
||||||
@@ -1451,7 +1320,7 @@ paths:
|
|||||||
$ref: '#/components/schemas/GroupDetailsResponse'
|
$ref: '#/components/schemas/GroupDetailsResponse'
|
||||||
examples:
|
examples:
|
||||||
'Example 1':
|
'Example 1':
|
||||||
value: {uid: groupIdString, name: DCGroup, description: 'This group represents Data Controller Users', isActive: true, users: []}
|
value: {groupId: 123, name: DCGroup, description: 'This group represents Data Controller Users', isActive: true, users: []}
|
||||||
summary: 'Create a new group. Admin only.'
|
summary: 'Create a new group. Admin only.'
|
||||||
tags:
|
tags:
|
||||||
- Group
|
- Group
|
||||||
@@ -1467,7 +1336,7 @@ paths:
|
|||||||
$ref: '#/components/schemas/GroupPayload'
|
$ref: '#/components/schemas/GroupPayload'
|
||||||
'/SASjsApi/group/by/groupname/{name}':
|
'/SASjsApi/group/by/groupname/{name}':
|
||||||
get:
|
get:
|
||||||
operationId: GetGroupByName
|
operationId: GetGroupByGroupName
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: Ok
|
description: Ok
|
||||||
@@ -1489,7 +1358,7 @@ paths:
|
|||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
'/SASjsApi/group/{uid}':
|
'/SASjsApi/group/{groupId}':
|
||||||
get:
|
get:
|
||||||
operationId: GetGroup
|
operationId: GetGroup
|
||||||
responses:
|
responses:
|
||||||
@@ -1509,11 +1378,12 @@ paths:
|
|||||||
-
|
-
|
||||||
description: 'The group''s identifier'
|
description: 'The group''s identifier'
|
||||||
in: path
|
in: path
|
||||||
name: uid
|
name: groupId
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
type: string
|
format: double
|
||||||
example: 12ByteString
|
type: number
|
||||||
|
example: 1234
|
||||||
delete:
|
delete:
|
||||||
operationId: DeleteGroup
|
operationId: DeleteGroup
|
||||||
responses:
|
responses:
|
||||||
@@ -1535,12 +1405,13 @@ paths:
|
|||||||
-
|
-
|
||||||
description: 'The group''s identifier'
|
description: 'The group''s identifier'
|
||||||
in: path
|
in: path
|
||||||
name: uid
|
name: groupId
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
type: string
|
format: double
|
||||||
example: 12ByteString
|
type: number
|
||||||
'/SASjsApi/group/{groupUid}/{userUid}':
|
example: 1234
|
||||||
|
'/SASjsApi/group/{groupId}/{userId}':
|
||||||
post:
|
post:
|
||||||
operationId: AddUserToGroup
|
operationId: AddUserToGroup
|
||||||
responses:
|
responses:
|
||||||
@@ -1552,7 +1423,7 @@ paths:
|
|||||||
$ref: '#/components/schemas/GroupDetailsResponse'
|
$ref: '#/components/schemas/GroupDetailsResponse'
|
||||||
examples:
|
examples:
|
||||||
'Example 1':
|
'Example 1':
|
||||||
value: {uid: groupIdString, name: DCGroup, description: 'This group represents Data Controller Users', isActive: true, users: []}
|
value: {groupId: 123, name: DCGroup, description: 'This group represents Data Controller Users', isActive: true, users: []}
|
||||||
summary: 'Add a user to a group. Admin task only.'
|
summary: 'Add a user to a group. Admin task only.'
|
||||||
tags:
|
tags:
|
||||||
- Group
|
- Group
|
||||||
@@ -1563,18 +1434,21 @@ paths:
|
|||||||
-
|
-
|
||||||
description: 'The group''s identifier'
|
description: 'The group''s identifier'
|
||||||
in: path
|
in: path
|
||||||
name: groupUid
|
name: groupId
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
type: string
|
format: double
|
||||||
example: 12ByteString
|
type: number
|
||||||
|
example: '1234'
|
||||||
-
|
-
|
||||||
description: 'The user''s identifier'
|
description: 'The user''s identifier'
|
||||||
in: path
|
in: path
|
||||||
name: userUid
|
name: userId
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
type: string
|
format: double
|
||||||
|
type: number
|
||||||
|
example: '6789'
|
||||||
delete:
|
delete:
|
||||||
operationId: RemoveUserFromGroup
|
operationId: RemoveUserFromGroup
|
||||||
responses:
|
responses:
|
||||||
@@ -1586,8 +1460,8 @@ paths:
|
|||||||
$ref: '#/components/schemas/GroupDetailsResponse'
|
$ref: '#/components/schemas/GroupDetailsResponse'
|
||||||
examples:
|
examples:
|
||||||
'Example 1':
|
'Example 1':
|
||||||
value: {uid: groupIdString, name: DCGroup, description: 'This group represents Data Controller Users', isActive: true, users: []}
|
value: {groupId: 123, name: DCGroup, description: 'This group represents Data Controller Users', isActive: true, users: []}
|
||||||
summary: 'Remove a user from a group. Admin task only.'
|
summary: 'Remove a user to a group. Admin task only.'
|
||||||
tags:
|
tags:
|
||||||
- Group
|
- Group
|
||||||
security:
|
security:
|
||||||
@@ -1597,19 +1471,21 @@ paths:
|
|||||||
-
|
-
|
||||||
description: 'The group''s identifier'
|
description: 'The group''s identifier'
|
||||||
in: path
|
in: path
|
||||||
name: groupUid
|
name: groupId
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
type: string
|
format: double
|
||||||
example: 12ByteString
|
type: number
|
||||||
|
example: '1234'
|
||||||
-
|
-
|
||||||
description: 'The user''s identifier'
|
description: 'The user''s identifier'
|
||||||
in: path
|
in: path
|
||||||
name: userUid
|
name: userId
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
type: string
|
format: double
|
||||||
example: 12ByteString
|
type: number
|
||||||
|
example: '6789'
|
||||||
/SASjsApi/info:
|
/SASjsApi/info:
|
||||||
get:
|
get:
|
||||||
operationId: Info
|
operationId: Info
|
||||||
@@ -1660,7 +1536,7 @@ paths:
|
|||||||
type: array
|
type: array
|
||||||
examples:
|
examples:
|
||||||
'Example 1':
|
'Example 1':
|
||||||
value: [{uid: permissionId1String, path: /SASjsApi/code/execute, type: Route, setting: Grant, user: {uid: user1-id, username: johnSnow01, displayName: 'John Snow', isAdmin: false}}, {uid: permissionId2String, path: /SASjsApi/code/execute, type: Route, setting: Grant, group: {uid: group1-id, name: DCGroup, description: 'This group represents Data Controller Users', isActive: true, users: []}}]
|
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."
|
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.'
|
summary: 'Get the list of permission rules. If the user is admin, all rules are returned.'
|
||||||
tags:
|
tags:
|
||||||
@@ -1680,7 +1556,7 @@ paths:
|
|||||||
$ref: '#/components/schemas/PermissionDetailsResponse'
|
$ref: '#/components/schemas/PermissionDetailsResponse'
|
||||||
examples:
|
examples:
|
||||||
'Example 1':
|
'Example 1':
|
||||||
value: {uid: permissionIdString, path: /SASjsApi/code/execute, type: Route, setting: Grant, user: {uid: userIdString, username: johnSnow01, displayName: 'John Snow', isAdmin: false}}
|
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.'
|
summary: 'Create a new permission. Admin only.'
|
||||||
tags:
|
tags:
|
||||||
- Permission
|
- Permission
|
||||||
@@ -1694,7 +1570,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/RegisterPermissionPayload'
|
$ref: '#/components/schemas/RegisterPermissionPayload'
|
||||||
'/SASjsApi/permission/{uid}':
|
'/SASjsApi/permission/{permissionId}':
|
||||||
patch:
|
patch:
|
||||||
operationId: UpdatePermission
|
operationId: UpdatePermission
|
||||||
responses:
|
responses:
|
||||||
@@ -1706,7 +1582,7 @@ paths:
|
|||||||
$ref: '#/components/schemas/PermissionDetailsResponse'
|
$ref: '#/components/schemas/PermissionDetailsResponse'
|
||||||
examples:
|
examples:
|
||||||
'Example 1':
|
'Example 1':
|
||||||
value: {uid: permissionIdString, path: /SASjsApi/code/execute, type: Route, setting: Grant, user: {uid: userIdString, username: johnSnow01, displayName: 'John Snow', isAdmin: false}}
|
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'
|
summary: 'Update permission setting. Admin only'
|
||||||
tags:
|
tags:
|
||||||
- Permission
|
- Permission
|
||||||
@@ -1715,11 +1591,14 @@ paths:
|
|||||||
bearerAuth: []
|
bearerAuth: []
|
||||||
parameters:
|
parameters:
|
||||||
-
|
-
|
||||||
|
description: 'The permission''s identifier'
|
||||||
in: path
|
in: path
|
||||||
name: uid
|
name: permissionId
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
type: string
|
format: double
|
||||||
|
type: number
|
||||||
|
example: 1234
|
||||||
requestBody:
|
requestBody:
|
||||||
required: true
|
required: true
|
||||||
content:
|
content:
|
||||||
@@ -1739,11 +1618,14 @@ paths:
|
|||||||
bearerAuth: []
|
bearerAuth: []
|
||||||
parameters:
|
parameters:
|
||||||
-
|
-
|
||||||
|
description: 'The user''s identifier'
|
||||||
in: path
|
in: path
|
||||||
name: uid
|
name: permissionId
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
type: string
|
format: double
|
||||||
|
type: number
|
||||||
|
example: 1234
|
||||||
/SASjsApi/session:
|
/SASjsApi/session:
|
||||||
get:
|
get:
|
||||||
operationId: Session
|
operationId: Session
|
||||||
@@ -1753,10 +1635,10 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/SessionResponse'
|
$ref: '#/components/schemas/UserResponse'
|
||||||
examples:
|
examples:
|
||||||
'Example 1':
|
'Example 1':
|
||||||
value: {id: userIdString, username: johnusername, displayName: John, isAdmin: false, needsToUpdatePassword: false}
|
value: {id: 123, username: johnusername, displayName: John, isAdmin: false}
|
||||||
summary: 'Get session info (username).'
|
summary: 'Get session info (username).'
|
||||||
tags:
|
tags:
|
||||||
- Session
|
- Session
|
||||||
@@ -1850,7 +1732,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
properties:
|
properties:
|
||||||
user: {properties: {needsToUpdatePassword: {type: boolean}, isAdmin: {type: boolean}, displayName: {type: string}, username: {type: string}, id: {}}, required: [needsToUpdatePassword, isAdmin, displayName, username, id], type: object}
|
user: {properties: {isAdmin: {type: boolean}, displayName: {type: string}, username: {type: string}, id: {type: number, format: double}}, required: [isAdmin, displayName, username, id], type: object}
|
||||||
loggedIn: {type: boolean}
|
loggedIn: {type: boolean}
|
||||||
required:
|
required:
|
||||||
- user
|
- user
|
||||||
@@ -1912,9 +1794,6 @@ tags:
|
|||||||
-
|
-
|
||||||
name: Auth
|
name: Auth
|
||||||
description: 'Operations about auth'
|
description: 'Operations about auth'
|
||||||
-
|
|
||||||
name: Auth_Config
|
|
||||||
description: 'Operations about external auth providers'
|
|
||||||
-
|
-
|
||||||
name: Client
|
name: Client
|
||||||
description: 'Operations about clients'
|
description: 'Operations about clients'
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
import axios from 'axios'
|
|
||||||
import Downloader from 'nodejs-file-downloader'
|
|
||||||
import { createFile, listFilesInFolder } from '@sasjs/utils'
|
|
||||||
|
|
||||||
import { sasJSCoreMacros, sasJSCoreMacrosInfo } from '../src/utils/file'
|
|
||||||
|
|
||||||
export const downloadMacros = async () => {
|
|
||||||
const url =
|
|
||||||
'https://api.github.com/repos/yabwon/SAS_PACKAGES/contents/SPF/Macros'
|
|
||||||
|
|
||||||
console.info(`Downloading macros from ${url}`)
|
|
||||||
|
|
||||||
await axios
|
|
||||||
.get(url)
|
|
||||||
.then(async (res) => {
|
|
||||||
await downloadFiles(res.data)
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
throw new Error(err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const downloadFiles = async function (fileList: any) {
|
|
||||||
for (const file of fileList) {
|
|
||||||
const downloader = new Downloader({
|
|
||||||
url: file.download_url,
|
|
||||||
directory: sasJSCoreMacros,
|
|
||||||
fileName: file.path.replace(/^SPF\/Macros/, ''),
|
|
||||||
cloneFiles: false
|
|
||||||
})
|
|
||||||
await downloader.download()
|
|
||||||
}
|
|
||||||
|
|
||||||
const fileNames = await listFilesInFolder(sasJSCoreMacros)
|
|
||||||
|
|
||||||
await createFile(sasJSCoreMacrosInfo, fileNames.join('\n'))
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadMacros()
|
|
||||||
@@ -3,10 +3,21 @@ import cors from 'cors'
|
|||||||
import { CorsType } from '../utils'
|
import { CorsType } from '../utils'
|
||||||
|
|
||||||
export const configureCors = (app: Express) => {
|
export const configureCors = (app: Express) => {
|
||||||
|
const { CORS } = process.env
|
||||||
|
|
||||||
|
if (CORS === CorsType.ENABLED) {
|
||||||
|
const whiteList = getWhiteListed()
|
||||||
|
|
||||||
|
console.log('All CORS Requests are enabled for:', whiteList)
|
||||||
|
app.use(cors({ credentials: true, origin: whiteList }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getWhiteListed = (): string[] => {
|
||||||
|
const whiteList: string[] = []
|
||||||
const { CORS, WHITELIST } = process.env
|
const { CORS, WHITELIST } = process.env
|
||||||
|
|
||||||
if (CORS === CorsType.ENABLED) {
|
if (CORS === CorsType.ENABLED) {
|
||||||
const whiteList: string[] = []
|
|
||||||
WHITELIST?.split(' ')
|
WHITELIST?.split(' ')
|
||||||
?.filter((url) => !!url)
|
?.filter((url) => !!url)
|
||||||
.forEach((url) => {
|
.forEach((url) => {
|
||||||
@@ -14,8 +25,6 @@ export const configureCors = (app: Express) => {
|
|||||||
// removing trailing slash of URLs listing for CORS
|
// removing trailing slash of URLs listing for CORS
|
||||||
whiteList.push(url.replace(/\/$/, ''))
|
whiteList.push(url.replace(/\/$/, ''))
|
||||||
})
|
})
|
||||||
|
|
||||||
process.logger.info('All CORS Requests are enabled for:', whiteList)
|
|
||||||
app.use(cors({ credentials: true, origin: whiteList }))
|
|
||||||
}
|
}
|
||||||
|
return whiteList
|
||||||
}
|
}
|
||||||
|
|||||||
29
api/src/app-modules/configureDomains.ts
Normal file
29
api/src/app-modules/configureDomains.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { Express } from 'express'
|
||||||
|
import { checkDomain } from '../middlewares'
|
||||||
|
import { getWhiteListed } from './configureCors'
|
||||||
|
|
||||||
|
export const configureDomains = (app: Express) => {
|
||||||
|
// const domains: string[] = []
|
||||||
|
const domains = new Set<string>()
|
||||||
|
const { ALLOWED_DOMAINS } = process.env
|
||||||
|
|
||||||
|
const allowedDomains = ALLOWED_DOMAINS?.trim().split(' ') ?? []
|
||||||
|
|
||||||
|
const whiteListed = getWhiteListed()
|
||||||
|
|
||||||
|
const combinedUrls = [...allowedDomains, ...whiteListed]
|
||||||
|
combinedUrls
|
||||||
|
.filter((domainName) => !!domainName)
|
||||||
|
.forEach((url) => {
|
||||||
|
try {
|
||||||
|
const domain = new URL(url)
|
||||||
|
domains.add(domain.hostname)
|
||||||
|
} catch (_) {}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (domains.size) {
|
||||||
|
process.allowedDomains = [...domains]
|
||||||
|
console.log('Allowed Domain(s):', process.allowedDomains)
|
||||||
|
app.use(checkDomain)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,36 +3,28 @@ import mongoose from 'mongoose'
|
|||||||
import session from 'express-session'
|
import session from 'express-session'
|
||||||
import MongoStore from 'connect-mongo'
|
import MongoStore from 'connect-mongo'
|
||||||
|
|
||||||
import { DatabaseType, ModeType, ProtocolType } from '../utils'
|
import { ModeType, ProtocolType } from '../utils'
|
||||||
|
|
||||||
export const configureExpressSession = (app: Express) => {
|
export const configureExpressSession = (app: Express) => {
|
||||||
const { MODE, DB_TYPE } = process.env
|
const { MODE } = process.env
|
||||||
|
|
||||||
if (MODE === ModeType.Server) {
|
if (MODE === ModeType.Server) {
|
||||||
let store: MongoStore | undefined
|
let store: MongoStore | undefined
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'test') {
|
if (process.env.NODE_ENV !== 'test') {
|
||||||
if (DB_TYPE === DatabaseType.COSMOS_MONGODB) {
|
store = MongoStore.create({
|
||||||
// COSMOS DB requires specific connection options (compatibility mode)
|
client: mongoose.connection!.getClient() as any,
|
||||||
// See: https://www.npmjs.com/package/connect-mongo#set-the-compatibility-mode
|
collectionName: 'sessions'
|
||||||
store = MongoStore.create({
|
})
|
||||||
client: mongoose.connection!.getClient() as any,
|
|
||||||
autoRemove: 'interval'
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
store = MongoStore.create({
|
|
||||||
client: mongoose.connection!.getClient() as any
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { PROTOCOL, ALLOWED_DOMAIN } = process.env
|
const { PROTOCOL } = process.env
|
||||||
const cookieOptions: CookieOptions = {
|
const cookieOptions: CookieOptions = {
|
||||||
secure: PROTOCOL === ProtocolType.HTTPS,
|
secure: PROTOCOL === ProtocolType.HTTPS,
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
sameSite: PROTOCOL === ProtocolType.HTTPS ? 'none' : undefined,
|
sameSite: PROTOCOL === ProtocolType.HTTPS ? 'none' : undefined,
|
||||||
maxAge: 24 * 60 * 60 * 1000, // 24 hours
|
maxAge: 24 * 60 * 60 * 1000, // 24 hours
|
||||||
domain: ALLOWED_DOMAIN?.trim() || undefined
|
domain: 'sas.4gl.io'
|
||||||
}
|
}
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export const configureLogger = (app: Express) => {
|
|||||||
path: logsFolder
|
path: logsFolder
|
||||||
})
|
})
|
||||||
|
|
||||||
process.logger.info('Writing Logs to :', path.join(logsFolder, filename))
|
console.log('Writing Logs to :', path.join(logsFolder, filename))
|
||||||
|
|
||||||
options = { stream: accessLogStream }
|
options = { stream: accessLogStream }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export * from './configureCors'
|
export * from './configureCors'
|
||||||
|
export * from './configureDomains'
|
||||||
export * from './configureExpressSession'
|
export * from './configureExpressSession'
|
||||||
export * from './configureLogger'
|
export * from './configureLogger'
|
||||||
export * from './configureSecurity'
|
export * from './configureSecurity'
|
||||||
|
|||||||
@@ -5,26 +5,21 @@ import dotenv from 'dotenv'
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
copySASjsCore,
|
copySASjsCore,
|
||||||
createWeboutSasFile,
|
|
||||||
getFilesFolder,
|
|
||||||
getPackagesFolder,
|
|
||||||
getWebBuildFolder,
|
getWebBuildFolder,
|
||||||
instantiateLogger,
|
instantiateLogger,
|
||||||
loadAppStreamConfig,
|
loadAppStreamConfig,
|
||||||
ReturnCode,
|
ReturnCode,
|
||||||
setProcessVariables,
|
setProcessVariables,
|
||||||
setupFilesFolder,
|
setupFolders,
|
||||||
setupPackagesFolder,
|
|
||||||
setupUserAutoExec,
|
|
||||||
verifyEnvVariables
|
verifyEnvVariables
|
||||||
} from './utils'
|
} from './utils'
|
||||||
import {
|
import {
|
||||||
configureCors,
|
configureCors,
|
||||||
|
configureDomains,
|
||||||
configureExpressSession,
|
configureExpressSession,
|
||||||
configureLogger,
|
configureLogger,
|
||||||
configureSecurity
|
configureSecurity
|
||||||
} from './app-modules'
|
} from './app-modules'
|
||||||
import { folderExists } from '@sasjs/utils'
|
|
||||||
|
|
||||||
dotenv.config()
|
dotenv.config()
|
||||||
|
|
||||||
@@ -35,7 +30,7 @@ if (verifyEnvVariables()) process.exit(ReturnCode.InvalidEnv)
|
|||||||
const app = express()
|
const app = express()
|
||||||
|
|
||||||
const onError: ErrorRequestHandler = (err, req, res, next) => {
|
const onError: ErrorRequestHandler = (err, req, res, next) => {
|
||||||
process.logger.error(err.stack)
|
console.error(err.stack)
|
||||||
res.status(500).send('Something broke!')
|
res.status(500).send('Something broke!')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,6 +49,11 @@ export default setProcessVariables().then(async () => {
|
|||||||
***********************************/
|
***********************************/
|
||||||
configureCors(app)
|
configureCors(app)
|
||||||
|
|
||||||
|
/***********************************
|
||||||
|
* Allowed Domains *
|
||||||
|
***********************************/
|
||||||
|
configureDomains(app)
|
||||||
|
|
||||||
/***********************************
|
/***********************************
|
||||||
* DB Connection & *
|
* DB Connection & *
|
||||||
* Express Sessions *
|
* Express Sessions *
|
||||||
@@ -68,21 +68,8 @@ export default setProcessVariables().then(async () => {
|
|||||||
// Currently only place we use it is SAS9 Mock - POST /SASLogon/login
|
// Currently only place we use it is SAS9 Mock - POST /SASLogon/login
|
||||||
app.use(express.urlencoded({ extended: true }))
|
app.use(express.urlencoded({ extended: true }))
|
||||||
|
|
||||||
await setupUserAutoExec()
|
await setupFolders()
|
||||||
|
await copySASjsCore()
|
||||||
if (!(await folderExists(getFilesFolder()))) await setupFilesFolder()
|
|
||||||
|
|
||||||
if (!(await folderExists(getPackagesFolder()))) await setupPackagesFolder()
|
|
||||||
|
|
||||||
const sasautosPath = path.join(process.driveLoc, 'sas', 'sasautos')
|
|
||||||
if (await folderExists(sasautosPath)) {
|
|
||||||
process.logger.warn(
|
|
||||||
`SASAUTOS was not refreshed. To force a refresh, delete the ${sasautosPath} folder`
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
await copySASjsCore()
|
|
||||||
await createWeboutSasFile()
|
|
||||||
}
|
|
||||||
|
|
||||||
// loading these modules after setting up variables due to
|
// loading these modules after setting up variables due to
|
||||||
// multer's usage of process var process.driveLoc
|
// multer's usage of process var process.driveLoc
|
||||||
|
|||||||
@@ -1,16 +1,4 @@
|
|||||||
import express from 'express'
|
import { Security, Route, Tags, Example, Post, Body, Query, Hidden } from 'tsoa'
|
||||||
import {
|
|
||||||
Security,
|
|
||||||
Route,
|
|
||||||
Tags,
|
|
||||||
Example,
|
|
||||||
Post,
|
|
||||||
Patch,
|
|
||||||
Request,
|
|
||||||
Body,
|
|
||||||
Query,
|
|
||||||
Hidden
|
|
||||||
} from 'tsoa'
|
|
||||||
import jwt from 'jsonwebtoken'
|
import jwt from 'jsonwebtoken'
|
||||||
import { InfoJWT } from '../types'
|
import { InfoJWT } from '../types'
|
||||||
import {
|
import {
|
||||||
@@ -20,21 +8,19 @@ import {
|
|||||||
removeTokensInDB,
|
removeTokensInDB,
|
||||||
saveTokensInDB
|
saveTokensInDB
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import Client from '../model/Client'
|
|
||||||
import User from '../model/User'
|
|
||||||
|
|
||||||
@Route('SASjsApi/auth')
|
@Route('SASjsApi/auth')
|
||||||
@Tags('Auth')
|
@Tags('Auth')
|
||||||
export class AuthController {
|
export class AuthController {
|
||||||
static authCodes: { [key: string]: { [key: string]: string } } = {}
|
static authCodes: { [key: string]: { [key: string]: string } } = {}
|
||||||
static saveCode = (userId: string, clientId: string, code: string) => {
|
static saveCode = (userId: number, clientId: string, code: string) => {
|
||||||
if (AuthController.authCodes[userId])
|
if (AuthController.authCodes[userId])
|
||||||
return (AuthController.authCodes[userId][clientId] = code)
|
return (AuthController.authCodes[userId][clientId] = code)
|
||||||
|
|
||||||
AuthController.authCodes[userId] = { [clientId]: code }
|
AuthController.authCodes[userId] = { [clientId]: code }
|
||||||
return AuthController.authCodes[userId][clientId]
|
return AuthController.authCodes[userId][clientId]
|
||||||
}
|
}
|
||||||
static deleteCode = (userId: string, clientId: string) =>
|
static deleteCode = (userId: number, clientId: string) =>
|
||||||
delete AuthController.authCodes[userId][clientId]
|
delete AuthController.authCodes[userId][clientId]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -75,18 +61,6 @@ export class AuthController {
|
|||||||
public async logout(@Query() @Hidden() data?: InfoJWT) {
|
public async logout(@Query() @Hidden() data?: InfoJWT) {
|
||||||
return logout(data!)
|
return logout(data!)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Update user's password.
|
|
||||||
*/
|
|
||||||
@Security('bearerAuth')
|
|
||||||
@Patch('updatePassword')
|
|
||||||
public async updatePassword(
|
|
||||||
@Request() req: express.Request,
|
|
||||||
@Body() body: UpdatePasswordPayload
|
|
||||||
) {
|
|
||||||
return updatePassword(req, body)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = async (data: any): Promise<TokenResponse> => {
|
const token = async (data: any): Promise<TokenResponse> => {
|
||||||
@@ -109,17 +83,8 @@ const token = async (data: any): Promise<TokenResponse> => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const client = await Client.findOne({ clientId })
|
const accessToken = generateAccessToken(userInfo)
|
||||||
if (!client) throw new Error('Invalid clientId.')
|
const refreshToken = generateRefreshToken(userInfo)
|
||||||
|
|
||||||
const accessToken = generateAccessToken(
|
|
||||||
userInfo,
|
|
||||||
client.accessTokenExpiration
|
|
||||||
)
|
|
||||||
const refreshToken = generateRefreshToken(
|
|
||||||
userInfo,
|
|
||||||
client.refreshTokenExpiration
|
|
||||||
)
|
|
||||||
|
|
||||||
await saveTokensInDB(userInfo.userId, clientId, accessToken, refreshToken)
|
await saveTokensInDB(userInfo.userId, clientId, accessToken, refreshToken)
|
||||||
|
|
||||||
@@ -127,17 +92,8 @@ const token = async (data: any): Promise<TokenResponse> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const refresh = async (userInfo: InfoJWT): Promise<TokenResponse> => {
|
const refresh = async (userInfo: InfoJWT): Promise<TokenResponse> => {
|
||||||
const client = await Client.findOne({ clientId: userInfo.clientId })
|
const accessToken = generateAccessToken(userInfo)
|
||||||
if (!client) throw new Error('Invalid clientId.')
|
const refreshToken = generateRefreshToken(userInfo)
|
||||||
|
|
||||||
const accessToken = generateAccessToken(
|
|
||||||
userInfo,
|
|
||||||
client.accessTokenExpiration
|
|
||||||
)
|
|
||||||
const refreshToken = generateRefreshToken(
|
|
||||||
userInfo,
|
|
||||||
client.refreshTokenExpiration
|
|
||||||
)
|
|
||||||
|
|
||||||
await saveTokensInDB(
|
await saveTokensInDB(
|
||||||
userInfo.userId,
|
userInfo.userId,
|
||||||
@@ -153,40 +109,6 @@ const logout = async (userInfo: InfoJWT) => {
|
|||||||
await removeTokensInDB(userInfo.userId, userInfo.clientId)
|
await removeTokensInDB(userInfo.userId, userInfo.clientId)
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatePassword = async (
|
|
||||||
req: express.Request,
|
|
||||||
data: UpdatePasswordPayload
|
|
||||||
) => {
|
|
||||||
const { currentPassword, newPassword } = data
|
|
||||||
const userId = req.user?.userId
|
|
||||||
const dbUser = await User.findOne({ _id: userId })
|
|
||||||
|
|
||||||
if (!dbUser)
|
|
||||||
throw {
|
|
||||||
code: 404,
|
|
||||||
message: `User not found!`
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dbUser?.authProvider) {
|
|
||||||
throw {
|
|
||||||
code: 405,
|
|
||||||
message:
|
|
||||||
'Can not update password of user that is created by an external auth provider.'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const validPass = dbUser.comparePassword(currentPassword)
|
|
||||||
if (!validPass)
|
|
||||||
throw {
|
|
||||||
code: 403,
|
|
||||||
message: `Invalid current password!`
|
|
||||||
}
|
|
||||||
|
|
||||||
dbUser.password = User.hashPassword(newPassword)
|
|
||||||
dbUser.needsToUpdatePassword = false
|
|
||||||
await dbUser.save()
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TokenPayload {
|
interface TokenPayload {
|
||||||
/**
|
/**
|
||||||
* Client ID
|
* Client ID
|
||||||
@@ -213,19 +135,6 @@ interface TokenResponse {
|
|||||||
refreshToken: string
|
refreshToken: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UpdatePasswordPayload {
|
|
||||||
/**
|
|
||||||
* Current Password
|
|
||||||
* @example "currentPasswordString"
|
|
||||||
*/
|
|
||||||
currentPassword: string
|
|
||||||
/**
|
|
||||||
* New Password
|
|
||||||
* @example "newPassword"
|
|
||||||
*/
|
|
||||||
newPassword: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const verifyAuthCode = async (
|
const verifyAuthCode = async (
|
||||||
clientId: string,
|
clientId: string,
|
||||||
code: string
|
code: string
|
||||||
|
|||||||
@@ -1,186 +0,0 @@
|
|||||||
import express from 'express'
|
|
||||||
import { Security, Route, Tags, Get, Post, Example } from 'tsoa'
|
|
||||||
|
|
||||||
import { LDAPClient, LDAPUser, LDAPGroup, AuthProviderType } from '../utils'
|
|
||||||
import { randomBytes } from 'crypto'
|
|
||||||
import User from '../model/User'
|
|
||||||
import Group from '../model/Group'
|
|
||||||
import Permission from '../model/Permission'
|
|
||||||
|
|
||||||
@Security('bearerAuth')
|
|
||||||
@Route('SASjsApi/authConfig')
|
|
||||||
@Tags('Auth_Config')
|
|
||||||
export class AuthConfigController {
|
|
||||||
/**
|
|
||||||
* @summary Gives the detail of Auth Mechanism.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Example({
|
|
||||||
ldap: {
|
|
||||||
LDAP_URL: 'ldaps://my.ldap.server:636',
|
|
||||||
LDAP_BIND_DN: 'cn=admin,ou=system,dc=cloudron',
|
|
||||||
LDAP_BIND_PASSWORD: 'secret',
|
|
||||||
LDAP_USERS_BASE_DN: 'ou=users,dc=cloudron',
|
|
||||||
LDAP_GROUPS_BASE_DN: 'ou=groups,dc=cloudron'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@Get('/')
|
|
||||||
public getDetail() {
|
|
||||||
return getAuthConfigDetail()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Synchronises LDAP users and groups with internal DB and returns the count of imported users and groups.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Example({
|
|
||||||
users: 5,
|
|
||||||
groups: 3
|
|
||||||
})
|
|
||||||
@Post('/synchroniseWithLDAP')
|
|
||||||
public async synchroniseWithLDAP() {
|
|
||||||
return synchroniseWithLDAP()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const synchroniseWithLDAP = async () => {
|
|
||||||
process.logger.info('Syncing LDAP with internal DB')
|
|
||||||
|
|
||||||
const permissions = await Permission.get({})
|
|
||||||
await Permission.deleteMany()
|
|
||||||
await User.deleteMany({ authProvider: AuthProviderType.LDAP })
|
|
||||||
await Group.deleteMany({ authProvider: AuthProviderType.LDAP })
|
|
||||||
|
|
||||||
const ldapClient = await LDAPClient.init()
|
|
||||||
|
|
||||||
process.logger.info('fetching LDAP users')
|
|
||||||
const users = await ldapClient.getAllLDAPUsers()
|
|
||||||
|
|
||||||
process.logger.info('inserting LDAP users to DB')
|
|
||||||
|
|
||||||
const existingUsers: string[] = []
|
|
||||||
const importedUsers: LDAPUser[] = []
|
|
||||||
|
|
||||||
for (const user of users) {
|
|
||||||
const usernameExists = await User.findOne({ username: user.username })
|
|
||||||
if (usernameExists) {
|
|
||||||
existingUsers.push(user.username)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
const hashPassword = User.hashPassword(randomBytes(64).toString('hex'))
|
|
||||||
|
|
||||||
await User.create({
|
|
||||||
displayName: user.displayName,
|
|
||||||
username: user.username,
|
|
||||||
password: hashPassword,
|
|
||||||
authProvider: AuthProviderType.LDAP,
|
|
||||||
needsToUpdatePassword: false
|
|
||||||
})
|
|
||||||
|
|
||||||
importedUsers.push(user)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existingUsers.length > 0) {
|
|
||||||
process.logger.info(
|
|
||||||
'Failed to insert following users as they already exist in DB:'
|
|
||||||
)
|
|
||||||
existingUsers.forEach((user) => process.logger.log(`* ${user}`))
|
|
||||||
}
|
|
||||||
|
|
||||||
process.logger.info('fetching LDAP groups')
|
|
||||||
const groups = await ldapClient.getAllLDAPGroups()
|
|
||||||
|
|
||||||
process.logger.info('inserting LDAP groups to DB')
|
|
||||||
|
|
||||||
const existingGroups: string[] = []
|
|
||||||
const importedGroups: LDAPGroup[] = []
|
|
||||||
|
|
||||||
for (const group of groups) {
|
|
||||||
const groupExists = await Group.findOne({ name: group.name })
|
|
||||||
if (groupExists) {
|
|
||||||
existingGroups.push(group.name)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
await Group.create({
|
|
||||||
name: group.name,
|
|
||||||
authProvider: AuthProviderType.LDAP
|
|
||||||
})
|
|
||||||
|
|
||||||
importedGroups.push(group)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existingGroups.length > 0) {
|
|
||||||
process.logger.info(
|
|
||||||
'Failed to insert following groups as they already exist in DB:'
|
|
||||||
)
|
|
||||||
existingGroups.forEach((group) => process.logger.log(`* ${group}`))
|
|
||||||
}
|
|
||||||
|
|
||||||
process.logger.info('associating users and groups')
|
|
||||||
|
|
||||||
for (const group of importedGroups) {
|
|
||||||
const dbGroup = await Group.findOne({ name: group.name })
|
|
||||||
if (dbGroup) {
|
|
||||||
for (const member of group.members) {
|
|
||||||
const user = importedUsers.find((user) => user.uid === member)
|
|
||||||
if (user) {
|
|
||||||
const dbUser = await User.findOne({ username: user.username })
|
|
||||||
if (dbUser) await dbGroup.addUser(dbUser)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
process.logger.info('setting permissions')
|
|
||||||
|
|
||||||
for (const permission of permissions) {
|
|
||||||
const newPermission = new Permission({
|
|
||||||
path: permission.path,
|
|
||||||
type: permission.type,
|
|
||||||
setting: permission.setting
|
|
||||||
})
|
|
||||||
|
|
||||||
if (permission.user) {
|
|
||||||
const dbUser = await User.findOne({ username: permission.user.username })
|
|
||||||
if (dbUser) newPermission.user = dbUser._id
|
|
||||||
} else if (permission.group) {
|
|
||||||
const dbGroup = await Group.findOne({ name: permission.group.name })
|
|
||||||
if (dbGroup) newPermission.group = dbGroup._id
|
|
||||||
}
|
|
||||||
await newPermission.save()
|
|
||||||
}
|
|
||||||
|
|
||||||
process.logger.info('LDAP synchronization completed!')
|
|
||||||
|
|
||||||
return {
|
|
||||||
userCount: importedUsers.length,
|
|
||||||
groupCount: importedGroups.length
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getAuthConfigDetail = () => {
|
|
||||||
const { AUTH_PROVIDERS } = process.env
|
|
||||||
|
|
||||||
const returnObj: any = {}
|
|
||||||
|
|
||||||
if (AUTH_PROVIDERS === AuthProviderType.LDAP) {
|
|
||||||
const {
|
|
||||||
LDAP_URL,
|
|
||||||
LDAP_BIND_DN,
|
|
||||||
LDAP_BIND_PASSWORD,
|
|
||||||
LDAP_USERS_BASE_DN,
|
|
||||||
LDAP_GROUPS_BASE_DN
|
|
||||||
} = process.env
|
|
||||||
|
|
||||||
returnObj.ldap = {
|
|
||||||
LDAP_URL: LDAP_URL ?? '',
|
|
||||||
LDAP_BIND_DN: LDAP_BIND_DN ?? '',
|
|
||||||
LDAP_BIND_PASSWORD: LDAP_BIND_PASSWORD ?? '',
|
|
||||||
LDAP_USERS_BASE_DN: LDAP_USERS_BASE_DN ?? '',
|
|
||||||
LDAP_GROUPS_BASE_DN: LDAP_GROUPS_BASE_DN ?? ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return returnObj
|
|
||||||
}
|
|
||||||
@@ -1,27 +1,18 @@
|
|||||||
import { Security, Route, Tags, Example, Post, Body, Get } from 'tsoa'
|
import { Security, Route, Tags, Example, Post, Body } from 'tsoa'
|
||||||
|
|
||||||
import Client, {
|
import Client, { ClientPayload } from '../model/Client'
|
||||||
ClientPayload,
|
|
||||||
NUMBER_OF_SECONDS_IN_A_DAY
|
|
||||||
} from '../model/Client'
|
|
||||||
|
|
||||||
@Security('bearerAuth')
|
@Security('bearerAuth')
|
||||||
@Route('SASjsApi/client')
|
@Route('SASjsApi/client')
|
||||||
@Tags('Client')
|
@Tags('Client')
|
||||||
export class ClientController {
|
export class ClientController {
|
||||||
/**
|
/**
|
||||||
* @summary Admin only task. Create client with the following attributes:
|
* @summary Create client with the following attributes: ClientId, ClientSecret. Admin only task.
|
||||||
* ClientId,
|
|
||||||
* ClientSecret,
|
|
||||||
* accessTokenExpiration (optional),
|
|
||||||
* refreshTokenExpiration (optional)
|
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@Example<ClientPayload>({
|
@Example<ClientPayload>({
|
||||||
clientId: 'someFormattedClientID1234',
|
clientId: 'someFormattedClientID1234',
|
||||||
clientSecret: 'someRandomCryptoString',
|
clientSecret: 'someRandomCryptoString'
|
||||||
accessTokenExpiration: NUMBER_OF_SECONDS_IN_A_DAY,
|
|
||||||
refreshTokenExpiration: NUMBER_OF_SECONDS_IN_A_DAY * 30
|
|
||||||
})
|
})
|
||||||
@Post('/')
|
@Post('/')
|
||||||
public async createClient(
|
public async createClient(
|
||||||
@@ -29,37 +20,10 @@ export class ClientController {
|
|||||||
): Promise<ClientPayload> {
|
): Promise<ClientPayload> {
|
||||||
return createClient(body)
|
return createClient(body)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Admin only task. Returns the list of all the clients
|
|
||||||
*/
|
|
||||||
@Example<ClientPayload[]>([
|
|
||||||
{
|
|
||||||
clientId: 'someClientID1234',
|
|
||||||
clientSecret: 'someRandomCryptoString',
|
|
||||||
accessTokenExpiration: NUMBER_OF_SECONDS_IN_A_DAY,
|
|
||||||
refreshTokenExpiration: NUMBER_OF_SECONDS_IN_A_DAY * 30
|
|
||||||
},
|
|
||||||
{
|
|
||||||
clientId: 'someOtherClientID',
|
|
||||||
clientSecret: 'someOtherRandomCryptoString',
|
|
||||||
accessTokenExpiration: NUMBER_OF_SECONDS_IN_A_DAY,
|
|
||||||
refreshTokenExpiration: NUMBER_OF_SECONDS_IN_A_DAY * 30
|
|
||||||
}
|
|
||||||
])
|
|
||||||
@Get('/')
|
|
||||||
public async getAllClients(): Promise<ClientPayload[]> {
|
|
||||||
return getAllClients()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const createClient = async (data: ClientPayload): Promise<ClientPayload> => {
|
const createClient = async (data: any): Promise<ClientPayload> => {
|
||||||
const {
|
const { clientId, clientSecret } = data
|
||||||
clientId,
|
|
||||||
clientSecret,
|
|
||||||
accessTokenExpiration,
|
|
||||||
refreshTokenExpiration
|
|
||||||
} = data
|
|
||||||
|
|
||||||
// Checking if client is already in the database
|
// Checking if client is already in the database
|
||||||
const clientExist = await Client.findOne({ clientId })
|
const clientExist = await Client.findOne({ clientId })
|
||||||
@@ -68,27 +32,13 @@ const createClient = async (data: ClientPayload): Promise<ClientPayload> => {
|
|||||||
// Create a new client
|
// Create a new client
|
||||||
const client = new Client({
|
const client = new Client({
|
||||||
clientId,
|
clientId,
|
||||||
clientSecret,
|
clientSecret
|
||||||
accessTokenExpiration,
|
|
||||||
refreshTokenExpiration
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const savedClient = await client.save()
|
const savedClient = await client.save()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
clientId: savedClient.clientId,
|
clientId: savedClient.clientId,
|
||||||
clientSecret: savedClient.clientSecret,
|
clientSecret: savedClient.clientSecret
|
||||||
accessTokenExpiration: savedClient.accessTokenExpiration,
|
|
||||||
refreshTokenExpiration: savedClient.refreshTokenExpiration
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAllClients = async (): Promise<ClientPayload[]> => {
|
|
||||||
return Client.find({}).select({
|
|
||||||
_id: 0,
|
|
||||||
clientId: 1,
|
|
||||||
clientSecret: 1,
|
|
||||||
accessTokenExpiration: 1,
|
|
||||||
refreshTokenExpiration: 1
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -12,29 +12,27 @@ import {
|
|||||||
|
|
||||||
import Group, { GroupPayload, PUBLIC_GROUP_NAME } from '../model/Group'
|
import Group, { GroupPayload, PUBLIC_GROUP_NAME } from '../model/Group'
|
||||||
import User from '../model/User'
|
import User from '../model/User'
|
||||||
import { GetUserBy, UserResponse } from './user'
|
import { UserResponse } from './user'
|
||||||
|
|
||||||
export interface GroupResponse {
|
export interface GroupResponse {
|
||||||
uid: string
|
groupId: number
|
||||||
name: string
|
name: string
|
||||||
description: string
|
description: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GroupDetailsResponse extends GroupResponse {
|
export interface GroupDetailsResponse {
|
||||||
|
groupId: number
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
isActive: boolean
|
isActive: boolean
|
||||||
users: UserResponse[]
|
users: UserResponse[]
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GetGroupBy {
|
interface GetGroupBy {
|
||||||
_id?: string
|
groupId?: number
|
||||||
name?: string
|
name?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
enum GroupAction {
|
|
||||||
AddUser = 'addUser',
|
|
||||||
RemoveUser = 'removeUser'
|
|
||||||
}
|
|
||||||
|
|
||||||
@Security('bearerAuth')
|
@Security('bearerAuth')
|
||||||
@Route('SASjsApi/group')
|
@Route('SASjsApi/group')
|
||||||
@Tags('Group')
|
@Tags('Group')
|
||||||
@@ -45,7 +43,7 @@ export class GroupController {
|
|||||||
*/
|
*/
|
||||||
@Example<GroupResponse[]>([
|
@Example<GroupResponse[]>([
|
||||||
{
|
{
|
||||||
uid: 'groupIdString',
|
groupId: 123,
|
||||||
name: 'DCGroup',
|
name: 'DCGroup',
|
||||||
description: 'This group represents Data Controller Users'
|
description: 'This group represents Data Controller Users'
|
||||||
}
|
}
|
||||||
@@ -60,7 +58,7 @@ export class GroupController {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@Example<GroupDetailsResponse>({
|
@Example<GroupDetailsResponse>({
|
||||||
uid: 'groupIdString',
|
groupId: 123,
|
||||||
name: 'DCGroup',
|
name: 'DCGroup',
|
||||||
description: 'This group represents Data Controller Users',
|
description: 'This group represents Data Controller Users',
|
||||||
isActive: true,
|
isActive: true,
|
||||||
@@ -79,7 +77,7 @@ export class GroupController {
|
|||||||
* @example dcgroup
|
* @example dcgroup
|
||||||
*/
|
*/
|
||||||
@Get('by/groupname/{name}')
|
@Get('by/groupname/{name}')
|
||||||
public async getGroupByName(
|
public async getGroupByGroupName(
|
||||||
@Path() name: string
|
@Path() name: string
|
||||||
): Promise<GroupDetailsResponse> {
|
): Promise<GroupDetailsResponse> {
|
||||||
return getGroup({ name })
|
return getGroup({ name })
|
||||||
@@ -87,79 +85,81 @@ export class GroupController {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Get list of members of a group (userName). All users can request this.
|
* @summary Get list of members of a group (userName). All users can request this.
|
||||||
* @param uid The group's identifier
|
* @param groupId The group's identifier
|
||||||
* @example uid "12ByteString"
|
* @example groupId 1234
|
||||||
*/
|
*/
|
||||||
@Get('{uid}')
|
@Get('{groupId}')
|
||||||
public async getGroup(@Path() uid: string): Promise<GroupDetailsResponse> {
|
public async getGroup(
|
||||||
return getGroup({ _id: uid })
|
@Path() groupId: number
|
||||||
|
): Promise<GroupDetailsResponse> {
|
||||||
|
return getGroup({ groupId })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Add a user to a group. Admin task only.
|
* @summary Add a user to a group. Admin task only.
|
||||||
* @param groupUid The group's identifier
|
* @param groupId The group's identifier
|
||||||
* @example groupUid "12ByteString"
|
* @example groupId "1234"
|
||||||
* @param userUid The user's identifier
|
* @param userId The user's identifier
|
||||||
* @example userId "12ByteString"
|
* @example userId "6789"
|
||||||
*/
|
*/
|
||||||
@Example<GroupDetailsResponse>({
|
@Example<GroupDetailsResponse>({
|
||||||
uid: 'groupIdString',
|
groupId: 123,
|
||||||
name: 'DCGroup',
|
name: 'DCGroup',
|
||||||
description: 'This group represents Data Controller Users',
|
description: 'This group represents Data Controller Users',
|
||||||
isActive: true,
|
isActive: true,
|
||||||
users: []
|
users: []
|
||||||
})
|
})
|
||||||
@Post('{groupUid}/{userUid}')
|
@Post('{groupId}/{userId}')
|
||||||
public async addUserToGroup(
|
public async addUserToGroup(
|
||||||
@Path() groupUid: string,
|
@Path() groupId: number,
|
||||||
@Path() userUid: string
|
@Path() userId: number
|
||||||
): Promise<GroupDetailsResponse> {
|
): Promise<GroupDetailsResponse> {
|
||||||
return addUserToGroup(groupUid, userUid)
|
return addUserToGroup(groupId, userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Remove a user from a group. Admin task only.
|
* @summary Remove a user to a group. Admin task only.
|
||||||
* @param groupUid The group's identifier
|
* @param groupId The group's identifier
|
||||||
* @example groupUid "12ByteString"
|
* @example groupId "1234"
|
||||||
* @param userUid The user's identifier
|
* @param userId The user's identifier
|
||||||
* @example userUid "12ByteString"
|
* @example userId "6789"
|
||||||
*/
|
*/
|
||||||
@Example<GroupDetailsResponse>({
|
@Example<GroupDetailsResponse>({
|
||||||
uid: 'groupIdString',
|
groupId: 123,
|
||||||
name: 'DCGroup',
|
name: 'DCGroup',
|
||||||
description: 'This group represents Data Controller Users',
|
description: 'This group represents Data Controller Users',
|
||||||
isActive: true,
|
isActive: true,
|
||||||
users: []
|
users: []
|
||||||
})
|
})
|
||||||
@Delete('{groupUid}/{userUid}')
|
@Delete('{groupId}/{userId}')
|
||||||
public async removeUserFromGroup(
|
public async removeUserFromGroup(
|
||||||
@Path() groupUid: string,
|
@Path() groupId: number,
|
||||||
@Path() userUid: string
|
@Path() userId: number
|
||||||
): Promise<GroupDetailsResponse> {
|
): Promise<GroupDetailsResponse> {
|
||||||
return removeUserFromGroup(groupUid, userUid)
|
return removeUserFromGroup(groupId, userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Delete a group. Admin task only.
|
* @summary Delete a group. Admin task only.
|
||||||
* @param uid The group's identifier
|
* @param groupId The group's identifier
|
||||||
* @example uid "12ByteString"
|
* @example groupId 1234
|
||||||
*/
|
*/
|
||||||
@Delete('{uid}')
|
@Delete('{groupId}')
|
||||||
public async deleteGroup(@Path() uid: string) {
|
public async deleteGroup(@Path() groupId: number) {
|
||||||
const group = await Group.findOne({ _id: uid })
|
const group = await Group.findOne({ groupId })
|
||||||
if (!group)
|
if (group) return await group.remove()
|
||||||
throw {
|
throw {
|
||||||
code: 404,
|
code: 404,
|
||||||
status: 'Not Found',
|
status: 'Not Found',
|
||||||
message: 'Group not found.'
|
message: 'Group not found.'
|
||||||
}
|
}
|
||||||
|
|
||||||
return await group.remove()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAllGroups = async (): Promise<GroupResponse[]> =>
|
const getAllGroups = async (): Promise<GroupResponse[]> =>
|
||||||
await Group.find({}).select('uid name description').exec()
|
await Group.find({})
|
||||||
|
.select({ _id: 0, groupId: 1, name: 1, description: 1 })
|
||||||
|
.exec()
|
||||||
|
|
||||||
const createGroup = async ({
|
const createGroup = async ({
|
||||||
name,
|
name,
|
||||||
@@ -184,7 +184,7 @@ const createGroup = async ({
|
|||||||
const savedGroup = await group.save()
|
const savedGroup = await group.save()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
uid: savedGroup.uid,
|
groupId: savedGroup.groupId,
|
||||||
name: savedGroup.name,
|
name: savedGroup.name,
|
||||||
description: savedGroup.description,
|
description: savedGroup.description,
|
||||||
isActive: savedGroup.isActive,
|
isActive: savedGroup.isActive,
|
||||||
@@ -195,12 +195,11 @@ const createGroup = async ({
|
|||||||
const getGroup = async (findBy: GetGroupBy): Promise<GroupDetailsResponse> => {
|
const getGroup = async (findBy: GetGroupBy): Promise<GroupDetailsResponse> => {
|
||||||
const group = (await Group.findOne(
|
const group = (await Group.findOne(
|
||||||
findBy,
|
findBy,
|
||||||
'uid name description isActive users'
|
'groupId name description isActive users -_id'
|
||||||
).populate(
|
).populate(
|
||||||
'users',
|
'users',
|
||||||
'uid username displayName isAdmin'
|
'id username displayName isAdmin -_id'
|
||||||
)) as unknown as GroupDetailsResponse
|
)) as unknown as GroupDetailsResponse
|
||||||
|
|
||||||
if (!group)
|
if (!group)
|
||||||
throw {
|
throw {
|
||||||
code: 404,
|
code: 404,
|
||||||
@@ -209,7 +208,7 @@ const getGroup = async (findBy: GetGroupBy): Promise<GroupDetailsResponse> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
uid: group.uid,
|
groupId: group.groupId,
|
||||||
name: group.name,
|
name: group.name,
|
||||||
description: group.description,
|
description: group.description,
|
||||||
isActive: group.isActive,
|
isActive: group.isActive,
|
||||||
@@ -218,23 +217,23 @@ const getGroup = async (findBy: GetGroupBy): Promise<GroupDetailsResponse> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const addUserToGroup = async (
|
const addUserToGroup = async (
|
||||||
groupUid: string,
|
groupId: number,
|
||||||
userUid: string
|
userId: number
|
||||||
): Promise<GroupDetailsResponse> =>
|
): Promise<GroupDetailsResponse> =>
|
||||||
updateUsersListInGroup(groupUid, userUid, GroupAction.AddUser)
|
updateUsersListInGroup(groupId, userId, 'addUser')
|
||||||
|
|
||||||
const removeUserFromGroup = async (
|
const removeUserFromGroup = async (
|
||||||
groupUid: string,
|
groupId: number,
|
||||||
userUid: string
|
userId: number
|
||||||
): Promise<GroupDetailsResponse> =>
|
): Promise<GroupDetailsResponse> =>
|
||||||
updateUsersListInGroup(groupUid, userUid, GroupAction.RemoveUser)
|
updateUsersListInGroup(groupId, userId, 'removeUser')
|
||||||
|
|
||||||
const updateUsersListInGroup = async (
|
const updateUsersListInGroup = async (
|
||||||
groupUid: string,
|
groupId: number,
|
||||||
userUid: string,
|
userId: number,
|
||||||
action: GroupAction
|
action: 'addUser' | 'removeUser'
|
||||||
): Promise<GroupDetailsResponse> => {
|
): Promise<GroupDetailsResponse> => {
|
||||||
const group = await Group.findOne({ _id: groupUid })
|
const group = await Group.findOne({ groupId })
|
||||||
if (!group)
|
if (!group)
|
||||||
throw {
|
throw {
|
||||||
code: 404,
|
code: 404,
|
||||||
@@ -249,14 +248,7 @@ const updateUsersListInGroup = async (
|
|||||||
message: `Can't add/remove user to '${PUBLIC_GROUP_NAME}' group.`
|
message: `Can't add/remove user to '${PUBLIC_GROUP_NAME}' group.`
|
||||||
}
|
}
|
||||||
|
|
||||||
if (group.authProvider)
|
const user = await User.findOne({ id: userId })
|
||||||
throw {
|
|
||||||
code: 405,
|
|
||||||
status: 'Method Not Allowed',
|
|
||||||
message: `Can't add/remove user to group created by external auth provider.`
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = await User.findOne({ _id: userUid })
|
|
||||||
if (!user)
|
if (!user)
|
||||||
throw {
|
throw {
|
||||||
code: 404,
|
code: 404,
|
||||||
@@ -264,15 +256,8 @@ const updateUsersListInGroup = async (
|
|||||||
message: 'User not found.'
|
message: 'User not found.'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.authProvider)
|
|
||||||
throw {
|
|
||||||
code: 405,
|
|
||||||
status: 'Method Not Allowed',
|
|
||||||
message: `Can't add/remove user to group created by external auth provider.`
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatedGroup =
|
const updatedGroup =
|
||||||
action === GroupAction.AddUser
|
action === 'addUser'
|
||||||
? await group.addUser(user)
|
? await group.addUser(user)
|
||||||
: await group.removeUser(user)
|
: await group.removeUser(user)
|
||||||
|
|
||||||
@@ -284,7 +269,7 @@ const updateUsersListInGroup = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
uid: updatedGroup.uid,
|
groupId: updatedGroup.groupId,
|
||||||
name: updatedGroup.name,
|
name: updatedGroup.name,
|
||||||
description: updatedGroup.description,
|
description: updatedGroup.description,
|
||||||
isActive: updatedGroup.isActive,
|
isActive: updatedGroup.isActive,
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
export * from './auth'
|
export * from './auth'
|
||||||
export * from './authConfig'
|
|
||||||
export * from './client'
|
export * from './client'
|
||||||
export * from './code'
|
export * from './code'
|
||||||
export * from './drive'
|
export * from './drive'
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ interface ExecuteFileParams {
|
|||||||
returnJson?: boolean
|
returnJson?: boolean
|
||||||
session?: Session
|
session?: Session
|
||||||
runTime: RunTimeType
|
runTime: RunTimeType
|
||||||
forceStringResult?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ExecuteProgramParams extends Omit<ExecuteFileParams, 'programPath'> {
|
interface ExecuteProgramParams extends Omit<ExecuteFileParams, 'programPath'> {
|
||||||
@@ -43,8 +42,7 @@ export class ExecutionController {
|
|||||||
otherArgs,
|
otherArgs,
|
||||||
returnJson,
|
returnJson,
|
||||||
session,
|
session,
|
||||||
runTime,
|
runTime
|
||||||
forceStringResult
|
|
||||||
}: ExecuteFileParams) {
|
}: ExecuteFileParams) {
|
||||||
const program = await readFile(programPath)
|
const program = await readFile(programPath)
|
||||||
|
|
||||||
@@ -55,8 +53,7 @@ export class ExecutionController {
|
|||||||
otherArgs,
|
otherArgs,
|
||||||
returnJson,
|
returnJson,
|
||||||
session,
|
session,
|
||||||
runTime,
|
runTime
|
||||||
forceStringResult
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,8 +63,7 @@ export class ExecutionController {
|
|||||||
vars,
|
vars,
|
||||||
otherArgs,
|
otherArgs,
|
||||||
session: sessionByFileUpload,
|
session: sessionByFileUpload,
|
||||||
runTime,
|
runTime
|
||||||
forceStringResult
|
|
||||||
}: ExecuteProgramParams): Promise<ExecuteReturnRaw> {
|
}: ExecuteProgramParams): Promise<ExecuteReturnRaw> {
|
||||||
const sessionController = getSessionController(runTime)
|
const sessionController = getSessionController(runTime)
|
||||||
|
|
||||||
@@ -78,7 +74,6 @@ export class ExecutionController {
|
|||||||
|
|
||||||
const logPath = path.join(session.path, 'log.log')
|
const logPath = path.join(session.path, 'log.log')
|
||||||
const headersPath = path.join(session.path, 'stpsrv_header.txt')
|
const headersPath = path.join(session.path, 'stpsrv_header.txt')
|
||||||
|
|
||||||
const weboutPath = path.join(session.path, 'webout.txt')
|
const weboutPath = path.join(session.path, 'webout.txt')
|
||||||
const tokenFile = path.join(session.path, 'reqHeaders.txt')
|
const tokenFile = path.join(session.path, 'reqHeaders.txt')
|
||||||
|
|
||||||
@@ -106,15 +101,10 @@ export class ExecutionController {
|
|||||||
? await readFile(headersPath)
|
? await readFile(headersPath)
|
||||||
: ''
|
: ''
|
||||||
const httpHeaders: HTTPHeaders = extractHeaders(headersContent)
|
const httpHeaders: HTTPHeaders = extractHeaders(headersContent)
|
||||||
|
|
||||||
if (isDebugOn(vars)) {
|
|
||||||
httpHeaders['content-type'] = 'text/plain'
|
|
||||||
}
|
|
||||||
|
|
||||||
const fileResponse: boolean = httpHeaders.hasOwnProperty('content-type')
|
const fileResponse: boolean = httpHeaders.hasOwnProperty('content-type')
|
||||||
|
|
||||||
const webout = (await fileExists(weboutPath))
|
const webout = (await fileExists(weboutPath))
|
||||||
? fileResponse && !forceStringResult
|
? fileResponse
|
||||||
? await readFileBinary(weboutPath)
|
? await readFileBinary(weboutPath)
|
||||||
: await readFile(weboutPath)
|
: await readFile(weboutPath)
|
||||||
: ''
|
: ''
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { Session } from '../../types'
|
|||||||
import { promisify } from 'util'
|
import { promisify } from 'util'
|
||||||
import { execFile } from 'child_process'
|
import { execFile } from 'child_process'
|
||||||
import {
|
import {
|
||||||
getPackagesFolder,
|
|
||||||
getSessionsFolder,
|
getSessionsFolder,
|
||||||
generateUniqueFileName,
|
generateUniqueFileName,
|
||||||
sysInitCompiledPath,
|
sysInitCompiledPath,
|
||||||
@@ -50,7 +49,7 @@ export class SessionController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const headersPath = path.join(session.path, 'stpsrv_header.txt')
|
const headersPath = path.join(session.path, 'stpsrv_header.txt')
|
||||||
await createFile(headersPath, 'content-type: text/html; charset=utf-8')
|
await createFile(headersPath, 'Content-type: text/plain')
|
||||||
|
|
||||||
this.sessions.push(session)
|
this.sessions.push(session)
|
||||||
return session
|
return session
|
||||||
@@ -94,7 +93,7 @@ export class SASSessionController extends SessionController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const headersPath = path.join(session.path, 'stpsrv_header.txt')
|
const headersPath = path.join(session.path, 'stpsrv_header.txt')
|
||||||
await createFile(headersPath, 'content-type: text/html; charset=utf-8\n')
|
await createFile(headersPath, 'Content-type: text/plain')
|
||||||
|
|
||||||
// we do not want to leave sessions running forever
|
// we do not want to leave sessions running forever
|
||||||
// we clean them up after a predefined period, if unused
|
// we clean them up after a predefined period, if unused
|
||||||
@@ -105,8 +104,7 @@ export class SASSessionController extends SessionController {
|
|||||||
|
|
||||||
// the autoexec file is executed on SAS startup
|
// the autoexec file is executed on SAS startup
|
||||||
const autoExecPath = path.join(sessionFolder, 'autoexec.sas')
|
const autoExecPath = path.join(sessionFolder, 'autoexec.sas')
|
||||||
const contentForAutoExec = `filename packages "${getPackagesFolder()}";
|
const contentForAutoExec = `/* compiled systemInit */
|
||||||
/* compiled systemInit */
|
|
||||||
${compiledSystemInitContent}
|
${compiledSystemInitContent}
|
||||||
/* autoexec */
|
/* autoexec */
|
||||||
${autoExecContent}`
|
${autoExecContent}`
|
||||||
@@ -134,24 +132,23 @@ ${autoExecContent}`
|
|||||||
session.path,
|
session.path,
|
||||||
'-AUTOEXEC',
|
'-AUTOEXEC',
|
||||||
autoExecPath,
|
autoExecPath,
|
||||||
process.sasLoc!.endsWith('sas.exe') ? '-nologo' : '',
|
isWindows() ? '-nologo' : '',
|
||||||
process.sasLoc!.endsWith('sas.exe') ? '-nosplash' : '',
|
process.sasLoc!.endsWith('sas.exe') ? '-nosplash' : '',
|
||||||
process.sasLoc!.endsWith('sas.exe') ? '-icon' : '',
|
process.sasLoc!.endsWith('sas.exe') ? '-icon' : '',
|
||||||
process.sasLoc!.endsWith('sas.exe') ? '-nodms' : '',
|
process.sasLoc!.endsWith('sas.exe') ? '-nodms' : '',
|
||||||
process.sasLoc!.endsWith('sas.exe') ? '-noterminal' : '',
|
process.sasLoc!.endsWith('sas.exe') ? '-noterminal' : '',
|
||||||
process.sasLoc!.endsWith('sas.exe') ? '-nostatuswin' : '',
|
process.sasLoc!.endsWith('sas.exe') ? '-nostatuswin' : '',
|
||||||
process.sasLoc!.endsWith('sas.exe') ? '-NOPRNGETLIST' : '',
|
|
||||||
process.sasLoc!.endsWith('sas.exe') ? '-SASINITIALFOLDER' : '',
|
process.sasLoc!.endsWith('sas.exe') ? '-SASINITIALFOLDER' : '',
|
||||||
process.sasLoc!.endsWith('sas.exe') ? session.path : ''
|
process.sasLoc!.endsWith('sas.exe') ? session.path : ''
|
||||||
])
|
])
|
||||||
.then(() => {
|
.then(() => {
|
||||||
session.completed = true
|
session.completed = true
|
||||||
process.logger.info('session completed', session)
|
console.log('session completed', session)
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
session.completed = true
|
session.completed = true
|
||||||
session.crashed = err.toString()
|
session.crashed = err.toString()
|
||||||
process.logger.error('session crashed', session.id, session.crashed)
|
console.log('session crashed', session.id, session.crashed)
|
||||||
})
|
})
|
||||||
|
|
||||||
// we have a triggered session - add to array
|
// we have a triggered session - add to array
|
||||||
@@ -171,10 +168,7 @@ ${autoExecContent}`
|
|||||||
while ((await fileExists(codeFilePath)) && !session.crashed) {}
|
while ((await fileExists(codeFilePath)) && !session.crashed) {}
|
||||||
|
|
||||||
if (session.crashed)
|
if (session.crashed)
|
||||||
process.logger.error(
|
console.log('session crashed! while waiting to be ready', session.crashed)
|
||||||
'session crashed! while waiting to be ready',
|
|
||||||
session.crashed
|
|
||||||
)
|
|
||||||
|
|
||||||
session.ready = true
|
session.ready = true
|
||||||
}
|
}
|
||||||
@@ -207,15 +201,12 @@ ${autoExecContent}`
|
|||||||
export const getSessionController = (
|
export const getSessionController = (
|
||||||
runTime: RunTimeType
|
runTime: RunTimeType
|
||||||
): SessionController => {
|
): SessionController => {
|
||||||
if (runTime === RunTimeType.SAS) {
|
if (process.sessionController) return process.sessionController
|
||||||
process.sasSessionController =
|
|
||||||
process.sasSessionController || new SASSessionController()
|
|
||||||
|
|
||||||
return process.sasSessionController
|
|
||||||
}
|
|
||||||
|
|
||||||
process.sessionController =
|
process.sessionController =
|
||||||
process.sessionController || new SessionController()
|
runTime === RunTimeType.SAS
|
||||||
|
? new SASSessionController()
|
||||||
|
: new SessionController()
|
||||||
|
|
||||||
return process.sessionController
|
return process.sessionController
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ export const createSASProgram = async (
|
|||||||
%mend;
|
%mend;
|
||||||
%_sasjs_server_init()
|
%_sasjs_server_init()
|
||||||
|
|
||||||
|
proc printto print="%sysfunc(getoption(log))";
|
||||||
|
run;
|
||||||
`
|
`
|
||||||
|
|
||||||
program = `
|
program = `
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { WriteStream, createWriteStream } from 'fs'
|
import fs from 'fs'
|
||||||
import { execFile } from 'child_process'
|
import { execFileSync } from 'child_process'
|
||||||
import { once } from 'stream'
|
import { once } from 'stream'
|
||||||
import { createFile, moveFile } from '@sasjs/utils'
|
import { createFile, moveFile } from '@sasjs/utils'
|
||||||
import { PreProgramVars, Session } from '../../types'
|
import { PreProgramVars, Session } from '../../types'
|
||||||
@@ -105,58 +105,30 @@ export const processProgram = async (
|
|||||||
throw new Error('Invalid runtime!')
|
throw new Error('Invalid runtime!')
|
||||||
}
|
}
|
||||||
|
|
||||||
await createFile(codePath, program)
|
try {
|
||||||
|
await createFile(codePath, program)
|
||||||
|
|
||||||
// create a stream that will write to console outputs to log file
|
// create a stream that will write to console outputs to log file
|
||||||
const writeStream = createWriteStream(logPath)
|
const writeStream = fs.createWriteStream(logPath)
|
||||||
// waiting for the open event so that we can have underlying file descriptor
|
|
||||||
await once(writeStream, 'open')
|
|
||||||
|
|
||||||
await execFilePromise(executablePath, [codePath], writeStream)
|
// waiting for the open event so that we can have underlying file descriptor
|
||||||
.then(() => {
|
await once(writeStream, 'open')
|
||||||
session.completed = true
|
|
||||||
process.logger.info('session completed', session)
|
execFileSync(executablePath, [codePath], {
|
||||||
})
|
stdio: ['ignore', writeStream, writeStream]
|
||||||
.catch((err) => {
|
|
||||||
session.completed = true
|
|
||||||
session.crashed = err.toString()
|
|
||||||
process.logger.error('session crashed', session.id, session.crashed)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// copy the code file to log and end write stream
|
// copy the code file to log and end write stream
|
||||||
writeStream.end(program)
|
writeStream.end(program)
|
||||||
|
|
||||||
|
session.completed = true
|
||||||
|
console.log('session completed', session)
|
||||||
|
} catch (err: any) {
|
||||||
|
session.completed = true
|
||||||
|
session.crashed = err.toString()
|
||||||
|
console.log('session crashed', session.id, session.crashed)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Promisified child_process.execFile
|
|
||||||
*
|
|
||||||
* @param file - The name or path of the executable file to run.
|
|
||||||
* @param args - List of string arguments.
|
|
||||||
* @param writeStream - Child process stdout and stderr will be piped to it.
|
|
||||||
*
|
|
||||||
* @returns {Promise<{ stdout: string, stderr: string }>}
|
|
||||||
*/
|
|
||||||
const execFilePromise = (
|
|
||||||
file: string,
|
|
||||||
args: string[],
|
|
||||||
writeStream: WriteStream
|
|
||||||
): Promise<{ stdout: string; stderr: string }> => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const child = execFile(file, args, (err, stdout, stderr) => {
|
|
||||||
if (err) reject(err)
|
|
||||||
|
|
||||||
resolve({ stdout, stderr })
|
|
||||||
})
|
|
||||||
|
|
||||||
child.stdout?.on('data', (data) => {
|
|
||||||
writeStream.write(data)
|
|
||||||
})
|
|
||||||
|
|
||||||
child.stderr?.on('data', (data) => {
|
|
||||||
writeStream.write(data)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
|
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
|
||||||
|
|||||||
@@ -2,16 +2,6 @@ import { readFile } from '@sasjs/utils'
|
|||||||
import express from 'express'
|
import express from 'express'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { Request, Post, Get } from 'tsoa'
|
import { Request, Post, Get } from 'tsoa'
|
||||||
import dotenv from 'dotenv'
|
|
||||||
import { ExecutionController } from './internal'
|
|
||||||
import {
|
|
||||||
getPreProgramVariables,
|
|
||||||
getRunTimeAndFilePath,
|
|
||||||
makeFilesNamesMap
|
|
||||||
} from '../utils'
|
|
||||||
import { MulterFile } from '../types/Upload'
|
|
||||||
|
|
||||||
dotenv.config()
|
|
||||||
|
|
||||||
export interface Sas9Response {
|
export interface Sas9Response {
|
||||||
content: string
|
content: string
|
||||||
@@ -26,17 +16,9 @@ export interface MockFileRead {
|
|||||||
|
|
||||||
export class MockSas9Controller {
|
export class MockSas9Controller {
|
||||||
private loggedIn: string | undefined
|
private loggedIn: string | undefined
|
||||||
private mocksPath = process.env.STATIC_MOCK_LOCATION || 'mocks'
|
|
||||||
|
|
||||||
@Get('/SASStoredProcess')
|
@Get('/SASStoredProcess')
|
||||||
public async sasStoredProcess(
|
public async sasStoredProcess(): Promise<Sas9Response> {
|
||||||
@Request() req: express.Request
|
|
||||||
): Promise<Sas9Response> {
|
|
||||||
const username = req.query._username?.toString() || undefined
|
|
||||||
const password = req.query._password?.toString() || undefined
|
|
||||||
|
|
||||||
if (username && password) this.loggedIn = req.body.username
|
|
||||||
|
|
||||||
if (!this.loggedIn) {
|
if (!this.loggedIn) {
|
||||||
return {
|
return {
|
||||||
content: '',
|
content: '',
|
||||||
@@ -44,87 +26,17 @@ export class MockSas9Controller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let program = req.query._program?.toString() || undefined
|
|
||||||
const filePath: string[] = program
|
|
||||||
? program.replace('/', '').split('/')
|
|
||||||
: ['generic', 'sas-stored-process']
|
|
||||||
|
|
||||||
if (program) {
|
|
||||||
return await getMockResponseFromFile([
|
|
||||||
process.cwd(),
|
|
||||||
this.mocksPath,
|
|
||||||
'sas9',
|
|
||||||
...filePath
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
return await getMockResponseFromFile([
|
return await getMockResponseFromFile([
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
'mocks',
|
'mocks',
|
||||||
|
'generic',
|
||||||
'sas9',
|
'sas9',
|
||||||
...filePath
|
'sas-stored-process'
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('/SASStoredProcess/do')
|
|
||||||
public async sasStoredProcessDoGet(
|
|
||||||
@Request() req: express.Request
|
|
||||||
): Promise<Sas9Response> {
|
|
||||||
const username = req.query._username?.toString() || undefined
|
|
||||||
const password = req.query._password?.toString() || undefined
|
|
||||||
|
|
||||||
if (username && password) this.loggedIn = username
|
|
||||||
|
|
||||||
if (!this.loggedIn) {
|
|
||||||
return {
|
|
||||||
content: '',
|
|
||||||
redirect: '/SASLogon/login'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const program = req.query._program ?? req.body?._program
|
|
||||||
const filePath: string[] = ['generic', 'sas-stored-process']
|
|
||||||
|
|
||||||
if (program) {
|
|
||||||
const vars = { ...req.query, ...req.body, _requestMethod: req.method }
|
|
||||||
const otherArgs = {}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { codePath, runTime } = await getRunTimeAndFilePath(
|
|
||||||
program + '.js'
|
|
||||||
)
|
|
||||||
|
|
||||||
const result = await new ExecutionController().executeFile({
|
|
||||||
programPath: codePath,
|
|
||||||
preProgramVariables: getPreProgramVariables(req),
|
|
||||||
vars: vars,
|
|
||||||
otherArgs: otherArgs,
|
|
||||||
runTime,
|
|
||||||
forceStringResult: true
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
content: result.result as string
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
process.logger.error('err', err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
content: 'No webout returned.'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return await getMockResponseFromFile([
|
|
||||||
process.cwd(),
|
|
||||||
'mocks',
|
|
||||||
'sas9',
|
|
||||||
...filePath
|
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/SASStoredProcess/do/')
|
@Post('/SASStoredProcess/do/')
|
||||||
public async sasStoredProcessDoPost(
|
public async sasStoredProcessDo(
|
||||||
@Request() req: express.Request
|
@Request() req: express.Request
|
||||||
): Promise<Sas9Response> {
|
): Promise<Sas9Response> {
|
||||||
if (!this.loggedIn) {
|
if (!this.loggedIn) {
|
||||||
@@ -141,38 +53,23 @@ export class MockSas9Controller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const program = req.query._program ?? req.body?._program
|
let program = req.query._program?.toString() || ''
|
||||||
const vars = {
|
program = program.replace('/', '')
|
||||||
...req.query,
|
|
||||||
...req.body,
|
|
||||||
_requestMethod: req.method,
|
|
||||||
_driveLoc: process.driveLoc
|
|
||||||
}
|
|
||||||
const filesNamesMap = req.files?.length
|
|
||||||
? makeFilesNamesMap(req.files as MulterFile[])
|
|
||||||
: null
|
|
||||||
const otherArgs = { filesNamesMap: filesNamesMap }
|
|
||||||
const { codePath, runTime } = await getRunTimeAndFilePath(program + '.js')
|
|
||||||
try {
|
|
||||||
const result = await new ExecutionController().executeFile({
|
|
||||||
programPath: codePath,
|
|
||||||
preProgramVariables: getPreProgramVariables(req),
|
|
||||||
vars: vars,
|
|
||||||
otherArgs: otherArgs,
|
|
||||||
runTime,
|
|
||||||
session: req.sasjsSession,
|
|
||||||
forceStringResult: true
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
const content = await getMockResponseFromFile([
|
||||||
content: result.result as string
|
process.cwd(),
|
||||||
}
|
'mocks',
|
||||||
} catch (err) {
|
...program.split('/')
|
||||||
process.logger.error('err', err)
|
])
|
||||||
|
|
||||||
|
if (content.error) {
|
||||||
|
return content
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const parsedContent = parseJsonIfValid(content.content)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content: 'No webout returned.'
|
content: parsedContent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,8 +85,8 @@ export class MockSas9Controller {
|
|||||||
return await getMockResponseFromFile([
|
return await getMockResponseFromFile([
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
'mocks',
|
'mocks',
|
||||||
'sas9',
|
|
||||||
'generic',
|
'generic',
|
||||||
|
'sas9',
|
||||||
'logged-in'
|
'logged-in'
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
@@ -198,27 +95,21 @@ export class MockSas9Controller {
|
|||||||
return await getMockResponseFromFile([
|
return await getMockResponseFromFile([
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
'mocks',
|
'mocks',
|
||||||
'sas9',
|
|
||||||
'generic',
|
'generic',
|
||||||
|
'sas9',
|
||||||
'login'
|
'login'
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/SASLogon/login')
|
@Post('/SASLogon/login')
|
||||||
public async loginPost(req: express.Request): Promise<Sas9Response> {
|
public async loginPost(req: express.Request): Promise<Sas9Response> {
|
||||||
if (req.body.lt && req.body.lt !== 'validtoken')
|
|
||||||
return {
|
|
||||||
content: '',
|
|
||||||
redirect: '/SASLogon/login'
|
|
||||||
}
|
|
||||||
|
|
||||||
this.loggedIn = req.body.username
|
this.loggedIn = req.body.username
|
||||||
|
|
||||||
return await getMockResponseFromFile([
|
return await getMockResponseFromFile([
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
'mocks',
|
'mocks',
|
||||||
'sas9',
|
|
||||||
'generic',
|
'generic',
|
||||||
|
'sas9',
|
||||||
'logged-in'
|
'logged-in'
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
@@ -231,8 +122,8 @@ export class MockSas9Controller {
|
|||||||
return await getMockResponseFromFile([
|
return await getMockResponseFromFile([
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
'mocks',
|
'mocks',
|
||||||
'sas9',
|
|
||||||
'generic',
|
'generic',
|
||||||
|
'sas9',
|
||||||
'public-access-denied'
|
'public-access-denied'
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
@@ -240,8 +131,8 @@ export class MockSas9Controller {
|
|||||||
return await getMockResponseFromFile([
|
return await getMockResponseFromFile([
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
'mocks',
|
'mocks',
|
||||||
'sas9',
|
|
||||||
'generic',
|
'generic',
|
||||||
|
'sas9',
|
||||||
'logged-out'
|
'logged-out'
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
@@ -261,6 +152,23 @@ export class MockSas9Controller {
|
|||||||
private isPublicAccount = () => this.loggedIn?.toLowerCase() === 'public'
|
private isPublicAccount = () => this.loggedIn?.toLowerCase() === 'public'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If JSON is valid it will be parsed otherwise will return text unaltered
|
||||||
|
* @param content string to be parsed
|
||||||
|
* @returns JSON or string
|
||||||
|
*/
|
||||||
|
const parseJsonIfValid = (content: string) => {
|
||||||
|
let fileContent = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
fileContent = JSON.parse(content)
|
||||||
|
} catch (err: any) {
|
||||||
|
fileContent = content
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileContent
|
||||||
|
}
|
||||||
|
|
||||||
const getMockResponseFromFile = async (
|
const getMockResponseFromFile = async (
|
||||||
filePath: string[]
|
filePath: string[]
|
||||||
): Promise<MockFileRead> => {
|
): Promise<MockFileRead> => {
|
||||||
@@ -269,7 +177,7 @@ const getMockResponseFromFile = async (
|
|||||||
|
|
||||||
let file = await readFile(filePathParsed).catch((err: any) => {
|
let file = await readFile(filePathParsed).catch((err: any) => {
|
||||||
const errMsg = `Error reading mocked file on path: ${filePathParsed}\nError: ${err}`
|
const errMsg = `Error reading mocked file on path: ${filePathParsed}\nError: ${err}`
|
||||||
process.logger.error(errMsg)
|
console.error(errMsg)
|
||||||
|
|
||||||
error = true
|
error = true
|
||||||
|
|
||||||
|
|||||||
@@ -56,9 +56,9 @@ interface RegisterPermissionPayload {
|
|||||||
principalType: PrincipalType
|
principalType: PrincipalType
|
||||||
/**
|
/**
|
||||||
* The id of user or group to which a rule is assigned.
|
* The id of user or group to which a rule is assigned.
|
||||||
* @example 'groupIdString'
|
* @example 123
|
||||||
*/
|
*/
|
||||||
principalId: string
|
principalId: number
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UpdatePermissionPayload {
|
interface UpdatePermissionPayload {
|
||||||
@@ -70,7 +70,7 @@ interface UpdatePermissionPayload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface PermissionDetailsResponse {
|
export interface PermissionDetailsResponse {
|
||||||
uid: string
|
permissionId: number
|
||||||
path: string
|
path: string
|
||||||
type: string
|
type: string
|
||||||
setting: string
|
setting: string
|
||||||
@@ -91,24 +91,24 @@ export class PermissionController {
|
|||||||
*/
|
*/
|
||||||
@Example<PermissionDetailsResponse[]>([
|
@Example<PermissionDetailsResponse[]>([
|
||||||
{
|
{
|
||||||
uid: 'permissionId1String',
|
permissionId: 123,
|
||||||
path: '/SASjsApi/code/execute',
|
path: '/SASjsApi/code/execute',
|
||||||
type: 'Route',
|
type: 'Route',
|
||||||
setting: 'Grant',
|
setting: 'Grant',
|
||||||
user: {
|
user: {
|
||||||
uid: 'user1-id',
|
id: 1,
|
||||||
username: 'johnSnow01',
|
username: 'johnSnow01',
|
||||||
displayName: 'John Snow',
|
displayName: 'John Snow',
|
||||||
isAdmin: false
|
isAdmin: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
uid: 'permissionId2String',
|
permissionId: 124,
|
||||||
path: '/SASjsApi/code/execute',
|
path: '/SASjsApi/code/execute',
|
||||||
type: 'Route',
|
type: 'Route',
|
||||||
setting: 'Grant',
|
setting: 'Grant',
|
||||||
group: {
|
group: {
|
||||||
uid: 'group1-id',
|
groupId: 1,
|
||||||
name: 'DCGroup',
|
name: 'DCGroup',
|
||||||
description: 'This group represents Data Controller Users',
|
description: 'This group represents Data Controller Users',
|
||||||
isActive: true,
|
isActive: true,
|
||||||
@@ -128,12 +128,12 @@ export class PermissionController {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@Example<PermissionDetailsResponse>({
|
@Example<PermissionDetailsResponse>({
|
||||||
uid: 'permissionIdString',
|
permissionId: 123,
|
||||||
path: '/SASjsApi/code/execute',
|
path: '/SASjsApi/code/execute',
|
||||||
type: 'Route',
|
type: 'Route',
|
||||||
setting: 'Grant',
|
setting: 'Grant',
|
||||||
user: {
|
user: {
|
||||||
uid: 'userIdString',
|
id: 1,
|
||||||
username: 'johnSnow01',
|
username: 'johnSnow01',
|
||||||
displayName: 'John Snow',
|
displayName: 'John Snow',
|
||||||
isAdmin: false
|
isAdmin: false
|
||||||
@@ -149,36 +149,36 @@ export class PermissionController {
|
|||||||
/**
|
/**
|
||||||
* @summary Update permission setting. Admin only
|
* @summary Update permission setting. Admin only
|
||||||
* @param permissionId The permission's identifier
|
* @param permissionId The permission's identifier
|
||||||
* @example permissionId "permissionIdString"
|
* @example permissionId 1234
|
||||||
*/
|
*/
|
||||||
@Example<PermissionDetailsResponse>({
|
@Example<PermissionDetailsResponse>({
|
||||||
uid: 'permissionIdString',
|
permissionId: 123,
|
||||||
path: '/SASjsApi/code/execute',
|
path: '/SASjsApi/code/execute',
|
||||||
type: 'Route',
|
type: 'Route',
|
||||||
setting: 'Grant',
|
setting: 'Grant',
|
||||||
user: {
|
user: {
|
||||||
uid: 'userIdString',
|
id: 1,
|
||||||
username: 'johnSnow01',
|
username: 'johnSnow01',
|
||||||
displayName: 'John Snow',
|
displayName: 'John Snow',
|
||||||
isAdmin: false
|
isAdmin: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@Patch('{uid}')
|
@Patch('{permissionId}')
|
||||||
public async updatePermission(
|
public async updatePermission(
|
||||||
@Path() uid: string,
|
@Path() permissionId: number,
|
||||||
@Body() body: UpdatePermissionPayload
|
@Body() body: UpdatePermissionPayload
|
||||||
): Promise<PermissionDetailsResponse> {
|
): Promise<PermissionDetailsResponse> {
|
||||||
return updatePermission(uid, body)
|
return updatePermission(permissionId, body)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Delete a permission. Admin only.
|
* @summary Delete a permission. Admin only.
|
||||||
* @param permissionId The user's identifier
|
* @param permissionId The user's identifier
|
||||||
* @example permissionId "permissionIdString"
|
* @example permissionId 1234
|
||||||
*/
|
*/
|
||||||
@Delete('{uid}')
|
@Delete('{permissionId}')
|
||||||
public async deletePermission(@Path() uid: string) {
|
public async deletePermission(@Path() permissionId: number) {
|
||||||
return deletePermission(uid)
|
return deletePermission(permissionId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +191,7 @@ const getAllPermissions = async (
|
|||||||
else {
|
else {
|
||||||
const permissions: PermissionDetailsResponse[] = []
|
const permissions: PermissionDetailsResponse[] = []
|
||||||
|
|
||||||
const dbUser = await User.findOne({ _id: user?.userId })
|
const dbUser = await User.findOne({ id: user?.userId })
|
||||||
if (!dbUser)
|
if (!dbUser)
|
||||||
throw {
|
throw {
|
||||||
code: 404,
|
code: 404,
|
||||||
@@ -227,7 +227,7 @@ const createPermission = async ({
|
|||||||
|
|
||||||
switch (principalType) {
|
switch (principalType) {
|
||||||
case PrincipalType.user: {
|
case PrincipalType.user: {
|
||||||
const userInDB = await User.findOne({ _id: principalId })
|
const userInDB = await User.findOne({ id: principalId })
|
||||||
if (!userInDB)
|
if (!userInDB)
|
||||||
throw {
|
throw {
|
||||||
code: 404,
|
code: 404,
|
||||||
@@ -259,7 +259,7 @@ const createPermission = async ({
|
|||||||
permission.user = userInDB._id
|
permission.user = userInDB._id
|
||||||
|
|
||||||
user = {
|
user = {
|
||||||
uid: userInDB.uid,
|
id: userInDB.id,
|
||||||
username: userInDB.username,
|
username: userInDB.username,
|
||||||
displayName: userInDB.displayName,
|
displayName: userInDB.displayName,
|
||||||
isAdmin: userInDB.isAdmin
|
isAdmin: userInDB.isAdmin
|
||||||
@@ -267,7 +267,7 @@ const createPermission = async ({
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
case PrincipalType.group: {
|
case PrincipalType.group: {
|
||||||
const groupInDB = await Group.findOne({ _id: principalId })
|
const groupInDB = await Group.findOne({ groupId: principalId })
|
||||||
if (!groupInDB)
|
if (!groupInDB)
|
||||||
throw {
|
throw {
|
||||||
code: 404,
|
code: 404,
|
||||||
@@ -291,13 +291,13 @@ const createPermission = async ({
|
|||||||
permission.group = groupInDB._id
|
permission.group = groupInDB._id
|
||||||
|
|
||||||
group = {
|
group = {
|
||||||
uid: groupInDB.uid,
|
groupId: groupInDB.groupId,
|
||||||
name: groupInDB.name,
|
name: groupInDB.name,
|
||||||
description: groupInDB.description,
|
description: groupInDB.description,
|
||||||
isActive: groupInDB.isActive,
|
isActive: groupInDB.isActive,
|
||||||
users: groupInDB.populate({
|
users: groupInDB.populate({
|
||||||
path: 'users',
|
path: 'users',
|
||||||
select: 'uid username displayName isAdmin -_id',
|
select: 'id username displayName isAdmin -_id',
|
||||||
options: { limit: 15 }
|
options: { limit: 15 }
|
||||||
}) as unknown as UserResponse[]
|
}) as unknown as UserResponse[]
|
||||||
}
|
}
|
||||||
@@ -314,7 +314,7 @@ const createPermission = async ({
|
|||||||
const savedPermission = await permission.save()
|
const savedPermission = await permission.save()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
uid: savedPermission.uid,
|
permissionId: savedPermission.permissionId,
|
||||||
path: savedPermission.path,
|
path: savedPermission.path,
|
||||||
type: savedPermission.type,
|
type: savedPermission.type,
|
||||||
setting: savedPermission.setting,
|
setting: savedPermission.setting,
|
||||||
@@ -324,21 +324,27 @@ const createPermission = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const updatePermission = async (
|
const updatePermission = async (
|
||||||
uid: string,
|
id: number,
|
||||||
data: UpdatePermissionPayload
|
data: UpdatePermissionPayload
|
||||||
): Promise<PermissionDetailsResponse> => {
|
): Promise<PermissionDetailsResponse> => {
|
||||||
const { setting } = data
|
const { setting } = data
|
||||||
|
|
||||||
const updatedPermission = (await Permission.findOneAndUpdate(
|
const updatedPermission = (await Permission.findOneAndUpdate(
|
||||||
{ _id: uid },
|
{ permissionId: id },
|
||||||
{ setting },
|
{ setting },
|
||||||
{ new: true }
|
{ new: true }
|
||||||
)
|
)
|
||||||
.select('uid path type setting')
|
.select({
|
||||||
.populate({ path: 'user', select: 'uid username displayName isAdmin' })
|
_id: 0,
|
||||||
|
permissionId: 1,
|
||||||
|
path: 1,
|
||||||
|
type: 1,
|
||||||
|
setting: 1
|
||||||
|
})
|
||||||
|
.populate({ path: 'user', select: 'id username displayName isAdmin -_id' })
|
||||||
.populate({
|
.populate({
|
||||||
path: 'group',
|
path: 'group',
|
||||||
select: 'groupId name description'
|
select: 'groupId name description -_id'
|
||||||
})) as unknown as PermissionDetailsResponse
|
})) as unknown as PermissionDetailsResponse
|
||||||
if (!updatedPermission)
|
if (!updatedPermission)
|
||||||
throw {
|
throw {
|
||||||
@@ -350,13 +356,13 @@ const updatePermission = async (
|
|||||||
return updatedPermission
|
return updatedPermission
|
||||||
}
|
}
|
||||||
|
|
||||||
const deletePermission = async (uid: string) => {
|
const deletePermission = async (id: number) => {
|
||||||
const permission = await Permission.findOne({ _id: uid })
|
const permission = await Permission.findOne({ permissionId: id })
|
||||||
if (!permission)
|
if (!permission)
|
||||||
throw {
|
throw {
|
||||||
code: 404,
|
code: 404,
|
||||||
status: 'Not Found',
|
status: 'Not Found',
|
||||||
message: 'Permission not found.'
|
message: 'Permission not found.'
|
||||||
}
|
}
|
||||||
await Permission.deleteOne({ _id: uid })
|
await Permission.deleteOne({ permissionId: id })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,6 @@ import express from 'express'
|
|||||||
import { Request, Security, Route, Tags, Example, Get } from 'tsoa'
|
import { Request, Security, Route, Tags, Example, Get } from 'tsoa'
|
||||||
import { UserResponse } from './user'
|
import { UserResponse } from './user'
|
||||||
|
|
||||||
interface SessionResponse extends Omit<UserResponse, 'uid'> {
|
|
||||||
id: string
|
|
||||||
needsToUpdatePassword?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
@Security('bearerAuth')
|
@Security('bearerAuth')
|
||||||
@Route('SASjsApi/session')
|
@Route('SASjsApi/session')
|
||||||
@Tags('Session')
|
@Tags('Session')
|
||||||
@@ -15,17 +10,16 @@ export class SessionController {
|
|||||||
* @summary Get session info (username).
|
* @summary Get session info (username).
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@Example<SessionResponse>({
|
@Example<UserResponse>({
|
||||||
id: 'userIdString',
|
id: 123,
|
||||||
username: 'johnusername',
|
username: 'johnusername',
|
||||||
displayName: 'John',
|
displayName: 'John',
|
||||||
isAdmin: false,
|
isAdmin: false
|
||||||
needsToUpdatePassword: false
|
|
||||||
})
|
})
|
||||||
@Get('/')
|
@Get('/')
|
||||||
public async session(
|
public async session(
|
||||||
@Request() request: express.Request
|
@Request() request: express.Request
|
||||||
): Promise<SessionResponse> {
|
): Promise<UserResponse> {
|
||||||
return session(request)
|
return session(request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -34,6 +28,5 @@ const session = (req: express.Request) => ({
|
|||||||
id: req.user!.userId,
|
id: req.user!.userId,
|
||||||
username: req.user!.username,
|
username: req.user!.username,
|
||||||
displayName: req.user!.displayName,
|
displayName: req.user!.displayName,
|
||||||
isAdmin: req.user!.isAdmin,
|
isAdmin: req.user!.isAdmin
|
||||||
needsToUpdatePassword: req.user!.needsToUpdatePassword
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -91,8 +91,6 @@ const execute = async (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
req.res?.header(httpHeaders)
|
|
||||||
|
|
||||||
if (result instanceof Buffer) {
|
if (result instanceof Buffer) {
|
||||||
;(req as any).sasHeaders = httpHeaders
|
;(req as any).sasHeaders = httpHeaders
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,23 +17,22 @@ import {
|
|||||||
import { desktopUser } from '../middlewares'
|
import { desktopUser } from '../middlewares'
|
||||||
|
|
||||||
import User, { UserPayload } from '../model/User'
|
import User, { UserPayload } from '../model/User'
|
||||||
import {
|
import { getUserAutoExec, updateUserAutoExec, ModeType } from '../utils'
|
||||||
getUserAutoExec,
|
import { GroupResponse } from './group'
|
||||||
updateUserAutoExec,
|
|
||||||
ModeType,
|
|
||||||
ALL_USERS_GROUP
|
|
||||||
} from '../utils'
|
|
||||||
import { GroupController, GroupResponse } from './group'
|
|
||||||
|
|
||||||
export interface UserResponse {
|
export interface UserResponse {
|
||||||
uid: string
|
id: number
|
||||||
username: string
|
username: string
|
||||||
displayName: string
|
displayName: string
|
||||||
isAdmin: boolean
|
isAdmin: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserDetailsResponse extends UserResponse {
|
export interface UserDetailsResponse {
|
||||||
|
id: number
|
||||||
|
displayName: string
|
||||||
|
username: string
|
||||||
isActive: boolean
|
isActive: boolean
|
||||||
|
isAdmin: boolean
|
||||||
autoExec?: string
|
autoExec?: string
|
||||||
groups?: GroupResponse[]
|
groups?: GroupResponse[]
|
||||||
}
|
}
|
||||||
@@ -48,13 +47,13 @@ export class UserController {
|
|||||||
*/
|
*/
|
||||||
@Example<UserResponse[]>([
|
@Example<UserResponse[]>([
|
||||||
{
|
{
|
||||||
uid: 'userIdString',
|
id: 123,
|
||||||
username: 'johnusername',
|
username: 'johnusername',
|
||||||
displayName: 'John',
|
displayName: 'John',
|
||||||
isAdmin: false
|
isAdmin: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
uid: 'anotherUserIdString',
|
id: 456,
|
||||||
username: 'starkusername',
|
username: 'starkusername',
|
||||||
displayName: 'Stark',
|
displayName: 'Stark',
|
||||||
isAdmin: true
|
isAdmin: true
|
||||||
@@ -70,7 +69,7 @@ export class UserController {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@Example<UserDetailsResponse>({
|
@Example<UserDetailsResponse>({
|
||||||
uid: 'userIdString',
|
id: 1234,
|
||||||
displayName: 'John Snow',
|
displayName: 'John Snow',
|
||||||
username: 'johnSnow01',
|
username: 'johnSnow01',
|
||||||
isAdmin: false,
|
isAdmin: false,
|
||||||
@@ -107,20 +106,20 @@ export class UserController {
|
|||||||
* Only Admin or user itself will get user autoExec code.
|
* Only Admin or user itself will get user autoExec code.
|
||||||
* @summary Get user properties - such as group memberships, userName, displayName.
|
* @summary Get user properties - such as group memberships, userName, displayName.
|
||||||
* @param userId The user's identifier
|
* @param userId The user's identifier
|
||||||
* @example userId "userIdString"
|
* @example userId 1234
|
||||||
*/
|
*/
|
||||||
@Get('{uid}')
|
@Get('{userId}')
|
||||||
public async getUser(
|
public async getUser(
|
||||||
@Request() req: express.Request,
|
@Request() req: express.Request,
|
||||||
@Path() uid: string
|
@Path() userId: number
|
||||||
): Promise<UserDetailsResponse> {
|
): Promise<UserDetailsResponse> {
|
||||||
const { MODE } = process.env
|
const { MODE } = process.env
|
||||||
|
|
||||||
if (MODE === ModeType.Desktop) return getDesktopAutoExec()
|
if (MODE === ModeType.Desktop) return getDesktopAutoExec()
|
||||||
|
|
||||||
const { user } = req
|
const { user } = req
|
||||||
const getAutoExec = user!.isAdmin || user!.userId === uid
|
const getAutoExec = user!.isAdmin || user!.userId == userId
|
||||||
return getUser({ _id: uid }, getAutoExec)
|
return getUser({ id: userId }, getAutoExec)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -129,7 +128,7 @@ export class UserController {
|
|||||||
* @example username "johnSnow01"
|
* @example username "johnSnow01"
|
||||||
*/
|
*/
|
||||||
@Example<UserDetailsResponse>({
|
@Example<UserDetailsResponse>({
|
||||||
uid: 'userIdString',
|
id: 1234,
|
||||||
displayName: 'John Snow',
|
displayName: 'John Snow',
|
||||||
username: 'johnSnow01',
|
username: 'johnSnow01',
|
||||||
isAdmin: false,
|
isAdmin: false,
|
||||||
@@ -154,7 +153,7 @@ export class UserController {
|
|||||||
* @example userId "1234"
|
* @example userId "1234"
|
||||||
*/
|
*/
|
||||||
@Example<UserDetailsResponse>({
|
@Example<UserDetailsResponse>({
|
||||||
uid: 'userIdString',
|
id: 1234,
|
||||||
displayName: 'John Snow',
|
displayName: 'John Snow',
|
||||||
username: 'johnSnow01',
|
username: 'johnSnow01',
|
||||||
isAdmin: false,
|
isAdmin: false,
|
||||||
@@ -162,7 +161,7 @@ export class UserController {
|
|||||||
})
|
})
|
||||||
@Patch('{userId}')
|
@Patch('{userId}')
|
||||||
public async updateUser(
|
public async updateUser(
|
||||||
@Path() userId: string,
|
@Path() userId: number,
|
||||||
@Body() body: UserPayload
|
@Body() body: UserPayload
|
||||||
): Promise<UserDetailsResponse> {
|
): Promise<UserDetailsResponse> {
|
||||||
const { MODE } = process.env
|
const { MODE } = process.env
|
||||||
@@ -170,7 +169,7 @@ export class UserController {
|
|||||||
if (MODE === ModeType.Desktop)
|
if (MODE === ModeType.Desktop)
|
||||||
return updateDesktopAutoExec(body.autoExec ?? '')
|
return updateDesktopAutoExec(body.autoExec ?? '')
|
||||||
|
|
||||||
return updateUser({ _id: userId }, body)
|
return updateUser({ id: userId }, body)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -194,27 +193,25 @@ export class UserController {
|
|||||||
*/
|
*/
|
||||||
@Delete('{userId}')
|
@Delete('{userId}')
|
||||||
public async deleteUser(
|
public async deleteUser(
|
||||||
@Path() userId: string,
|
@Path() userId: number,
|
||||||
@Body() body: { password?: string },
|
@Body() body: { password?: string },
|
||||||
@Query() @Hidden() isAdmin: boolean = false
|
@Query() @Hidden() isAdmin: boolean = false
|
||||||
) {
|
) {
|
||||||
return deleteUser({ _id: userId }, isAdmin, body)
|
return deleteUser({ id: userId }, isAdmin, body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAllUsers = async (): Promise<UserResponse[]> =>
|
const getAllUsers = async (): Promise<UserResponse[]> =>
|
||||||
await User.find({}).select('uid username displayName isAdmin').exec()
|
await User.find({})
|
||||||
|
.select({ _id: 0, id: 1, username: 1, displayName: 1, isAdmin: 1 })
|
||||||
|
.exec()
|
||||||
|
|
||||||
const createUser = async (data: UserPayload): Promise<UserDetailsResponse> => {
|
const createUser = async (data: UserPayload): Promise<UserDetailsResponse> => {
|
||||||
const { displayName, username, password, isAdmin, isActive, autoExec } = data
|
const { displayName, username, password, isAdmin, isActive, autoExec } = data
|
||||||
|
|
||||||
// Checking if user is already in the database
|
// Checking if user is already in the database
|
||||||
const usernameExist = await User.findOne({ username })
|
const usernameExist = await User.findOne({ username })
|
||||||
if (usernameExist)
|
if (usernameExist) throw new Error('Username already exists.')
|
||||||
throw {
|
|
||||||
code: 409,
|
|
||||||
message: 'Username already exists.'
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hash passwords
|
// Hash passwords
|
||||||
const hashPassword = User.hashPassword(password)
|
const hashPassword = User.hashPassword(password)
|
||||||
@@ -231,17 +228,8 @@ const createUser = async (data: UserPayload): Promise<UserDetailsResponse> => {
|
|||||||
|
|
||||||
const savedUser = await user.save()
|
const savedUser = await user.save()
|
||||||
|
|
||||||
const groupController = new GroupController()
|
|
||||||
const allUsersGroup = await groupController
|
|
||||||
.getGroupByName(ALL_USERS_GROUP.name)
|
|
||||||
.catch(() => {})
|
|
||||||
|
|
||||||
if (allUsersGroup) {
|
|
||||||
await groupController.addUserToGroup(allUsersGroup.uid, savedUser.uid)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
uid: savedUser.uid,
|
id: savedUser.id,
|
||||||
displayName: savedUser.displayName,
|
displayName: savedUser.displayName,
|
||||||
username: savedUser.username,
|
username: savedUser.username,
|
||||||
isActive: savedUser.isActive,
|
isActive: savedUser.isActive,
|
||||||
@@ -250,8 +238,8 @@ const createUser = async (data: UserPayload): Promise<UserDetailsResponse> => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetUserBy {
|
interface GetUserBy {
|
||||||
_id?: string
|
id?: number
|
||||||
username?: string
|
username?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,20 +249,16 @@ const getUser = async (
|
|||||||
): Promise<UserDetailsResponse> => {
|
): Promise<UserDetailsResponse> => {
|
||||||
const user = (await User.findOne(
|
const user = (await User.findOne(
|
||||||
findBy,
|
findBy,
|
||||||
`uid displayName username isActive isAdmin autoExec`
|
`id displayName username isActive isAdmin autoExec -_id`
|
||||||
).populate(
|
).populate(
|
||||||
'groups',
|
'groups',
|
||||||
'uid name description'
|
'groupId name description -_id'
|
||||||
)) as unknown as UserDetailsResponse
|
)) as unknown as UserDetailsResponse
|
||||||
|
|
||||||
if (!user)
|
if (!user) throw new Error('User is not found.')
|
||||||
throw {
|
|
||||||
code: 404,
|
|
||||||
message: 'User is not found.'
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
uid: user.uid,
|
id: user.id,
|
||||||
displayName: user.displayName,
|
displayName: user.displayName,
|
||||||
username: user.username,
|
username: user.username,
|
||||||
isActive: user.isActive,
|
isActive: user.isActive,
|
||||||
@@ -287,7 +271,7 @@ const getUser = async (
|
|||||||
const getDesktopAutoExec = async () => {
|
const getDesktopAutoExec = async () => {
|
||||||
return {
|
return {
|
||||||
...desktopUser,
|
...desktopUser,
|
||||||
uid: desktopUser.userId,
|
id: desktopUser.userId,
|
||||||
autoExec: await getUserAutoExec()
|
autoExec: await getUserAutoExec()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -300,36 +284,15 @@ const updateUser = async (
|
|||||||
|
|
||||||
const params: any = { displayName, isAdmin, isActive, autoExec }
|
const params: any = { displayName, isAdmin, isActive, autoExec }
|
||||||
|
|
||||||
const user = await User.findOne(findBy)
|
|
||||||
|
|
||||||
if (username && username !== user?.username && user?.authProvider) {
|
|
||||||
throw {
|
|
||||||
code: 405,
|
|
||||||
message:
|
|
||||||
'Can not update username of user that is created by an external auth provider.'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (displayName && displayName !== user?.displayName && user?.authProvider) {
|
|
||||||
throw {
|
|
||||||
code: 405,
|
|
||||||
message:
|
|
||||||
'Can not update display name of user that is created by an external auth provider.'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (username) {
|
if (username) {
|
||||||
// Checking if user is already in the database
|
// Checking if user is already in the database
|
||||||
const usernameExist = await User.findOne({ username })
|
const usernameExist = await User.findOne({ username })
|
||||||
if (usernameExist) {
|
if (usernameExist) {
|
||||||
if (
|
if (
|
||||||
(findBy._id && usernameExist.uid !== findBy._id) ||
|
(findBy.id && usernameExist.id != findBy.id) ||
|
||||||
(findBy.username && usernameExist.username !== findBy.username)
|
(findBy.username && usernameExist.username != findBy.username)
|
||||||
)
|
)
|
||||||
throw {
|
throw new Error('Username already exists.')
|
||||||
code: 409,
|
|
||||||
message: 'Username already exists.'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
params.username = username
|
params.username = username
|
||||||
}
|
}
|
||||||
@@ -342,13 +305,10 @@ const updateUser = async (
|
|||||||
const updatedUser = await User.findOneAndUpdate(findBy, params, { new: true })
|
const updatedUser = await User.findOneAndUpdate(findBy, params, { new: true })
|
||||||
|
|
||||||
if (!updatedUser)
|
if (!updatedUser)
|
||||||
throw {
|
throw new Error(`Unable to find user with ${findBy.id || findBy.username}`)
|
||||||
code: 404,
|
|
||||||
message: `Unable to find user with ${findBy._id || findBy.username}`
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
uid: updatedUser.uid,
|
id: updatedUser.id,
|
||||||
username: updatedUser.username,
|
username: updatedUser.username,
|
||||||
displayName: updatedUser.displayName,
|
displayName: updatedUser.displayName,
|
||||||
isAdmin: updatedUser.isAdmin,
|
isAdmin: updatedUser.isAdmin,
|
||||||
@@ -361,7 +321,7 @@ const updateDesktopAutoExec = async (autoExec: string) => {
|
|||||||
await updateUserAutoExec(autoExec)
|
await updateUserAutoExec(autoExec)
|
||||||
return {
|
return {
|
||||||
...desktopUser,
|
...desktopUser,
|
||||||
uid: desktopUser.userId,
|
id: desktopUser.userId,
|
||||||
autoExec
|
autoExec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -372,19 +332,11 @@ const deleteUser = async (
|
|||||||
{ password }: { password?: string }
|
{ password }: { password?: string }
|
||||||
) => {
|
) => {
|
||||||
const user = await User.findOne(findBy)
|
const user = await User.findOne(findBy)
|
||||||
if (!user)
|
if (!user) throw new Error('User is not found.')
|
||||||
throw {
|
|
||||||
code: 404,
|
|
||||||
message: 'User is not found.'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isAdmin) {
|
if (!isAdmin) {
|
||||||
const validPass = user.comparePassword(password!)
|
const validPass = user.comparePassword(password!)
|
||||||
if (!validPass)
|
if (!validPass) throw new Error('Invalid password.')
|
||||||
throw {
|
|
||||||
code: 401,
|
|
||||||
message: 'Invalid password.'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await User.deleteOne(findBy)
|
await User.deleteOne(findBy)
|
||||||
|
|||||||
@@ -1,17 +1,11 @@
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import express from 'express'
|
import express from 'express'
|
||||||
import { Request, Route, Tags, Post, Body, Get, Example } from 'tsoa'
|
import { Request, Route, Tags, Post, Body, Get, Example } from 'tsoa'
|
||||||
import { readFile, convertSecondsToHms } from '@sasjs/utils'
|
import { readFile } from '@sasjs/utils'
|
||||||
|
|
||||||
import User from '../model/User'
|
import User from '../model/User'
|
||||||
import Client from '../model/Client'
|
import Client from '../model/Client'
|
||||||
import {
|
import { getWebBuildFolder, generateAuthCode } from '../utils'
|
||||||
getWebBuildFolder,
|
|
||||||
generateAuthCode,
|
|
||||||
RateLimiter,
|
|
||||||
AuthProviderType,
|
|
||||||
LDAPClient
|
|
||||||
} from '../utils'
|
|
||||||
import { InfoJWT } from '../types'
|
import { InfoJWT } from '../types'
|
||||||
import { AuthController } from './auth'
|
import { AuthController } from './auth'
|
||||||
|
|
||||||
@@ -84,37 +78,10 @@ const login = async (
|
|||||||
) => {
|
) => {
|
||||||
// Authenticate User
|
// Authenticate User
|
||||||
const user = await User.findOne({ username })
|
const user = await User.findOne({ username })
|
||||||
|
if (!user) throw new Error('Username is not found.')
|
||||||
|
|
||||||
let validPass = false
|
const validPass = user.comparePassword(password)
|
||||||
|
if (!validPass) throw new Error('Invalid password.')
|
||||||
if (user) {
|
|
||||||
if (
|
|
||||||
process.env.AUTH_PROVIDERS === AuthProviderType.LDAP &&
|
|
||||||
user.authProvider === AuthProviderType.LDAP
|
|
||||||
) {
|
|
||||||
const ldapClient = await LDAPClient.init()
|
|
||||||
validPass = await ldapClient
|
|
||||||
.verifyUser(username, password)
|
|
||||||
.catch(() => false)
|
|
||||||
} else {
|
|
||||||
validPass = user.comparePassword(password)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// code to prevent brute force attack
|
|
||||||
|
|
||||||
const rateLimiter = RateLimiter.getInstance()
|
|
||||||
|
|
||||||
if (!validPass) {
|
|
||||||
const retrySecs = await rateLimiter.consume(req.ip, user?.username)
|
|
||||||
if (retrySecs > 0) throw errors.tooManyRequests(retrySecs)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!user) throw errors.userNotFound
|
|
||||||
if (!validPass) throw errors.invalidPassword
|
|
||||||
|
|
||||||
// Reset on successful authorization
|
|
||||||
rateLimiter.resetOnSuccess(req.ip, user.username)
|
|
||||||
|
|
||||||
req.session.loggedIn = true
|
req.session.loggedIn = true
|
||||||
req.session.user = {
|
req.session.user = {
|
||||||
@@ -124,8 +91,7 @@ const login = async (
|
|||||||
displayName: user.displayName,
|
displayName: user.displayName,
|
||||||
isAdmin: user.isAdmin,
|
isAdmin: user.isAdmin,
|
||||||
isActive: user.isActive,
|
isActive: user.isActive,
|
||||||
autoExec: user.autoExec,
|
autoExec: user.autoExec
|
||||||
needsToUpdatePassword: user.needsToUpdatePassword
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -134,8 +100,7 @@ const login = async (
|
|||||||
id: user.id,
|
id: user.id,
|
||||||
username: user.username,
|
username: user.username,
|
||||||
displayName: user.displayName,
|
displayName: user.displayName,
|
||||||
isAdmin: user.isAdmin,
|
isAdmin: user.isAdmin
|
||||||
needsToUpdatePassword: user.needsToUpdatePassword
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -192,18 +157,3 @@ interface AuthorizeResponse {
|
|||||||
*/
|
*/
|
||||||
code: string
|
code: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const errors = {
|
|
||||||
invalidPassword: {
|
|
||||||
code: 401,
|
|
||||||
message: 'Invalid Password.'
|
|
||||||
},
|
|
||||||
userNotFound: {
|
|
||||||
code: 401,
|
|
||||||
message: 'Username is not found.'
|
|
||||||
},
|
|
||||||
tooManyRequests: (seconds: number) => ({
|
|
||||||
code: 429,
|
|
||||||
message: `Too Many Requests! Retry after ${convertSecondsToHms(seconds)}`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -76,13 +76,12 @@ const authenticateToken = async (
|
|||||||
const { MODE } = process.env
|
const { MODE } = process.env
|
||||||
if (MODE === ModeType.Desktop) {
|
if (MODE === ModeType.Desktop) {
|
||||||
req.user = {
|
req.user = {
|
||||||
userId: '1234',
|
userId: 1234,
|
||||||
clientId: 'desktopModeClientId',
|
clientId: 'desktopModeClientId',
|
||||||
username: 'desktopModeUsername',
|
username: 'desktopModeUsername',
|
||||||
displayName: 'desktopModeDisplayName',
|
displayName: 'desktopModeDisplayName',
|
||||||
isAdmin: true,
|
isAdmin: true,
|
||||||
isActive: true,
|
isActive: true
|
||||||
needsToUpdatePassword: false
|
|
||||||
}
|
}
|
||||||
req.accessToken = 'desktopModeAccessToken'
|
req.accessToken = 'desktopModeAccessToken'
|
||||||
return next()
|
return next()
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
PermissionSettingForRoute,
|
PermissionSettingForRoute,
|
||||||
PermissionType
|
PermissionType
|
||||||
} from '../controllers/permission'
|
} from '../controllers/permission'
|
||||||
import { getPath, isPublicRoute, TopLevelRoutes } from '../utils'
|
import { getPath, isPublicRoute } from '../utils'
|
||||||
|
|
||||||
export const authorize: RequestHandler = async (req, res, next) => {
|
export const authorize: RequestHandler = async (req, res, next) => {
|
||||||
const { user } = req
|
const { user } = req
|
||||||
@@ -18,13 +18,10 @@ export const authorize: RequestHandler = async (req, res, next) => {
|
|||||||
// no need to check for permissions when route is Public
|
// no need to check for permissions when route is Public
|
||||||
if (await isPublicRoute(req)) return next()
|
if (await isPublicRoute(req)) return next()
|
||||||
|
|
||||||
const dbUser = await User.findOne({ _id: user.userId })
|
const dbUser = await User.findOne({ id: user.userId })
|
||||||
if (!dbUser) return res.sendStatus(401)
|
if (!dbUser) return res.sendStatus(401)
|
||||||
|
|
||||||
const path = getPath(req)
|
const path = getPath(req)
|
||||||
const { baseUrl } = req
|
|
||||||
const topLevelRoute =
|
|
||||||
TopLevelRoutes.find((route) => baseUrl.startsWith(route)) || baseUrl
|
|
||||||
|
|
||||||
// find permission w.r.t user
|
// find permission w.r.t user
|
||||||
const permission = await Permission.findOne({
|
const permission = await Permission.findOne({
|
||||||
@@ -38,21 +35,6 @@ export const authorize: RequestHandler = async (req, res, next) => {
|
|||||||
else return res.sendStatus(401)
|
else return res.sendStatus(401)
|
||||||
}
|
}
|
||||||
|
|
||||||
// find permission w.r.t user on top level
|
|
||||||
const topLevelPermission = await Permission.findOne({
|
|
||||||
path: topLevelRoute,
|
|
||||||
type: PermissionType.route,
|
|
||||||
user: dbUser._id
|
|
||||||
})
|
|
||||||
|
|
||||||
if (topLevelPermission) {
|
|
||||||
if (topLevelPermission.setting === PermissionSettingForRoute.grant)
|
|
||||||
return next()
|
|
||||||
else return res.sendStatus(401)
|
|
||||||
}
|
|
||||||
|
|
||||||
let isPermissionDenied = false
|
|
||||||
|
|
||||||
// find permission w.r.t user's groups
|
// find permission w.r.t user's groups
|
||||||
for (const group of dbUser.groups) {
|
for (const group of dbUser.groups) {
|
||||||
const groupPermission = await Permission.findOne({
|
const groupPermission = await Permission.findOne({
|
||||||
@@ -60,28 +42,8 @@ export const authorize: RequestHandler = async (req, res, next) => {
|
|||||||
type: PermissionType.route,
|
type: PermissionType.route,
|
||||||
group
|
group
|
||||||
})
|
})
|
||||||
|
if (groupPermission?.setting === PermissionSettingForRoute.grant)
|
||||||
if (groupPermission) {
|
return next()
|
||||||
if (groupPermission.setting === PermissionSettingForRoute.grant) {
|
|
||||||
return next()
|
|
||||||
} else {
|
|
||||||
isPermissionDenied = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isPermissionDenied) {
|
|
||||||
// find permission w.r.t user's groups on top level
|
|
||||||
for (const group of dbUser.groups) {
|
|
||||||
const groupPermission = await Permission.findOne({
|
|
||||||
path: topLevelRoute,
|
|
||||||
type: PermissionType.route,
|
|
||||||
group
|
|
||||||
})
|
|
||||||
if (groupPermission?.setting === PermissionSettingForRoute.grant)
|
|
||||||
return next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.sendStatus(401)
|
return res.sendStatus(401)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
import { RequestHandler } from 'express'
|
|
||||||
import { convertSecondsToHms } from '@sasjs/utils'
|
|
||||||
import { RateLimiter } from '../utils'
|
|
||||||
|
|
||||||
export const bruteForceProtection: RequestHandler = async (req, res, next) => {
|
|
||||||
const ip = req.ip
|
|
||||||
const username = req.body.username
|
|
||||||
|
|
||||||
const rateLimiter = RateLimiter.getInstance()
|
|
||||||
|
|
||||||
const retrySecs = await rateLimiter.check(ip, username)
|
|
||||||
|
|
||||||
if (retrySecs > 0) {
|
|
||||||
res
|
|
||||||
.status(429)
|
|
||||||
.send(`Too Many Requests! Retry after ${convertSecondsToHms(retrySecs)}`)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
next()
|
|
||||||
}
|
|
||||||
17
api/src/middlewares/checkDomain.ts
Normal file
17
api/src/middlewares/checkDomain.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { RequestHandler } from 'express'
|
||||||
|
|
||||||
|
export const checkDomain: RequestHandler = (req, res, next) => {
|
||||||
|
const { allowedDomains } = process
|
||||||
|
|
||||||
|
// pass if no allowed domain is specified
|
||||||
|
if (!allowedDomains.length) return next()
|
||||||
|
|
||||||
|
if (allowedDomains.includes(req.hostname)) return next()
|
||||||
|
|
||||||
|
console.log('allowedDomains', allowedDomains)
|
||||||
|
console.log('hostname not allowed', req.hostname)
|
||||||
|
res.writeHead(404, {
|
||||||
|
'Content-Type': 'text/plain'
|
||||||
|
})
|
||||||
|
return res.end('Not found')
|
||||||
|
}
|
||||||
@@ -28,11 +28,10 @@ export const desktopRestrict: RequestHandler = (req, res, next) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const desktopUser: RequestUser = {
|
export const desktopUser: RequestUser = {
|
||||||
userId: '12345',
|
userId: 12345,
|
||||||
clientId: 'desktop_app',
|
clientId: 'desktop_app',
|
||||||
username: userInfo().username,
|
username: userInfo().username,
|
||||||
displayName: userInfo().username,
|
displayName: userInfo().username,
|
||||||
isAdmin: true,
|
isAdmin: true,
|
||||||
isActive: true,
|
isActive: true
|
||||||
needsToUpdatePassword: false
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export * from './authenticateToken'
|
export * from './authenticateToken'
|
||||||
export * from './authorize'
|
export * from './authorize'
|
||||||
|
export * from './checkDomain'
|
||||||
export * from './csrfProtection'
|
export * from './csrfProtection'
|
||||||
export * from './desktop'
|
export * from './desktop'
|
||||||
export * from './verifyAdmin'
|
export * from './verifyAdmin'
|
||||||
export * from './verifyAdminIfNeeded'
|
export * from './verifyAdminIfNeeded'
|
||||||
export * from './bruteForceProtection'
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ export const verifyAdminIfNeeded: RequestHandler = (req, res, next) => {
|
|||||||
if (!user?.isAdmin) {
|
if (!user?.isAdmin) {
|
||||||
let adminAccountRequired: boolean = true
|
let adminAccountRequired: boolean = true
|
||||||
|
|
||||||
if (req.params.uid) {
|
if (req.params.userId) {
|
||||||
adminAccountRequired = user?.userId !== req.params.uid
|
adminAccountRequired = user?.userId !== parseInt(req.params.userId)
|
||||||
} else if (req.params.username) {
|
} else if (req.params.username) {
|
||||||
adminAccountRequired = user?.username !== req.params.username
|
adminAccountRequired = user?.username !== req.params.username
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import mongoose, { Schema } from 'mongoose'
|
import mongoose, { Schema } from 'mongoose'
|
||||||
|
|
||||||
export const NUMBER_OF_SECONDS_IN_A_DAY = 86400
|
|
||||||
export interface ClientPayload {
|
export interface ClientPayload {
|
||||||
/**
|
/**
|
||||||
* Client ID
|
* Client ID
|
||||||
@@ -12,16 +11,6 @@ export interface ClientPayload {
|
|||||||
* @example "someRandomCryptoString"
|
* @example "someRandomCryptoString"
|
||||||
*/
|
*/
|
||||||
clientSecret: string
|
clientSecret: string
|
||||||
/**
|
|
||||||
* Number of seconds after which access token will expire. Default is 86400 (1 day)
|
|
||||||
* @example 86400
|
|
||||||
*/
|
|
||||||
accessTokenExpiration?: number
|
|
||||||
/**
|
|
||||||
* Number of seconds after which access token will expire. Default is 2592000 (30 days)
|
|
||||||
* @example 2592000
|
|
||||||
*/
|
|
||||||
refreshTokenExpiration?: number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ClientSchema = new Schema<ClientPayload>({
|
const ClientSchema = new Schema<ClientPayload>({
|
||||||
@@ -32,14 +21,6 @@ const ClientSchema = new Schema<ClientPayload>({
|
|||||||
clientSecret: {
|
clientSecret: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
},
|
|
||||||
accessTokenExpiration: {
|
|
||||||
type: Number,
|
|
||||||
default: NUMBER_OF_SECONDS_IN_A_DAY
|
|
||||||
},
|
|
||||||
refreshTokenExpiration: {
|
|
||||||
type: Number,
|
|
||||||
default: NUMBER_OF_SECONDS_IN_A_DAY * 30
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Schema, model, Document, Model } from 'mongoose'
|
import mongoose, { Schema, model, Document, Model } from 'mongoose'
|
||||||
import { GroupDetailsResponse } from '../controllers'
|
import { GroupDetailsResponse } from '../controllers'
|
||||||
import User, { IUser } from './User'
|
import User, { IUser } from './User'
|
||||||
import { AuthProviderType } from '../utils'
|
const AutoIncrement = require('mongoose-sequence')(mongoose)
|
||||||
|
|
||||||
export const PUBLIC_GROUP_NAME = 'public'
|
export const PUBLIC_GROUP_NAME = 'Public'
|
||||||
|
|
||||||
export interface GroupPayload {
|
export interface GroupPayload {
|
||||||
/**
|
/**
|
||||||
@@ -24,12 +24,9 @@ export interface GroupPayload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface IGroupDocument extends GroupPayload, Document {
|
interface IGroupDocument extends GroupPayload, Document {
|
||||||
|
groupId: number
|
||||||
isActive: boolean
|
isActive: boolean
|
||||||
users: Schema.Types.ObjectId[]
|
users: Schema.Types.ObjectId[]
|
||||||
authProvider?: AuthProviderType
|
|
||||||
|
|
||||||
// Declare virtual properties as read-only properties
|
|
||||||
readonly uid: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IGroup extends IGroupDocument {
|
interface IGroup extends IGroupDocument {
|
||||||
@@ -39,46 +36,28 @@ interface IGroup extends IGroupDocument {
|
|||||||
}
|
}
|
||||||
interface IGroupModel extends Model<IGroup> {}
|
interface IGroupModel extends Model<IGroup> {}
|
||||||
|
|
||||||
const opts = {
|
const groupSchema = new Schema<IGroupDocument>({
|
||||||
toJSON: {
|
name: {
|
||||||
virtuals: true,
|
type: String,
|
||||||
transform: function (doc: any, ret: any, options: any) {
|
required: true,
|
||||||
delete ret._id
|
unique: true
|
||||||
delete ret.id
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const groupSchema = new Schema<IGroupDocument>(
|
|
||||||
{
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
unique: true
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
type: String,
|
|
||||||
default: 'Group description.'
|
|
||||||
},
|
|
||||||
authProvider: {
|
|
||||||
type: String,
|
|
||||||
enum: AuthProviderType
|
|
||||||
},
|
|
||||||
isActive: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
users: [{ type: Schema.Types.ObjectId, ref: 'User' }]
|
|
||||||
},
|
},
|
||||||
opts
|
description: {
|
||||||
)
|
type: String,
|
||||||
|
default: 'Group description.'
|
||||||
groupSchema.virtual('uid').get(function () {
|
},
|
||||||
return this._id.toString()
|
isActive: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
users: [{ type: Schema.Types.ObjectId, ref: 'User' }]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
groupSchema.plugin(AutoIncrement, { inc_field: 'groupId' })
|
||||||
|
|
||||||
|
// Hooks
|
||||||
groupSchema.post('save', function (group: IGroup, next: Function) {
|
groupSchema.post('save', function (group: IGroup, next: Function) {
|
||||||
group.populate('users', 'uid username displayName').then(function () {
|
group.populate('users', 'id username displayName -_id').then(function () {
|
||||||
next()
|
next()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Schema, model, Document, Model } from 'mongoose'
|
import mongoose, { Schema, model, Document, Model } from 'mongoose'
|
||||||
|
const AutoIncrement = require('mongoose-sequence')(mongoose)
|
||||||
import { PermissionDetailsResponse } from '../controllers'
|
import { PermissionDetailsResponse } from '../controllers'
|
||||||
|
|
||||||
interface GetPermissionBy {
|
interface GetPermissionBy {
|
||||||
@@ -10,11 +11,9 @@ interface IPermissionDocument extends Document {
|
|||||||
path: string
|
path: string
|
||||||
type: string
|
type: string
|
||||||
setting: string
|
setting: string
|
||||||
|
permissionId: number
|
||||||
user: Schema.Types.ObjectId
|
user: Schema.Types.ObjectId
|
||||||
group: Schema.Types.ObjectId
|
group: Schema.Types.ObjectId
|
||||||
|
|
||||||
// Declare virtual properties as read-only properties
|
|
||||||
readonly uid: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IPermission extends IPermissionDocument {}
|
interface IPermission extends IPermissionDocument {}
|
||||||
@@ -23,54 +22,44 @@ interface IPermissionModel extends Model<IPermission> {
|
|||||||
get(getBy: GetPermissionBy): Promise<PermissionDetailsResponse[]>
|
get(getBy: GetPermissionBy): Promise<PermissionDetailsResponse[]>
|
||||||
}
|
}
|
||||||
|
|
||||||
const opts = {
|
const permissionSchema = new Schema<IPermissionDocument>({
|
||||||
toJSON: {
|
path: {
|
||||||
virtuals: true,
|
type: String,
|
||||||
transform: function (doc: any, ret: any, options: any) {
|
required: true
|
||||||
delete ret._id
|
|
||||||
delete ret.id
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const permissionSchema = new Schema<IPermissionDocument>(
|
|
||||||
{
|
|
||||||
path: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
type: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
setting: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
user: { type: Schema.Types.ObjectId, ref: 'User' },
|
|
||||||
group: { type: Schema.Types.ObjectId, ref: 'Group' }
|
|
||||||
},
|
},
|
||||||
opts
|
type: {
|
||||||
)
|
type: String,
|
||||||
|
required: true
|
||||||
permissionSchema.virtual('uid').get(function () {
|
},
|
||||||
return this._id.toString()
|
setting: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
user: { type: Schema.Types.ObjectId, ref: 'User' },
|
||||||
|
group: { type: Schema.Types.ObjectId, ref: 'Group' }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
permissionSchema.plugin(AutoIncrement, { inc_field: 'permissionId' })
|
||||||
|
|
||||||
// Static Methods
|
// Static Methods
|
||||||
permissionSchema.static('get', async function (getBy: GetPermissionBy): Promise<
|
permissionSchema.static('get', async function (getBy: GetPermissionBy): Promise<
|
||||||
PermissionDetailsResponse[]
|
PermissionDetailsResponse[]
|
||||||
> {
|
> {
|
||||||
return (await this.find(getBy)
|
return (await this.find(getBy)
|
||||||
.select('uid path type setting')
|
.select({
|
||||||
.populate({ path: 'user', select: 'uid username displayName isAdmin' })
|
_id: 0,
|
||||||
|
permissionId: 1,
|
||||||
|
path: 1,
|
||||||
|
type: 1,
|
||||||
|
setting: 1
|
||||||
|
})
|
||||||
|
.populate({ path: 'user', select: 'id username displayName isAdmin -_id' })
|
||||||
.populate({
|
.populate({
|
||||||
path: 'group',
|
path: 'group',
|
||||||
select: 'uid name description',
|
select: 'groupId name description -_id',
|
||||||
populate: {
|
populate: {
|
||||||
path: 'users',
|
path: 'users',
|
||||||
select: 'uid username displayName isAdmin',
|
select: 'id username displayName isAdmin -_id',
|
||||||
options: { limit: 15 }
|
options: { limit: 15 }
|
||||||
}
|
}
|
||||||
})) as unknown as PermissionDetailsResponse[]
|
})) as unknown as PermissionDetailsResponse[]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Schema, model, Document, Model, ObjectId } from 'mongoose'
|
import mongoose, { Schema, model, Document, Model } from 'mongoose'
|
||||||
|
const AutoIncrement = require('mongoose-sequence')(mongoose)
|
||||||
import bcrypt from 'bcryptjs'
|
import bcrypt from 'bcryptjs'
|
||||||
import { AuthProviderType } from '../utils'
|
|
||||||
|
|
||||||
export interface UserPayload {
|
export interface UserPayload {
|
||||||
/**
|
/**
|
||||||
@@ -36,16 +36,12 @@ export interface UserPayload {
|
|||||||
|
|
||||||
interface IUserDocument extends UserPayload, Document {
|
interface IUserDocument extends UserPayload, Document {
|
||||||
_id: Schema.Types.ObjectId
|
_id: Schema.Types.ObjectId
|
||||||
|
id: number
|
||||||
isAdmin: boolean
|
isAdmin: boolean
|
||||||
isActive: boolean
|
isActive: boolean
|
||||||
needsToUpdatePassword: boolean
|
|
||||||
autoExec: string
|
autoExec: string
|
||||||
groups: Schema.Types.ObjectId[]
|
groups: Schema.Types.ObjectId[]
|
||||||
tokens: [{ [key: string]: string }]
|
tokens: [{ [key: string]: string }]
|
||||||
authProvider?: AuthProviderType
|
|
||||||
|
|
||||||
// Declare virtual properties as read-only properties
|
|
||||||
readonly uid: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IUser extends IUserDocument {
|
export interface IUser extends IUserDocument {
|
||||||
@@ -56,75 +52,51 @@ export interface IUser extends IUserDocument {
|
|||||||
interface IUserModel extends Model<IUser> {
|
interface IUserModel extends Model<IUser> {
|
||||||
hashPassword(password: string): string
|
hashPassword(password: string): string
|
||||||
}
|
}
|
||||||
const opts = {
|
|
||||||
toJSON: {
|
|
||||||
virtuals: true,
|
|
||||||
transform: function (doc: any, ret: any, options: any) {
|
|
||||||
delete ret._id
|
|
||||||
delete ret.id
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const userSchema = new Schema<IUserDocument>(
|
const userSchema = new Schema<IUserDocument>({
|
||||||
{
|
displayName: {
|
||||||
displayName: {
|
type: String,
|
||||||
type: String,
|
required: true
|
||||||
required: true
|
|
||||||
},
|
|
||||||
username: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
unique: true
|
|
||||||
},
|
|
||||||
password: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
authProvider: {
|
|
||||||
type: String,
|
|
||||||
enum: AuthProviderType
|
|
||||||
},
|
|
||||||
isAdmin: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
isActive: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
needsToUpdatePassword: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
autoExec: {
|
|
||||||
type: String
|
|
||||||
},
|
|
||||||
groups: [{ type: Schema.Types.ObjectId, ref: 'Group' }],
|
|
||||||
tokens: [
|
|
||||||
{
|
|
||||||
clientId: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
accessToken: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
refreshToken: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
opts
|
username: {
|
||||||
)
|
type: String,
|
||||||
|
required: true,
|
||||||
userSchema.virtual('uid').get(function () {
|
unique: true
|
||||||
return this._id.toString()
|
},
|
||||||
|
password: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
isAdmin: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
isActive: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
autoExec: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
groups: [{ type: Schema.Types.ObjectId, ref: 'Group' }],
|
||||||
|
tokens: [
|
||||||
|
{
|
||||||
|
clientId: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
accessToken: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
refreshToken: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
})
|
})
|
||||||
|
userSchema.plugin(AutoIncrement, { inc_field: 'id' })
|
||||||
|
|
||||||
// Static Methods
|
// Static Methods
|
||||||
userSchema.static('hashPassword', (password: string): string => {
|
userSchema.static('hashPassword', (password: string): string => {
|
||||||
|
|||||||
@@ -7,28 +7,12 @@ import {
|
|||||||
authenticateRefreshToken
|
authenticateRefreshToken
|
||||||
} from '../../middlewares'
|
} from '../../middlewares'
|
||||||
|
|
||||||
import { tokenValidation, updatePasswordValidation } from '../../utils'
|
import { tokenValidation } from '../../utils'
|
||||||
import { InfoJWT } from '../../types'
|
import { InfoJWT } from '../../types'
|
||||||
|
|
||||||
const authRouter = express.Router()
|
const authRouter = express.Router()
|
||||||
const controller = new AuthController()
|
const controller = new AuthController()
|
||||||
|
|
||||||
authRouter.patch(
|
|
||||||
'/updatePassword',
|
|
||||||
authenticateAccessToken,
|
|
||||||
async (req, res) => {
|
|
||||||
const { error, value: body } = updatePasswordValidation(req.body)
|
|
||||||
if (error) return res.status(400).send(error.details[0].message)
|
|
||||||
|
|
||||||
try {
|
|
||||||
await controller.updatePassword(req, body)
|
|
||||||
res.sendStatus(204)
|
|
||||||
} catch (err: any) {
|
|
||||||
res.status(err.code).send(err.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
authRouter.post('/token', async (req, res) => {
|
authRouter.post('/token', async (req, res) => {
|
||||||
const { error, value: body } = tokenValidation(req.body)
|
const { error, value: body } = tokenValidation(req.body)
|
||||||
if (error) return res.status(400).send(error.details[0].message)
|
if (error) return res.status(400).send(error.details[0].message)
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
import express from 'express'
|
|
||||||
import { AuthConfigController } from '../../controllers'
|
|
||||||
const authConfigRouter = express.Router()
|
|
||||||
|
|
||||||
authConfigRouter.get('/', async (req, res) => {
|
|
||||||
const controller = new AuthConfigController()
|
|
||||||
try {
|
|
||||||
const response = controller.getDetail()
|
|
||||||
res.send(response)
|
|
||||||
} catch (err: any) {
|
|
||||||
res.status(500).send(err.toString())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
authConfigRouter.post('/synchroniseWithLDAP', async (req, res) => {
|
|
||||||
const controller = new AuthConfigController()
|
|
||||||
try {
|
|
||||||
const response = await controller.synchroniseWithLDAP()
|
|
||||||
res.send(response)
|
|
||||||
} catch (err: any) {
|
|
||||||
res.status(500).send(err.toString())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export default authConfigRouter
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import express from 'express'
|
import express from 'express'
|
||||||
import { ClientController } from '../../controllers'
|
import { ClientController } from '../../controllers'
|
||||||
import { registerClientValidation } from '../../utils'
|
import { registerClientValidation } from '../../utils'
|
||||||
import { authenticateAccessToken, verifyAdmin } from '../../middlewares'
|
|
||||||
|
|
||||||
const clientRouter = express.Router()
|
const clientRouter = express.Router()
|
||||||
|
|
||||||
@@ -18,19 +17,4 @@ clientRouter.post('/', async (req, res) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
clientRouter.get(
|
|
||||||
'/',
|
|
||||||
authenticateAccessToken,
|
|
||||||
verifyAdmin,
|
|
||||||
async (req, res) => {
|
|
||||||
const controller = new ClientController()
|
|
||||||
try {
|
|
||||||
const response = await controller.getAllClients()
|
|
||||||
res.send(response)
|
|
||||||
} catch (err: any) {
|
|
||||||
res.status(403).send(err.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
export default clientRouter
|
export default clientRouter
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
import express from 'express'
|
import express from 'express'
|
||||||
import { GroupController } from '../../controllers/'
|
import { GroupController } from '../../controllers/'
|
||||||
import { authenticateAccessToken, verifyAdmin } from '../../middlewares'
|
import { authenticateAccessToken, verifyAdmin } from '../../middlewares'
|
||||||
import {
|
import { getGroupValidation, registerGroupValidation } from '../../utils'
|
||||||
getGroupValidation,
|
|
||||||
registerGroupValidation,
|
|
||||||
uidValidation
|
|
||||||
} from '../../utils'
|
|
||||||
|
|
||||||
const groupRouter = express.Router()
|
const groupRouter = express.Router()
|
||||||
|
|
||||||
@@ -22,7 +18,11 @@ groupRouter.post(
|
|||||||
const response = await controller.createGroup(body)
|
const response = await controller.createGroup(body)
|
||||||
res.send(response)
|
res.send(response)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
res.status(err.code).send(err.message)
|
const statusCode = err.code
|
||||||
|
|
||||||
|
delete err.code
|
||||||
|
|
||||||
|
res.status(statusCode).send(err.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -33,22 +33,27 @@ groupRouter.get('/', authenticateAccessToken, async (req, res) => {
|
|||||||
const response = await controller.getAllGroups()
|
const response = await controller.getAllGroups()
|
||||||
res.send(response)
|
res.send(response)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
res.status(err.code).send(err.message)
|
const statusCode = err.code
|
||||||
|
|
||||||
|
delete err.code
|
||||||
|
|
||||||
|
res.status(statusCode).send(err.message)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
groupRouter.get('/:uid', authenticateAccessToken, async (req, res) => {
|
groupRouter.get('/:groupId', authenticateAccessToken, async (req, res) => {
|
||||||
const { error: uidError, value: params } = uidValidation(req.params)
|
const { groupId } = req.params
|
||||||
if (uidError) return res.status(400).send(uidError.details[0].message)
|
|
||||||
|
|
||||||
const { uid } = params
|
|
||||||
|
|
||||||
const controller = new GroupController()
|
const controller = new GroupController()
|
||||||
try {
|
try {
|
||||||
const response = await controller.getGroup(uid)
|
const response = await controller.getGroup(parseInt(groupId))
|
||||||
res.send(response)
|
res.send(response)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
res.status(err.code).send(err.message)
|
const statusCode = err.code
|
||||||
|
|
||||||
|
delete err.code
|
||||||
|
|
||||||
|
res.status(statusCode).send(err.message)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -63,64 +68,83 @@ groupRouter.get(
|
|||||||
|
|
||||||
const controller = new GroupController()
|
const controller = new GroupController()
|
||||||
try {
|
try {
|
||||||
const response = await controller.getGroupByName(name)
|
const response = await controller.getGroupByGroupName(name)
|
||||||
res.send(response)
|
res.send(response)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
res.status(err.code).send(err.message)
|
const statusCode = err.code
|
||||||
|
|
||||||
|
delete err.code
|
||||||
|
|
||||||
|
res.status(statusCode).send(err.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
groupRouter.post(
|
groupRouter.post(
|
||||||
'/:groupUid/:userUid',
|
'/:groupId/:userId',
|
||||||
authenticateAccessToken,
|
authenticateAccessToken,
|
||||||
verifyAdmin,
|
verifyAdmin,
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
const { groupUid, userUid } = req.params
|
const { groupId, userId } = req.params
|
||||||
|
|
||||||
const controller = new GroupController()
|
const controller = new GroupController()
|
||||||
try {
|
try {
|
||||||
const response = await controller.addUserToGroup(groupUid, userUid)
|
const response = await controller.addUserToGroup(
|
||||||
|
parseInt(groupId),
|
||||||
|
parseInt(userId)
|
||||||
|
)
|
||||||
res.send(response)
|
res.send(response)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
res.status(err.code).send(err.message)
|
const statusCode = err.code
|
||||||
|
|
||||||
|
delete err.code
|
||||||
|
|
||||||
|
res.status(statusCode).send(err.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
groupRouter.delete(
|
groupRouter.delete(
|
||||||
'/:groupUid/:userUid',
|
'/:groupId/:userId',
|
||||||
authenticateAccessToken,
|
authenticateAccessToken,
|
||||||
verifyAdmin,
|
verifyAdmin,
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
const { groupUid, userUid } = req.params
|
const { groupId, userId } = req.params
|
||||||
|
|
||||||
const controller = new GroupController()
|
const controller = new GroupController()
|
||||||
try {
|
try {
|
||||||
const response = await controller.removeUserFromGroup(groupUid, userUid)
|
const response = await controller.removeUserFromGroup(
|
||||||
|
parseInt(groupId),
|
||||||
|
parseInt(userId)
|
||||||
|
)
|
||||||
res.send(response)
|
res.send(response)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
res.status(err.code).send(err.message)
|
const statusCode = err.code
|
||||||
|
|
||||||
|
delete err.code
|
||||||
|
|
||||||
|
res.status(statusCode).send(err.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
groupRouter.delete(
|
groupRouter.delete(
|
||||||
'/:uid',
|
'/:groupId',
|
||||||
authenticateAccessToken,
|
authenticateAccessToken,
|
||||||
verifyAdmin,
|
verifyAdmin,
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
const { error: uidError, value: params } = uidValidation(req.params)
|
const { groupId } = req.params
|
||||||
if (uidError) return res.status(400).send(uidError.details[0].message)
|
|
||||||
|
|
||||||
const { uid } = params
|
|
||||||
|
|
||||||
const controller = new GroupController()
|
const controller = new GroupController()
|
||||||
try {
|
try {
|
||||||
await controller.deleteGroup(uid)
|
await controller.deleteGroup(parseInt(groupId))
|
||||||
res.status(200).send('Group Deleted!')
|
res.status(200).send('Group Deleted!')
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
res.status(err.code).send(err.message)
|
const statusCode = err.code
|
||||||
|
|
||||||
|
delete err.code
|
||||||
|
|
||||||
|
res.status(statusCode).send(err.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import clientRouter from './client'
|
|||||||
import authRouter from './auth'
|
import authRouter from './auth'
|
||||||
import sessionRouter from './session'
|
import sessionRouter from './session'
|
||||||
import permissionRouter from './permission'
|
import permissionRouter from './permission'
|
||||||
import authConfigRouter from './authConfig'
|
|
||||||
|
|
||||||
const router = express.Router()
|
const router = express.Router()
|
||||||
|
|
||||||
@@ -44,14 +43,6 @@ router.use(
|
|||||||
permissionRouter
|
permissionRouter
|
||||||
)
|
)
|
||||||
|
|
||||||
router.use(
|
|
||||||
'/authConfig',
|
|
||||||
desktopRestrict,
|
|
||||||
authenticateAccessToken,
|
|
||||||
verifyAdmin,
|
|
||||||
authConfigRouter
|
|
||||||
)
|
|
||||||
|
|
||||||
router.use(
|
router.use(
|
||||||
'/',
|
'/',
|
||||||
swaggerUi.serve,
|
swaggerUi.serve,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { PermissionController } from '../../controllers/'
|
|||||||
import { verifyAdmin } from '../../middlewares'
|
import { verifyAdmin } from '../../middlewares'
|
||||||
import {
|
import {
|
||||||
registerPermissionValidation,
|
registerPermissionValidation,
|
||||||
uidValidation,
|
|
||||||
updatePermissionValidation
|
updatePermissionValidation
|
||||||
} from '../../utils'
|
} from '../../utils'
|
||||||
|
|
||||||
@@ -35,17 +34,14 @@ permissionRouter.post('/', verifyAdmin, async (req, res) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
permissionRouter.patch('/:uid', verifyAdmin, async (req: any, res) => {
|
permissionRouter.patch('/:permissionId', verifyAdmin, async (req: any, res) => {
|
||||||
const { error: uidError, value: params } = uidValidation(req.params)
|
const { permissionId } = req.params
|
||||||
if (uidError) return res.status(400).send(uidError.details[0].message)
|
|
||||||
|
|
||||||
const { uid } = params
|
|
||||||
|
|
||||||
const { error, value: body } = updatePermissionValidation(req.body)
|
const { error, value: body } = updatePermissionValidation(req.body)
|
||||||
if (error) return res.status(400).send(error.details[0].message)
|
if (error) return res.status(400).send(error.details[0].message)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await controller.updatePermission(uid, body)
|
const response = await controller.updatePermission(permissionId, body)
|
||||||
res.send(response)
|
res.send(response)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const statusCode = err.code
|
const statusCode = err.code
|
||||||
@@ -54,18 +50,20 @@ permissionRouter.patch('/:uid', verifyAdmin, async (req: any, res) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
permissionRouter.delete('/:uid', verifyAdmin, async (req: any, res) => {
|
permissionRouter.delete(
|
||||||
const { error: uidError, value: params } = uidValidation(req.params)
|
'/:permissionId',
|
||||||
if (uidError) return res.status(400).send(uidError.details[0].message)
|
verifyAdmin,
|
||||||
|
async (req: any, res) => {
|
||||||
|
const { permissionId } = req.params
|
||||||
|
|
||||||
const { uid } = params
|
try {
|
||||||
try {
|
await controller.deletePermission(permissionId)
|
||||||
await controller.deletePermission(uid)
|
res.status(200).send('Permission Deleted!')
|
||||||
res.status(200).send('Permission Deleted!')
|
} catch (err: any) {
|
||||||
} catch (err: any) {
|
const statusCode = err.code
|
||||||
const statusCode = err.code
|
delete err.code
|
||||||
delete err.code
|
res.status(statusCode).send(err.message)
|
||||||
res.status(statusCode).send(err.message)
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
export default permissionRouter
|
export default permissionRouter
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import {
|
|||||||
generateAccessToken,
|
generateAccessToken,
|
||||||
generateAuthCode,
|
generateAuthCode,
|
||||||
generateRefreshToken,
|
generateRefreshToken,
|
||||||
randomBytesHexString,
|
|
||||||
saveTokensInDB,
|
saveTokensInDB,
|
||||||
verifyTokenInDB
|
verifyTokenInDB
|
||||||
} from '../../../utils'
|
} from '../../../utils'
|
||||||
@@ -21,6 +20,7 @@ import {
|
|||||||
const clientId = 'someclientID'
|
const clientId = 'someclientID'
|
||||||
const clientSecret = 'someclientSecret'
|
const clientSecret = 'someclientSecret'
|
||||||
const user = {
|
const user = {
|
||||||
|
id: 1234,
|
||||||
displayName: 'Test User',
|
displayName: 'Test User',
|
||||||
username: 'testUsername',
|
username: 'testUsername',
|
||||||
password: '87654321',
|
password: '87654321',
|
||||||
@@ -52,7 +52,7 @@ describe('auth', () => {
|
|||||||
describe('token', () => {
|
describe('token', () => {
|
||||||
const userInfo: InfoJWT = {
|
const userInfo: InfoJWT = {
|
||||||
clientId,
|
clientId,
|
||||||
userId: randomBytesHexString(12)
|
userId: user.id
|
||||||
}
|
}
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await userController.createUser(user)
|
await userController.createUser(user)
|
||||||
@@ -151,10 +151,10 @@ describe('auth', () => {
|
|||||||
currentUser = await userController.createUser(user)
|
currentUser = await userController.createUser(user)
|
||||||
refreshToken = generateRefreshToken({
|
refreshToken = generateRefreshToken({
|
||||||
clientId,
|
clientId,
|
||||||
userId: currentUser.uid
|
userId: currentUser.id
|
||||||
})
|
})
|
||||||
await saveTokensInDB(
|
await saveTokensInDB(
|
||||||
currentUser.uid,
|
currentUser.id,
|
||||||
clientId,
|
clientId,
|
||||||
'accessToken',
|
'accessToken',
|
||||||
refreshToken
|
refreshToken
|
||||||
@@ -202,11 +202,11 @@ describe('auth', () => {
|
|||||||
currentUser = await userController.createUser(user)
|
currentUser = await userController.createUser(user)
|
||||||
accessToken = generateAccessToken({
|
accessToken = generateAccessToken({
|
||||||
clientId,
|
clientId,
|
||||||
userId: currentUser.uid
|
userId: currentUser.id
|
||||||
})
|
})
|
||||||
|
|
||||||
await saveTokensInDB(
|
await saveTokensInDB(
|
||||||
currentUser.uid,
|
currentUser.id,
|
||||||
clientId,
|
clientId,
|
||||||
accessToken,
|
accessToken,
|
||||||
'refreshToken'
|
'refreshToken'
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import request from 'supertest'
|
|||||||
import appPromise from '../../../app'
|
import appPromise from '../../../app'
|
||||||
import { UserController, ClientController } from '../../../controllers/'
|
import { UserController, ClientController } from '../../../controllers/'
|
||||||
import { generateAccessToken, saveTokensInDB } from '../../../utils'
|
import { generateAccessToken, saveTokensInDB } from '../../../utils'
|
||||||
import { NUMBER_OF_SECONDS_IN_A_DAY } from '../../../model/Client'
|
|
||||||
|
|
||||||
const client = {
|
const client = {
|
||||||
clientId: 'someclientID',
|
clientId: 'someclientID',
|
||||||
@@ -27,7 +26,6 @@ describe('client', () => {
|
|||||||
let app: Express
|
let app: Express
|
||||||
let con: Mongoose
|
let con: Mongoose
|
||||||
let mongoServer: MongoMemoryServer
|
let mongoServer: MongoMemoryServer
|
||||||
let adminAccessToken: string
|
|
||||||
const userController = new UserController()
|
const userController = new UserController()
|
||||||
const clientController = new ClientController()
|
const clientController = new ClientController()
|
||||||
|
|
||||||
@@ -36,18 +34,6 @@ describe('client', () => {
|
|||||||
|
|
||||||
mongoServer = await MongoMemoryServer.create()
|
mongoServer = await MongoMemoryServer.create()
|
||||||
con = await mongoose.connect(mongoServer.getUri())
|
con = await mongoose.connect(mongoServer.getUri())
|
||||||
|
|
||||||
const dbUser = await userController.createUser(adminUser)
|
|
||||||
adminAccessToken = generateAccessToken({
|
|
||||||
clientId: client.clientId,
|
|
||||||
userId: dbUser.uid
|
|
||||||
})
|
|
||||||
await saveTokensInDB(
|
|
||||||
dbUser.uid,
|
|
||||||
client.clientId,
|
|
||||||
adminAccessToken,
|
|
||||||
'refreshToken'
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
@@ -57,6 +43,22 @@ describe('client', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('create', () => {
|
describe('create', () => {
|
||||||
|
let adminAccessToken: string
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const dbUser = await userController.createUser(adminUser)
|
||||||
|
adminAccessToken = generateAccessToken({
|
||||||
|
clientId: client.clientId,
|
||||||
|
userId: dbUser.id
|
||||||
|
})
|
||||||
|
await saveTokensInDB(
|
||||||
|
dbUser.id,
|
||||||
|
client.clientId,
|
||||||
|
adminAccessToken,
|
||||||
|
'refreshToken'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
const collections = mongoose.connection.collections
|
const collections = mongoose.connection.collections
|
||||||
const collection = collections['clients']
|
const collection = collections['clients']
|
||||||
@@ -95,10 +97,10 @@ describe('client', () => {
|
|||||||
const dbUser = await userController.createUser(user)
|
const dbUser = await userController.createUser(user)
|
||||||
const accessToken = generateAccessToken({
|
const accessToken = generateAccessToken({
|
||||||
clientId: client.clientId,
|
clientId: client.clientId,
|
||||||
userId: dbUser.uid
|
userId: dbUser.id
|
||||||
})
|
})
|
||||||
await saveTokensInDB(
|
await saveTokensInDB(
|
||||||
dbUser.uid,
|
dbUser.id,
|
||||||
client.clientId,
|
client.clientId,
|
||||||
accessToken,
|
accessToken,
|
||||||
'refreshToken'
|
'refreshToken'
|
||||||
@@ -155,80 +157,4 @@ describe('client', () => {
|
|||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('get', () => {
|
|
||||||
afterEach(async () => {
|
|
||||||
const collections = mongoose.connection.collections
|
|
||||||
const collection = collections['clients']
|
|
||||||
await collection.deleteMany({})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should respond with an array of all clients', async () => {
|
|
||||||
await clientController.createClient(newClient)
|
|
||||||
await clientController.createClient({
|
|
||||||
clientId: 'clientID',
|
|
||||||
clientSecret: 'clientSecret'
|
|
||||||
})
|
|
||||||
|
|
||||||
const res = await request(app)
|
|
||||||
.get('/SASjsApi/client')
|
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
|
||||||
.send()
|
|
||||||
.expect(200)
|
|
||||||
|
|
||||||
const expected = [
|
|
||||||
{
|
|
||||||
clientId: 'newClientID',
|
|
||||||
clientSecret: 'newClientSecret',
|
|
||||||
accessTokenExpiration: NUMBER_OF_SECONDS_IN_A_DAY,
|
|
||||||
refreshTokenExpiration: NUMBER_OF_SECONDS_IN_A_DAY * 30
|
|
||||||
},
|
|
||||||
{
|
|
||||||
clientId: 'clientID',
|
|
||||||
clientSecret: 'clientSecret',
|
|
||||||
accessTokenExpiration: NUMBER_OF_SECONDS_IN_A_DAY,
|
|
||||||
refreshTokenExpiration: NUMBER_OF_SECONDS_IN_A_DAY * 30
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
expect(res.body).toEqual(expected)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should respond with Unauthorized if access token is not present', async () => {
|
|
||||||
const res = await request(app).get('/SASjsApi/client').send().expect(401)
|
|
||||||
|
|
||||||
expect(res.text).toEqual('Unauthorized')
|
|
||||||
expect(res.body).toEqual({})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should respond with Forbideen if access token is not of an admin account', async () => {
|
|
||||||
const user = {
|
|
||||||
displayName: 'User 2',
|
|
||||||
username: 'username2',
|
|
||||||
password: '12345678',
|
|
||||||
isAdmin: false,
|
|
||||||
isActive: true
|
|
||||||
}
|
|
||||||
const dbUser = await userController.createUser(user)
|
|
||||||
const accessToken = generateAccessToken({
|
|
||||||
clientId: client.clientId,
|
|
||||||
userId: dbUser.uid
|
|
||||||
})
|
|
||||||
await saveTokensInDB(
|
|
||||||
dbUser.uid,
|
|
||||||
client.clientId,
|
|
||||||
accessToken,
|
|
||||||
'refreshToken'
|
|
||||||
)
|
|
||||||
|
|
||||||
const res = await request(app)
|
|
||||||
.get('/SASjsApi/client')
|
|
||||||
.auth(accessToken, { type: 'bearer' })
|
|
||||||
.send()
|
|
||||||
.expect(401)
|
|
||||||
|
|
||||||
expect(res.text).toEqual('Admin account required')
|
|
||||||
expect(res.body).toEqual({})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -71,31 +71,31 @@ describe('drive', () => {
|
|||||||
con = await mongoose.connect(mongoServer.getUri())
|
con = await mongoose.connect(mongoServer.getUri())
|
||||||
|
|
||||||
const dbUser = await controller.createUser(user)
|
const dbUser = await controller.createUser(user)
|
||||||
accessToken = await generateAndSaveToken(dbUser.uid)
|
accessToken = await generateAndSaveToken(dbUser.id)
|
||||||
await permissionController.createPermission({
|
await permissionController.createPermission({
|
||||||
...permission,
|
...permission,
|
||||||
path: '/SASjsApi/drive/deploy',
|
path: '/SASjsApi/drive/deploy',
|
||||||
principalId: dbUser.uid
|
principalId: dbUser.id
|
||||||
})
|
})
|
||||||
await permissionController.createPermission({
|
await permissionController.createPermission({
|
||||||
...permission,
|
...permission,
|
||||||
path: '/SASjsApi/drive/deploy/upload',
|
path: '/SASjsApi/drive/deploy/upload',
|
||||||
principalId: dbUser.uid
|
principalId: dbUser.id
|
||||||
})
|
})
|
||||||
await permissionController.createPermission({
|
await permissionController.createPermission({
|
||||||
...permission,
|
...permission,
|
||||||
path: '/SASjsApi/drive/file',
|
path: '/SASjsApi/drive/file',
|
||||||
principalId: dbUser.uid
|
principalId: dbUser.id
|
||||||
})
|
})
|
||||||
await permissionController.createPermission({
|
await permissionController.createPermission({
|
||||||
...permission,
|
...permission,
|
||||||
path: '/SASjsApi/drive/folder',
|
path: '/SASjsApi/drive/folder',
|
||||||
principalId: dbUser.uid
|
principalId: dbUser.id
|
||||||
})
|
})
|
||||||
await permissionController.createPermission({
|
await permissionController.createPermission({
|
||||||
...permission,
|
...permission,
|
||||||
path: '/SASjsApi/drive/rename',
|
path: '/SASjsApi/drive/rename',
|
||||||
principalId: dbUser.uid
|
principalId: dbUser.id
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1197,7 +1197,7 @@ const getExampleService = (): ServiceMember =>
|
|||||||
((getTreeExample().members[0] as FolderMember).members[0] as FolderMember)
|
((getTreeExample().members[0] as FolderMember).members[0] as FolderMember)
|
||||||
.members[0] as ServiceMember
|
.members[0] as ServiceMember
|
||||||
|
|
||||||
const generateAndSaveToken = async (userId: string) => {
|
const generateAndSaveToken = async (userId: number) => {
|
||||||
const adminAccessToken = generateAccessToken({
|
const adminAccessToken = generateAccessToken({
|
||||||
clientId,
|
clientId,
|
||||||
userId
|
userId
|
||||||
|
|||||||
@@ -4,14 +4,8 @@ import { MongoMemoryServer } from 'mongodb-memory-server'
|
|||||||
import request from 'supertest'
|
import request from 'supertest'
|
||||||
import appPromise from '../../../app'
|
import appPromise from '../../../app'
|
||||||
import { UserController, GroupController } from '../../../controllers/'
|
import { UserController, GroupController } from '../../../controllers/'
|
||||||
import {
|
import { generateAccessToken, saveTokensInDB } from '../../../utils'
|
||||||
generateAccessToken,
|
import { PUBLIC_GROUP_NAME } from '../../../model/Group'
|
||||||
saveTokensInDB,
|
|
||||||
AuthProviderType
|
|
||||||
} from '../../../utils'
|
|
||||||
import Group, { PUBLIC_GROUP_NAME } from '../../../model/Group'
|
|
||||||
import User from '../../../model/User'
|
|
||||||
import { randomBytes } from 'crypto'
|
|
||||||
|
|
||||||
const clientId = 'someclientID'
|
const clientId = 'someclientID'
|
||||||
const adminUser = {
|
const adminUser = {
|
||||||
@@ -76,7 +70,7 @@ describe('group', () => {
|
|||||||
.send(group)
|
.send(group)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(res.body.uid).toBeTruthy()
|
expect(res.body.groupId).toBeTruthy()
|
||||||
expect(res.body.name).toEqual(group.name)
|
expect(res.body.name).toEqual(group.name)
|
||||||
expect(res.body.description).toEqual(group.description)
|
expect(res.body.description).toEqual(group.description)
|
||||||
expect(res.body.isActive).toEqual(true)
|
expect(res.body.isActive).toEqual(true)
|
||||||
@@ -156,7 +150,7 @@ describe('group', () => {
|
|||||||
const dbGroup = await groupController.createGroup(group)
|
const dbGroup = await groupController.createGroup(group)
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.delete(`/SASjsApi/group/${dbGroup.uid}`)
|
.delete(`/SASjsApi/group/${dbGroup.groupId}`)
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send()
|
.send()
|
||||||
.expect(200)
|
.expect(200)
|
||||||
@@ -175,17 +169,17 @@ describe('group', () => {
|
|||||||
username: 'deletegroup2'
|
username: 'deletegroup2'
|
||||||
})
|
})
|
||||||
|
|
||||||
await groupController.addUserToGroup(dbGroup.uid, dbUser1.uid)
|
await groupController.addUserToGroup(dbGroup.groupId, dbUser1.id)
|
||||||
await groupController.addUserToGroup(dbGroup.uid, dbUser2.uid)
|
await groupController.addUserToGroup(dbGroup.groupId, dbUser2.id)
|
||||||
|
|
||||||
await request(app)
|
await request(app)
|
||||||
.delete(`/SASjsApi/group/${dbGroup.uid}`)
|
.delete(`/SASjsApi/group/${dbGroup.groupId}`)
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send()
|
.send()
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
const res1 = await request(app)
|
const res1 = await request(app)
|
||||||
.get(`/SASjsApi/user/${dbUser1.uid}`)
|
.get(`/SASjsApi/user/${dbUser1.id}`)
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send()
|
.send()
|
||||||
.expect(200)
|
.expect(200)
|
||||||
@@ -193,7 +187,7 @@ describe('group', () => {
|
|||||||
expect(res1.body.groups).toEqual([])
|
expect(res1.body.groups).toEqual([])
|
||||||
|
|
||||||
const res2 = await request(app)
|
const res2 = await request(app)
|
||||||
.get(`/SASjsApi/user/${dbUser2.uid}`)
|
.get(`/SASjsApi/user/${dbUser2.id}`)
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send()
|
.send()
|
||||||
.expect(200)
|
.expect(200)
|
||||||
@@ -202,10 +196,8 @@ describe('group', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should respond with Not Found if groupId is incorrect', async () => {
|
it('should respond with Not Found if groupId is incorrect', async () => {
|
||||||
const hexValue = randomBytes(12).toString('hex')
|
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.delete(`/SASjsApi/group/${hexValue}`)
|
.delete(`/SASjsApi/group/1234`)
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send()
|
.send()
|
||||||
.expect(404)
|
.expect(404)
|
||||||
@@ -232,7 +224,7 @@ describe('group', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.delete(`/SASjsApi/group/${dbGroup.uid}`)
|
.delete(`/SASjsApi/group/${dbGroup.groupId}`)
|
||||||
.auth(accessToken, { type: 'bearer' })
|
.auth(accessToken, { type: 'bearer' })
|
||||||
.send()
|
.send()
|
||||||
.expect(401)
|
.expect(401)
|
||||||
@@ -248,15 +240,15 @@ describe('group', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should respond with group', async () => {
|
it('should respond with group', async () => {
|
||||||
const { uid } = await groupController.createGroup(group)
|
const { groupId } = await groupController.createGroup(group)
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.get(`/SASjsApi/group/${uid}`)
|
.get(`/SASjsApi/group/${groupId}`)
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send()
|
.send()
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(res.body.uid).toBeTruthy()
|
expect(res.body.groupId).toBeTruthy()
|
||||||
expect(res.body.name).toEqual(group.name)
|
expect(res.body.name).toEqual(group.name)
|
||||||
expect(res.body.description).toEqual(group.description)
|
expect(res.body.description).toEqual(group.description)
|
||||||
expect(res.body.isActive).toEqual(true)
|
expect(res.body.isActive).toEqual(true)
|
||||||
@@ -269,15 +261,15 @@ describe('group', () => {
|
|||||||
username: 'get' + user.username
|
username: 'get' + user.username
|
||||||
})
|
})
|
||||||
|
|
||||||
const { uid } = await groupController.createGroup(group)
|
const { groupId } = await groupController.createGroup(group)
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.get(`/SASjsApi/group/${uid}`)
|
.get(`/SASjsApi/group/${groupId}`)
|
||||||
.auth(accessToken, { type: 'bearer' })
|
.auth(accessToken, { type: 'bearer' })
|
||||||
.send()
|
.send()
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(res.body.uid).toBeTruthy()
|
expect(res.body.groupId).toBeTruthy()
|
||||||
expect(res.body.name).toEqual(group.name)
|
expect(res.body.name).toEqual(group.name)
|
||||||
expect(res.body.description).toEqual(group.description)
|
expect(res.body.description).toEqual(group.description)
|
||||||
expect(res.body.isActive).toEqual(true)
|
expect(res.body.isActive).toEqual(true)
|
||||||
@@ -295,10 +287,8 @@ describe('group', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should respond with Not Found if groupId is incorrect', async () => {
|
it('should respond with Not Found if groupId is incorrect', async () => {
|
||||||
const hexValue = randomBytes(12).toString('hex')
|
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.get(`/SASjsApi/group/${hexValue}`)
|
.get('/SASjsApi/group/1234')
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send()
|
.send()
|
||||||
.expect(404)
|
.expect(404)
|
||||||
@@ -317,7 +307,7 @@ describe('group', () => {
|
|||||||
.send()
|
.send()
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(res.body.uid).toBeTruthy()
|
expect(res.body.groupId).toBeTruthy()
|
||||||
expect(res.body.name).toEqual(group.name)
|
expect(res.body.name).toEqual(group.name)
|
||||||
expect(res.body.description).toEqual(group.description)
|
expect(res.body.description).toEqual(group.description)
|
||||||
expect(res.body.isActive).toEqual(true)
|
expect(res.body.isActive).toEqual(true)
|
||||||
@@ -338,7 +328,7 @@ describe('group', () => {
|
|||||||
.send()
|
.send()
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(res.body.uid).toBeTruthy()
|
expect(res.body.groupId).toBeTruthy()
|
||||||
expect(res.body.name).toEqual(group.name)
|
expect(res.body.name).toEqual(group.name)
|
||||||
expect(res.body.description).toEqual(group.description)
|
expect(res.body.description).toEqual(group.description)
|
||||||
expect(res.body.isActive).toEqual(true)
|
expect(res.body.isActive).toEqual(true)
|
||||||
@@ -384,7 +374,7 @@ describe('group', () => {
|
|||||||
|
|
||||||
expect(res.body).toEqual([
|
expect(res.body).toEqual([
|
||||||
{
|
{
|
||||||
uid: expect.anything(),
|
groupId: expect.anything(),
|
||||||
name: group.name,
|
name: group.name,
|
||||||
description: group.description
|
description: group.description
|
||||||
}
|
}
|
||||||
@@ -406,7 +396,7 @@ describe('group', () => {
|
|||||||
|
|
||||||
expect(res.body).toEqual([
|
expect(res.body).toEqual([
|
||||||
{
|
{
|
||||||
uid: expect.anything(),
|
groupId: expect.anything(),
|
||||||
name: group.name,
|
name: group.name,
|
||||||
description: group.description
|
description: group.description
|
||||||
}
|
}
|
||||||
@@ -431,18 +421,18 @@ describe('group', () => {
|
|||||||
const dbUser = await userController.createUser(user)
|
const dbUser = await userController.createUser(user)
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.post(`/SASjsApi/group/${dbGroup.uid}/${dbUser.uid}`)
|
.post(`/SASjsApi/group/${dbGroup.groupId}/${dbUser.id}`)
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send()
|
.send()
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(res.body.uid).toBeTruthy()
|
expect(res.body.groupId).toBeTruthy()
|
||||||
expect(res.body.name).toEqual(group.name)
|
expect(res.body.name).toEqual(group.name)
|
||||||
expect(res.body.description).toEqual(group.description)
|
expect(res.body.description).toEqual(group.description)
|
||||||
expect(res.body.isActive).toEqual(true)
|
expect(res.body.isActive).toEqual(true)
|
||||||
expect(res.body.users).toEqual([
|
expect(res.body.users).toEqual([
|
||||||
{
|
{
|
||||||
uid: expect.anything(),
|
id: expect.anything(),
|
||||||
username: user.username,
|
username: user.username,
|
||||||
displayName: user.displayName
|
displayName: user.displayName
|
||||||
}
|
}
|
||||||
@@ -457,20 +447,20 @@ describe('group', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
await request(app)
|
await request(app)
|
||||||
.post(`/SASjsApi/group/${dbGroup.uid}/${dbUser.uid}`)
|
.post(`/SASjsApi/group/${dbGroup.groupId}/${dbUser.id}`)
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send()
|
.send()
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.get(`/SASjsApi/user/${dbUser.uid}`)
|
.get(`/SASjsApi/user/${dbUser.id}`)
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send()
|
.send()
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(res.body.groups).toEqual([
|
expect(res.body.groups).toEqual([
|
||||||
{
|
{
|
||||||
uid: expect.anything(),
|
groupId: expect.anything(),
|
||||||
name: group.name,
|
name: group.name,
|
||||||
description: group.description
|
description: group.description
|
||||||
}
|
}
|
||||||
@@ -483,21 +473,21 @@ describe('group', () => {
|
|||||||
...user,
|
...user,
|
||||||
username: 'addUserRandomUser'
|
username: 'addUserRandomUser'
|
||||||
})
|
})
|
||||||
await groupController.addUserToGroup(dbGroup.uid, dbUser.uid)
|
await groupController.addUserToGroup(dbGroup.groupId, dbUser.id)
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.post(`/SASjsApi/group/${dbGroup.uid}/${dbUser.uid}`)
|
.post(`/SASjsApi/group/${dbGroup.groupId}/${dbUser.id}`)
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send()
|
.send()
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(res.body.uid).toBeTruthy()
|
expect(res.body.groupId).toBeTruthy()
|
||||||
expect(res.body.name).toEqual(group.name)
|
expect(res.body.name).toEqual(group.name)
|
||||||
expect(res.body.description).toEqual(group.description)
|
expect(res.body.description).toEqual(group.description)
|
||||||
expect(res.body.isActive).toEqual(true)
|
expect(res.body.isActive).toEqual(true)
|
||||||
expect(res.body.users).toEqual([
|
expect(res.body.users).toEqual([
|
||||||
{
|
{
|
||||||
uid: expect.anything(),
|
id: expect.anything(),
|
||||||
username: 'addUserRandomUser',
|
username: 'addUserRandomUser',
|
||||||
displayName: user.displayName
|
displayName: user.displayName
|
||||||
}
|
}
|
||||||
@@ -531,10 +521,8 @@ describe('group', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should respond with Not Found if groupId is incorrect', async () => {
|
it('should respond with Not Found if groupId is incorrect', async () => {
|
||||||
const hexValue = randomBytes(12).toString('hex')
|
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.post(`/SASjsApi/group/${hexValue}/123`)
|
.post('/SASjsApi/group/123/123')
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send()
|
.send()
|
||||||
.expect(404)
|
.expect(404)
|
||||||
@@ -545,10 +533,8 @@ describe('group', () => {
|
|||||||
|
|
||||||
it('should respond with Not Found if userId is incorrect', async () => {
|
it('should respond with Not Found if userId is incorrect', async () => {
|
||||||
const dbGroup = await groupController.createGroup(group)
|
const dbGroup = await groupController.createGroup(group)
|
||||||
const hexValue = randomBytes(12).toString('hex')
|
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.post(`/SASjsApi/group/${dbGroup.uid}/${hexValue}`)
|
.post(`/SASjsApi/group/${dbGroup.groupId}/123`)
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send()
|
.send()
|
||||||
.expect(404)
|
.expect(404)
|
||||||
@@ -565,7 +551,7 @@ describe('group', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.post(`/SASjsApi/group/${dbGroup.uid}/${dbUser.uid}`)
|
.post(`/SASjsApi/group/${dbGroup.groupId}/${dbUser.id}`)
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send()
|
.send()
|
||||||
.expect(400)
|
.expect(400)
|
||||||
@@ -574,46 +560,6 @@ describe('group', () => {
|
|||||||
`Can't add/remove user to '${PUBLIC_GROUP_NAME}' group.`
|
`Can't add/remove user to '${PUBLIC_GROUP_NAME}' group.`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should respond with Method Not Allowed if group is created by an external authProvider', async () => {
|
|
||||||
const dbGroup = await Group.create({
|
|
||||||
...group,
|
|
||||||
authProvider: AuthProviderType.LDAP
|
|
||||||
})
|
|
||||||
const dbUser = await userController.createUser({
|
|
||||||
...user,
|
|
||||||
username: 'ldapGroupUser'
|
|
||||||
})
|
|
||||||
|
|
||||||
const res = await request(app)
|
|
||||||
.post(`/SASjsApi/group/${dbGroup.uid}/${dbUser.uid}`)
|
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
|
||||||
.send()
|
|
||||||
.expect(405)
|
|
||||||
|
|
||||||
expect(res.text).toEqual(
|
|
||||||
`Can't add/remove user to group created by external auth provider.`
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should respond with Method Not Allowed if user is created by an external authProvider', async () => {
|
|
||||||
const dbGroup = await groupController.createGroup(group)
|
|
||||||
const dbUser = await User.create({
|
|
||||||
...user,
|
|
||||||
username: 'ldapUser',
|
|
||||||
authProvider: AuthProviderType.LDAP
|
|
||||||
})
|
|
||||||
|
|
||||||
const res = await request(app)
|
|
||||||
.post(`/SASjsApi/group/${dbGroup.uid}/${dbUser.uid}`)
|
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
|
||||||
.send()
|
|
||||||
.expect(405)
|
|
||||||
|
|
||||||
expect(res.text).toEqual(
|
|
||||||
`Can't add/remove user to group created by external auth provider.`
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('RemoveUser', () => {
|
describe('RemoveUser', () => {
|
||||||
@@ -627,15 +573,15 @@ describe('group', () => {
|
|||||||
...user,
|
...user,
|
||||||
username: 'removeUserRandomUser'
|
username: 'removeUserRandomUser'
|
||||||
})
|
})
|
||||||
await groupController.addUserToGroup(dbGroup.uid, dbUser.uid)
|
await groupController.addUserToGroup(dbGroup.groupId, dbUser.id)
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.delete(`/SASjsApi/group/${dbGroup.uid}/${dbUser.uid}`)
|
.delete(`/SASjsApi/group/${dbGroup.groupId}/${dbUser.id}`)
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send()
|
.send()
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(res.body.uid).toBeTruthy()
|
expect(res.body.groupId).toBeTruthy()
|
||||||
expect(res.body.name).toEqual(group.name)
|
expect(res.body.name).toEqual(group.name)
|
||||||
expect(res.body.description).toEqual(group.description)
|
expect(res.body.description).toEqual(group.description)
|
||||||
expect(res.body.isActive).toEqual(true)
|
expect(res.body.isActive).toEqual(true)
|
||||||
@@ -648,16 +594,16 @@ describe('group', () => {
|
|||||||
...user,
|
...user,
|
||||||
username: 'removeGroupFromUser'
|
username: 'removeGroupFromUser'
|
||||||
})
|
})
|
||||||
await groupController.addUserToGroup(dbGroup.uid, dbUser.uid)
|
await groupController.addUserToGroup(dbGroup.groupId, dbUser.id)
|
||||||
|
|
||||||
await request(app)
|
await request(app)
|
||||||
.delete(`/SASjsApi/group/${dbGroup.uid}/${dbUser.uid}`)
|
.delete(`/SASjsApi/group/${dbGroup.groupId}/${dbUser.id}`)
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send()
|
.send()
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.get(`/SASjsApi/user/${dbUser.uid}`)
|
.get(`/SASjsApi/user/${dbUser.id}`)
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send()
|
.send()
|
||||||
.expect(200)
|
.expect(200)
|
||||||
@@ -665,46 +611,6 @@ describe('group', () => {
|
|||||||
expect(res.body.groups).toEqual([])
|
expect(res.body.groups).toEqual([])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should respond with Method Not Allowed if group is created by an external authProvider', async () => {
|
|
||||||
const dbGroup = await Group.create({
|
|
||||||
...group,
|
|
||||||
authProvider: AuthProviderType.LDAP
|
|
||||||
})
|
|
||||||
const dbUser = await userController.createUser({
|
|
||||||
...user,
|
|
||||||
username: 'removeLdapGroupUser'
|
|
||||||
})
|
|
||||||
|
|
||||||
const res = await request(app)
|
|
||||||
.delete(`/SASjsApi/group/${dbGroup.uid}/${dbUser.uid}`)
|
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
|
||||||
.send()
|
|
||||||
.expect(405)
|
|
||||||
|
|
||||||
expect(res.text).toEqual(
|
|
||||||
`Can't add/remove user to group created by external auth provider.`
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should respond with Method Not Allowed if user is created by an external authProvider', async () => {
|
|
||||||
const dbGroup = await groupController.createGroup(group)
|
|
||||||
const dbUser = await User.create({
|
|
||||||
...user,
|
|
||||||
username: 'removeLdapUser',
|
|
||||||
authProvider: AuthProviderType.LDAP
|
|
||||||
})
|
|
||||||
|
|
||||||
const res = await request(app)
|
|
||||||
.delete(`/SASjsApi/group/${dbGroup.uid}/${dbUser.uid}`)
|
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
|
||||||
.send()
|
|
||||||
.expect(405)
|
|
||||||
|
|
||||||
expect(res.text).toEqual(
|
|
||||||
`Can't add/remove user to group created by external auth provider.`
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should respond with Unauthorized if access token is not present', async () => {
|
it('should respond with Unauthorized if access token is not present', async () => {
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.delete('/SASjsApi/group/123/123')
|
.delete('/SASjsApi/group/123/123')
|
||||||
@@ -732,10 +638,8 @@ describe('group', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should respond with Not Found if groupId is incorrect', async () => {
|
it('should respond with Not Found if groupId is incorrect', async () => {
|
||||||
const hexValue = randomBytes(12).toString('hex')
|
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.delete(`/SASjsApi/group/${hexValue}/123`)
|
.delete('/SASjsApi/group/123/123')
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send()
|
.send()
|
||||||
.expect(404)
|
.expect(404)
|
||||||
@@ -746,10 +650,8 @@ describe('group', () => {
|
|||||||
|
|
||||||
it('should respond with Not Found if userId is incorrect', async () => {
|
it('should respond with Not Found if userId is incorrect', async () => {
|
||||||
const dbGroup = await groupController.createGroup(group)
|
const dbGroup = await groupController.createGroup(group)
|
||||||
const hexValue = randomBytes(12).toString('hex')
|
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.delete(`/SASjsApi/group/${dbGroup.uid}/${hexValue}`)
|
.delete(`/SASjsApi/group/${dbGroup.groupId}/123`)
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send()
|
.send()
|
||||||
.expect(404)
|
.expect(404)
|
||||||
@@ -765,10 +667,10 @@ const generateSaveTokenAndCreateUser = async (
|
|||||||
): Promise<string> => {
|
): Promise<string> => {
|
||||||
const dbUser = await userController.createUser(someUser ?? adminUser)
|
const dbUser = await userController.createUser(someUser ?? adminUser)
|
||||||
|
|
||||||
return generateAndSaveToken(dbUser.uid)
|
return generateAndSaveToken(dbUser.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
const generateAndSaveToken = async (userId: string) => {
|
const generateAndSaveToken = async (userId: number) => {
|
||||||
const adminAccessToken = generateAccessToken({
|
const adminAccessToken = generateAccessToken({
|
||||||
clientId,
|
clientId,
|
||||||
userId
|
userId
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import {
|
|||||||
PermissionDetailsResponse
|
PermissionDetailsResponse
|
||||||
} from '../../../controllers'
|
} from '../../../controllers'
|
||||||
import { generateAccessToken, saveTokensInDB } from '../../../utils'
|
import { generateAccessToken, saveTokensInDB } from '../../../utils'
|
||||||
import { randomBytes } from 'crypto'
|
|
||||||
|
|
||||||
const deployPayload = {
|
const deployPayload = {
|
||||||
appLoc: 'string',
|
appLoc: 'string',
|
||||||
@@ -104,10 +103,10 @@ describe('permission', () => {
|
|||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.post('/SASjsApi/permission')
|
.post('/SASjsApi/permission')
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send({ ...permission, principalId: dbUser.uid })
|
.send({ ...permission, principalId: dbUser.id })
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(res.body.uid).toBeTruthy()
|
expect(res.body.permissionId).toBeTruthy()
|
||||||
expect(res.body.path).toEqual(permission.path)
|
expect(res.body.path).toEqual(permission.path)
|
||||||
expect(res.body.type).toEqual(permission.type)
|
expect(res.body.type).toEqual(permission.type)
|
||||||
expect(res.body.setting).toEqual(permission.setting)
|
expect(res.body.setting).toEqual(permission.setting)
|
||||||
@@ -123,11 +122,11 @@ describe('permission', () => {
|
|||||||
.send({
|
.send({
|
||||||
...permission,
|
...permission,
|
||||||
principalType: 'group',
|
principalType: 'group',
|
||||||
principalId: dbGroup.uid
|
principalId: dbGroup.groupId
|
||||||
})
|
})
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(res.body.uid).toBeTruthy()
|
expect(res.body.permissionId).toBeTruthy()
|
||||||
expect(res.body.path).toEqual(permission.path)
|
expect(res.body.path).toEqual(permission.path)
|
||||||
expect(res.body.type).toEqual(permission.type)
|
expect(res.body.type).toEqual(permission.type)
|
||||||
expect(res.body.setting).toEqual(permission.setting)
|
expect(res.body.setting).toEqual(permission.setting)
|
||||||
@@ -145,7 +144,7 @@ describe('permission', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should respond with Unauthorized if access token is not of an admin account', async () => {
|
it('should respond with Unauthorized if access token is not of an admin account', async () => {
|
||||||
const accessToken = await generateAndSaveToken(dbUser.uid)
|
const accessToken = await generateAndSaveToken(dbUser.id)
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.post('/SASjsApi/permission')
|
.post('/SASjsApi/permission')
|
||||||
@@ -282,19 +281,17 @@ describe('permission', () => {
|
|||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should respond with Bad Request if principalId is not a string of 24 hex characters', async () => {
|
it('should respond with Bad Request if principalId is not a number', async () => {
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.post('/SASjsApi/permission')
|
.post('/SASjsApi/permission')
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send({
|
.send({
|
||||||
...permission,
|
...permission,
|
||||||
principalId: randomBytes(10).toString('hex')
|
principalId: 'someCharacters'
|
||||||
})
|
})
|
||||||
.expect(400)
|
.expect(400)
|
||||||
|
|
||||||
expect(res.text).toEqual(
|
expect(res.text).toEqual('"principalId" must be a number')
|
||||||
'"principalId" length must be 24 characters long'
|
|
||||||
)
|
|
||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -310,7 +307,7 @@ describe('permission', () => {
|
|||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send({
|
.send({
|
||||||
...permission,
|
...permission,
|
||||||
principalId: adminUser.uid
|
principalId: adminUser.id
|
||||||
})
|
})
|
||||||
.expect(400)
|
.expect(400)
|
||||||
|
|
||||||
@@ -324,7 +321,7 @@ describe('permission', () => {
|
|||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send({
|
.send({
|
||||||
...permission,
|
...permission,
|
||||||
principalId: randomBytes(12).toString('hex')
|
principalId: 123
|
||||||
})
|
})
|
||||||
.expect(404)
|
.expect(404)
|
||||||
|
|
||||||
@@ -339,7 +336,7 @@ describe('permission', () => {
|
|||||||
.send({
|
.send({
|
||||||
...permission,
|
...permission,
|
||||||
principalType: 'group',
|
principalType: 'group',
|
||||||
principalId: randomBytes(12).toString('hex')
|
principalId: 123
|
||||||
})
|
})
|
||||||
.expect(404)
|
.expect(404)
|
||||||
|
|
||||||
@@ -350,13 +347,13 @@ describe('permission', () => {
|
|||||||
it('should respond with Conflict (409) if permission already exists', async () => {
|
it('should respond with Conflict (409) if permission already exists', async () => {
|
||||||
await permissionController.createPermission({
|
await permissionController.createPermission({
|
||||||
...permission,
|
...permission,
|
||||||
principalId: dbUser.uid
|
principalId: dbUser.id
|
||||||
})
|
})
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.post('/SASjsApi/permission')
|
.post('/SASjsApi/permission')
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send({ ...permission, principalId: dbUser.uid })
|
.send({ ...permission, principalId: dbUser.id })
|
||||||
.expect(409)
|
.expect(409)
|
||||||
|
|
||||||
expect(res.text).toEqual(
|
expect(res.text).toEqual(
|
||||||
@@ -371,7 +368,7 @@ describe('permission', () => {
|
|||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
dbPermission = await permissionController.createPermission({
|
dbPermission = await permissionController.createPermission({
|
||||||
...permission,
|
...permission,
|
||||||
principalId: dbUser.uid
|
principalId: dbUser.id
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -381,7 +378,7 @@ describe('permission', () => {
|
|||||||
|
|
||||||
it('should respond with updated permission', async () => {
|
it('should respond with updated permission', async () => {
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.patch(`/SASjsApi/permission/${dbPermission?.uid}`)
|
.patch(`/SASjsApi/permission/${dbPermission?.permissionId}`)
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send({ setting: PermissionSettingForRoute.deny })
|
.send({ setting: PermissionSettingForRoute.deny })
|
||||||
.expect(200)
|
.expect(200)
|
||||||
@@ -391,7 +388,7 @@ describe('permission', () => {
|
|||||||
|
|
||||||
it('should respond with Unauthorized if access token is not present', async () => {
|
it('should respond with Unauthorized if access token is not present', async () => {
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.patch(`/SASjsApi/permission/${dbPermission?.uid}`)
|
.patch(`/SASjsApi/permission/${dbPermission?.permissionId}`)
|
||||||
.send()
|
.send()
|
||||||
.expect(401)
|
.expect(401)
|
||||||
|
|
||||||
@@ -406,7 +403,7 @@ describe('permission', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.patch(`/SASjsApi/permission/${dbPermission?.uid}`)
|
.patch(`/SASjsApi/permission/${dbPermission?.permissionId}`)
|
||||||
.auth(accessToken, { type: 'bearer' })
|
.auth(accessToken, { type: 'bearer' })
|
||||||
.send()
|
.send()
|
||||||
.expect(401)
|
.expect(401)
|
||||||
@@ -417,7 +414,7 @@ describe('permission', () => {
|
|||||||
|
|
||||||
it('should respond with Bad Request if setting is missing', async () => {
|
it('should respond with Bad Request if setting is missing', async () => {
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.patch(`/SASjsApi/permission/${dbPermission?.uid}`)
|
.patch(`/SASjsApi/permission/${dbPermission?.permissionId}`)
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send()
|
.send()
|
||||||
.expect(400)
|
.expect(400)
|
||||||
@@ -428,7 +425,7 @@ describe('permission', () => {
|
|||||||
|
|
||||||
it('should respond with Bad Request if setting is invalid', async () => {
|
it('should respond with Bad Request if setting is invalid', async () => {
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.patch(`/SASjsApi/permission/${dbPermission?.uid}`)
|
.patch(`/SASjsApi/permission/${dbPermission?.permissionId}`)
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send({
|
.send({
|
||||||
setting: 'invalid'
|
setting: 'invalid'
|
||||||
@@ -440,9 +437,8 @@ describe('permission', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should respond with not found (404) if permission with provided id does not exist', async () => {
|
it('should respond with not found (404) if permission with provided id does not exist', async () => {
|
||||||
const hexValue = randomBytes(12).toString('hex')
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.patch(`/SASjsApi/permission/${hexValue}`)
|
.patch('/SASjsApi/permission/123')
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send({
|
.send({
|
||||||
setting: PermissionSettingForRoute.deny
|
setting: PermissionSettingForRoute.deny
|
||||||
@@ -458,10 +454,10 @@ describe('permission', () => {
|
|||||||
it('should delete permission', async () => {
|
it('should delete permission', async () => {
|
||||||
const dbPermission = await permissionController.createPermission({
|
const dbPermission = await permissionController.createPermission({
|
||||||
...permission,
|
...permission,
|
||||||
principalId: dbUser.uid
|
principalId: dbUser.id
|
||||||
})
|
})
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.delete(`/SASjsApi/permission/${dbPermission?.uid}`)
|
.delete(`/SASjsApi/permission/${dbPermission?.permissionId}`)
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send()
|
.send()
|
||||||
.expect(200)
|
.expect(200)
|
||||||
@@ -470,10 +466,8 @@ describe('permission', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
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 exists', async () => {
|
||||||
const hexValue = randomBytes(12).toString('hex')
|
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.delete(`/SASjsApi/permission/${hexValue}`)
|
.delete('/SASjsApi/permission/123')
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send()
|
.send()
|
||||||
.expect(404)
|
.expect(404)
|
||||||
@@ -487,12 +481,12 @@ describe('permission', () => {
|
|||||||
await permissionController.createPermission({
|
await permissionController.createPermission({
|
||||||
...permission,
|
...permission,
|
||||||
path: '/test-1',
|
path: '/test-1',
|
||||||
principalId: dbUser.uid
|
principalId: dbUser.id
|
||||||
})
|
})
|
||||||
await permissionController.createPermission({
|
await permissionController.createPermission({
|
||||||
...permission,
|
...permission,
|
||||||
path: '/test-2',
|
path: '/test-2',
|
||||||
principalId: dbUser.uid
|
principalId: dbUser.id
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -511,12 +505,12 @@ describe('permission', () => {
|
|||||||
...user,
|
...user,
|
||||||
username: 'get' + user.username
|
username: 'get' + user.username
|
||||||
})
|
})
|
||||||
const accessToken = await generateAndSaveToken(nonAdminUser.uid)
|
const accessToken = await generateAndSaveToken(nonAdminUser.id)
|
||||||
await permissionController.createPermission({
|
await permissionController.createPermission({
|
||||||
path: '/test-1',
|
path: '/test-1',
|
||||||
type: PermissionType.route,
|
type: PermissionType.route,
|
||||||
principalType: PrincipalType.user,
|
principalType: PrincipalType.user,
|
||||||
principalId: nonAdminUser.uid,
|
principalId: nonAdminUser.id,
|
||||||
setting: PermissionSettingForRoute.grant
|
setting: PermissionSettingForRoute.grant
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -537,7 +531,7 @@ describe('permission', () => {
|
|||||||
await permissionController.createPermission({
|
await permissionController.createPermission({
|
||||||
...permission,
|
...permission,
|
||||||
path: '/SASjsApi/drive/deploy',
|
path: '/SASjsApi/drive/deploy',
|
||||||
principalId: dbUser.uid
|
principalId: dbUser.id
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -557,7 +551,7 @@ describe('permission', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should create files in SASJS drive', async () => {
|
it('should create files in SASJS drive', async () => {
|
||||||
const accessToken = await generateAndSaveToken(dbUser.uid)
|
const accessToken = await generateAndSaveToken(dbUser.id)
|
||||||
|
|
||||||
await request(app)
|
await request(app)
|
||||||
.get('/SASjsApi/drive/deploy')
|
.get('/SASjsApi/drive/deploy')
|
||||||
@@ -567,7 +561,7 @@ describe('permission', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should respond unauthorized', async () => {
|
it('should respond unauthorized', async () => {
|
||||||
const accessToken = await generateAndSaveToken(dbUser.uid)
|
const accessToken = await generateAndSaveToken(dbUser.id)
|
||||||
|
|
||||||
await request(app)
|
await request(app)
|
||||||
.get('/SASjsApi/drive/deploy/upload')
|
.get('/SASjsApi/drive/deploy/upload')
|
||||||
@@ -583,10 +577,10 @@ const generateSaveTokenAndCreateUser = async (
|
|||||||
): Promise<string> => {
|
): Promise<string> => {
|
||||||
const dbUser = await userController.createUser(someUser ?? adminUser)
|
const dbUser = await userController.createUser(someUser ?? adminUser)
|
||||||
|
|
||||||
return generateAndSaveToken(dbUser.uid)
|
return generateAndSaveToken(dbUser.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
const generateAndSaveToken = async (userId: string) => {
|
const generateAndSaveToken = async (userId: number) => {
|
||||||
const adminAccessToken = generateAccessToken({
|
const adminAccessToken = generateAccessToken({
|
||||||
clientId,
|
clientId,
|
||||||
userId
|
userId
|
||||||
|
|||||||
@@ -58,12 +58,12 @@ describe('stp', () => {
|
|||||||
mongoServer = await MongoMemoryServer.create()
|
mongoServer = await MongoMemoryServer.create()
|
||||||
con = await mongoose.connect(mongoServer.getUri())
|
con = await mongoose.connect(mongoServer.getUri())
|
||||||
const dbUser = await userController.createUser(user)
|
const dbUser = await userController.createUser(user)
|
||||||
accessToken = await generateAndSaveToken(dbUser.uid)
|
accessToken = await generateAndSaveToken(dbUser.id)
|
||||||
await permissionController.createPermission({
|
await permissionController.createPermission({
|
||||||
path: '/SASjsApi/stp/execute',
|
path: '/SASjsApi/stp/execute',
|
||||||
type: PermissionType.route,
|
type: PermissionType.route,
|
||||||
principalType: PrincipalType.user,
|
principalType: PrincipalType.user,
|
||||||
principalId: dbUser.uid,
|
principalId: dbUser.id,
|
||||||
setting: PermissionSettingForRoute.grant
|
setting: PermissionSettingForRoute.grant
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -456,7 +456,7 @@ const makeRequestAndAssert = async (
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const generateAndSaveToken = async (userId: string) => {
|
const generateAndSaveToken = async (userId: number) => {
|
||||||
const accessToken = generateAccessToken({
|
const accessToken = generateAccessToken({
|
||||||
clientId,
|
clientId,
|
||||||
userId
|
userId
|
||||||
|
|||||||
@@ -1,16 +1,10 @@
|
|||||||
import { randomBytes } from 'crypto'
|
|
||||||
import { Express } from 'express'
|
import { Express } from 'express'
|
||||||
import mongoose, { Mongoose } from 'mongoose'
|
import mongoose, { Mongoose } from 'mongoose'
|
||||||
import { MongoMemoryServer } from 'mongodb-memory-server'
|
import { MongoMemoryServer } from 'mongodb-memory-server'
|
||||||
import request from 'supertest'
|
import request from 'supertest'
|
||||||
import appPromise from '../../../app'
|
import appPromise from '../../../app'
|
||||||
import { UserController, GroupController } from '../../../controllers/'
|
import { UserController, GroupController } from '../../../controllers/'
|
||||||
import {
|
import { generateAccessToken, saveTokensInDB } from '../../../utils'
|
||||||
generateAccessToken,
|
|
||||||
saveTokensInDB,
|
|
||||||
AuthProviderType
|
|
||||||
} from '../../../utils'
|
|
||||||
import User from '../../../model/User'
|
|
||||||
|
|
||||||
const clientId = 'someclientID'
|
const clientId = 'someclientID'
|
||||||
const adminUser = {
|
const adminUser = {
|
||||||
@@ -102,9 +96,9 @@ describe('user', () => {
|
|||||||
const dbUser = await controller.createUser(user)
|
const dbUser = await controller.createUser(user)
|
||||||
const accessToken = generateAccessToken({
|
const accessToken = generateAccessToken({
|
||||||
clientId,
|
clientId,
|
||||||
userId: dbUser.uid
|
userId: dbUser.id
|
||||||
})
|
})
|
||||||
await saveTokensInDB(dbUser.uid, clientId, accessToken, 'refreshToken')
|
await saveTokensInDB(dbUser.id, clientId, accessToken, 'refreshToken')
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.post('/SASjsApi/user')
|
.post('/SASjsApi/user')
|
||||||
@@ -116,16 +110,16 @@ describe('user', () => {
|
|||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should respond with Conflict if username is already present', async () => {
|
it('should respond with Forbidden if username is already present', async () => {
|
||||||
await controller.createUser(user)
|
await controller.createUser(user)
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.post('/SASjsApi/user')
|
.post('/SASjsApi/user')
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send(user)
|
.send(user)
|
||||||
.expect(409)
|
.expect(403)
|
||||||
|
|
||||||
expect(res.text).toEqual('Username already exists.')
|
expect(res.text).toEqual('Error: Username already exists.')
|
||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -188,7 +182,7 @@ describe('user', () => {
|
|||||||
const newDisplayName = 'My new display Name'
|
const newDisplayName = 'My new display Name'
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.patch(`/SASjsApi/user/${dbUser.uid}`)
|
.patch(`/SASjsApi/user/${dbUser.id}`)
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send({ ...user, displayName: newDisplayName })
|
.send({ ...user, displayName: newDisplayName })
|
||||||
.expect(200)
|
.expect(200)
|
||||||
@@ -201,11 +195,11 @@ describe('user', () => {
|
|||||||
|
|
||||||
it('should respond with updated user when user himself requests', async () => {
|
it('should respond with updated user when user himself requests', async () => {
|
||||||
const dbUser = await controller.createUser(user)
|
const dbUser = await controller.createUser(user)
|
||||||
const accessToken = await generateAndSaveToken(dbUser.uid)
|
const accessToken = await generateAndSaveToken(dbUser.id)
|
||||||
const newDisplayName = 'My new display Name'
|
const newDisplayName = 'My new display Name'
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.patch(`/SASjsApi/user/${dbUser.uid}`)
|
.patch(`/SASjsApi/user/${dbUser.id}`)
|
||||||
.auth(accessToken, { type: 'bearer' })
|
.auth(accessToken, { type: 'bearer' })
|
||||||
.send({
|
.send({
|
||||||
displayName: newDisplayName,
|
displayName: newDisplayName,
|
||||||
@@ -222,46 +216,16 @@ describe('user', () => {
|
|||||||
|
|
||||||
it('should respond with Bad Request, only admin can update isAdmin/isActive', async () => {
|
it('should respond with Bad Request, only admin can update isAdmin/isActive', async () => {
|
||||||
const dbUser = await controller.createUser(user)
|
const dbUser = await controller.createUser(user)
|
||||||
const accessToken = await generateAndSaveToken(dbUser.uid)
|
const accessToken = await generateAndSaveToken(dbUser.id)
|
||||||
const newDisplayName = 'My new display Name'
|
const newDisplayName = 'My new display Name'
|
||||||
|
|
||||||
await request(app)
|
await request(app)
|
||||||
.patch(`/SASjsApi/user/${dbUser.uid}`)
|
.patch(`/SASjsApi/user/${dbUser.id}`)
|
||||||
.auth(accessToken, { type: 'bearer' })
|
.auth(accessToken, { type: 'bearer' })
|
||||||
.send({ ...user, displayName: newDisplayName })
|
.send({ ...user, displayName: newDisplayName })
|
||||||
.expect(400)
|
.expect(400)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should respond with Method Not Allowed, when updating username of user created by an external auth provider', async () => {
|
|
||||||
const dbUser = await User.create({
|
|
||||||
...user,
|
|
||||||
authProvider: AuthProviderType.LDAP
|
|
||||||
})
|
|
||||||
const accessToken = await generateAndSaveToken(dbUser!.id)
|
|
||||||
const newUsername = 'newUsername'
|
|
||||||
|
|
||||||
await request(app)
|
|
||||||
.patch(`/SASjsApi/user/${dbUser!.id}`)
|
|
||||||
.auth(accessToken, { type: 'bearer' })
|
|
||||||
.send({ username: newUsername })
|
|
||||||
.expect(405)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should respond with Method Not Allowed, when updating displayName of user created by an external auth provider', async () => {
|
|
||||||
const dbUser = await User.create({
|
|
||||||
...user,
|
|
||||||
authProvider: AuthProviderType.LDAP
|
|
||||||
})
|
|
||||||
const accessToken = await generateAndSaveToken(dbUser!.id)
|
|
||||||
const newDisplayName = 'My new display Name'
|
|
||||||
|
|
||||||
await request(app)
|
|
||||||
.patch(`/SASjsApi/user/${dbUser!.id}`)
|
|
||||||
.auth(accessToken, { type: 'bearer' })
|
|
||||||
.send({ displayName: newDisplayName })
|
|
||||||
.expect(405)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should respond with Unauthorized if access token is not present', async () => {
|
it('should respond with Unauthorized if access token is not present', async () => {
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.patch('/SASjsApi/user/1234')
|
.patch('/SASjsApi/user/1234')
|
||||||
@@ -278,10 +242,10 @@ describe('user', () => {
|
|||||||
...user,
|
...user,
|
||||||
username: 'randomUser'
|
username: 'randomUser'
|
||||||
})
|
})
|
||||||
const accessToken = await generateAndSaveToken(dbUser2.uid)
|
const accessToken = await generateAndSaveToken(dbUser2.id)
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.patch(`/SASjsApi/user/${dbUser1.uid}`)
|
.patch(`/SASjsApi/user/${dbUser1.id}`)
|
||||||
.auth(accessToken, { type: 'bearer' })
|
.auth(accessToken, { type: 'bearer' })
|
||||||
.send(user)
|
.send(user)
|
||||||
.expect(401)
|
.expect(401)
|
||||||
@@ -290,7 +254,7 @@ describe('user', () => {
|
|||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should respond with Conflict if username is already present', async () => {
|
it('should respond with Forbidden if username is already present', async () => {
|
||||||
const dbUser1 = await controller.createUser(user)
|
const dbUser1 = await controller.createUser(user)
|
||||||
const dbUser2 = await controller.createUser({
|
const dbUser2 = await controller.createUser({
|
||||||
...user,
|
...user,
|
||||||
@@ -298,12 +262,12 @@ describe('user', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.patch(`/SASjsApi/user/${dbUser1.uid}`)
|
.patch(`/SASjsApi/user/${dbUser1.id}`)
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send({ username: dbUser2.username })
|
.send({ username: dbUser2.username })
|
||||||
.expect(409)
|
.expect(403)
|
||||||
|
|
||||||
expect(res.text).toEqual('Username already exists.')
|
expect(res.text).toEqual('Error: Username already exists.')
|
||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -326,7 +290,7 @@ describe('user', () => {
|
|||||||
|
|
||||||
it('should respond with updated user when user himself requests', async () => {
|
it('should respond with updated user when user himself requests', async () => {
|
||||||
const dbUser = await controller.createUser(user)
|
const dbUser = await controller.createUser(user)
|
||||||
const accessToken = await generateAndSaveToken(dbUser.uid)
|
const accessToken = await generateAndSaveToken(dbUser.id)
|
||||||
const newDisplayName = 'My new display Name'
|
const newDisplayName = 'My new display Name'
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
@@ -347,7 +311,7 @@ describe('user', () => {
|
|||||||
|
|
||||||
it('should respond with Bad Request, only admin can update isAdmin/isActive', async () => {
|
it('should respond with Bad Request, only admin can update isAdmin/isActive', async () => {
|
||||||
const dbUser = await controller.createUser(user)
|
const dbUser = await controller.createUser(user)
|
||||||
const accessToken = await generateAndSaveToken(dbUser.uid)
|
const accessToken = await generateAndSaveToken(dbUser.id)
|
||||||
const newDisplayName = 'My new display Name'
|
const newDisplayName = 'My new display Name'
|
||||||
|
|
||||||
await request(app)
|
await request(app)
|
||||||
@@ -373,10 +337,10 @@ describe('user', () => {
|
|||||||
...user,
|
...user,
|
||||||
username: 'randomUser'
|
username: 'randomUser'
|
||||||
})
|
})
|
||||||
const accessToken = await generateAndSaveToken(dbUser2.uid)
|
const accessToken = await generateAndSaveToken(dbUser2.id)
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.patch(`/SASjsApi/user/${dbUser1.uid}`)
|
.patch(`/SASjsApi/user/${dbUser1.id}`)
|
||||||
.auth(accessToken, { type: 'bearer' })
|
.auth(accessToken, { type: 'bearer' })
|
||||||
.send(user)
|
.send(user)
|
||||||
.expect(401)
|
.expect(401)
|
||||||
@@ -385,7 +349,7 @@ describe('user', () => {
|
|||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should respond with Conflict if username is already present', async () => {
|
it('should respond with Forbidden if username is already present', async () => {
|
||||||
const dbUser1 = await controller.createUser(user)
|
const dbUser1 = await controller.createUser(user)
|
||||||
const dbUser2 = await controller.createUser({
|
const dbUser2 = await controller.createUser({
|
||||||
...user,
|
...user,
|
||||||
@@ -396,9 +360,9 @@ describe('user', () => {
|
|||||||
.patch(`/SASjsApi/user/by/username/${dbUser1.username}`)
|
.patch(`/SASjsApi/user/by/username/${dbUser1.username}`)
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send({ username: dbUser2.username })
|
.send({ username: dbUser2.username })
|
||||||
.expect(409)
|
.expect(403)
|
||||||
|
|
||||||
expect(res.text).toEqual('Username already exists.')
|
expect(res.text).toEqual('Error: Username already exists.')
|
||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -419,7 +383,7 @@ describe('user', () => {
|
|||||||
const dbUser = await controller.createUser(user)
|
const dbUser = await controller.createUser(user)
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.delete(`/SASjsApi/user/${dbUser.uid}`)
|
.delete(`/SASjsApi/user/${dbUser.id}`)
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send()
|
.send()
|
||||||
.expect(200)
|
.expect(200)
|
||||||
@@ -429,10 +393,10 @@ describe('user', () => {
|
|||||||
|
|
||||||
it('should respond with OK when user himself requests', async () => {
|
it('should respond with OK when user himself requests', async () => {
|
||||||
const dbUser = await controller.createUser(user)
|
const dbUser = await controller.createUser(user)
|
||||||
const accessToken = await generateAndSaveToken(dbUser.uid)
|
const accessToken = await generateAndSaveToken(dbUser.id)
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.delete(`/SASjsApi/user/${dbUser.uid}`)
|
.delete(`/SASjsApi/user/${dbUser.id}`)
|
||||||
.auth(accessToken, { type: 'bearer' })
|
.auth(accessToken, { type: 'bearer' })
|
||||||
.send({ password: user.password })
|
.send({ password: user.password })
|
||||||
.expect(200)
|
.expect(200)
|
||||||
@@ -442,10 +406,10 @@ describe('user', () => {
|
|||||||
|
|
||||||
it('should respond with Bad Request when user himself requests and password is missing', async () => {
|
it('should respond with Bad Request when user himself requests and password is missing', async () => {
|
||||||
const dbUser = await controller.createUser(user)
|
const dbUser = await controller.createUser(user)
|
||||||
const accessToken = await generateAndSaveToken(dbUser.uid)
|
const accessToken = await generateAndSaveToken(dbUser.id)
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.delete(`/SASjsApi/user/${dbUser.uid}`)
|
.delete(`/SASjsApi/user/${dbUser.id}`)
|
||||||
.auth(accessToken, { type: 'bearer' })
|
.auth(accessToken, { type: 'bearer' })
|
||||||
.send()
|
.send()
|
||||||
.expect(400)
|
.expect(400)
|
||||||
@@ -470,10 +434,10 @@ describe('user', () => {
|
|||||||
...user,
|
...user,
|
||||||
username: 'randomUser'
|
username: 'randomUser'
|
||||||
})
|
})
|
||||||
const accessToken = await generateAndSaveToken(dbUser2.uid)
|
const accessToken = await generateAndSaveToken(dbUser2.id)
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.delete(`/SASjsApi/user/${dbUser1.uid}`)
|
.delete(`/SASjsApi/user/${dbUser1.id}`)
|
||||||
.auth(accessToken, { type: 'bearer' })
|
.auth(accessToken, { type: 'bearer' })
|
||||||
.send(user)
|
.send(user)
|
||||||
.expect(401)
|
.expect(401)
|
||||||
@@ -482,17 +446,17 @@ describe('user', () => {
|
|||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should respond with Unauthorized when user himself requests and password is incorrect', async () => {
|
it('should respond with Forbidden when user himself requests and password is incorrect', async () => {
|
||||||
const dbUser = await controller.createUser(user)
|
const dbUser = await controller.createUser(user)
|
||||||
const accessToken = await generateAndSaveToken(dbUser.uid)
|
const accessToken = await generateAndSaveToken(dbUser.id)
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.delete(`/SASjsApi/user/${dbUser.uid}`)
|
.delete(`/SASjsApi/user/${dbUser.id}`)
|
||||||
.auth(accessToken, { type: 'bearer' })
|
.auth(accessToken, { type: 'bearer' })
|
||||||
.send({ password: 'incorrectpassword' })
|
.send({ password: 'incorrectpassword' })
|
||||||
.expect(401)
|
.expect(403)
|
||||||
|
|
||||||
expect(res.text).toEqual('Invalid password.')
|
expect(res.text).toEqual('Error: Invalid password.')
|
||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -511,7 +475,7 @@ describe('user', () => {
|
|||||||
|
|
||||||
it('should respond with OK when user himself requests', async () => {
|
it('should respond with OK when user himself requests', async () => {
|
||||||
const dbUser = await controller.createUser(user)
|
const dbUser = await controller.createUser(user)
|
||||||
const accessToken = await generateAndSaveToken(dbUser.uid)
|
const accessToken = await generateAndSaveToken(dbUser.id)
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.delete(`/SASjsApi/user/by/username/${dbUser.username}`)
|
.delete(`/SASjsApi/user/by/username/${dbUser.username}`)
|
||||||
@@ -524,7 +488,7 @@ describe('user', () => {
|
|||||||
|
|
||||||
it('should respond with Bad Request when user himself requests and password is missing', async () => {
|
it('should respond with Bad Request when user himself requests and password is missing', async () => {
|
||||||
const dbUser = await controller.createUser(user)
|
const dbUser = await controller.createUser(user)
|
||||||
const accessToken = await generateAndSaveToken(dbUser.uid)
|
const accessToken = await generateAndSaveToken(dbUser.id)
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.delete(`/SASjsApi/user/by/username/${dbUser.username}`)
|
.delete(`/SASjsApi/user/by/username/${dbUser.username}`)
|
||||||
@@ -552,7 +516,7 @@ describe('user', () => {
|
|||||||
...user,
|
...user,
|
||||||
username: 'randomUser'
|
username: 'randomUser'
|
||||||
})
|
})
|
||||||
const accessToken = await generateAndSaveToken(dbUser2.uid)
|
const accessToken = await generateAndSaveToken(dbUser2.id)
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.delete(`/SASjsApi/user/by/username/${dbUser1.username}`)
|
.delete(`/SASjsApi/user/by/username/${dbUser1.username}`)
|
||||||
@@ -564,17 +528,17 @@ describe('user', () => {
|
|||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should respond with Unauthorized when user himself requests and password is incorrect', async () => {
|
it('should respond with Forbidden when user himself requests and password is incorrect', async () => {
|
||||||
const dbUser = await controller.createUser(user)
|
const dbUser = await controller.createUser(user)
|
||||||
const accessToken = await generateAndSaveToken(dbUser.uid)
|
const accessToken = await generateAndSaveToken(dbUser.id)
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.delete(`/SASjsApi/user/by/username/${dbUser.username}`)
|
.delete(`/SASjsApi/user/by/username/${dbUser.username}`)
|
||||||
.auth(accessToken, { type: 'bearer' })
|
.auth(accessToken, { type: 'bearer' })
|
||||||
.send({ password: 'incorrectpassword' })
|
.send({ password: 'incorrectpassword' })
|
||||||
.expect(401)
|
.expect(403)
|
||||||
|
|
||||||
expect(res.text).toEqual('Invalid password.')
|
expect(res.text).toEqual('Error: Invalid password.')
|
||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -593,7 +557,7 @@ describe('user', () => {
|
|||||||
|
|
||||||
it('should respond with user autoExec when same user requests', async () => {
|
it('should respond with user autoExec when same user requests', async () => {
|
||||||
const dbUser = await controller.createUser(user)
|
const dbUser = await controller.createUser(user)
|
||||||
const userId = dbUser.uid
|
const userId = dbUser.id
|
||||||
const accessToken = await generateAndSaveToken(userId)
|
const accessToken = await generateAndSaveToken(userId)
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
@@ -612,7 +576,7 @@ describe('user', () => {
|
|||||||
|
|
||||||
it('should respond with user autoExec when admin user requests', async () => {
|
it('should respond with user autoExec when admin user requests', async () => {
|
||||||
const dbUser = await controller.createUser(user)
|
const dbUser = await controller.createUser(user)
|
||||||
const userId = dbUser.uid
|
const userId = dbUser.id
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.get(`/SASjsApi/user/${userId}`)
|
.get(`/SASjsApi/user/${userId}`)
|
||||||
@@ -635,7 +599,7 @@ describe('user', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const dbUser = await controller.createUser(user)
|
const dbUser = await controller.createUser(user)
|
||||||
const userId = dbUser.uid
|
const userId = dbUser.id
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.get(`/SASjsApi/user/${userId}`)
|
.get(`/SASjsApi/user/${userId}`)
|
||||||
@@ -653,7 +617,7 @@ describe('user', () => {
|
|||||||
|
|
||||||
it('should respond with user along with associated groups', async () => {
|
it('should respond with user along with associated groups', async () => {
|
||||||
const dbUser = await controller.createUser(user)
|
const dbUser = await controller.createUser(user)
|
||||||
const userId = dbUser.uid
|
const userId = dbUser.id
|
||||||
const accessToken = await generateAndSaveToken(userId)
|
const accessToken = await generateAndSaveToken(userId)
|
||||||
|
|
||||||
const group = {
|
const group = {
|
||||||
@@ -662,7 +626,7 @@ describe('user', () => {
|
|||||||
}
|
}
|
||||||
const groupController = new GroupController()
|
const groupController = new GroupController()
|
||||||
const dbGroup = await groupController.createGroup(group)
|
const dbGroup = await groupController.createGroup(group)
|
||||||
await groupController.addUserToGroup(dbGroup.uid, dbUser.uid)
|
await groupController.addUserToGroup(dbGroup.groupId, dbUser.id)
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.get(`/SASjsApi/user/${userId}`)
|
.get(`/SASjsApi/user/${userId}`)
|
||||||
@@ -688,25 +652,23 @@ describe('user', () => {
|
|||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should respond with Not Found if userId is incorrect', async () => {
|
it('should respond with Forbidden if userId is incorrect', async () => {
|
||||||
await controller.createUser(user)
|
await controller.createUser(user)
|
||||||
|
|
||||||
const hexValue = randomBytes(12).toString('hex')
|
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.get(`/SASjsApi/user/${hexValue}`)
|
.get('/SASjsApi/user/1234')
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send()
|
.send()
|
||||||
.expect(404)
|
.expect(403)
|
||||||
|
|
||||||
expect(res.text).toEqual('User is not found.')
|
expect(res.text).toEqual('Error: User is not found.')
|
||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('by username', () => {
|
describe('by username', () => {
|
||||||
it('should respond with user autoExec when same user requests', async () => {
|
it('should respond with user autoExec when same user requests', async () => {
|
||||||
const dbUser = await controller.createUser(user)
|
const dbUser = await controller.createUser(user)
|
||||||
const userId = dbUser.uid
|
const userId = dbUser.id
|
||||||
const accessToken = await generateAndSaveToken(userId)
|
const accessToken = await generateAndSaveToken(userId)
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
@@ -769,16 +731,16 @@ describe('user', () => {
|
|||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should respond with Not Found if username is incorrect', async () => {
|
it('should respond with Forbidden if username is incorrect', async () => {
|
||||||
await controller.createUser(user)
|
await controller.createUser(user)
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.get('/SASjsApi/user/by/username/randomUsername')
|
.get('/SASjsApi/user/by/username/randomUsername')
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send()
|
.send()
|
||||||
.expect(404)
|
.expect(403)
|
||||||
|
|
||||||
expect(res.text).toEqual('User is not found.')
|
expect(res.text).toEqual('Error: User is not found.')
|
||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -806,13 +768,13 @@ describe('user', () => {
|
|||||||
|
|
||||||
expect(res.body).toEqual([
|
expect(res.body).toEqual([
|
||||||
{
|
{
|
||||||
uid: expect.anything(),
|
id: expect.anything(),
|
||||||
username: adminUser.username,
|
username: adminUser.username,
|
||||||
displayName: adminUser.displayName,
|
displayName: adminUser.displayName,
|
||||||
isAdmin: adminUser.isAdmin
|
isAdmin: adminUser.isAdmin
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
uid: expect.anything(),
|
id: expect.anything(),
|
||||||
username: user.username,
|
username: user.username,
|
||||||
displayName: user.displayName,
|
displayName: user.displayName,
|
||||||
isAdmin: user.isAdmin
|
isAdmin: user.isAdmin
|
||||||
@@ -834,13 +796,13 @@ describe('user', () => {
|
|||||||
|
|
||||||
expect(res.body).toEqual([
|
expect(res.body).toEqual([
|
||||||
{
|
{
|
||||||
uid: expect.anything(),
|
id: expect.anything(),
|
||||||
username: adminUser.username,
|
username: adminUser.username,
|
||||||
displayName: adminUser.displayName,
|
displayName: adminUser.displayName,
|
||||||
isAdmin: adminUser.isAdmin
|
isAdmin: adminUser.isAdmin
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
uid: expect.anything(),
|
id: expect.anything(),
|
||||||
username: 'randomUser',
|
username: 'randomUser',
|
||||||
displayName: user.displayName,
|
displayName: user.displayName,
|
||||||
isAdmin: user.isAdmin
|
isAdmin: user.isAdmin
|
||||||
@@ -862,10 +824,10 @@ const generateSaveTokenAndCreateUser = async (
|
|||||||
): Promise<string> => {
|
): Promise<string> => {
|
||||||
const dbUser = await controller.createUser(someUser ?? adminUser)
|
const dbUser = await controller.createUser(someUser ?? adminUser)
|
||||||
|
|
||||||
return generateAndSaveToken(dbUser.uid)
|
return generateAndSaveToken(dbUser.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
const generateAndSaveToken = async (userId: string) => {
|
const generateAndSaveToken = async (userId: number) => {
|
||||||
const adminAccessToken = generateAccessToken({
|
const adminAccessToken = generateAccessToken({
|
||||||
clientId,
|
clientId,
|
||||||
userId
|
userId
|
||||||
|
|||||||
@@ -47,6 +47,72 @@ describe('web', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('SASLogon/login', () => {
|
||||||
|
let csrfToken: string
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
;({ csrfToken } = await getCSRF(app))
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
const collections = mongoose.connection.collections
|
||||||
|
const collection = collections['users']
|
||||||
|
await collection.deleteMany({})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should respond with successful login', async () => {
|
||||||
|
await userController.createUser(user)
|
||||||
|
|
||||||
|
const res = await request(app)
|
||||||
|
.post('/SASLogon/login')
|
||||||
|
.set('x-xsrf-token', csrfToken)
|
||||||
|
.send({
|
||||||
|
username: user.username,
|
||||||
|
password: user.password
|
||||||
|
})
|
||||||
|
.expect(200)
|
||||||
|
|
||||||
|
expect(res.body.loggedIn).toBeTruthy()
|
||||||
|
expect(res.body.user).toEqual({
|
||||||
|
id: expect.any(Number),
|
||||||
|
username: user.username,
|
||||||
|
displayName: user.displayName,
|
||||||
|
isAdmin: user.isAdmin
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should respond with Bad Request if CSRF Token is not present', async () => {
|
||||||
|
await userController.createUser(user)
|
||||||
|
|
||||||
|
const res = await request(app)
|
||||||
|
.post('/SASLogon/login')
|
||||||
|
.send({
|
||||||
|
username: user.username,
|
||||||
|
password: user.password
|
||||||
|
})
|
||||||
|
.expect(400)
|
||||||
|
|
||||||
|
expect(res.text).toEqual('Invalid CSRF token!')
|
||||||
|
expect(res.body).toEqual({})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should respond with Bad Request if CSRF Token is invalid', async () => {
|
||||||
|
await userController.createUser(user)
|
||||||
|
|
||||||
|
const res = await request(app)
|
||||||
|
.post('/SASLogon/login')
|
||||||
|
.set('x-xsrf-token', 'INVALID_CSRF_TOKEN')
|
||||||
|
.send({
|
||||||
|
username: user.username,
|
||||||
|
password: user.password
|
||||||
|
})
|
||||||
|
.expect(400)
|
||||||
|
|
||||||
|
expect(res.text).toEqual('Invalid CSRF token!')
|
||||||
|
expect(res.body).toEqual({})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('SASLogon/authorize', () => {
|
describe('SASLogon/authorize', () => {
|
||||||
let csrfToken: string
|
let csrfToken: string
|
||||||
let authCookies: string
|
let authCookies: string
|
||||||
@@ -117,147 +183,6 @@ describe('web', () => {
|
|||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('SASLogon/login', () => {
|
|
||||||
let csrfToken: string
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
;({ csrfToken } = await getCSRF(app))
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(async () => {
|
|
||||||
const collections = mongoose.connection.collections
|
|
||||||
const collection = collections['users']
|
|
||||||
await collection.deleteMany({})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should respond with successful login', async () => {
|
|
||||||
await userController.createUser(user)
|
|
||||||
|
|
||||||
const res = await request(app)
|
|
||||||
.post('/SASLogon/login')
|
|
||||||
.set('x-xsrf-token', csrfToken)
|
|
||||||
.send({
|
|
||||||
username: user.username,
|
|
||||||
password: user.password
|
|
||||||
})
|
|
||||||
.expect(200)
|
|
||||||
|
|
||||||
expect(res.body.loggedIn).toBeTruthy()
|
|
||||||
expect(res.body.user).toEqual({
|
|
||||||
id: expect.any(String),
|
|
||||||
username: user.username,
|
|
||||||
displayName: user.displayName,
|
|
||||||
isAdmin: user.isAdmin,
|
|
||||||
needsToUpdatePassword: true
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should respond with too many requests when attempting with invalid password for a same user too many times', async () => {
|
|
||||||
await userController.createUser(user)
|
|
||||||
|
|
||||||
const promises: request.Test[] = []
|
|
||||||
|
|
||||||
const maxConsecutiveFailsByUsernameAndIp = Number(
|
|
||||||
process.env.MAX_CONSECUTIVE_FAILS_BY_USERNAME_AND_IP
|
|
||||||
)
|
|
||||||
|
|
||||||
Array(maxConsecutiveFailsByUsernameAndIp + 1)
|
|
||||||
.fill(0)
|
|
||||||
.map((_, i) => {
|
|
||||||
promises.push(
|
|
||||||
request(app)
|
|
||||||
.post('/SASLogon/login')
|
|
||||||
.set('x-xsrf-token', csrfToken)
|
|
||||||
.send({
|
|
||||||
username: user.username,
|
|
||||||
password: 'invalid-password'
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
await Promise.all(promises)
|
|
||||||
|
|
||||||
const res = await request(app)
|
|
||||||
.post('/SASLogon/login')
|
|
||||||
.set('x-xsrf-token', csrfToken)
|
|
||||||
.send({
|
|
||||||
username: user.username,
|
|
||||||
password: user.password
|
|
||||||
})
|
|
||||||
.expect(429)
|
|
||||||
|
|
||||||
expect(res.text).toContain('Too Many Requests!')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should respond with too many requests when attempting with invalid credentials for different users but with same ip too many times', async () => {
|
|
||||||
await userController.createUser(user)
|
|
||||||
|
|
||||||
const promises: request.Test[] = []
|
|
||||||
|
|
||||||
const maxWrongAttemptsByIpPerDay = Number(
|
|
||||||
process.env.MAX_WRONG_ATTEMPTS_BY_IP_PER_DAY
|
|
||||||
)
|
|
||||||
|
|
||||||
Array(maxWrongAttemptsByIpPerDay + 1)
|
|
||||||
.fill(0)
|
|
||||||
.map((_, i) => {
|
|
||||||
promises.push(
|
|
||||||
request(app)
|
|
||||||
.post('/SASLogon/login')
|
|
||||||
.set('x-xsrf-token', csrfToken)
|
|
||||||
.send({
|
|
||||||
username: `user${i}`,
|
|
||||||
password: 'invalid-password'
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
await Promise.all(promises)
|
|
||||||
|
|
||||||
const res = await request(app)
|
|
||||||
.post('/SASLogon/login')
|
|
||||||
.set('x-xsrf-token', csrfToken)
|
|
||||||
.send({
|
|
||||||
username: user.username,
|
|
||||||
password: user.password
|
|
||||||
})
|
|
||||||
.expect(429)
|
|
||||||
|
|
||||||
expect(res.text).toContain('Too Many Requests!')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should respond with Bad Request if CSRF Token is not present', async () => {
|
|
||||||
await userController.createUser(user)
|
|
||||||
|
|
||||||
const res = await request(app)
|
|
||||||
.post('/SASLogon/login')
|
|
||||||
.send({
|
|
||||||
username: user.username,
|
|
||||||
password: user.password
|
|
||||||
})
|
|
||||||
.expect(400)
|
|
||||||
|
|
||||||
expect(res.text).toEqual('Invalid CSRF token!')
|
|
||||||
expect(res.body).toEqual({})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should respond with Bad Request if CSRF Token is invalid', async () => {
|
|
||||||
await userController.createUser(user)
|
|
||||||
|
|
||||||
const res = await request(app)
|
|
||||||
.post('/SASLogon/login')
|
|
||||||
.set('x-xsrf-token', 'INVALID_CSRF_TOKEN')
|
|
||||||
.send({
|
|
||||||
username: user.username,
|
|
||||||
password: user.password
|
|
||||||
})
|
|
||||||
.expect(400)
|
|
||||||
|
|
||||||
expect(res.text).toEqual('Invalid CSRF token!')
|
|
||||||
expect(res.body).toEqual({})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const getCSRF = async (app: Express) => {
|
const getCSRF = async (app: Express) => {
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import {
|
|||||||
deleteUserValidation,
|
deleteUserValidation,
|
||||||
getUserValidation,
|
getUserValidation,
|
||||||
registerUserValidation,
|
registerUserValidation,
|
||||||
uidValidation,
|
|
||||||
updateUserValidation
|
updateUserValidation
|
||||||
} from '../../utils'
|
} from '../../utils'
|
||||||
|
|
||||||
@@ -24,7 +23,7 @@ userRouter.post('/', authenticateAccessToken, verifyAdmin, async (req, res) => {
|
|||||||
const response = await controller.createUser(body)
|
const response = await controller.createUser(body)
|
||||||
res.send(response)
|
res.send(response)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
res.status(err.code).send(err.message)
|
res.status(403).send(err.toString())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -34,7 +33,7 @@ userRouter.get('/', authenticateAccessToken, async (req, res) => {
|
|||||||
const response = await controller.getAllUsers()
|
const response = await controller.getAllUsers()
|
||||||
res.send(response)
|
res.send(response)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
res.status(err.code).send(err.message)
|
res.status(403).send(err.toString())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -52,23 +51,20 @@ userRouter.get(
|
|||||||
const response = await controller.getUserByUsername(req, username)
|
const response = await controller.getUserByUsername(req, username)
|
||||||
res.send(response)
|
res.send(response)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
res.status(err.code).send(err.message)
|
res.status(403).send(err.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
userRouter.get('/:uid', authenticateAccessToken, async (req, res) => {
|
userRouter.get('/:userId', authenticateAccessToken, async (req, res) => {
|
||||||
const { error, value: params } = uidValidation(req.params)
|
const { userId } = req.params
|
||||||
if (error) return res.status(400).send(error.details[0].message)
|
|
||||||
|
|
||||||
const { uid } = params
|
|
||||||
|
|
||||||
const controller = new UserController()
|
const controller = new UserController()
|
||||||
try {
|
try {
|
||||||
const response = await controller.getUser(req, uid)
|
const response = await controller.getUser(req, parseInt(userId))
|
||||||
res.send(response)
|
res.send(response)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
res.status(err.code).send(err.message)
|
res.status(403).send(err.toString())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -95,22 +91,18 @@ userRouter.patch(
|
|||||||
const response = await controller.updateUserByUsername(username, body)
|
const response = await controller.updateUserByUsername(username, body)
|
||||||
res.send(response)
|
res.send(response)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
res.status(err.code).send(err.message)
|
res.status(403).send(err.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
userRouter.patch(
|
userRouter.patch(
|
||||||
'/:uid',
|
'/:userId',
|
||||||
authenticateAccessToken,
|
authenticateAccessToken,
|
||||||
verifyAdminIfNeeded,
|
verifyAdminIfNeeded,
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
const { user } = req
|
const { user } = req
|
||||||
|
const { userId } = req.params
|
||||||
const { error: uidError, value: params } = uidValidation(req.params)
|
|
||||||
if (uidError) return res.status(400).send(uidError.details[0].message)
|
|
||||||
|
|
||||||
const { uid } = params
|
|
||||||
|
|
||||||
// only an admin can update `isActive` and `isAdmin` fields
|
// only an admin can update `isActive` and `isAdmin` fields
|
||||||
const { error, value: body } = updateUserValidation(req.body, user!.isAdmin)
|
const { error, value: body } = updateUserValidation(req.body, user!.isAdmin)
|
||||||
@@ -118,10 +110,10 @@ userRouter.patch(
|
|||||||
|
|
||||||
const controller = new UserController()
|
const controller = new UserController()
|
||||||
try {
|
try {
|
||||||
const response = await controller.updateUser(uid, body)
|
const response = await controller.updateUser(parseInt(userId), body)
|
||||||
res.send(response)
|
res.send(response)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
res.status(err.code).send(err.message)
|
res.status(403).send(err.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -149,22 +141,18 @@ userRouter.delete(
|
|||||||
await controller.deleteUserByUsername(username, data, user!.isAdmin)
|
await controller.deleteUserByUsername(username, data, user!.isAdmin)
|
||||||
res.status(200).send('Account Deleted!')
|
res.status(200).send('Account Deleted!')
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
res.status(err.code).send(err.message)
|
res.status(403).send(err.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
userRouter.delete(
|
userRouter.delete(
|
||||||
'/:uid',
|
'/:userId',
|
||||||
authenticateAccessToken,
|
authenticateAccessToken,
|
||||||
verifyAdminIfNeeded,
|
verifyAdminIfNeeded,
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
const { user } = req
|
const { user } = req
|
||||||
|
const { userId } = req.params
|
||||||
const { error: uidError, value: params } = uidValidation(req.params)
|
|
||||||
if (uidError) return res.status(400).send(uidError.details[0].message)
|
|
||||||
|
|
||||||
const { uid } = params
|
|
||||||
|
|
||||||
// only an admin can delete user without providing password
|
// only an admin can delete user without providing password
|
||||||
const { error, value: data } = deleteUserValidation(req.body, user!.isAdmin)
|
const { error, value: data } = deleteUserValidation(req.body, user!.isAdmin)
|
||||||
@@ -172,10 +160,10 @@ userRouter.delete(
|
|||||||
|
|
||||||
const controller = new UserController()
|
const controller = new UserController()
|
||||||
try {
|
try {
|
||||||
await controller.deleteUser(uid, data, user!.isAdmin)
|
await controller.deleteUser(parseInt(userId), data, user!.isAdmin)
|
||||||
res.status(200).send('Account Deleted!')
|
res.status(200).send('Account Deleted!')
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
res.status(err.code).send(err.message)
|
res.status(403).send(err.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export const publishAppStream = async (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const sasJsPort = process.env.PORT || 5000
|
const sasJsPort = process.env.PORT || 5000
|
||||||
process.logger.info(
|
console.log(
|
||||||
'Serving Stream App: ',
|
'Serving Stream App: ',
|
||||||
`http://localhost:${sasJsPort}/AppStream/${streamServiceName}`
|
`http://localhost:${sasJsPort}/AppStream/${streamServiceName}`
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -15,5 +15,5 @@ export const setupRoutes = (app: Express) => {
|
|||||||
appStreamRouter(req, res, next)
|
appStreamRouter(req, res, next)
|
||||||
})
|
})
|
||||||
|
|
||||||
app.use('/', webRouter)
|
app.use('/', csrfProtection, webRouter)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import sas9WebRouter from './sas9-web'
|
|||||||
import sasViyaWebRouter from './sasviya-web'
|
import sasViyaWebRouter from './sasviya-web'
|
||||||
import webRouter from './web'
|
import webRouter from './web'
|
||||||
import { MOCK_SERVERTYPEType } from '../../utils'
|
import { MOCK_SERVERTYPEType } from '../../utils'
|
||||||
import { csrfProtection } from '../../middlewares'
|
|
||||||
|
|
||||||
const router = express.Router()
|
const router = express.Router()
|
||||||
|
|
||||||
@@ -19,7 +18,7 @@ switch (MOCK_SERVERTYPE) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
router.use('/', csrfProtection, webRouter)
|
router.use('/', webRouter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,25 +2,12 @@ import express from 'express'
|
|||||||
import { generateCSRFToken } from '../../middlewares'
|
import { generateCSRFToken } from '../../middlewares'
|
||||||
import { WebController } from '../../controllers'
|
import { WebController } from '../../controllers'
|
||||||
import { MockSas9Controller } from '../../controllers/mock-sas9'
|
import { MockSas9Controller } from '../../controllers/mock-sas9'
|
||||||
import multer from 'multer'
|
|
||||||
import path from 'path'
|
|
||||||
import dotenv from 'dotenv'
|
|
||||||
import { FileUploadController } from '../../controllers/internal'
|
|
||||||
|
|
||||||
dotenv.config()
|
|
||||||
|
|
||||||
const sas9WebRouter = express.Router()
|
const sas9WebRouter = express.Router()
|
||||||
const webController = new WebController()
|
const webController = new WebController()
|
||||||
// Mock controller must be singleton because it keeps the states
|
// Mock controller must be singleton because it keeps the states
|
||||||
// for example `isLoggedIn` and potentially more in future mocks
|
// for example `isLoggedIn` and potentially more in future mocks
|
||||||
const controller = new MockSas9Controller()
|
const controller = new MockSas9Controller()
|
||||||
const fileUploadController = new FileUploadController()
|
|
||||||
|
|
||||||
const mockPath = process.env.STATIC_MOCK_LOCATION || 'mocks'
|
|
||||||
|
|
||||||
const upload = multer({
|
|
||||||
dest: path.join(process.cwd(), mockPath, 'sas9', 'files-received')
|
|
||||||
})
|
|
||||||
|
|
||||||
sas9WebRouter.get('/', async (req, res) => {
|
sas9WebRouter.get('/', async (req, res) => {
|
||||||
let response
|
let response
|
||||||
@@ -40,7 +27,7 @@ sas9WebRouter.get('/', async (req, res) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
sas9WebRouter.get('/SASStoredProcess', async (req, res) => {
|
sas9WebRouter.get('/SASStoredProcess', async (req, res) => {
|
||||||
const response = await controller.sasStoredProcess(req)
|
const response = await controller.sasStoredProcess()
|
||||||
|
|
||||||
if (response.redirect) {
|
if (response.redirect) {
|
||||||
res.redirect(response.redirect)
|
res.redirect(response.redirect)
|
||||||
@@ -54,8 +41,8 @@ sas9WebRouter.get('/SASStoredProcess', async (req, res) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
sas9WebRouter.get('/SASStoredProcess/do/', async (req, res) => {
|
sas9WebRouter.post('/SASStoredProcess/do/', async (req, res) => {
|
||||||
const response = await controller.sasStoredProcessDoGet(req)
|
const response = await controller.sasStoredProcessDo(req)
|
||||||
|
|
||||||
if (response.redirect) {
|
if (response.redirect) {
|
||||||
res.redirect(response.redirect)
|
res.redirect(response.redirect)
|
||||||
@@ -69,26 +56,6 @@ sas9WebRouter.get('/SASStoredProcess/do/', async (req, res) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
sas9WebRouter.post(
|
|
||||||
'/SASStoredProcess/do/',
|
|
||||||
fileUploadController.preUploadMiddleware,
|
|
||||||
fileUploadController.getMulterUploadObject().any(),
|
|
||||||
async (req, res) => {
|
|
||||||
const response = await controller.sasStoredProcessDoPost(req)
|
|
||||||
|
|
||||||
if (response.redirect) {
|
|
||||||
res.redirect(response.redirect)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
res.send(response.content)
|
|
||||||
} catch (err: any) {
|
|
||||||
res.status(403).send(err.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
sas9WebRouter.get('/SASLogon/login', async (req, res) => {
|
sas9WebRouter.get('/SASLogon/login', async (req, res) => {
|
||||||
const response = await controller.loginGet()
|
const response = await controller.loginGet()
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
import express from 'express'
|
import express from 'express'
|
||||||
import { generateCSRFToken } from '../../middlewares'
|
import { generateCSRFToken } from '../../middlewares'
|
||||||
import { WebController } from '../../controllers/web'
|
import { WebController } from '../../controllers/web'
|
||||||
import {
|
import { authenticateAccessToken, desktopRestrict } from '../../middlewares'
|
||||||
authenticateAccessToken,
|
|
||||||
bruteForceProtection,
|
|
||||||
desktopRestrict
|
|
||||||
} from '../../middlewares'
|
|
||||||
import { authorizeValidation, loginWebValidation } from '../../utils'
|
import { authorizeValidation, loginWebValidation } from '../../utils'
|
||||||
|
|
||||||
const webRouter = express.Router()
|
const webRouter = express.Router()
|
||||||
@@ -18,10 +14,7 @@ webRouter.get('/', async (req, res) => {
|
|||||||
} catch (_) {
|
} catch (_) {
|
||||||
response = '<html><head></head><body>Web Build is not present</body></html>'
|
response = '<html><head></head><body>Web Build is not present</body></html>'
|
||||||
} finally {
|
} finally {
|
||||||
const { ALLOWED_DOMAIN } = process.env
|
const codeToInject = `<script>document.cookie = 'XSRF-TOKEN=${generateCSRFToken()}; Max-Age=86400; SameSite=Strict; Path=/;'</script>`
|
||||||
const allowedDomain = ALLOWED_DOMAIN?.trim()
|
|
||||||
const domain = allowedDomain ? ` Domain=${allowedDomain};` : ''
|
|
||||||
const codeToInject = `<script>document.cookie = 'XSRF-TOKEN=${generateCSRFToken()};${domain} Max-Age=86400; SameSite=Strict; Path=/;'</script>`
|
|
||||||
const injectedContent = response?.replace(
|
const injectedContent = response?.replace(
|
||||||
'</head>',
|
'</head>',
|
||||||
`${codeToInject}</head>`
|
`${codeToInject}</head>`
|
||||||
@@ -31,26 +24,17 @@ webRouter.get('/', async (req, res) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
webRouter.post(
|
webRouter.post('/SASLogon/login', desktopRestrict, async (req, res) => {
|
||||||
'/SASLogon/login',
|
const { error, value: body } = loginWebValidation(req.body)
|
||||||
desktopRestrict,
|
if (error) return res.status(400).send(error.details[0].message)
|
||||||
bruteForceProtection,
|
|
||||||
async (req, res) => {
|
|
||||||
const { error, value: body } = loginWebValidation(req.body)
|
|
||||||
if (error) return res.status(400).send(error.details[0].message)
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await controller.login(req, body)
|
const response = await controller.login(req, body)
|
||||||
res.send(response)
|
res.send(response)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (err instanceof Error) {
|
res.status(403).send(err.toString())
|
||||||
res.status(500).send(err.toString())
|
|
||||||
} else {
|
|
||||||
res.status(err.code).send(err.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
})
|
||||||
|
|
||||||
webRouter.post(
|
webRouter.post(
|
||||||
'/SASLogon/authorize',
|
'/SASLogon/authorize',
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ appPromise.then(async (app) => {
|
|||||||
const protocol = process.env.PROTOCOL || 'http'
|
const protocol = process.env.PROTOCOL || 'http'
|
||||||
const sasJsPort = process.env.PORT || 5000
|
const sasJsPort = process.env.PORT || 5000
|
||||||
|
|
||||||
process.logger.info('PROTOCOL: ', protocol)
|
console.log('PROTOCOL: ', protocol)
|
||||||
|
|
||||||
if (protocol !== 'https') {
|
if (protocol !== 'https') {
|
||||||
app.listen(sasJsPort, () => {
|
app.listen(sasJsPort, () => {
|
||||||
process.logger.info(
|
console.log(
|
||||||
`⚡️[server]: Server is running at http://localhost:${sasJsPort}`
|
`⚡️[server]: Server is running at http://localhost:${sasJsPort}`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -20,7 +20,7 @@ appPromise.then(async (app) => {
|
|||||||
|
|
||||||
const httpsServer = createServer({ key, cert, ca }, app)
|
const httpsServer = createServer({ key, cert, ca }, app)
|
||||||
httpsServer.listen(sasJsPort, () => {
|
httpsServer.listen(sasJsPort, () => {
|
||||||
process.logger.info(
|
console.log(
|
||||||
`⚡️[server]: Server is running at https://localhost:${sasJsPort}`
|
`⚡️[server]: Server is running at https://localhost:${sasJsPort}`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export interface InfoJWT {
|
export interface InfoJWT {
|
||||||
clientId: string
|
clientId: string
|
||||||
userId: string
|
userId: number
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
export interface PreProgramVars {
|
export interface PreProgramVars {
|
||||||
username: string
|
username: string
|
||||||
userId: string
|
userId: number
|
||||||
displayName: string
|
displayName: string
|
||||||
serverUrl: string
|
serverUrl: string
|
||||||
httpHeaders: string[]
|
httpHeaders: string[]
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
export interface RequestUser {
|
export interface RequestUser {
|
||||||
userId: string
|
userId: number
|
||||||
clientId: string
|
clientId: string
|
||||||
username: string
|
username: string
|
||||||
displayName: string
|
displayName: string
|
||||||
isAdmin: boolean
|
isAdmin: boolean
|
||||||
isActive: boolean
|
isActive: boolean
|
||||||
needsToUpdatePassword: boolean
|
|
||||||
autoExec?: string
|
autoExec?: string
|
||||||
}
|
}
|
||||||
|
|||||||
3
api/src/types/system/process.d.ts
vendored
3
api/src/types/system/process.d.ts
vendored
@@ -5,14 +5,13 @@ declare namespace NodeJS {
|
|||||||
pythonLoc?: string
|
pythonLoc?: string
|
||||||
rLoc?: string
|
rLoc?: string
|
||||||
driveLoc: string
|
driveLoc: string
|
||||||
sasjsRoot: string
|
|
||||||
logsLoc: string
|
logsLoc: string
|
||||||
logsUUID: string
|
logsUUID: string
|
||||||
sessionController?: import('../../controllers/internal').SessionController
|
sessionController?: import('../../controllers/internal').SessionController
|
||||||
sasSessionController?: import('../../controllers/internal').SASSessionController
|
|
||||||
appStreamConfig: import('../').AppStreamConfig
|
appStreamConfig: import('../').AppStreamConfig
|
||||||
logger: import('@sasjs/utils/logger').Logger
|
logger: import('@sasjs/utils/logger').Logger
|
||||||
runTimes: import('../../utils').RunTimeType[]
|
runTimes: import('../../utils').RunTimeType[]
|
||||||
secrets: import('../../model/Configuration').ConfigurationType
|
secrets: import('../../model/Configuration').ConfigurationType
|
||||||
|
allowedDomains: string[]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export const loadAppStreamConfig = async () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
process.logger.info('App Stream Config loaded!')
|
console.log('App Stream Config loaded!')
|
||||||
}
|
}
|
||||||
|
|
||||||
export const addEntryToAppStreamConfig = (
|
export const addEntryToAppStreamConfig = (
|
||||||
|
|||||||
@@ -8,6 +8,6 @@ export const connectDB = async () => {
|
|||||||
throw new Error('Unable to connect to DB!')
|
throw new Error('Unable to connect to DB!')
|
||||||
}
|
}
|
||||||
|
|
||||||
process.logger.success('Connected to DB!')
|
console.log('Connected to DB!')
|
||||||
return seedDB()
|
return seedDB()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { getMacrosFolder, sasJSCoreMacros, sasJSCoreMacrosInfo } from '.'
|
|||||||
export const copySASjsCore = async () => {
|
export const copySASjsCore = async () => {
|
||||||
if (process.env.NODE_ENV === 'test') return
|
if (process.env.NODE_ENV === 'test') return
|
||||||
|
|
||||||
process.logger.log('Copying Macros from container to drive.')
|
console.log('Copying Macros from container to drive(tmp).')
|
||||||
|
|
||||||
const macrosDrivePath = getMacrosFolder()
|
const macrosDrivePath = getMacrosFolder()
|
||||||
|
|
||||||
@@ -30,5 +30,5 @@ export const copySASjsCore = async () => {
|
|||||||
await createFile(macroFileDestPath, macroContent)
|
await createFile(macroFileDestPath, macroContent)
|
||||||
})
|
})
|
||||||
|
|
||||||
process.logger.info('Macros Drive Path:', macrosDrivePath)
|
console.log('Macros Drive Path:', macrosDrivePath)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
import path from 'path'
|
|
||||||
import { createFile } from '@sasjs/utils'
|
|
||||||
import { getMacrosFolder } from './file'
|
|
||||||
|
|
||||||
const fileContent = `%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);
|
|
||||||
%ms_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt
|
|
||||||
,missing=&missing
|
|
||||||
,showmeta=&showmeta
|
|
||||||
,maxobs=&maxobs
|
|
||||||
)
|
|
||||||
%mend;`
|
|
||||||
|
|
||||||
export const createWeboutSasFile = async () => {
|
|
||||||
const macrosDrivePath = getMacrosFolder()
|
|
||||||
process.logger.log(`Creating webout.sas at ${macrosDrivePath}`)
|
|
||||||
const filePath = path.join(macrosDrivePath, 'webout.sas')
|
|
||||||
await createFile(filePath, fileContent)
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
import { randomBytes } from 'crypto'
|
|
||||||
|
|
||||||
export const randomBytesHexString = (bytesCount: number) =>
|
|
||||||
randomBytes(bytesCount).toString('hex')
|
|
||||||
@@ -10,7 +10,7 @@ export const sysInitCompiledPath = path.join(
|
|||||||
'systemInitCompiled.sas'
|
'systemInitCompiled.sas'
|
||||||
)
|
)
|
||||||
|
|
||||||
export const sasJSCoreMacros = path.join(apiRoot, 'sas', 'sasautos')
|
export const sasJSCoreMacros = path.join(apiRoot, 'sasjscore')
|
||||||
export const sasJSCoreMacrosInfo = path.join(sasJSCoreMacros, '.macrolist')
|
export const sasJSCoreMacrosInfo = path.join(sasJSCoreMacros, '.macrolist')
|
||||||
|
|
||||||
export const getWebBuildFolder = () => path.join(codebaseRoot, 'web', 'build')
|
export const getWebBuildFolder = () => path.join(codebaseRoot, 'web', 'build')
|
||||||
@@ -20,24 +20,19 @@ export const getSasjsHomeFolder = () => path.join(homedir(), '.sasjs-server')
|
|||||||
export const getDesktopUserAutoExecPath = () =>
|
export const getDesktopUserAutoExecPath = () =>
|
||||||
path.join(getSasjsHomeFolder(), 'user-autoexec.sas')
|
path.join(getSasjsHomeFolder(), 'user-autoexec.sas')
|
||||||
|
|
||||||
export const getSasjsRootFolder = () => process.sasjsRoot
|
export const getSasjsRootFolder = () => process.driveLoc
|
||||||
|
|
||||||
export const getSasjsDriveFolder = () => process.driveLoc
|
|
||||||
|
|
||||||
export const getLogFolder = () => process.logsLoc
|
export const getLogFolder = () => process.logsLoc
|
||||||
|
|
||||||
export const getAppStreamConfigPath = () =>
|
export const getAppStreamConfigPath = () =>
|
||||||
path.join(getSasjsDriveFolder(), 'appStreamConfig.json')
|
path.join(getSasjsRootFolder(), 'appStreamConfig.json')
|
||||||
|
|
||||||
export const getMacrosFolder = () =>
|
export const getMacrosFolder = () =>
|
||||||
path.join(getSasjsDriveFolder(), 'sas', 'sasautos')
|
path.join(getSasjsRootFolder(), 'sasjscore')
|
||||||
|
|
||||||
export const getPackagesFolder = () =>
|
|
||||||
path.join(getSasjsDriveFolder(), 'sas', 'sas_packages')
|
|
||||||
|
|
||||||
export const getUploadsFolder = () => path.join(getSasjsRootFolder(), 'uploads')
|
export const getUploadsFolder = () => path.join(getSasjsRootFolder(), 'uploads')
|
||||||
|
|
||||||
export const getFilesFolder = () => path.join(getSasjsDriveFolder(), 'files')
|
export const getFilesFolder = () => path.join(getSasjsRootFolder(), 'files')
|
||||||
|
|
||||||
export const getWeboutFolder = () => path.join(getSasjsRootFolder(), 'webouts')
|
export const getWeboutFolder = () => path.join(getSasjsRootFolder(), 'webouts')
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import jwt from 'jsonwebtoken'
|
import jwt from 'jsonwebtoken'
|
||||||
import { InfoJWT } from '../types'
|
import { InfoJWT } from '../types'
|
||||||
import { NUMBER_OF_SECONDS_IN_A_DAY } from '../model/Client'
|
|
||||||
|
|
||||||
export const generateAccessToken = (data: InfoJWT, expiry?: number) =>
|
export const generateAccessToken = (data: InfoJWT) =>
|
||||||
jwt.sign(data, process.secrets.ACCESS_TOKEN_SECRET, {
|
jwt.sign(data, process.secrets.ACCESS_TOKEN_SECRET, {
|
||||||
expiresIn: expiry ? expiry : NUMBER_OF_SECONDS_IN_A_DAY
|
expiresIn: '1day'
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import jwt from 'jsonwebtoken'
|
import jwt from 'jsonwebtoken'
|
||||||
import { InfoJWT } from '../types'
|
import { InfoJWT } from '../types'
|
||||||
import { NUMBER_OF_SECONDS_IN_A_DAY } from '../model/Client'
|
|
||||||
|
|
||||||
export const generateRefreshToken = (data: InfoJWT, expiry?: number) =>
|
export const generateRefreshToken = (data: InfoJWT) =>
|
||||||
jwt.sign(data, process.secrets.REFRESH_TOKEN_SECRET, {
|
jwt.sign(data, process.secrets.REFRESH_TOKEN_SECRET, {
|
||||||
expiresIn: expiry ? expiry : NUMBER_OF_SECONDS_IN_A_DAY
|
expiresIn: '30 days'
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { Request } from 'express'
|
import { Request } from 'express'
|
||||||
|
|
||||||
export const TopLevelRoutes = ['/AppStream', '/SASjsApi']
|
|
||||||
|
|
||||||
const StaticAuthorizedRoutes = [
|
const StaticAuthorizedRoutes = [
|
||||||
|
'/AppStream',
|
||||||
'/SASjsApi/code/execute',
|
'/SASjsApi/code/execute',
|
||||||
'/SASjsApi/stp/execute',
|
'/SASjsApi/stp/execute',
|
||||||
'/SASjsApi/drive/deploy',
|
'/SASjsApi/drive/deploy',
|
||||||
@@ -16,7 +15,7 @@ const StaticAuthorizedRoutes = [
|
|||||||
export const getAuthorizedRoutes = () => {
|
export const getAuthorizedRoutes = () => {
|
||||||
const streamingApps = Object.keys(process.appStreamConfig)
|
const streamingApps = Object.keys(process.appStreamConfig)
|
||||||
const streamingAppsRoutes = streamingApps.map((app) => `/AppStream/${app}`)
|
const streamingAppsRoutes = streamingApps.map((app) => `/AppStream/${app}`)
|
||||||
return [...TopLevelRoutes, ...StaticAuthorizedRoutes, ...streamingAppsRoutes]
|
return [...StaticAuthorizedRoutes, ...streamingAppsRoutes]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getPath = (req: Request) => {
|
export const getPath = (req: Request) => {
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ export const getCertificates = async () => {
|
|||||||
const certPath = CERT_CHAIN ?? (await getFileInput('Certificate Chain (PEM)'))
|
const certPath = CERT_CHAIN ?? (await getFileInput('Certificate Chain (PEM)'))
|
||||||
const caPath = CA_ROOT
|
const caPath = CA_ROOT
|
||||||
|
|
||||||
process.logger.info('keyPath: ', keyPath)
|
console.log('keyPath: ', keyPath)
|
||||||
process.logger.info('certPath: ', certPath)
|
console.log('certPath: ', certPath)
|
||||||
if (caPath) process.logger.info('caPath: ', caPath)
|
if (caPath) console.log('caPath: ', caPath)
|
||||||
|
|
||||||
const key = await readFile(keyPath)
|
const key = await readFile(keyPath)
|
||||||
const cert = await readFile(certPath)
|
const cert = await readFile(certPath)
|
||||||
|
|||||||
@@ -18,12 +18,10 @@ export const getPreProgramVariables = (req: Request): PreProgramVars => {
|
|||||||
|
|
||||||
if (cookies.length) httpHeaders.push(`cookie: ${cookies.join('; ')}`)
|
if (cookies.length) httpHeaders.push(`cookie: ${cookies.join('; ')}`)
|
||||||
|
|
||||||
//In desktop mode when mocking mode is enabled, user was undefined.
|
|
||||||
//So this is workaround.
|
|
||||||
return {
|
return {
|
||||||
username: user ? user.username : 'demo',
|
username: user!.username,
|
||||||
userId: user ? user.userId : 'demoId',
|
userId: user!.userId,
|
||||||
displayName: user ? user.displayName : 'demo',
|
displayName: user!.displayName,
|
||||||
serverUrl: protocol + host,
|
serverUrl: protocol + host,
|
||||||
httpHeaders
|
httpHeaders
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import User from '../model/User'
|
|||||||
const isValidToken = async (
|
const isValidToken = async (
|
||||||
token: string,
|
token: string,
|
||||||
key: string,
|
key: string,
|
||||||
userId: string,
|
userId: number,
|
||||||
clientId: string
|
clientId: string
|
||||||
) => {
|
) => {
|
||||||
const promise = new Promise<boolean>((resolve, reject) =>
|
const promise = new Promise<boolean>((resolve, reject) =>
|
||||||
@@ -22,8 +22,8 @@ const isValidToken = async (
|
|||||||
return await promise.then(() => true).catch(() => false)
|
return await promise.then(() => true).catch(() => false)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getTokensFromDB = async (userId: string, clientId: string) => {
|
export const getTokensFromDB = async (userId: number, clientId: string) => {
|
||||||
const user = await User.findOne({ _id: userId })
|
const user = await User.findOne({ id: userId })
|
||||||
if (!user) return
|
if (!user) return
|
||||||
|
|
||||||
const currentTokenObj = user.tokens.find(
|
const currentTokenObj = user.tokens.find(
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
export * from './appStreamConfig'
|
export * from './appStreamConfig'
|
||||||
export * from './connectDB'
|
export * from './connectDB'
|
||||||
export * from './copySASjsCore'
|
export * from './copySASjsCore'
|
||||||
export * from './createWeboutSasFile'
|
|
||||||
export * from './crypto'
|
|
||||||
export * from './desktopAutoExec'
|
export * from './desktopAutoExec'
|
||||||
export * from './extractHeaders'
|
export * from './extractHeaders'
|
||||||
export * from './extractName'
|
export * from './extractName'
|
||||||
@@ -20,17 +18,14 @@ export * from './getTokensFromDB'
|
|||||||
export * from './instantiateLogger'
|
export * from './instantiateLogger'
|
||||||
export * from './isDebugOn'
|
export * from './isDebugOn'
|
||||||
export * from './isPublicRoute'
|
export * from './isPublicRoute'
|
||||||
export * from './ldapClient'
|
export * from './zipped'
|
||||||
export * from './parseLogToArray'
|
export * from './parseLogToArray'
|
||||||
export * from './rateLimiter'
|
|
||||||
export * from './removeTokensInDB'
|
export * from './removeTokensInDB'
|
||||||
export * from './saveTokensInDB'
|
export * from './saveTokensInDB'
|
||||||
export * from './seedDB'
|
export * from './seedDB'
|
||||||
export * from './setProcessVariables'
|
export * from './setProcessVariables'
|
||||||
export * from './setupFolders'
|
export * from './setupFolders'
|
||||||
export * from './setupUserAutoExec'
|
|
||||||
export * from './upload'
|
export * from './upload'
|
||||||
export * from './validation'
|
export * from './validation'
|
||||||
export * from './verifyEnvVariables'
|
export * from './verifyEnvVariables'
|
||||||
export * from './verifyTokenInDB'
|
export * from './verifyTokenInDB'
|
||||||
export * from './zipped'
|
|
||||||
|
|||||||
@@ -22,11 +22,10 @@ export const isPublicRoute = async (req: Request): Promise<boolean> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const publicUser: RequestUser = {
|
export const publicUser: RequestUser = {
|
||||||
userId: 'public_user_id',
|
userId: 0,
|
||||||
clientId: 'public_app',
|
clientId: 'public_app',
|
||||||
username: 'publicUser',
|
username: 'publicUser',
|
||||||
displayName: 'Public User',
|
displayName: 'Public User',
|
||||||
isAdmin: false,
|
isAdmin: false,
|
||||||
isActive: true,
|
isActive: true
|
||||||
needsToUpdatePassword: false
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,163 +0,0 @@
|
|||||||
import { createClient, Client } from 'ldapjs'
|
|
||||||
import { ReturnCode } from './verifyEnvVariables'
|
|
||||||
|
|
||||||
export interface LDAPUser {
|
|
||||||
uid: string
|
|
||||||
username: string
|
|
||||||
displayName: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LDAPGroup {
|
|
||||||
name: string
|
|
||||||
members: string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export class LDAPClient {
|
|
||||||
private ldapClient: Client
|
|
||||||
private static classInstance: LDAPClient | null
|
|
||||||
|
|
||||||
private constructor() {
|
|
||||||
process.logger.info('creating LDAP client')
|
|
||||||
this.ldapClient = createClient({ url: process.env.LDAP_URL as string })
|
|
||||||
|
|
||||||
this.ldapClient.on('error', (error) => {
|
|
||||||
process.logger.error(error.message)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
static async init() {
|
|
||||||
if (!LDAPClient.classInstance) {
|
|
||||||
LDAPClient.classInstance = new LDAPClient()
|
|
||||||
|
|
||||||
process.logger.info('binding LDAP client')
|
|
||||||
await LDAPClient.classInstance.bind().catch((error) => {
|
|
||||||
LDAPClient.classInstance = null
|
|
||||||
throw error
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return LDAPClient.classInstance
|
|
||||||
}
|
|
||||||
|
|
||||||
private async bind() {
|
|
||||||
const promise = new Promise<void>((resolve, reject) => {
|
|
||||||
const { LDAP_BIND_DN, LDAP_BIND_PASSWORD } = process.env
|
|
||||||
this.ldapClient.bind(LDAP_BIND_DN!, LDAP_BIND_PASSWORD!, (error) => {
|
|
||||||
if (error) reject(error)
|
|
||||||
|
|
||||||
resolve()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
await promise.catch((error) => {
|
|
||||||
throw new Error(error.message)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async getAllLDAPUsers() {
|
|
||||||
const promise = new Promise<LDAPUser[]>((resolve, reject) => {
|
|
||||||
const { LDAP_USERS_BASE_DN } = process.env
|
|
||||||
const filter = `(objectClass=*)`
|
|
||||||
|
|
||||||
this.ldapClient.search(
|
|
||||||
LDAP_USERS_BASE_DN!,
|
|
||||||
{ filter },
|
|
||||||
(error, result) => {
|
|
||||||
if (error) reject(error)
|
|
||||||
|
|
||||||
const users: LDAPUser[] = []
|
|
||||||
|
|
||||||
result.on('searchEntry', (entry) => {
|
|
||||||
users.push({
|
|
||||||
uid: entry.object.uid as string,
|
|
||||||
username: entry.object.username as string,
|
|
||||||
displayName: entry.object.displayname as string
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
result.on('end', (result) => {
|
|
||||||
resolve(users)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
return await promise
|
|
||||||
.then((res) => res)
|
|
||||||
.catch((error) => {
|
|
||||||
throw new Error(error.message)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async getAllLDAPGroups() {
|
|
||||||
const promise = new Promise<LDAPGroup[]>((resolve, reject) => {
|
|
||||||
const { LDAP_GROUPS_BASE_DN } = process.env
|
|
||||||
|
|
||||||
this.ldapClient.search(LDAP_GROUPS_BASE_DN!, {}, (error, result) => {
|
|
||||||
if (error) reject(error)
|
|
||||||
|
|
||||||
const groups: LDAPGroup[] = []
|
|
||||||
|
|
||||||
result.on('searchEntry', (entry) => {
|
|
||||||
const members =
|
|
||||||
typeof entry.object.memberuid === 'string'
|
|
||||||
? [entry.object.memberuid]
|
|
||||||
: entry.object.memberuid
|
|
||||||
groups.push({
|
|
||||||
name: entry.object.cn as string,
|
|
||||||
members
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
result.on('end', (result) => {
|
|
||||||
resolve(groups)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return await promise
|
|
||||||
.then((res) => res)
|
|
||||||
.catch((error) => {
|
|
||||||
throw new Error(error.message)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async verifyUser(username: string, password: string) {
|
|
||||||
const promise = new Promise<boolean>((resolve, reject) => {
|
|
||||||
const { LDAP_USERS_BASE_DN } = process.env
|
|
||||||
const filter = `(username=${username})`
|
|
||||||
|
|
||||||
this.ldapClient.search(
|
|
||||||
LDAP_USERS_BASE_DN!,
|
|
||||||
{ filter },
|
|
||||||
(error, result) => {
|
|
||||||
if (error) reject(error)
|
|
||||||
|
|
||||||
const items: any = []
|
|
||||||
|
|
||||||
result.on('searchEntry', (entry) => {
|
|
||||||
items.push(entry.object)
|
|
||||||
})
|
|
||||||
|
|
||||||
result.on('end', (result) => {
|
|
||||||
if (result?.status !== 0 || items.length === 0) return reject()
|
|
||||||
|
|
||||||
// pick the first found
|
|
||||||
const user = items[0]
|
|
||||||
|
|
||||||
this.ldapClient.bind(user.dn, password, (error) => {
|
|
||||||
if (error) return reject(error)
|
|
||||||
|
|
||||||
resolve(true)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
return await promise
|
|
||||||
.then(() => true)
|
|
||||||
.catch(() => {
|
|
||||||
throw new Error('Invalid password.')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -22,12 +22,12 @@ export const getEnvCSPDirectives = (
|
|||||||
try {
|
try {
|
||||||
cspConfigJson = JSON.parse(file)
|
cspConfigJson = JSON.parse(file)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
process.logger.error(
|
console.error(
|
||||||
'Parsing Content Security Policy JSON config failed. Make sure it is valid json'
|
'Parsing Content Security Policy JSON config failed. Make sure it is valid json'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
process.logger.error('Error reading HELMET CSP config file', e)
|
console.error('Error reading HELMET CSP config file', e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,123 +0,0 @@
|
|||||||
import { RateLimiterMemory } from 'rate-limiter-flexible'
|
|
||||||
|
|
||||||
export class RateLimiter {
|
|
||||||
private static instance: RateLimiter
|
|
||||||
private limiterSlowBruteByIP: RateLimiterMemory
|
|
||||||
private limiterConsecutiveFailsByUsernameAndIP: RateLimiterMemory
|
|
||||||
private maxWrongAttemptsByIpPerDay: number
|
|
||||||
private maxConsecutiveFailsByUsernameAndIp: number
|
|
||||||
|
|
||||||
private constructor() {
|
|
||||||
const {
|
|
||||||
MAX_WRONG_ATTEMPTS_BY_IP_PER_DAY,
|
|
||||||
MAX_CONSECUTIVE_FAILS_BY_USERNAME_AND_IP
|
|
||||||
} = process.env
|
|
||||||
|
|
||||||
this.maxWrongAttemptsByIpPerDay = Number(MAX_WRONG_ATTEMPTS_BY_IP_PER_DAY)
|
|
||||||
this.maxConsecutiveFailsByUsernameAndIp = Number(
|
|
||||||
MAX_CONSECUTIVE_FAILS_BY_USERNAME_AND_IP
|
|
||||||
)
|
|
||||||
|
|
||||||
this.limiterSlowBruteByIP = new RateLimiterMemory({
|
|
||||||
keyPrefix: 'login_fail_ip_per_day',
|
|
||||||
points: this.maxWrongAttemptsByIpPerDay,
|
|
||||||
duration: 60 * 60 * 24,
|
|
||||||
blockDuration: 60 * 60 * 24 // Block for 1 day
|
|
||||||
})
|
|
||||||
|
|
||||||
this.limiterConsecutiveFailsByUsernameAndIP = new RateLimiterMemory({
|
|
||||||
keyPrefix: 'login_fail_consecutive_username_and_ip',
|
|
||||||
points: this.maxConsecutiveFailsByUsernameAndIp,
|
|
||||||
duration: 60 * 60 * 24 * 24, // Store number for 24 days since first fail
|
|
||||||
blockDuration: 60 * 60 // Block for 1 hour
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public static getInstance() {
|
|
||||||
if (!RateLimiter.instance) {
|
|
||||||
RateLimiter.instance = new RateLimiter()
|
|
||||||
}
|
|
||||||
return RateLimiter.instance
|
|
||||||
}
|
|
||||||
|
|
||||||
private getUsernameIPKey(ip: string, username: string) {
|
|
||||||
return `${username}_${ip}`
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method checks for brute force attack
|
|
||||||
* If attack is detected then returns the number of seconds after which user can make another request
|
|
||||||
* Else returns 0
|
|
||||||
*/
|
|
||||||
public async check(ip: string, username: string) {
|
|
||||||
const usernameIPkey = this.getUsernameIPKey(ip, username)
|
|
||||||
|
|
||||||
const [resSlowByIP, resUsernameAndIP] = await Promise.all([
|
|
||||||
this.limiterSlowBruteByIP.get(ip),
|
|
||||||
this.limiterConsecutiveFailsByUsernameAndIP.get(usernameIPkey)
|
|
||||||
])
|
|
||||||
|
|
||||||
// NOTE: To make use of blockDuration option, comparison in both following if statements should have greater than symbol
|
|
||||||
// otherwise, blockDuration option will not work
|
|
||||||
// For more info see: https://github.com/animir/node-rate-limiter-flexible/wiki/Options#blockduration
|
|
||||||
|
|
||||||
// Check if IP or Username + IP is already blocked
|
|
||||||
if (
|
|
||||||
resSlowByIP !== null &&
|
|
||||||
resSlowByIP.consumedPoints > this.maxWrongAttemptsByIpPerDay
|
|
||||||
) {
|
|
||||||
return Math.ceil(resSlowByIP.msBeforeNext / 1000)
|
|
||||||
} else if (
|
|
||||||
resUsernameAndIP !== null &&
|
|
||||||
resUsernameAndIP.consumedPoints > this.maxConsecutiveFailsByUsernameAndIp
|
|
||||||
) {
|
|
||||||
return Math.ceil(resUsernameAndIP.msBeforeNext / 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Consume 1 point from limiters on wrong attempt and block if limits reached
|
|
||||||
* If limit is reached, return the number of seconds after which user can make another request
|
|
||||||
* Else return 0
|
|
||||||
*/
|
|
||||||
public async consume(ip: string, username?: string) {
|
|
||||||
try {
|
|
||||||
const promises = [this.limiterSlowBruteByIP.consume(ip)]
|
|
||||||
if (username) {
|
|
||||||
const usernameIPkey = this.getUsernameIPKey(ip, username)
|
|
||||||
|
|
||||||
// Count failed attempts by Username + IP only for registered users
|
|
||||||
promises.push(
|
|
||||||
this.limiterConsecutiveFailsByUsernameAndIP.consume(usernameIPkey)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all(promises)
|
|
||||||
} catch (rlRejected: any) {
|
|
||||||
if (rlRejected instanceof Error) {
|
|
||||||
throw rlRejected
|
|
||||||
} else {
|
|
||||||
// based upon the implementation of consume method of RateLimiterMemory
|
|
||||||
// we are sure that rlRejected will contain msBeforeNext
|
|
||||||
// for further reference,
|
|
||||||
// see https://github.com/animir/node-rate-limiter-flexible/wiki/Overall-example#login-endpoint-protection
|
|
||||||
// or see https://github.com/animir/node-rate-limiter-flexible#ratelimiterres-object
|
|
||||||
return Math.ceil(rlRejected.msBeforeNext / 1000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
public async resetOnSuccess(ip: string, username: string) {
|
|
||||||
const usernameIPkey = this.getUsernameIPKey(ip, username)
|
|
||||||
const resUsernameAndIP =
|
|
||||||
await this.limiterConsecutiveFailsByUsernameAndIP.get(usernameIPkey)
|
|
||||||
|
|
||||||
if (resUsernameAndIP !== null && resUsernameAndIP.consumedPoints > 0) {
|
|
||||||
await this.limiterConsecutiveFailsByUsernameAndIP.delete(usernameIPkey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import User from '../model/User'
|
import User from '../model/User'
|
||||||
|
|
||||||
export const removeTokensInDB = async (userId: string, clientId: string) => {
|
export const removeTokensInDB = async (userId: number, clientId: string) => {
|
||||||
const user = await User.findOne({ _id: userId })
|
const user = await User.findOne({ id: userId })
|
||||||
if (!user) return
|
if (!user) return
|
||||||
|
|
||||||
const tokenObjIndex = user.tokens.findIndex(
|
const tokenObjIndex = user.tokens.findIndex(
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import User from '../model/User'
|
import User from '../model/User'
|
||||||
|
|
||||||
export const saveTokensInDB = async (
|
export const saveTokensInDB = async (
|
||||||
userId: string,
|
userId: number,
|
||||||
clientId: string,
|
clientId: string,
|
||||||
accessToken: string,
|
accessToken: string,
|
||||||
refreshToken: string
|
refreshToken: string
|
||||||
) => {
|
) => {
|
||||||
const user = await User.findOne({ _id: userId })
|
const user = await User.findOne({ id: userId })
|
||||||
if (!user) return
|
if (!user) return
|
||||||
|
|
||||||
const currentTokenObj = user.tokens.find(
|
const currentTokenObj = user.tokens.find(
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import bcrypt from 'bcryptjs'
|
|
||||||
import Client from '../model/Client'
|
import Client from '../model/Client'
|
||||||
import Group, { PUBLIC_GROUP_NAME } from '../model/Group'
|
import Group, { PUBLIC_GROUP_NAME } from '../model/Group'
|
||||||
import User, { IUser } from '../model/User'
|
import User from '../model/User'
|
||||||
import Configuration, { ConfigurationType } from '../model/Configuration'
|
import Configuration, { ConfigurationType } from '../model/Configuration'
|
||||||
import { ResetAdminPasswordType } from './verifyEnvVariables'
|
|
||||||
|
|
||||||
import { randomBytes } from 'crypto'
|
import { randomBytes } from 'crypto'
|
||||||
|
|
||||||
@@ -21,16 +19,16 @@ export const seedDB = async (): Promise<ConfigurationType> => {
|
|||||||
const client = new Client(CLIENT)
|
const client = new Client(CLIENT)
|
||||||
await client.save()
|
await client.save()
|
||||||
|
|
||||||
process.logger.success(`DB Seed - client created: ${CLIENT.clientId}`)
|
console.log(`DB Seed - client created: ${CLIENT.clientId}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checking if 'AllUsers' Group is already in the database
|
// Checking if 'AllUsers' Group is already in the database
|
||||||
let groupExist = await Group.findOne({ name: ALL_USERS_GROUP.name })
|
let groupExist = await Group.findOne({ name: GROUP.name })
|
||||||
if (!groupExist) {
|
if (!groupExist) {
|
||||||
const group = new Group(ALL_USERS_GROUP)
|
const group = new Group(GROUP)
|
||||||
groupExist = await group.save()
|
groupExist = await group.save()
|
||||||
|
|
||||||
process.logger.success(`DB Seed - Group created: ${ALL_USERS_GROUP.name}`)
|
console.log(`DB Seed - Group created: ${GROUP.name}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checking if 'Public' Group is already in the database
|
// Checking if 'Public' Group is already in the database
|
||||||
@@ -39,28 +37,22 @@ export const seedDB = async (): Promise<ConfigurationType> => {
|
|||||||
const group = new Group(PUBLIC_GROUP)
|
const group = new Group(PUBLIC_GROUP)
|
||||||
await group.save()
|
await group.save()
|
||||||
|
|
||||||
process.logger.success(`DB Seed - Group created: ${PUBLIC_GROUP.name}`)
|
console.log(`DB Seed - Group created: ${PUBLIC_GROUP.name}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const ADMIN_USER = getAdminUser()
|
|
||||||
|
|
||||||
// Checking if user is already in the database
|
// Checking if user is already in the database
|
||||||
let usernameExist = await User.findOne({ username: ADMIN_USER.username })
|
let usernameExist = await User.findOne({ username: ADMIN_USER.username })
|
||||||
if (usernameExist) {
|
if (!usernameExist) {
|
||||||
usernameExist = await resetAdminPassword(usernameExist, ADMIN_USER.password)
|
|
||||||
} else {
|
|
||||||
const user = new User(ADMIN_USER)
|
const user = new User(ADMIN_USER)
|
||||||
usernameExist = await user.save()
|
usernameExist = await user.save()
|
||||||
|
|
||||||
process.logger.success(
|
console.log(`DB Seed - admin account created: ${ADMIN_USER.username}`)
|
||||||
`DB Seed - admin account created: ${ADMIN_USER.username}`
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (usernameExist.isAdmin && !groupExist.hasUser(usernameExist)) {
|
if (!groupExist.hasUser(usernameExist)) {
|
||||||
groupExist.addUser(usernameExist)
|
groupExist.addUser(usernameExist)
|
||||||
process.logger.success(
|
console.log(
|
||||||
`DB Seed - admin account '${ADMIN_USER.username}' added to Group '${ALL_USERS_GROUP.name}'`
|
`DB Seed - admin account '${ADMIN_USER.username}' added to Group '${GROUP.name}'`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +62,7 @@ export const seedDB = async (): Promise<ConfigurationType> => {
|
|||||||
const configuration = new Configuration(SECRETS)
|
const configuration = new Configuration(SECRETS)
|
||||||
configExist = await configuration.save()
|
configExist = await configuration.save()
|
||||||
|
|
||||||
process.logger.success('DB Seed - configuration added')
|
console.log('DB Seed - configuration added')
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -81,8 +73,8 @@ export const seedDB = async (): Promise<ConfigurationType> => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ALL_USERS_GROUP = {
|
const GROUP = {
|
||||||
name: 'all-users',
|
name: 'AllUsers',
|
||||||
description: 'Group contains all users'
|
description: 'Group contains all users'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,52 +88,11 @@ const CLIENT = {
|
|||||||
clientId: 'clientID1',
|
clientId: 'clientID1',
|
||||||
clientSecret: 'clientSecret'
|
clientSecret: 'clientSecret'
|
||||||
}
|
}
|
||||||
|
const ADMIN_USER = {
|
||||||
const getAdminUser = () => {
|
id: 1,
|
||||||
const { ADMIN_USERNAME, ADMIN_PASSWORD_INITIAL } = process.env
|
displayName: 'Super Admin',
|
||||||
|
username: 'secretuser',
|
||||||
const salt = bcrypt.genSaltSync(10)
|
password: '$2a$10$hKvcVEZdhEQZCcxt6npazO6mY4jJkrzWvfQ5stdBZi8VTTwVMCVXO',
|
||||||
const hashedPassword = bcrypt.hashSync(ADMIN_PASSWORD_INITIAL as string, salt)
|
isAdmin: true,
|
||||||
|
isActive: true
|
||||||
return {
|
|
||||||
displayName: 'Super Admin',
|
|
||||||
username: ADMIN_USERNAME,
|
|
||||||
password: hashedPassword,
|
|
||||||
isAdmin: true,
|
|
||||||
isActive: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const resetAdminPassword = async (user: IUser, password: string) => {
|
|
||||||
const { ADMIN_PASSWORD_RESET } = process.env
|
|
||||||
|
|
||||||
if (ADMIN_PASSWORD_RESET === ResetAdminPasswordType.YES) {
|
|
||||||
if (!user.isAdmin) {
|
|
||||||
process.logger.error(
|
|
||||||
`Can not reset the password of non-admin user (${user.username}) on startup.`
|
|
||||||
)
|
|
||||||
|
|
||||||
return user
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user.authProvider) {
|
|
||||||
process.logger.error(
|
|
||||||
`Can not reset the password of admin (${user.username}) with ${user.authProvider} as authentication mechanism.`
|
|
||||||
)
|
|
||||||
|
|
||||||
return user
|
|
||||||
}
|
|
||||||
|
|
||||||
process.logger.info(
|
|
||||||
`DB Seed - resetting password for admin user: ${user.username}`
|
|
||||||
)
|
|
||||||
|
|
||||||
user.password = password
|
|
||||||
user.needsToUpdatePassword = true
|
|
||||||
user = await user.save()
|
|
||||||
|
|
||||||
process.logger.success(`DB Seed - successfully reset the password`)
|
|
||||||
}
|
|
||||||
|
|
||||||
return user
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ export const setProcessVariables = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'test') {
|
if (process.env.NODE_ENV === 'test') {
|
||||||
process.sasjsRoot = path.join(process.cwd(), 'sasjs_root')
|
process.driveLoc = path.join(process.cwd(), 'sasjs_root')
|
||||||
process.driveLoc = path.join(process.cwd(), 'sasjs_root', 'drive')
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,6 +32,7 @@ export const setProcessVariables = async () => {
|
|||||||
process.rLoc = process.env.R_PATH
|
process.rLoc = process.env.R_PATH
|
||||||
} else {
|
} else {
|
||||||
const { sasLoc, nodeLoc, pythonLoc, rLoc } = await getDesktopFields()
|
const { sasLoc, nodeLoc, pythonLoc, rLoc } = await getDesktopFields()
|
||||||
|
|
||||||
process.sasLoc = sasLoc
|
process.sasLoc = sasLoc
|
||||||
process.nodeLoc = nodeLoc
|
process.nodeLoc = nodeLoc
|
||||||
process.pythonLoc = pythonLoc
|
process.pythonLoc = pythonLoc
|
||||||
@@ -42,19 +42,11 @@ export const setProcessVariables = async () => {
|
|||||||
const { SASJS_ROOT } = process.env
|
const { SASJS_ROOT } = process.env
|
||||||
const absPath = getAbsolutePath(SASJS_ROOT ?? 'sasjs_root', process.cwd())
|
const absPath = getAbsolutePath(SASJS_ROOT ?? 'sasjs_root', process.cwd())
|
||||||
await createFolder(absPath)
|
await createFolder(absPath)
|
||||||
process.sasjsRoot = getRealPath(absPath)
|
process.driveLoc = getRealPath(absPath)
|
||||||
|
|
||||||
const { DRIVE_LOCATION } = process.env
|
|
||||||
const absDrivePath = getAbsolutePath(
|
|
||||||
DRIVE_LOCATION ?? path.join(process.sasjsRoot, 'drive'),
|
|
||||||
process.cwd()
|
|
||||||
)
|
|
||||||
await createFolder(absDrivePath)
|
|
||||||
process.driveLoc = getRealPath(absDrivePath)
|
|
||||||
|
|
||||||
const { LOG_LOCATION } = process.env
|
const { LOG_LOCATION } = process.env
|
||||||
const absLogsPath = getAbsolutePath(
|
const absLogsPath = getAbsolutePath(
|
||||||
LOG_LOCATION ?? path.join(process.sasjsRoot, 'logs'),
|
LOG_LOCATION ?? `sasjs_root${path.sep}logs`,
|
||||||
process.cwd()
|
process.cwd()
|
||||||
)
|
)
|
||||||
await createFolder(absLogsPath)
|
await createFolder(absLogsPath)
|
||||||
@@ -62,8 +54,8 @@ export const setProcessVariables = async () => {
|
|||||||
|
|
||||||
process.logsUUID = 'SASJS_LOGS_SEPARATOR_163ee17b6ff24f028928972d80a26784'
|
process.logsUUID = 'SASJS_LOGS_SEPARATOR_163ee17b6ff24f028928972d80a26784'
|
||||||
|
|
||||||
process.logger.info('sasLoc: ', process.sasLoc)
|
console.log('sasLoc: ', process.sasLoc)
|
||||||
process.logger.info('sasDrive: ', process.driveLoc)
|
console.log('sasDrive: ', process.driveLoc)
|
||||||
process.logger.info('sasLogs: ', process.logsLoc)
|
console.log('sasLogs: ', process.logsLoc)
|
||||||
process.logger.info('runTimes: ', process.runTimes)
|
console.log('runTimes: ', process.runTimes)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
import { createFolder } from '@sasjs/utils'
|
import { createFile, createFolder, fileExists } from '@sasjs/utils'
|
||||||
import { getFilesFolder, getPackagesFolder } from './file'
|
import { getDesktopUserAutoExecPath, getFilesFolder } from './file'
|
||||||
|
import { ModeType } from './verifyEnvVariables'
|
||||||
|
|
||||||
export const setupFilesFolder = async () => await createFolder(getFilesFolder())
|
export const setupFolders = async () => {
|
||||||
|
const drivePath = getFilesFolder()
|
||||||
|
await createFolder(drivePath)
|
||||||
|
|
||||||
export const setupPackagesFolder = async () =>
|
if (process.env.MODE === ModeType.Desktop) {
|
||||||
await createFolder(getPackagesFolder())
|
if (!(await fileExists(getDesktopUserAutoExecPath()))) {
|
||||||
|
await createFile(getDesktopUserAutoExecPath(), '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
import { createFile, fileExists } from '@sasjs/utils'
|
|
||||||
import { getDesktopUserAutoExecPath } from './file'
|
|
||||||
import { ModeType } from './verifyEnvVariables'
|
|
||||||
|
|
||||||
export const setupUserAutoExec = async () => {
|
|
||||||
if (process.env.MODE === ModeType.Desktop) {
|
|
||||||
if (!(await fileExists(getDesktopUserAutoExecPath()))) {
|
|
||||||
await createFile(getDesktopUserAutoExecPath(), '')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,11 +12,6 @@ const groupnameSchema = Joi.string().lowercase().alphanum().min(3).max(16)
|
|||||||
|
|
||||||
export const blockFileRegex = /\.(exe|sh|htaccess)$/i
|
export const blockFileRegex = /\.(exe|sh|htaccess)$/i
|
||||||
|
|
||||||
export const uidValidation = (data: any) =>
|
|
||||||
Joi.object({
|
|
||||||
uid: Joi.string().length(24).hex().required()
|
|
||||||
}).validate(data)
|
|
||||||
|
|
||||||
export const getUserValidation = (data: any): Joi.ValidationResult =>
|
export const getUserValidation = (data: any): Joi.ValidationResult =>
|
||||||
Joi.object({
|
Joi.object({
|
||||||
username: usernameSchema.required()
|
username: usernameSchema.required()
|
||||||
@@ -90,18 +85,10 @@ export const updateUserValidation = (
|
|||||||
return Joi.object(validationChecks).validate(data)
|
return Joi.object(validationChecks).validate(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updatePasswordValidation = (data: any): Joi.ValidationResult =>
|
|
||||||
Joi.object({
|
|
||||||
currentPassword: Joi.string().required(),
|
|
||||||
newPassword: passwordSchema.required()
|
|
||||||
}).validate(data)
|
|
||||||
|
|
||||||
export const registerClientValidation = (data: any): Joi.ValidationResult =>
|
export const registerClientValidation = (data: any): Joi.ValidationResult =>
|
||||||
Joi.object({
|
Joi.object({
|
||||||
clientId: Joi.string().required(),
|
clientId: Joi.string().required(),
|
||||||
clientSecret: Joi.string().required(),
|
clientSecret: Joi.string().required()
|
||||||
accessTokenExpiration: Joi.number(),
|
|
||||||
refreshTokenExpiration: Joi.number()
|
|
||||||
}).validate(data)
|
}).validate(data)
|
||||||
|
|
||||||
export const registerPermissionValidation = (data: any): Joi.ValidationResult =>
|
export const registerPermissionValidation = (data: any): Joi.ValidationResult =>
|
||||||
@@ -118,7 +105,7 @@ export const registerPermissionValidation = (data: any): Joi.ValidationResult =>
|
|||||||
principalType: Joi.string()
|
principalType: Joi.string()
|
||||||
.required()
|
.required()
|
||||||
.valid(...Object.values(PrincipalType)),
|
.valid(...Object.values(PrincipalType)),
|
||||||
principalId: Joi.string().length(24).hex().required()
|
principalId: Joi.number().required()
|
||||||
}).validate(data)
|
}).validate(data)
|
||||||
|
|
||||||
export const updatePermissionValidation = (data: any): Joi.ValidationResult =>
|
export const updatePermissionValidation = (data: any): Joi.ValidationResult =>
|
||||||
|
|||||||
@@ -8,10 +8,6 @@ export enum ModeType {
|
|||||||
Desktop = 'desktop'
|
Desktop = 'desktop'
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AuthProviderType {
|
|
||||||
LDAP = 'ldap'
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum ProtocolType {
|
export enum ProtocolType {
|
||||||
HTTP = 'http',
|
HTTP = 'http',
|
||||||
HTTPS = 'https'
|
HTTPS = 'https'
|
||||||
@@ -47,16 +43,6 @@ export enum ReturnCode {
|
|||||||
InvalidEnv
|
InvalidEnv
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum DatabaseType {
|
|
||||||
MONGO = 'mongodb',
|
|
||||||
COSMOS_MONGODB = 'cosmos_mongodb'
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum ResetAdminPasswordType {
|
|
||||||
YES = 'YES',
|
|
||||||
NO = 'NO'
|
|
||||||
}
|
|
||||||
|
|
||||||
export const verifyEnvVariables = (): ReturnCode => {
|
export const verifyEnvVariables = (): ReturnCode => {
|
||||||
const errors: string[] = []
|
const errors: string[] = []
|
||||||
|
|
||||||
@@ -78,14 +64,6 @@ export const verifyEnvVariables = (): ReturnCode => {
|
|||||||
|
|
||||||
errors.push(...verifyExecutablePaths())
|
errors.push(...verifyExecutablePaths())
|
||||||
|
|
||||||
errors.push(...verifyLDAPVariables())
|
|
||||||
|
|
||||||
errors.push(...verifyDbType())
|
|
||||||
|
|
||||||
errors.push(...verifyRateLimiter())
|
|
||||||
|
|
||||||
errors.push(...verifyAdminUserConfig())
|
|
||||||
|
|
||||||
if (errors.length) {
|
if (errors.length) {
|
||||||
process.logger?.error(
|
process.logger?.error(
|
||||||
`Invalid environment variable(s) provided: \n${errors.join('\n')}`
|
`Invalid environment variable(s) provided: \n${errors.join('\n')}`
|
||||||
@@ -126,22 +104,13 @@ const verifyMODE = (): string[] => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.MODE === ModeType.Server) {
|
if (process.env.MODE === ModeType.Server) {
|
||||||
const { DB_CONNECT, AUTH_PROVIDERS } = process.env
|
const { DB_CONNECT } = process.env
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'test') {
|
if (process.env.NODE_ENV !== 'test')
|
||||||
if (!DB_CONNECT)
|
if (!DB_CONNECT)
|
||||||
errors.push(
|
errors.push(
|
||||||
`- DB_CONNECT is required for PROTOCOL '${ModeType.Server}'`
|
`- DB_CONNECT is required for PROTOCOL '${ModeType.Server}'`
|
||||||
)
|
)
|
||||||
|
|
||||||
if (AUTH_PROVIDERS) {
|
|
||||||
const authProvidersType = Object.values(AuthProviderType)
|
|
||||||
if (!authProvidersType.includes(AUTH_PROVIDERS as AuthProviderType))
|
|
||||||
errors.push(
|
|
||||||
`- AUTH_PROVIDERS '${AUTH_PROVIDERS}'\n - valid options ${authProvidersType}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors
|
return errors
|
||||||
@@ -311,158 +280,11 @@ const verifyExecutablePaths = (): string[] => {
|
|||||||
return errors
|
return errors
|
||||||
}
|
}
|
||||||
|
|
||||||
const verifyLDAPVariables = () => {
|
|
||||||
const errors: string[] = []
|
|
||||||
const {
|
|
||||||
LDAP_URL,
|
|
||||||
LDAP_BIND_DN,
|
|
||||||
LDAP_BIND_PASSWORD,
|
|
||||||
LDAP_USERS_BASE_DN,
|
|
||||||
LDAP_GROUPS_BASE_DN,
|
|
||||||
MODE,
|
|
||||||
AUTH_PROVIDERS
|
|
||||||
} = process.env
|
|
||||||
|
|
||||||
if (MODE === ModeType.Server && AUTH_PROVIDERS === AuthProviderType.LDAP) {
|
|
||||||
if (!LDAP_URL) {
|
|
||||||
errors.push(
|
|
||||||
`- LDAP_URL is required for AUTH_PROVIDER '${AuthProviderType.LDAP}'`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!LDAP_BIND_DN) {
|
|
||||||
errors.push(
|
|
||||||
`- LDAP_BIND_DN is required for AUTH_PROVIDER '${AuthProviderType.LDAP}'`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!LDAP_BIND_PASSWORD) {
|
|
||||||
errors.push(
|
|
||||||
`- LDAP_BIND_PASSWORD is required for AUTH_PROVIDER '${AuthProviderType.LDAP}'`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!LDAP_USERS_BASE_DN) {
|
|
||||||
errors.push(
|
|
||||||
`- LDAP_USERS_BASE_DN is required for AUTH_PROVIDER '${AuthProviderType.LDAP}'`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!LDAP_GROUPS_BASE_DN) {
|
|
||||||
errors.push(
|
|
||||||
`- LDAP_GROUPS_BASE_DN is required for AUTH_PROVIDER '${AuthProviderType.LDAP}'`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors
|
|
||||||
}
|
|
||||||
|
|
||||||
const verifyDbType = () => {
|
|
||||||
const errors: string[] = []
|
|
||||||
|
|
||||||
const { MODE, DB_TYPE } = process.env
|
|
||||||
|
|
||||||
if (MODE === ModeType.Server) {
|
|
||||||
if (DB_TYPE) {
|
|
||||||
const dbTypes = Object.values(DatabaseType)
|
|
||||||
if (!dbTypes.includes(DB_TYPE as DatabaseType))
|
|
||||||
errors.push(`- DB_TYPE '${DB_TYPE}'\n - valid options ${dbTypes}`)
|
|
||||||
} else {
|
|
||||||
process.env.DB_TYPE = DEFAULTS.DB_TYPE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors
|
|
||||||
}
|
|
||||||
|
|
||||||
const verifyRateLimiter = () => {
|
|
||||||
const errors: string[] = []
|
|
||||||
const {
|
|
||||||
MODE,
|
|
||||||
MAX_WRONG_ATTEMPTS_BY_IP_PER_DAY,
|
|
||||||
MAX_CONSECUTIVE_FAILS_BY_USERNAME_AND_IP
|
|
||||||
} = process.env
|
|
||||||
if (MODE === ModeType.Server) {
|
|
||||||
if (MAX_WRONG_ATTEMPTS_BY_IP_PER_DAY) {
|
|
||||||
if (
|
|
||||||
!isNumeric(MAX_WRONG_ATTEMPTS_BY_IP_PER_DAY) ||
|
|
||||||
Number(MAX_WRONG_ATTEMPTS_BY_IP_PER_DAY) < 1
|
|
||||||
) {
|
|
||||||
errors.push(
|
|
||||||
`- Invalid value for 'MAX_WRONG_ATTEMPTS_BY_IP_PER_DAY' - Only positive number is acceptable`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
process.env.MAX_WRONG_ATTEMPTS_BY_IP_PER_DAY =
|
|
||||||
DEFAULTS.MAX_WRONG_ATTEMPTS_BY_IP_PER_DAY
|
|
||||||
}
|
|
||||||
|
|
||||||
if (MAX_CONSECUTIVE_FAILS_BY_USERNAME_AND_IP) {
|
|
||||||
if (
|
|
||||||
!isNumeric(MAX_CONSECUTIVE_FAILS_BY_USERNAME_AND_IP) ||
|
|
||||||
Number(MAX_CONSECUTIVE_FAILS_BY_USERNAME_AND_IP) < 1
|
|
||||||
) {
|
|
||||||
errors.push(
|
|
||||||
`- Invalid value for 'MAX_CONSECUTIVE_FAILS_BY_USERNAME_AND_IP' - Only positive number is acceptable`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
process.env.MAX_CONSECUTIVE_FAILS_BY_USERNAME_AND_IP =
|
|
||||||
DEFAULTS.MAX_CONSECUTIVE_FAILS_BY_USERNAME_AND_IP
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors
|
|
||||||
}
|
|
||||||
|
|
||||||
const verifyAdminUserConfig = () => {
|
|
||||||
const errors: string[] = []
|
|
||||||
const { MODE, ADMIN_USERNAME, ADMIN_PASSWORD_INITIAL, ADMIN_PASSWORD_RESET } =
|
|
||||||
process.env
|
|
||||||
if (MODE === ModeType.Server) {
|
|
||||||
if (ADMIN_USERNAME) {
|
|
||||||
process.env.ADMIN_USERNAME = ADMIN_USERNAME.toLowerCase()
|
|
||||||
} else {
|
|
||||||
process.env.ADMIN_USERNAME = DEFAULTS.ADMIN_USERNAME
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ADMIN_PASSWORD_INITIAL)
|
|
||||||
process.env.ADMIN_PASSWORD_INITIAL = DEFAULTS.ADMIN_PASSWORD_INITIAL
|
|
||||||
|
|
||||||
if (ADMIN_PASSWORD_RESET) {
|
|
||||||
const resetPasswordTypes = Object.values(ResetAdminPasswordType)
|
|
||||||
if (
|
|
||||||
!resetPasswordTypes.includes(
|
|
||||||
ADMIN_PASSWORD_RESET as ResetAdminPasswordType
|
|
||||||
)
|
|
||||||
)
|
|
||||||
errors.push(
|
|
||||||
`- ADMIN_PASSWORD_RESET '${ADMIN_PASSWORD_RESET}'\n - valid options ${resetPasswordTypes}`
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
process.env.ADMIN_PASSWORD_RESET = DEFAULTS.ADMIN_PASSWORD_RESET
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors
|
|
||||||
}
|
|
||||||
|
|
||||||
const isNumeric = (val: string): boolean => {
|
|
||||||
return !isNaN(Number(val))
|
|
||||||
}
|
|
||||||
|
|
||||||
const DEFAULTS = {
|
const DEFAULTS = {
|
||||||
MODE: ModeType.Desktop,
|
MODE: ModeType.Desktop,
|
||||||
PROTOCOL: ProtocolType.HTTP,
|
PROTOCOL: ProtocolType.HTTP,
|
||||||
PORT: '5000',
|
PORT: '5000',
|
||||||
HELMET_COEP: HelmetCoepType.TRUE,
|
HELMET_COEP: HelmetCoepType.TRUE,
|
||||||
LOG_FORMAT_MORGAN: LOG_FORMAT_MORGANType.Common,
|
LOG_FORMAT_MORGAN: LOG_FORMAT_MORGANType.Common,
|
||||||
RUN_TIMES: RunTimeType.SAS,
|
RUN_TIMES: RunTimeType.SAS
|
||||||
DB_TYPE: DatabaseType.MONGO,
|
|
||||||
MAX_WRONG_ATTEMPTS_BY_IP_PER_DAY: '100',
|
|
||||||
MAX_CONSECUTIVE_FAILS_BY_USERNAME_AND_IP: '10',
|
|
||||||
ADMIN_USERNAME: 'secretuser',
|
|
||||||
ADMIN_PASSWORD_INITIAL: 'secretpassword',
|
|
||||||
ADMIN_PASSWORD_RESET: ResetAdminPasswordType.NO
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { RequestUser } from '../types'
|
|||||||
export const fetchLatestAutoExec = async (
|
export const fetchLatestAutoExec = async (
|
||||||
reqUser: RequestUser
|
reqUser: RequestUser
|
||||||
): Promise<RequestUser | undefined> => {
|
): Promise<RequestUser | undefined> => {
|
||||||
const dbUser = await User.findOne({ _id: reqUser.userId })
|
const dbUser = await User.findOne({ id: reqUser.userId })
|
||||||
|
|
||||||
if (!dbUser) return undefined
|
if (!dbUser) return undefined
|
||||||
|
|
||||||
@@ -15,18 +15,17 @@ export const fetchLatestAutoExec = async (
|
|||||||
displayName: dbUser.displayName,
|
displayName: dbUser.displayName,
|
||||||
isAdmin: dbUser.isAdmin,
|
isAdmin: dbUser.isAdmin,
|
||||||
isActive: dbUser.isActive,
|
isActive: dbUser.isActive,
|
||||||
needsToUpdatePassword: dbUser.needsToUpdatePassword,
|
|
||||||
autoExec: dbUser.autoExec
|
autoExec: dbUser.autoExec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const verifyTokenInDB = async (
|
export const verifyTokenInDB = async (
|
||||||
userId: string,
|
userId: number,
|
||||||
clientId: string,
|
clientId: string,
|
||||||
token: string,
|
token: string,
|
||||||
tokenType: 'accessToken' | 'refreshToken'
|
tokenType: 'accessToken' | 'refreshToken'
|
||||||
): Promise<RequestUser | undefined> => {
|
): Promise<RequestUser | undefined> => {
|
||||||
const dbUser = await User.findOne({ _id: userId })
|
const dbUser = await User.findOne({ id: userId })
|
||||||
|
|
||||||
if (!dbUser) return undefined
|
if (!dbUser) return undefined
|
||||||
|
|
||||||
@@ -42,7 +41,6 @@ export const verifyTokenInDB = async (
|
|||||||
displayName: dbUser.displayName,
|
displayName: dbUser.displayName,
|
||||||
isAdmin: dbUser.isAdmin,
|
isAdmin: dbUser.isAdmin,
|
||||||
isActive: dbUser.isActive,
|
isActive: dbUser.isActive,
|
||||||
needsToUpdatePassword: dbUser.needsToUpdatePassword,
|
|
||||||
autoExec: dbUser.autoExec
|
autoExec: dbUser.autoExec
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es5",
|
"target": "ES6",
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"rootDir": "./",
|
"rootDir": "./",
|
||||||
"outDir": "./build",
|
"outDir": "./build",
|
||||||
|
|||||||
@@ -15,10 +15,6 @@
|
|||||||
"name": "Auth",
|
"name": "Auth",
|
||||||
"description": "Operations about auth"
|
"description": "Operations about auth"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "Auth_Config",
|
|
||||||
"description": "Operations about external auth providers"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "Client",
|
"name": "Client",
|
||||||
"description": "Operations about clients"
|
"description": "Operations about clients"
|
||||||
|
|||||||
8854
web/package-lock.json
generated
8854
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user