mirror of
https://github.com/sasjs/server.git
synced 2025-12-10 19:34:34 +00:00
Compare commits
16 Commits
vite-built
...
v0.23.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2295a518f0 | ||
|
|
1e5d621817 | ||
| 4d64420c45 | |||
|
|
799339de30 | ||
|
|
042ed41189 | ||
| 424f0fc1fa | |||
|
|
deafebde05 | ||
|
|
b66dc86b01 | ||
|
|
3bb05974d2 | ||
|
|
d1c1a59e91 | ||
|
|
668aff83fd | ||
| 3fc06b80fc | |||
| bbd7786c6c | |||
| 68f0c5c588 | |||
| a14266077d | |||
| f915c51b07 |
35
CHANGELOG.md
35
CHANGELOG.md
@@ -1,3 +1,38 @@
|
||||
## [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)
|
||||
|
||||
|
||||
|
||||
12
README.md
12
README.md
@@ -125,9 +125,19 @@ PRIVATE_KEY=privkey.pem (required)
|
||||
CERT_CHAIN=certificate.pem (required)
|
||||
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
|
||||
|
||||
# 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`
|
||||
# If enabled, be sure to also configure the WHITELIST of third party servers.
|
||||
CORS=
|
||||
|
||||
@@ -14,6 +14,14 @@ 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
|
||||
|
||||
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>
|
||||
|
||||
RUN_TIMES=[sas,js,py | js,py | sas | sas,js] default considered as sas
|
||||
SAS_PATH=/opt/sas/sas9/SASHome/SASFoundation/9.4/sas
|
||||
NODE_PATH=~/.nvm/versions/node/v16.14.0/bin/node
|
||||
|
||||
370
api/package-lock.json
generated
370
api/package-lock.json
generated
@@ -19,6 +19,7 @@
|
||||
"helmet": "^5.0.2",
|
||||
"joi": "^17.4.2",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"ldapjs": "2.3.3",
|
||||
"mongoose": "^6.0.12",
|
||||
"mongoose-sequence": "^5.3.1",
|
||||
"morgan": "^1.10.0",
|
||||
@@ -40,6 +41,7 @@
|
||||
"@types/express-session": "^1.17.4",
|
||||
"@types/jest": "^26.0.24",
|
||||
"@types/jsonwebtoken": "^8.5.5",
|
||||
"@types/ldapjs": "^2.2.4",
|
||||
"@types/mongoose-sequence": "^3.0.6",
|
||||
"@types/morgan": "^1.9.3",
|
||||
"@types/multer": "^1.4.7",
|
||||
@@ -48,11 +50,13 @@
|
||||
"@types/swagger-ui-express": "^4.1.3",
|
||||
"@types/unzipper": "^0.10.5",
|
||||
"adm-zip": "^0.5.9",
|
||||
"axios": "0.27.2",
|
||||
"csrf": "^3.1.0",
|
||||
"dotenv": "^10.0.0",
|
||||
"http-headers-validation": "^0.0.1",
|
||||
"jest": "^27.0.6",
|
||||
"mongodb-memory-server": "^8.0.0",
|
||||
"nodejs-file-downloader": "4.10.2",
|
||||
"nodemon": "^2.0.7",
|
||||
"pkg": "5.6.0",
|
||||
"prettier": "^2.3.1",
|
||||
@@ -2034,6 +2038,15 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/ldapjs": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/ldapjs/-/ldapjs-2.2.4.tgz",
|
||||
"integrity": "sha512-+ZMVolW4N1zpnQ4SgH8nfpIFuiDOfbnXSbwQoBiLaq8mF0vo8FOKotQzKkfoWxbV0lWU1d4V+keZZ07klyOSng==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/mime": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
|
||||
@@ -2209,6 +2222,11 @@
|
||||
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/abstract-logging": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz",
|
||||
"integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA=="
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
"version": "1.3.7",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
|
||||
@@ -2464,6 +2482,14 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/asn1": {
|
||||
"version": "0.2.6",
|
||||
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
|
||||
"integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
|
||||
"dependencies": {
|
||||
"safer-buffer": "~2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/asn1.js": {
|
||||
"version": "5.4.1",
|
||||
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz",
|
||||
@@ -2475,6 +2501,14 @@
|
||||
"safer-buffer": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/assert-plus": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
|
||||
"integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/async": {
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
|
||||
@@ -2507,6 +2541,30 @@
|
||||
"node": ">= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
|
||||
"integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.14.9",
|
||||
"form-data": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axios/node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-jest": {
|
||||
"version": "27.0.6",
|
||||
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.0.6.tgz",
|
||||
@@ -2636,6 +2694,17 @@
|
||||
"@babel/core": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/backoff": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/backoff/-/backoff-2.5.0.tgz",
|
||||
"integrity": "sha512-wC5ihrnUXmR2douXmXLCe5O3zg3GKIyvRi/hi58a/XyRxVI+3/yM0PYueQOZXPXQ9pxBislYkw+sF9b7C/RuMA==",
|
||||
"dependencies": {
|
||||
"precond": "0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
@@ -4006,6 +4075,14 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/extsprintf": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz",
|
||||
"integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==",
|
||||
"engines": [
|
||||
"node >=0.6.0"
|
||||
]
|
||||
},
|
||||
"node_modules/fast-glob": {
|
||||
"version": "3.2.11",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
|
||||
@@ -4134,6 +4211,26 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz",
|
||||
@@ -6718,6 +6815,35 @@
|
||||
"node": ">8"
|
||||
}
|
||||
},
|
||||
"node_modules/ldap-filter": {
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/ldap-filter/-/ldap-filter-0.3.3.tgz",
|
||||
"integrity": "sha512-/tFkx5WIn4HuO+6w9lsfxq4FN3O+fDZeO9Mek8dCD8rTUpqzRa766BOBO7BcGkn3X86m5+cBm1/2S/Shzz7gMg==",
|
||||
"dependencies": {
|
||||
"assert-plus": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/ldapjs": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/ldapjs/-/ldapjs-2.3.3.tgz",
|
||||
"integrity": "sha512-75QiiLJV/PQqtpH+HGls44dXweviFwQ6SiIK27EqzKQ5jU/7UFrl2E5nLdQ3IYRBzJ/AVFJI66u0MZ0uofKYwg==",
|
||||
"dependencies": {
|
||||
"abstract-logging": "^2.0.0",
|
||||
"asn1": "^0.2.4",
|
||||
"assert-plus": "^1.0.0",
|
||||
"backoff": "^2.5.0",
|
||||
"ldap-filter": "^0.3.3",
|
||||
"once": "^1.4.0",
|
||||
"vasync": "^2.2.0",
|
||||
"verror": "^1.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/leven": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
|
||||
@@ -7439,6 +7565,18 @@
|
||||
"integrity": "sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/nodejs-file-downloader": {
|
||||
"version": "4.10.2",
|
||||
"resolved": "https://registry.npmjs.org/nodejs-file-downloader/-/nodejs-file-downloader-4.10.2.tgz",
|
||||
"integrity": "sha512-pTVlytER/4wxcIpEhLXoqhuJ7WH1+xSFNLbI0wPmbwH3pWlJRRebb1Kbu91mz1CyOJmO4sj6YLH1wkF1B6efrQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.1",
|
||||
"https-proxy-agent": "^5.0.0",
|
||||
"mime-types": "^2.1.27",
|
||||
"sanitize-filename": "^1.6.3"
|
||||
}
|
||||
},
|
||||
"node_modules/nodemon": {
|
||||
"version": "2.0.19",
|
||||
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.19.tgz",
|
||||
@@ -8007,6 +8145,14 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/precond": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/precond/-/precond-0.2.3.tgz",
|
||||
"integrity": "sha512-QCYG84SgGyGzqJ/vlMsxeXd/pgL/I94ixdNFyh1PusWmTCyVfPJjZ1K1jvHtsbfnXQs2TSkEP2fR7QiMZAnKFQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/prelude-ls": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
|
||||
@@ -8381,6 +8527,15 @@
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"node_modules/sanitize-filename": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz",
|
||||
"integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"truncate-utf8-bytes": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/saslprep": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz",
|
||||
@@ -9235,6 +9390,15 @@
|
||||
"resolved": "https://registry.npmjs.org/traverse-chain/-/traverse-chain-0.1.0.tgz",
|
||||
"integrity": "sha1-YdvC1Ttp/2CRoSoWj9fUMxB+QPE="
|
||||
},
|
||||
"node_modules/truncate-utf8-bytes": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
|
||||
"integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"utf8-byte-length": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-jest": {
|
||||
"version": "27.0.3",
|
||||
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-27.0.3.tgz",
|
||||
@@ -9538,6 +9702,12 @@
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
|
||||
"integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0="
|
||||
},
|
||||
"node_modules/utf8-byte-length": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz",
|
||||
"integrity": "sha512-4+wkEYLBbWxqTahEsWrhxepcoVOJ+1z5PGIjPZxRkytcdSUaNjIjBM7Xn8E+pdSuV7SzvWovBFA54FO0JSoqhA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
@@ -9605,6 +9775,43 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/vasync": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/vasync/-/vasync-2.2.1.tgz",
|
||||
"integrity": "sha512-Hq72JaTpcTFdWiNA4Y22Amej2GH3BFmBaKPPlDZ4/oC8HNn2ISHLkFrJU4Ds8R3jcUi7oo5Y9jcMHKjES+N9wQ==",
|
||||
"engines": [
|
||||
"node >=0.6.0"
|
||||
],
|
||||
"dependencies": {
|
||||
"verror": "1.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vasync/node_modules/verror": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
|
||||
"integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==",
|
||||
"engines": [
|
||||
"node >=0.6.0"
|
||||
],
|
||||
"dependencies": {
|
||||
"assert-plus": "^1.0.0",
|
||||
"core-util-is": "1.0.2",
|
||||
"extsprintf": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/verror": {
|
||||
"version": "1.10.1",
|
||||
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz",
|
||||
"integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==",
|
||||
"dependencies": {
|
||||
"assert-plus": "^1.0.0",
|
||||
"core-util-is": "1.0.2",
|
||||
"extsprintf": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/w3c-hr-time": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz",
|
||||
@@ -11497,6 +11704,15 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/ldapjs": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/ldapjs/-/ldapjs-2.2.4.tgz",
|
||||
"integrity": "sha512-+ZMVolW4N1zpnQ4SgH8nfpIFuiDOfbnXSbwQoBiLaq8mF0vo8FOKotQzKkfoWxbV0lWU1d4V+keZZ07klyOSng==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/mime": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
|
||||
@@ -11671,6 +11887,11 @@
|
||||
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
|
||||
"dev": true
|
||||
},
|
||||
"abstract-logging": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz",
|
||||
"integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA=="
|
||||
},
|
||||
"accepts": {
|
||||
"version": "1.3.7",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
|
||||
@@ -11869,6 +12090,14 @@
|
||||
"is-string": "^1.0.7"
|
||||
}
|
||||
},
|
||||
"asn1": {
|
||||
"version": "0.2.6",
|
||||
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
|
||||
"integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
|
||||
"requires": {
|
||||
"safer-buffer": "~2.1.0"
|
||||
}
|
||||
},
|
||||
"asn1.js": {
|
||||
"version": "5.4.1",
|
||||
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz",
|
||||
@@ -11880,6 +12109,11 @@
|
||||
"safer-buffer": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"assert-plus": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
|
||||
"integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw=="
|
||||
},
|
||||
"async": {
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
|
||||
@@ -11909,6 +12143,29 @@
|
||||
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
|
||||
"dev": true
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
|
||||
"integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"follow-redirects": "^1.14.9",
|
||||
"form-data": "^4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"babel-jest": {
|
||||
"version": "27.0.6",
|
||||
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.0.6.tgz",
|
||||
@@ -12007,6 +12264,14 @@
|
||||
"babel-preset-current-node-syntax": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"backoff": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/backoff/-/backoff-2.5.0.tgz",
|
||||
"integrity": "sha512-wC5ihrnUXmR2douXmXLCe5O3zg3GKIyvRi/hi58a/XyRxVI+3/yM0PYueQOZXPXQ9pxBislYkw+sF9b7C/RuMA==",
|
||||
"requires": {
|
||||
"precond": "0.2"
|
||||
}
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
@@ -13057,6 +13322,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"extsprintf": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz",
|
||||
"integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA=="
|
||||
},
|
||||
"fast-glob": {
|
||||
"version": "3.2.11",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
|
||||
@@ -13167,6 +13437,12 @@
|
||||
"path-exists": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
|
||||
"dev": true
|
||||
},
|
||||
"form-data": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz",
|
||||
@@ -15097,6 +15373,29 @@
|
||||
"asn1.js": "^5.4.1"
|
||||
}
|
||||
},
|
||||
"ldap-filter": {
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/ldap-filter/-/ldap-filter-0.3.3.tgz",
|
||||
"integrity": "sha512-/tFkx5WIn4HuO+6w9lsfxq4FN3O+fDZeO9Mek8dCD8rTUpqzRa766BOBO7BcGkn3X86m5+cBm1/2S/Shzz7gMg==",
|
||||
"requires": {
|
||||
"assert-plus": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"ldapjs": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/ldapjs/-/ldapjs-2.3.3.tgz",
|
||||
"integrity": "sha512-75QiiLJV/PQqtpH+HGls44dXweviFwQ6SiIK27EqzKQ5jU/7UFrl2E5nLdQ3IYRBzJ/AVFJI66u0MZ0uofKYwg==",
|
||||
"requires": {
|
||||
"abstract-logging": "^2.0.0",
|
||||
"asn1": "^0.2.4",
|
||||
"assert-plus": "^1.0.0",
|
||||
"backoff": "^2.5.0",
|
||||
"ldap-filter": "^0.3.3",
|
||||
"once": "^1.4.0",
|
||||
"vasync": "^2.2.0",
|
||||
"verror": "^1.8.1"
|
||||
}
|
||||
},
|
||||
"leven": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
|
||||
@@ -15654,6 +15953,18 @@
|
||||
"integrity": "sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==",
|
||||
"dev": true
|
||||
},
|
||||
"nodejs-file-downloader": {
|
||||
"version": "4.10.2",
|
||||
"resolved": "https://registry.npmjs.org/nodejs-file-downloader/-/nodejs-file-downloader-4.10.2.tgz",
|
||||
"integrity": "sha512-pTVlytER/4wxcIpEhLXoqhuJ7WH1+xSFNLbI0wPmbwH3pWlJRRebb1Kbu91mz1CyOJmO4sj6YLH1wkF1B6efrQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"follow-redirects": "^1.15.1",
|
||||
"https-proxy-agent": "^5.0.0",
|
||||
"mime-types": "^2.1.27",
|
||||
"sanitize-filename": "^1.6.3"
|
||||
}
|
||||
},
|
||||
"nodemon": {
|
||||
"version": "2.0.19",
|
||||
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.19.tgz",
|
||||
@@ -16068,6 +16379,11 @@
|
||||
"tunnel-agent": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"precond": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/precond/-/precond-0.2.3.tgz",
|
||||
"integrity": "sha512-QCYG84SgGyGzqJ/vlMsxeXd/pgL/I94ixdNFyh1PusWmTCyVfPJjZ1K1jvHtsbfnXQs2TSkEP2fR7QiMZAnKFQ=="
|
||||
},
|
||||
"prelude-ls": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
|
||||
@@ -16327,6 +16643,15 @@
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"sanitize-filename": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz",
|
||||
"integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"truncate-utf8-bytes": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"saslprep": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz",
|
||||
@@ -16990,6 +17315,15 @@
|
||||
"resolved": "https://registry.npmjs.org/traverse-chain/-/traverse-chain-0.1.0.tgz",
|
||||
"integrity": "sha1-YdvC1Ttp/2CRoSoWj9fUMxB+QPE="
|
||||
},
|
||||
"truncate-utf8-bytes": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
|
||||
"integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"utf8-byte-length": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"ts-jest": {
|
||||
"version": "27.0.3",
|
||||
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-27.0.3.tgz",
|
||||
@@ -17212,6 +17546,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"utf8-byte-length": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz",
|
||||
"integrity": "sha512-4+wkEYLBbWxqTahEsWrhxepcoVOJ+1z5PGIjPZxRkytcdSUaNjIjBM7Xn8E+pdSuV7SzvWovBFA54FO0JSoqhA==",
|
||||
"dev": true
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
@@ -17263,6 +17603,36 @@
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
|
||||
},
|
||||
"vasync": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/vasync/-/vasync-2.2.1.tgz",
|
||||
"integrity": "sha512-Hq72JaTpcTFdWiNA4Y22Amej2GH3BFmBaKPPlDZ4/oC8HNn2ISHLkFrJU4Ds8R3jcUi7oo5Y9jcMHKjES+N9wQ==",
|
||||
"requires": {
|
||||
"verror": "1.10.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"verror": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
|
||||
"integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==",
|
||||
"requires": {
|
||||
"assert-plus": "^1.0.0",
|
||||
"core-util-is": "1.0.2",
|
||||
"extsprintf": "^1.2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"verror": {
|
||||
"version": "1.10.1",
|
||||
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz",
|
||||
"integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==",
|
||||
"requires": {
|
||||
"assert-plus": "^1.0.0",
|
||||
"core-util-is": "1.0.2",
|
||||
"extsprintf": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"w3c-hr-time": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"description": "Api of SASjs server",
|
||||
"main": "./src/server.ts",
|
||||
"scripts": {
|
||||
"initial": "npm run swagger && npm run compileSysInit && npm run copySASjsCore",
|
||||
"initial": "npm run swagger && npm run compileSysInit && npm run copySASjsCore && npm run downloadMacros",
|
||||
"prestart": "npm run initial",
|
||||
"prebuild": "npm run initial",
|
||||
"start": "NODE_ENV=development nodemon ./src/server.ts",
|
||||
@@ -17,20 +17,21 @@
|
||||
"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}\"",
|
||||
"exe": "npm run build && pkg .",
|
||||
"copy:files": "npm run public:copy && npm run sasjsbuild:copy && npm run sasjscore:copy && npm run web:copy",
|
||||
"copy:files": "npm run public:copy && npm run sasjsbuild:copy && npm run sas:copy && npm run web:copy",
|
||||
"public:copy": "cp -r ./public/ ./build/public/",
|
||||
"sasjsbuild:copy": "cp -r ./sasjsbuild/ ./build/sasjsbuild/",
|
||||
"sasjscore:copy": "cp -r ./sasjscore/ ./build/sasjscore/",
|
||||
"sas:copy": "cp -r ./sas/ ./build/sas/",
|
||||
"web:copy": "rimraf web && mkdir web && cp -r ../web/build/ ./web/build/",
|
||||
"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",
|
||||
"pkg": {
|
||||
"assets": [
|
||||
"./build/public/**/*",
|
||||
"./build/sasjsbuild/**/*",
|
||||
"./build/sasjscore/**/*",
|
||||
"./build/sas/**/*",
|
||||
"./web/build/**/*"
|
||||
],
|
||||
"targets": [
|
||||
@@ -58,6 +59,7 @@
|
||||
"helmet": "^5.0.2",
|
||||
"joi": "^17.4.2",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"ldapjs": "2.3.3",
|
||||
"mongoose": "^6.0.12",
|
||||
"mongoose-sequence": "^5.3.1",
|
||||
"morgan": "^1.10.0",
|
||||
@@ -76,6 +78,7 @@
|
||||
"@types/express-session": "^1.17.4",
|
||||
"@types/jest": "^26.0.24",
|
||||
"@types/jsonwebtoken": "^8.5.5",
|
||||
"@types/ldapjs": "^2.2.4",
|
||||
"@types/mongoose-sequence": "^3.0.6",
|
||||
"@types/morgan": "^1.9.3",
|
||||
"@types/multer": "^1.4.7",
|
||||
@@ -84,11 +87,13 @@
|
||||
"@types/swagger-ui-express": "^4.1.3",
|
||||
"@types/unzipper": "^0.10.5",
|
||||
"adm-zip": "^0.5.9",
|
||||
"axios": "0.27.2",
|
||||
"csrf": "^3.1.0",
|
||||
"dotenv": "^10.0.0",
|
||||
"http-headers-validation": "^0.0.1",
|
||||
"jest": "^27.0.6",
|
||||
"mongodb-memory-server": "^8.0.0",
|
||||
"nodejs-file-downloader": "4.10.2",
|
||||
"nodemon": "^2.0.7",
|
||||
"pkg": "5.6.0",
|
||||
"prettier": "^2.3.1",
|
||||
|
||||
@@ -622,6 +622,51 @@ paths:
|
||||
-
|
||||
bearerAuth: []
|
||||
parameters: []
|
||||
/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:
|
||||
post:
|
||||
operationId: CreateClient
|
||||
@@ -1794,6 +1839,9 @@ tags:
|
||||
-
|
||||
name: Auth
|
||||
description: 'Operations about auth'
|
||||
-
|
||||
name: Auth_Config
|
||||
description: 'Operations about external auth providers'
|
||||
-
|
||||
name: Client
|
||||
description: 'Operations about clients'
|
||||
|
||||
39
api/scripts/downloadMacros.ts
Normal file
39
api/scripts/downloadMacros.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
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()
|
||||
185
api/src/controllers/authConfig.ts
Normal file
185
api/src/controllers/authConfig.ts
Normal file
@@ -0,0 +1,185 @@
|
||||
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
|
||||
})
|
||||
|
||||
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
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
|
||||
import Group, { GroupPayload, PUBLIC_GROUP_NAME } from '../model/Group'
|
||||
import User from '../model/User'
|
||||
import { AuthProviderType } from '../utils'
|
||||
import { UserResponse } from './user'
|
||||
|
||||
export interface GroupResponse {
|
||||
@@ -147,12 +148,14 @@ export class GroupController {
|
||||
@Delete('{groupId}')
|
||||
public async deleteGroup(@Path() groupId: number) {
|
||||
const group = await Group.findOne({ groupId })
|
||||
if (group) return await group.remove()
|
||||
throw {
|
||||
code: 404,
|
||||
status: 'Not Found',
|
||||
message: 'Group not found.'
|
||||
}
|
||||
if (!group)
|
||||
throw {
|
||||
code: 404,
|
||||
status: 'Not Found',
|
||||
message: 'Group not found.'
|
||||
}
|
||||
|
||||
return await group.remove()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,6 +251,13 @@ const updateUsersListInGroup = async (
|
||||
message: `Can't add/remove user to '${PUBLIC_GROUP_NAME}' group.`
|
||||
}
|
||||
|
||||
if (group.authProvider)
|
||||
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: userId })
|
||||
if (!user)
|
||||
throw {
|
||||
@@ -256,6 +266,13 @@ const updateUsersListInGroup = async (
|
||||
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 =
|
||||
action === 'addUser'
|
||||
? await group.addUser(user)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './auth'
|
||||
export * from './authConfig'
|
||||
export * from './client'
|
||||
export * from './code'
|
||||
export * from './drive'
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Session } from '../../types'
|
||||
import { promisify } from 'util'
|
||||
import { execFile } from 'child_process'
|
||||
import {
|
||||
getPackagesFolder,
|
||||
getSessionsFolder,
|
||||
generateUniqueFileName,
|
||||
sysInitCompiledPath,
|
||||
@@ -104,7 +105,8 @@ export class SASSessionController extends SessionController {
|
||||
|
||||
// the autoexec file is executed on SAS startup
|
||||
const autoExecPath = path.join(sessionFolder, 'autoexec.sas')
|
||||
const contentForAutoExec = `/* compiled systemInit */
|
||||
const contentForAutoExec = `filename packages "${getPackagesFolder()}";
|
||||
/* compiled systemInit */
|
||||
${compiledSystemInitContent}
|
||||
/* autoexec */
|
||||
${autoExecContent}`
|
||||
|
||||
@@ -17,7 +17,12 @@ import {
|
||||
import { desktopUser } from '../middlewares'
|
||||
|
||||
import User, { UserPayload } from '../model/User'
|
||||
import { getUserAutoExec, updateUserAutoExec, ModeType } from '../utils'
|
||||
import {
|
||||
getUserAutoExec,
|
||||
updateUserAutoExec,
|
||||
ModeType,
|
||||
AuthProviderType
|
||||
} from '../utils'
|
||||
import { GroupResponse } from './group'
|
||||
|
||||
export interface UserResponse {
|
||||
@@ -211,7 +216,11 @@ const createUser = async (data: UserPayload): Promise<UserDetailsResponse> => {
|
||||
|
||||
// Checking if user is already in the database
|
||||
const usernameExist = await User.findOne({ username })
|
||||
if (usernameExist) throw new Error('Username already exists.')
|
||||
if (usernameExist)
|
||||
throw {
|
||||
code: 409,
|
||||
message: 'Username already exists.'
|
||||
}
|
||||
|
||||
// Hash passwords
|
||||
const hashPassword = User.hashPassword(password)
|
||||
@@ -255,7 +264,11 @@ const getUser = async (
|
||||
'groupId name description -_id'
|
||||
)) as unknown as UserDetailsResponse
|
||||
|
||||
if (!user) throw new Error('User is not found.')
|
||||
if (!user)
|
||||
throw {
|
||||
code: 404,
|
||||
message: 'User is not found.'
|
||||
}
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
@@ -284,6 +297,24 @@ const updateUser = async (
|
||||
|
||||
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) {
|
||||
// Checking if user is already in the database
|
||||
const usernameExist = await User.findOne({ username })
|
||||
@@ -292,7 +323,10 @@ const updateUser = async (
|
||||
(findBy.id && usernameExist.id != findBy.id) ||
|
||||
(findBy.username && usernameExist.username != findBy.username)
|
||||
)
|
||||
throw new Error('Username already exists.')
|
||||
throw {
|
||||
code: 409,
|
||||
message: 'Username already exists.'
|
||||
}
|
||||
}
|
||||
params.username = username
|
||||
}
|
||||
@@ -305,7 +339,10 @@ const updateUser = async (
|
||||
const updatedUser = await User.findOneAndUpdate(findBy, params, { new: true })
|
||||
|
||||
if (!updatedUser)
|
||||
throw new Error(`Unable to find user with ${findBy.id || findBy.username}`)
|
||||
throw {
|
||||
code: 404,
|
||||
message: `Unable to find user with ${findBy.id || findBy.username}`
|
||||
}
|
||||
|
||||
return {
|
||||
id: updatedUser.id,
|
||||
@@ -332,11 +369,19 @@ const deleteUser = async (
|
||||
{ password }: { password?: string }
|
||||
) => {
|
||||
const user = await User.findOne(findBy)
|
||||
if (!user) throw new Error('User is not found.')
|
||||
if (!user)
|
||||
throw {
|
||||
code: 404,
|
||||
message: 'User is not found.'
|
||||
}
|
||||
|
||||
if (!isAdmin) {
|
||||
const validPass = user.comparePassword(password!)
|
||||
if (!validPass) throw new Error('Invalid password.')
|
||||
if (!validPass)
|
||||
throw {
|
||||
code: 401,
|
||||
message: 'Invalid password.'
|
||||
}
|
||||
}
|
||||
|
||||
await User.deleteOne(findBy)
|
||||
|
||||
@@ -5,7 +5,12 @@ import { readFile } from '@sasjs/utils'
|
||||
|
||||
import User from '../model/User'
|
||||
import Client from '../model/Client'
|
||||
import { getWebBuildFolder, generateAuthCode } from '../utils'
|
||||
import {
|
||||
getWebBuildFolder,
|
||||
generateAuthCode,
|
||||
AuthProviderType,
|
||||
LDAPClient
|
||||
} from '../utils'
|
||||
import { InfoJWT } from '../types'
|
||||
import { AuthController } from './auth'
|
||||
|
||||
@@ -80,8 +85,16 @@ const login = async (
|
||||
const user = await User.findOne({ username })
|
||||
if (!user) throw new Error('Username is not found.')
|
||||
|
||||
const validPass = user.comparePassword(password)
|
||||
if (!validPass) throw new Error('Invalid password.')
|
||||
if (
|
||||
process.env.AUTH_PROVIDERS === AuthProviderType.LDAP &&
|
||||
user.authProvider === AuthProviderType.LDAP
|
||||
) {
|
||||
const ldapClient = await LDAPClient.init()
|
||||
await ldapClient.verifyUser(username, password)
|
||||
} else {
|
||||
const validPass = user.comparePassword(password)
|
||||
if (!validPass) throw new Error('Invalid password.')
|
||||
}
|
||||
|
||||
req.session.loggedIn = true
|
||||
req.session.user = {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import mongoose, { Schema, model, Document, Model } from 'mongoose'
|
||||
import { GroupDetailsResponse } from '../controllers'
|
||||
import User, { IUser } from './User'
|
||||
import { AuthProviderType } from '../utils'
|
||||
const AutoIncrement = require('mongoose-sequence')(mongoose)
|
||||
|
||||
export const PUBLIC_GROUP_NAME = 'Public'
|
||||
@@ -27,6 +28,7 @@ interface IGroupDocument extends GroupPayload, Document {
|
||||
groupId: number
|
||||
isActive: boolean
|
||||
users: Schema.Types.ObjectId[]
|
||||
authProvider?: AuthProviderType
|
||||
}
|
||||
|
||||
interface IGroup extends IGroupDocument {
|
||||
@@ -46,6 +48,10 @@ const groupSchema = new Schema<IGroupDocument>({
|
||||
type: String,
|
||||
default: 'Group description.'
|
||||
},
|
||||
authProvider: {
|
||||
type: String,
|
||||
enum: AuthProviderType
|
||||
},
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import mongoose, { Schema, model, Document, Model } from 'mongoose'
|
||||
const AutoIncrement = require('mongoose-sequence')(mongoose)
|
||||
import bcrypt from 'bcryptjs'
|
||||
import { AuthProviderType } from '../utils'
|
||||
|
||||
export interface UserPayload {
|
||||
/**
|
||||
@@ -42,6 +43,7 @@ interface IUserDocument extends UserPayload, Document {
|
||||
autoExec: string
|
||||
groups: Schema.Types.ObjectId[]
|
||||
tokens: [{ [key: string]: string }]
|
||||
authProvider?: AuthProviderType
|
||||
}
|
||||
|
||||
export interface IUser extends IUserDocument {
|
||||
@@ -67,6 +69,10 @@ const userSchema = new Schema<IUserDocument>({
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
authProvider: {
|
||||
type: String,
|
||||
enum: AuthProviderType
|
||||
},
|
||||
isAdmin: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
|
||||
25
api/src/routes/api/authConfig.ts
Normal file
25
api/src/routes/api/authConfig.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
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
|
||||
@@ -18,11 +18,7 @@ groupRouter.post(
|
||||
const response = await controller.createGroup(body)
|
||||
res.send(response)
|
||||
} catch (err: any) {
|
||||
const statusCode = err.code
|
||||
|
||||
delete err.code
|
||||
|
||||
res.status(statusCode).send(err.message)
|
||||
res.status(err.code).send(err.message)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -33,11 +29,7 @@ groupRouter.get('/', authenticateAccessToken, async (req, res) => {
|
||||
const response = await controller.getAllGroups()
|
||||
res.send(response)
|
||||
} catch (err: any) {
|
||||
const statusCode = err.code
|
||||
|
||||
delete err.code
|
||||
|
||||
res.status(statusCode).send(err.message)
|
||||
res.status(err.code).send(err.message)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -49,11 +41,7 @@ groupRouter.get('/:groupId', authenticateAccessToken, async (req, res) => {
|
||||
const response = await controller.getGroup(parseInt(groupId))
|
||||
res.send(response)
|
||||
} catch (err: any) {
|
||||
const statusCode = err.code
|
||||
|
||||
delete err.code
|
||||
|
||||
res.status(statusCode).send(err.message)
|
||||
res.status(err.code).send(err.message)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -71,11 +59,7 @@ groupRouter.get(
|
||||
const response = await controller.getGroupByGroupName(name)
|
||||
res.send(response)
|
||||
} catch (err: any) {
|
||||
const statusCode = err.code
|
||||
|
||||
delete err.code
|
||||
|
||||
res.status(statusCode).send(err.message)
|
||||
res.status(err.code).send(err.message)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -95,11 +79,7 @@ groupRouter.post(
|
||||
)
|
||||
res.send(response)
|
||||
} catch (err: any) {
|
||||
const statusCode = err.code
|
||||
|
||||
delete err.code
|
||||
|
||||
res.status(statusCode).send(err.message)
|
||||
res.status(err.code).send(err.message)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -119,11 +99,7 @@ groupRouter.delete(
|
||||
)
|
||||
res.send(response)
|
||||
} catch (err: any) {
|
||||
const statusCode = err.code
|
||||
|
||||
delete err.code
|
||||
|
||||
res.status(statusCode).send(err.message)
|
||||
res.status(err.code).send(err.message)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -140,11 +116,7 @@ groupRouter.delete(
|
||||
await controller.deleteGroup(parseInt(groupId))
|
||||
res.status(200).send('Group Deleted!')
|
||||
} catch (err: any) {
|
||||
const statusCode = err.code
|
||||
|
||||
delete err.code
|
||||
|
||||
res.status(statusCode).send(err.message)
|
||||
res.status(err.code).send(err.message)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -18,6 +18,7 @@ import clientRouter from './client'
|
||||
import authRouter from './auth'
|
||||
import sessionRouter from './session'
|
||||
import permissionRouter from './permission'
|
||||
import authConfigRouter from './authConfig'
|
||||
|
||||
const router = express.Router()
|
||||
|
||||
@@ -43,6 +44,14 @@ router.use(
|
||||
permissionRouter
|
||||
)
|
||||
|
||||
router.use(
|
||||
'/authConfig',
|
||||
desktopRestrict,
|
||||
authenticateAccessToken,
|
||||
verifyAdmin,
|
||||
authConfigRouter
|
||||
)
|
||||
|
||||
router.use(
|
||||
'/',
|
||||
swaggerUi.serve,
|
||||
|
||||
@@ -4,8 +4,13 @@ import { MongoMemoryServer } from 'mongodb-memory-server'
|
||||
import request from 'supertest'
|
||||
import appPromise from '../../../app'
|
||||
import { UserController, GroupController } from '../../../controllers/'
|
||||
import { generateAccessToken, saveTokensInDB } from '../../../utils'
|
||||
import { PUBLIC_GROUP_NAME } from '../../../model/Group'
|
||||
import {
|
||||
generateAccessToken,
|
||||
saveTokensInDB,
|
||||
AuthProviderType
|
||||
} from '../../../utils'
|
||||
import Group, { PUBLIC_GROUP_NAME } from '../../../model/Group'
|
||||
import User from '../../../model/User'
|
||||
|
||||
const clientId = 'someclientID'
|
||||
const adminUser = {
|
||||
@@ -560,6 +565,46 @@ describe('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.groupId}/${dbUser.id}`)
|
||||
.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.groupId}/${dbUser.id}`)
|
||||
.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', () => {
|
||||
@@ -611,6 +656,46 @@ describe('group', () => {
|
||||
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.groupId}/${dbUser.id}`)
|
||||
.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.groupId}/${dbUser.id}`)
|
||||
.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 () => {
|
||||
const res = await request(app)
|
||||
.delete('/SASjsApi/group/123/123')
|
||||
|
||||
@@ -4,7 +4,12 @@ import { MongoMemoryServer } from 'mongodb-memory-server'
|
||||
import request from 'supertest'
|
||||
import appPromise from '../../../app'
|
||||
import { UserController, GroupController } from '../../../controllers/'
|
||||
import { generateAccessToken, saveTokensInDB } from '../../../utils'
|
||||
import {
|
||||
generateAccessToken,
|
||||
saveTokensInDB,
|
||||
AuthProviderType
|
||||
} from '../../../utils'
|
||||
import User from '../../../model/User'
|
||||
|
||||
const clientId = 'someclientID'
|
||||
const adminUser = {
|
||||
@@ -110,16 +115,16 @@ describe('user', () => {
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Forbidden if username is already present', async () => {
|
||||
it('should respond with Conflict if username is already present', async () => {
|
||||
await controller.createUser(user)
|
||||
|
||||
const res = await request(app)
|
||||
.post('/SASjsApi/user')
|
||||
.auth(adminAccessToken, { type: 'bearer' })
|
||||
.send(user)
|
||||
.expect(403)
|
||||
.expect(409)
|
||||
|
||||
expect(res.text).toEqual('Error: Username already exists.')
|
||||
expect(res.text).toEqual('Username already exists.')
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
@@ -226,6 +231,36 @@ describe('user', () => {
|
||||
.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 () => {
|
||||
const res = await request(app)
|
||||
.patch('/SASjsApi/user/1234')
|
||||
@@ -254,7 +289,7 @@ describe('user', () => {
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Forbidden if username is already present', async () => {
|
||||
it('should respond with Conflict if username is already present', async () => {
|
||||
const dbUser1 = await controller.createUser(user)
|
||||
const dbUser2 = await controller.createUser({
|
||||
...user,
|
||||
@@ -265,9 +300,9 @@ describe('user', () => {
|
||||
.patch(`/SASjsApi/user/${dbUser1.id}`)
|
||||
.auth(adminAccessToken, { type: 'bearer' })
|
||||
.send({ username: dbUser2.username })
|
||||
.expect(403)
|
||||
.expect(409)
|
||||
|
||||
expect(res.text).toEqual('Error: Username already exists.')
|
||||
expect(res.text).toEqual('Username already exists.')
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
@@ -349,7 +384,7 @@ describe('user', () => {
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Forbidden if username is already present', async () => {
|
||||
it('should respond with Conflict if username is already present', async () => {
|
||||
const dbUser1 = await controller.createUser(user)
|
||||
const dbUser2 = await controller.createUser({
|
||||
...user,
|
||||
@@ -360,9 +395,9 @@ describe('user', () => {
|
||||
.patch(`/SASjsApi/user/by/username/${dbUser1.username}`)
|
||||
.auth(adminAccessToken, { type: 'bearer' })
|
||||
.send({ username: dbUser2.username })
|
||||
.expect(403)
|
||||
.expect(409)
|
||||
|
||||
expect(res.text).toEqual('Error: Username already exists.')
|
||||
expect(res.text).toEqual('Username already exists.')
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
})
|
||||
@@ -446,7 +481,7 @@ describe('user', () => {
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Forbidden when user himself requests and password is incorrect', async () => {
|
||||
it('should respond with Unauthorized when user himself requests and password is incorrect', async () => {
|
||||
const dbUser = await controller.createUser(user)
|
||||
const accessToken = await generateAndSaveToken(dbUser.id)
|
||||
|
||||
@@ -454,9 +489,9 @@ describe('user', () => {
|
||||
.delete(`/SASjsApi/user/${dbUser.id}`)
|
||||
.auth(accessToken, { type: 'bearer' })
|
||||
.send({ password: 'incorrectpassword' })
|
||||
.expect(403)
|
||||
.expect(401)
|
||||
|
||||
expect(res.text).toEqual('Error: Invalid password.')
|
||||
expect(res.text).toEqual('Invalid password.')
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
@@ -528,7 +563,7 @@ describe('user', () => {
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Forbidden when user himself requests and password is incorrect', async () => {
|
||||
it('should respond with Unauthorized when user himself requests and password is incorrect', async () => {
|
||||
const dbUser = await controller.createUser(user)
|
||||
const accessToken = await generateAndSaveToken(dbUser.id)
|
||||
|
||||
@@ -536,9 +571,9 @@ describe('user', () => {
|
||||
.delete(`/SASjsApi/user/by/username/${dbUser.username}`)
|
||||
.auth(accessToken, { type: 'bearer' })
|
||||
.send({ password: 'incorrectpassword' })
|
||||
.expect(403)
|
||||
.expect(401)
|
||||
|
||||
expect(res.text).toEqual('Error: Invalid password.')
|
||||
expect(res.text).toEqual('Invalid password.')
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
})
|
||||
@@ -652,16 +687,16 @@ describe('user', () => {
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Forbidden if userId is incorrect', async () => {
|
||||
it('should respond with Not Found if userId is incorrect', async () => {
|
||||
await controller.createUser(user)
|
||||
|
||||
const res = await request(app)
|
||||
.get('/SASjsApi/user/1234')
|
||||
.auth(adminAccessToken, { type: 'bearer' })
|
||||
.send()
|
||||
.expect(403)
|
||||
.expect(404)
|
||||
|
||||
expect(res.text).toEqual('Error: User is not found.')
|
||||
expect(res.text).toEqual('User is not found.')
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
@@ -731,16 +766,16 @@ describe('user', () => {
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Forbidden if username is incorrect', async () => {
|
||||
it('should respond with Not Found if username is incorrect', async () => {
|
||||
await controller.createUser(user)
|
||||
|
||||
const res = await request(app)
|
||||
.get('/SASjsApi/user/by/username/randomUsername')
|
||||
.auth(adminAccessToken, { type: 'bearer' })
|
||||
.send()
|
||||
.expect(403)
|
||||
.expect(404)
|
||||
|
||||
expect(res.text).toEqual('Error: User is not found.')
|
||||
expect(res.text).toEqual('User is not found.')
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -23,7 +23,7 @@ userRouter.post('/', authenticateAccessToken, verifyAdmin, async (req, res) => {
|
||||
const response = await controller.createUser(body)
|
||||
res.send(response)
|
||||
} catch (err: any) {
|
||||
res.status(403).send(err.toString())
|
||||
res.status(err.code).send(err.message)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -33,7 +33,7 @@ userRouter.get('/', authenticateAccessToken, async (req, res) => {
|
||||
const response = await controller.getAllUsers()
|
||||
res.send(response)
|
||||
} catch (err: any) {
|
||||
res.status(403).send(err.toString())
|
||||
res.status(err.code).send(err.message)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -51,7 +51,7 @@ userRouter.get(
|
||||
const response = await controller.getUserByUsername(req, username)
|
||||
res.send(response)
|
||||
} catch (err: any) {
|
||||
res.status(403).send(err.toString())
|
||||
res.status(err.code).send(err.message)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -64,7 +64,7 @@ userRouter.get('/:userId', authenticateAccessToken, async (req, res) => {
|
||||
const response = await controller.getUser(req, parseInt(userId))
|
||||
res.send(response)
|
||||
} catch (err: any) {
|
||||
res.status(403).send(err.toString())
|
||||
res.status(err.code).send(err.message)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -91,7 +91,7 @@ userRouter.patch(
|
||||
const response = await controller.updateUserByUsername(username, body)
|
||||
res.send(response)
|
||||
} catch (err: any) {
|
||||
res.status(403).send(err.toString())
|
||||
res.status(err.code).send(err.message)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -113,7 +113,7 @@ userRouter.patch(
|
||||
const response = await controller.updateUser(parseInt(userId), body)
|
||||
res.send(response)
|
||||
} catch (err: any) {
|
||||
res.status(403).send(err.toString())
|
||||
res.status(err.code).send(err.message)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -141,7 +141,7 @@ userRouter.delete(
|
||||
await controller.deleteUserByUsername(username, data, user!.isAdmin)
|
||||
res.status(200).send('Account Deleted!')
|
||||
} catch (err: any) {
|
||||
res.status(403).send(err.toString())
|
||||
res.status(err.code).send(err.message)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -163,7 +163,7 @@ userRouter.delete(
|
||||
await controller.deleteUser(parseInt(userId), data, user!.isAdmin)
|
||||
res.status(200).send('Account Deleted!')
|
||||
} catch (err: any) {
|
||||
res.status(403).send(err.toString())
|
||||
res.status(err.code).send(err.message)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -10,7 +10,7 @@ export const sysInitCompiledPath = path.join(
|
||||
'systemInitCompiled.sas'
|
||||
)
|
||||
|
||||
export const sasJSCoreMacros = path.join(apiRoot, 'sasjscore')
|
||||
export const sasJSCoreMacros = path.join(apiRoot, 'sas', 'sasautos')
|
||||
export const sasJSCoreMacrosInfo = path.join(sasJSCoreMacros, '.macrolist')
|
||||
|
||||
export const getWebBuildFolder = () => path.join(codebaseRoot, 'web', 'build')
|
||||
@@ -28,7 +28,10 @@ export const getAppStreamConfigPath = () =>
|
||||
path.join(getSasjsRootFolder(), 'appStreamConfig.json')
|
||||
|
||||
export const getMacrosFolder = () =>
|
||||
path.join(getSasjsRootFolder(), 'sasjscore')
|
||||
path.join(getSasjsRootFolder(), 'sas', 'sasautos')
|
||||
|
||||
export const getPackagesFolder = () =>
|
||||
path.join(getSasjsRootFolder(), 'sas', 'sas_packages')
|
||||
|
||||
export const getUploadsFolder = () => path.join(getSasjsRootFolder(), 'uploads')
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ export * from './getTokensFromDB'
|
||||
export * from './instantiateLogger'
|
||||
export * from './isDebugOn'
|
||||
export * from './isPublicRoute'
|
||||
export * from './ldapClient'
|
||||
export * from './zipped'
|
||||
export * from './parseLogToArray'
|
||||
export * from './removeTokensInDB'
|
||||
|
||||
163
api/src/utils/ldapClient.ts
Normal file
163
api/src/utils/ldapClient.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
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.')
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,15 @@
|
||||
import { createFile, createFolder, fileExists } from '@sasjs/utils'
|
||||
import { getDesktopUserAutoExecPath, getFilesFolder } from './file'
|
||||
import {
|
||||
getDesktopUserAutoExecPath,
|
||||
getFilesFolder,
|
||||
getPackagesFolder
|
||||
} from './file'
|
||||
import { ModeType } from './verifyEnvVariables'
|
||||
|
||||
export const setupFolders = async () => {
|
||||
const drivePath = getFilesFolder()
|
||||
await createFolder(drivePath)
|
||||
await createFolder(getPackagesFolder())
|
||||
|
||||
if (process.env.MODE === ModeType.Desktop) {
|
||||
if (!(await fileExists(getDesktopUserAutoExecPath()))) {
|
||||
|
||||
@@ -8,6 +8,10 @@ export enum ModeType {
|
||||
Desktop = 'desktop'
|
||||
}
|
||||
|
||||
export enum AuthProviderType {
|
||||
LDAP = 'ldap'
|
||||
}
|
||||
|
||||
export enum ProtocolType {
|
||||
HTTP = 'http',
|
||||
HTTPS = 'https'
|
||||
@@ -64,6 +68,8 @@ export const verifyEnvVariables = (): ReturnCode => {
|
||||
|
||||
errors.push(...verifyExecutablePaths())
|
||||
|
||||
errors.push(...verifyLDAPVariables())
|
||||
|
||||
if (errors.length) {
|
||||
process.logger?.error(
|
||||
`Invalid environment variable(s) provided: \n${errors.join('\n')}`
|
||||
@@ -104,13 +110,22 @@ const verifyMODE = (): string[] => {
|
||||
}
|
||||
|
||||
if (process.env.MODE === ModeType.Server) {
|
||||
const { DB_CONNECT } = process.env
|
||||
const { DB_CONNECT, AUTH_PROVIDERS } = process.env
|
||||
|
||||
if (process.env.NODE_ENV !== 'test')
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
if (!DB_CONNECT)
|
||||
errors.push(
|
||||
`- 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
|
||||
@@ -280,6 +295,53 @@ const verifyExecutablePaths = () => {
|
||||
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 DEFAULTS = {
|
||||
MODE: ModeType.Desktop,
|
||||
PROTOCOL: ProtocolType.HTTP,
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
"name": "Auth",
|
||||
"description": "Operations about auth"
|
||||
},
|
||||
{
|
||||
"name": "Auth_Config",
|
||||
"description": "Operations about external auth providers"
|
||||
},
|
||||
{
|
||||
"name": "Client",
|
||||
"description": "Operations about clients"
|
||||
|
||||
151
web/src/containers/Settings/authConfig.tsx
Normal file
151
web/src/containers/Settings/authConfig.tsx
Normal file
@@ -0,0 +1,151 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import axios from 'axios'
|
||||
import {
|
||||
Box,
|
||||
Grid,
|
||||
CircularProgress,
|
||||
Card,
|
||||
CardHeader,
|
||||
Divider,
|
||||
CardContent,
|
||||
TextField,
|
||||
CardActions,
|
||||
Button,
|
||||
Typography
|
||||
} from '@mui/material'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
const AuthConfig = () => {
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [authDetail, setAuthDetail] = useState<any>({})
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true)
|
||||
axios
|
||||
.get(`/SASjsApi/authConfig`)
|
||||
.then((res: any) => {
|
||||
setAuthDetail(res.data)
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.error('Failed: ' + err.response?.data || err.text, {
|
||||
theme: 'dark',
|
||||
position: toast.POSITION.BOTTOM_RIGHT
|
||||
})
|
||||
})
|
||||
.finally(() => setIsLoading(false))
|
||||
}, [])
|
||||
|
||||
const synchroniseWithLDAP = () => {
|
||||
setIsLoading(true)
|
||||
axios
|
||||
.post(`/SASjsApi/authConfig/synchroniseWithLDAP`)
|
||||
.then((res: any) => {
|
||||
const { userCount, groupCount } = res.data
|
||||
toast.success(
|
||||
`Imported ${userCount} ${
|
||||
userCount > 1 ? 'users' : 'user'
|
||||
} and ${groupCount} ${groupCount > 1 ? 'groups' : 'group'}`,
|
||||
{
|
||||
theme: 'dark',
|
||||
position: toast.POSITION.BOTTOM_RIGHT
|
||||
}
|
||||
)
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.error('Failed: ' + err.response?.data || err.text, {
|
||||
theme: 'dark',
|
||||
position: toast.POSITION.BOTTOM_RIGHT
|
||||
})
|
||||
})
|
||||
.finally(() => setIsLoading(false))
|
||||
}
|
||||
|
||||
return isLoading ? (
|
||||
<CircularProgress
|
||||
style={{ position: 'absolute', left: '50%', top: '50%' }}
|
||||
/>
|
||||
) : (
|
||||
<Box>
|
||||
{Object.entries(authDetail).length === 0 && (
|
||||
<Typography>No external Auth Provider is used</Typography>
|
||||
)}
|
||||
{authDetail.ldap && (
|
||||
<Card>
|
||||
<CardHeader title="LDAP Authentication" />
|
||||
<Divider />
|
||||
<CardContent>
|
||||
<Grid container spacing={4}>
|
||||
<Grid item md={6} xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="LDAP_URL"
|
||||
name="LDAP_URL"
|
||||
value={authDetail.ldap.LDAP_URL}
|
||||
variant="outlined"
|
||||
disabled
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item md={6} xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="LDAP_BIND_DN"
|
||||
name="LDAP_BIND_DN"
|
||||
value={authDetail.ldap.LDAP_BIND_DN}
|
||||
variant="outlined"
|
||||
disabled={true}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item md={6} xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="LDAP_BIND_PASSWORD"
|
||||
name="LDAP_BIND_PASSWORD"
|
||||
type="password"
|
||||
value={authDetail.ldap.LDAP_BIND_PASSWORD}
|
||||
variant="outlined"
|
||||
disabled
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item md={6} xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="LDAP_USERS_BASE_DN"
|
||||
name="LDAP_USERS_BASE_DN"
|
||||
value={authDetail.ldap.LDAP_USERS_BASE_DN}
|
||||
variant="outlined"
|
||||
disabled={true}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item md={6} xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="LDAP_GROUPS_BASE_DN"
|
||||
name="LDAP_GROUPS_BASE_DN"
|
||||
value={authDetail.ldap.LDAP_GROUPS_BASE_DN}
|
||||
variant="outlined"
|
||||
disabled={true}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</CardContent>
|
||||
<Divider />
|
||||
<CardActions>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
onClick={synchroniseWithLDAP}
|
||||
>
|
||||
Synchronise
|
||||
</Button>
|
||||
</CardActions>
|
||||
</Card>
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default AuthConfig
|
||||
@@ -7,6 +7,7 @@ import TabPanel from '@mui/lab/TabPanel'
|
||||
|
||||
import Permission from './permission'
|
||||
import Profile from './profile'
|
||||
import AuthConfig from './authConfig'
|
||||
|
||||
import { AppContext, ModeType } from '../../context/appContext'
|
||||
import PermissionsContextProvider from '../../context/permissionsContext'
|
||||
@@ -59,6 +60,9 @@ const Settings = () => {
|
||||
{appContext.mode === ModeType.Server && (
|
||||
<StyledTab label="Permissions" value="permission" />
|
||||
)}
|
||||
{appContext.mode === ModeType.Server && appContext.isAdmin && (
|
||||
<StyledTab label="Auth Config" value="auth_config" />
|
||||
)}
|
||||
</TabList>
|
||||
</Box>
|
||||
<StyledTabpanel value="profile">
|
||||
@@ -69,6 +73,9 @@ const Settings = () => {
|
||||
<Permission />
|
||||
</PermissionsContextProvider>
|
||||
</StyledTabpanel>
|
||||
<StyledTabpanel value="auth_config">
|
||||
<AuthConfig />
|
||||
</StyledTabpanel>
|
||||
</TabContext>
|
||||
</Box>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user