mirror of
https://github.com/sasjs/server.git
synced 2025-12-12 20:04:36 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cc6f8a64b5 |
1
.github/workflows/build.yml
vendored
1
.github/workflows/build.yml
vendored
@@ -54,7 +54,6 @@ jobs:
|
|||||||
ACCESS_TOKEN_SECRET: ${{secrets.ACCESS_TOKEN_SECRET}}
|
ACCESS_TOKEN_SECRET: ${{secrets.ACCESS_TOKEN_SECRET}}
|
||||||
REFRESH_TOKEN_SECRET: ${{secrets.REFRESH_TOKEN_SECRET}}
|
REFRESH_TOKEN_SECRET: ${{secrets.REFRESH_TOKEN_SECRET}}
|
||||||
AUTH_CODE_SECRET: ${{secrets.AUTH_CODE_SECRET}}
|
AUTH_CODE_SECRET: ${{secrets.AUTH_CODE_SECRET}}
|
||||||
SESSION_SECRET: ${{secrets.SESSION_SECRET}}
|
|
||||||
|
|
||||||
- name: Build Package
|
- name: Build Package
|
||||||
working-directory: ./api
|
working-directory: ./api
|
||||||
|
|||||||
10
.github/workflows/release.yml
vendored
10
.github/workflows/release.yml
vendored
@@ -8,20 +8,10 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
node-version: [lts/*]
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
|
||||||
uses: actions/setup-node@v2
|
|
||||||
with:
|
|
||||||
node-version: ${{ matrix.node-version }}
|
|
||||||
|
|
||||||
- name: Install Dependencies WEB
|
- name: Install Dependencies WEB
|
||||||
working-directory: ./web
|
working-directory: ./web
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,4 +11,3 @@ sasjscore/
|
|||||||
certificates/
|
certificates/
|
||||||
executables/
|
executables/
|
||||||
.env
|
.env
|
||||||
api/csp.config.json
|
|
||||||
|
|||||||
122
CHANGELOG.md
122
CHANGELOG.md
@@ -2,128 +2,6 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||||
|
|
||||||
### [0.0.75](https://github.com/sasjs/server/compare/v0.0.69...v0.0.75) (2022-05-12)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* CSP_DISABLE env option ([dd3acce](https://github.com/sasjs/server/commit/dd3acce3935e7cfc0b2c44a401314306915a3a10))
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* added more cookies to req ([4a8e32d](https://github.com/sasjs/server/commit/4a8e32dd20b540b6dc92d749fad90d6c7fc69376))
|
|
||||||
* bumping core ([c0b57b9](https://github.com/sasjs/server/commit/c0b57b9e76d6db33fc64a68556a8be979dd69e40))
|
|
||||||
* csp updates ([7cfa239](https://github.com/sasjs/server/commit/7cfa2398e12c5e515d27c896f36ff91604c2124d))
|
|
||||||
* helmet config on http mode ([b0fdaaa](https://github.com/sasjs/server/commit/b0fdaaaa79e3135699c51effac0388d8ec5ab23b))
|
|
||||||
* moved getAuthCode from api to web routes ([b40de8f](https://github.com/sasjs/server/commit/b40de8fa6a5aa763ed25a6fe6a381e483e0ab824))
|
|
||||||
* reqHeadrs.txt will contain headers to access APIs ([636301e](https://github.com/sasjs/server/commit/636301e664416fb085f704d83deb7f39ee0a91a7))
|
|
||||||
* **web:** seperate container for auth code ([5888f04](https://github.com/sasjs/server/commit/5888f04e08a32c6d2c7bcfcbc3a1d32425bff3b3))
|
|
||||||
|
|
||||||
### [0.0.74](https://github.com/sasjs/server/compare/v0.0.73...v0.0.74) (2022-05-12)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* csp updates ([7cfa239](https://github.com/sasjs/server/commit/7cfa2398e12c5e515d27c896f36ff91604c2124d))
|
|
||||||
|
|
||||||
### [0.0.73](https://github.com/sasjs/server/compare/v0.0.72...v0.0.73) (2022-05-10)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* helmet config on http mode ([b0fdaaa](https://github.com/sasjs/server/commit/b0fdaaaa79e3135699c51effac0388d8ec5ab23b))
|
|
||||||
|
|
||||||
### [0.0.72](https://github.com/sasjs/server/compare/v0.0.71...v0.0.72) (2022-05-09)
|
|
||||||
|
|
||||||
### [0.0.71](https://github.com/sasjs/server/compare/v0.0.70...v0.0.71) (2022-05-07)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* added more cookies to req ([4a8e32d](https://github.com/sasjs/server/commit/4a8e32dd20b540b6dc92d749fad90d6c7fc69376))
|
|
||||||
* bumping core ([c0b57b9](https://github.com/sasjs/server/commit/c0b57b9e76d6db33fc64a68556a8be979dd69e40))
|
|
||||||
* reqHeadrs.txt will contain headers to access APIs ([636301e](https://github.com/sasjs/server/commit/636301e664416fb085f704d83deb7f39ee0a91a7))
|
|
||||||
|
|
||||||
### [0.0.70](https://github.com/sasjs/server/compare/v0.0.69...v0.0.70) (2022-05-06)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* CSP_DISABLE env option ([dd3acce](https://github.com/sasjs/server/commit/dd3acce3935e7cfc0b2c44a401314306915a3a10))
|
|
||||||
|
|
||||||
### [0.0.69](https://github.com/sasjs/server/compare/v0.0.68...v0.0.69) (2022-05-02)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **upload:** appStream uses CSRF + Session authentication ([1f89279](https://github.com/sasjs/server/commit/1f8927926405887f3d134c0a1dd6452ffa33876e))
|
|
||||||
|
|
||||||
### [0.0.68](https://github.com/sasjs/server/compare/v0.0.67...v0.0.68) (2022-05-02)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* using monaco editor locally ([2548c82](https://github.com/sasjs/server/commit/2548c82dfe1149e62a570a00546dddd9e30049b1))
|
|
||||||
|
|
||||||
### [0.0.67](https://github.com/sasjs/server/compare/v0.0.66...v0.0.67) (2022-05-01)
|
|
||||||
|
|
||||||
### [0.0.66](https://github.com/sasjs/server/compare/v0.0.64...v0.0.66) (2022-05-01)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* added swagger ui init file manually ([e2a97fc](https://github.com/sasjs/server/commit/e2a97fcb7c54a57a7ca118677cfce93fe9430d8f))
|
|
||||||
* consume swagger api with CSRF ([5aaac24](https://github.com/sasjs/server/commit/5aaac24080362d6ce0c5d1157798a9343f40ae2a))
|
|
||||||
|
|
||||||
### [0.0.65](https://github.com/sasjs/server/compare/v0.0.64...v0.0.65) (2022-05-01)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* consume swagger api with CSRF ([5aaac24](https://github.com/sasjs/server/commit/5aaac24080362d6ce0c5d1157798a9343f40ae2a))
|
|
||||||
|
|
||||||
### [0.0.64](https://github.com/sasjs/server/compare/v0.0.63...v0.0.64) (2022-04-30)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* removed fileExists for serving web ([7b39cc0](https://github.com/sasjs/server/commit/7b39cc06d358f5ffecb87955040c4eb0fcc7469e))
|
|
||||||
|
|
||||||
### [0.0.63](https://github.com/sasjs/server/compare/v0.0.62...v0.0.63) (2022-04-30)
|
|
||||||
|
|
||||||
### [0.0.62](https://github.com/sasjs/server/compare/v0.0.61...v0.0.62) (2022-04-30)
|
|
||||||
|
|
||||||
### [0.0.61](https://github.com/sasjs/server/compare/v0.0.59...v0.0.61) (2022-04-30)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* added CSRF check for granting access via session authentication ([b060ad1](https://github.com/sasjs/server/commit/b060ad1b8e0bbc61c20dc25be553bba4cc4d2716))
|
|
||||||
* setting CSRF Token for only rendering SPA ([b4b60c6](https://github.com/sasjs/server/commit/b4b60c69cf67a42f4797f7f1afe68b7a5eec2998))
|
|
||||||
|
|
||||||
### [0.0.60](https://github.com/sasjs/server/compare/v0.0.59...v0.0.60) (2022-04-30)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* added CSRF check for granting access via session authentication ([b060ad1](https://github.com/sasjs/server/commit/b060ad1b8e0bbc61c20dc25be553bba4cc4d2716))
|
|
||||||
* setting CSRF Token for only rendering SPA ([b4b60c6](https://github.com/sasjs/server/commit/b4b60c69cf67a42f4797f7f1afe68b7a5eec2998))
|
|
||||||
|
|
||||||
### [0.0.59](https://github.com/sasjs/server/compare/v0.0.58...v0.0.59) (2022-04-29)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* enabled csrf tokens for web component ([e462aeb](https://github.com/sasjs/server/commit/e462aebdc01f3c0068ed0074473a2063412dcf45))
|
|
||||||
* enabled session based authentication for web ([5da93f3](https://github.com/sasjs/server/commit/5da93f318aad10b1c67032a467191e4dbb99f411))
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* fetch client from DB for each request ([4ad8c81](https://github.com/sasjs/server/commit/4ad8c81e4927c1a82220ec015a781b095c8e859e))
|
|
||||||
* **web:** show display name instead of username ([e57443f](https://github.com/sasjs/server/commit/e57443f1ed662a022494bb93d79c3d2f10a2d082))
|
|
||||||
|
|
||||||
### [0.0.58](https://github.com/sasjs/server/compare/v0.0.57...v0.0.58) (2022-04-24)
|
### [0.0.58](https://github.com/sasjs/server/compare/v0.0.57...v0.0.58) (2022-04-24)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
73
README.md
73
README.md
@@ -48,22 +48,15 @@ When launching the app, it will make use of specific environment variables. Thes
|
|||||||
Example contents of a `.env` file:
|
Example contents of a `.env` file:
|
||||||
|
|
||||||
```
|
```
|
||||||
#
|
# options: [desktop|server] default: `desktop`
|
||||||
## Core Settings
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
# MODE options: [desktop|server] default: `desktop`
|
|
||||||
# Desktop mode is single user and designed for workstation use
|
|
||||||
# Server mode is multi-user and suitable for intranet / internet use
|
|
||||||
MODE=
|
MODE=
|
||||||
|
|
||||||
# Path to SAS executable (sas.exe / sas.sh)
|
# options: [disable|enable] default: `disable` for `server` & `enable` for `desktop`
|
||||||
SAS_PATH=/path/to/sas/executable.exe
|
# If enabled, be sure to also configure the WHITELIST of third party servers.
|
||||||
|
CORS=
|
||||||
|
|
||||||
# Path to working directory
|
# options: <http://localhost:3000 https://abc.com ...> space separated urls
|
||||||
# This location is for SAS WORK, staged files, DRIVE, configuration etc
|
WHITELIST=
|
||||||
DRIVE_PATH=/tmp
|
|
||||||
|
|
||||||
# options: [http|https] default: http
|
# options: [http|https] default: http
|
||||||
PROTOCOL=
|
PROTOCOL=
|
||||||
@@ -72,22 +65,16 @@ PROTOCOL=
|
|||||||
PORT=
|
PORT=
|
||||||
|
|
||||||
|
|
||||||
#
|
# optional
|
||||||
## Additional SAS Options
|
# for MODE: `desktop`, prompts user
|
||||||
#
|
# for MODE: `server` gets value from api/package.json `configuration.sasPath`
|
||||||
|
SAS_PATH=/path/to/sas/executable.exe
|
||||||
|
|
||||||
|
|
||||||
# On windows use SAS_OPTIONS and on unix use SASV9_OPTIONS
|
# optional
|
||||||
# Any options set here are automatically applied in the SAS session
|
# for MODE: `desktop`, prompts user
|
||||||
# See: https://documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/hostunx/p0wrdmqp8k0oyyn1xbx3bp3qy2wl.htm
|
# for MODE: `server` defaults to /tmp
|
||||||
# And: https://documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/hostwin/p0drw76qo0gig2n1kcoliekh605k.htm#p09y7hx0grw1gin1giuvrjyx61m6
|
DRIVE_PATH=/tmp
|
||||||
SAS_OPTIONS= -NOXCMD
|
|
||||||
SASV9_OPTIONS= -NOXCMD
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
## Additional Web Server Options
|
|
||||||
#
|
|
||||||
|
|
||||||
# ENV variables required for PROTOCOL: `https`
|
# ENV variables required for PROTOCOL: `https`
|
||||||
PRIVATE_KEY=privkey.pem
|
PRIVATE_KEY=privkey.pem
|
||||||
@@ -97,33 +84,15 @@ FULL_CHAIN=fullchain.pem
|
|||||||
ACCESS_TOKEN_SECRET=<secret>
|
ACCESS_TOKEN_SECRET=<secret>
|
||||||
REFRESH_TOKEN_SECRET=<secret>
|
REFRESH_TOKEN_SECRET=<secret>
|
||||||
AUTH_CODE_SECRET=<secret>
|
AUTH_CODE_SECRET=<secret>
|
||||||
SESSION_SECRET=<secret>
|
|
||||||
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: [disable|enable] default: `disable` for `server` & `enable` for `desktop`
|
# SAS Options
|
||||||
# If enabled, be sure to also configure the WHITELIST of third party servers.
|
# On windows use SAS_OPTIONS and on unix use SASV9_OPTIONS
|
||||||
CORS=
|
# Any options set here are automatically applied in the SAS session
|
||||||
|
# See: https://documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/hostunx/p0wrdmqp8k0oyyn1xbx3bp3qy2wl.htm
|
||||||
# options: <http://localhost:3000 https://abc.com ...> space separated urls
|
# And: https://documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/hostwin/p0drw76qo0gig2n1kcoliekh605k.htm#p09y7hx0grw1gin1giuvrjyx61m6
|
||||||
WHITELIST=
|
SAS_OPTIONS= -NOXCMD
|
||||||
|
SASV9_OPTIONS= -NOXCMD
|
||||||
# HELMET Cross Origin Embedder Policy
|
|
||||||
# Sets the Cross-Origin-Embedder-Policy header to require-corp when `true`
|
|
||||||
# options: [true|false] default: true
|
|
||||||
# Docs: https://helmetjs.github.io/#reference (`crossOriginEmbedderPolicy`)
|
|
||||||
HELMET_COEP=
|
|
||||||
|
|
||||||
# HELMET Content Security Policy
|
|
||||||
# Path to a json file containing HELMET `contentSecurityPolicy` directives
|
|
||||||
# Docs: https://helmetjs.github.io/#reference
|
|
||||||
#
|
|
||||||
# Example config:
|
|
||||||
# {
|
|
||||||
# "img-src": ["'self'", "data:"],
|
|
||||||
# "script-src": ["'self'", "'unsafe-inline'"],
|
|
||||||
# "script-src-attr": ["'self'", "'unsafe-inline'"]
|
|
||||||
# }
|
|
||||||
HELMET_CSP_CONFIG_PATH=./csp.config.json
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,13 @@
|
|||||||
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
|
||||||
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
|
||||||
PRIVATE_KEY=privkey.pem
|
PRIVATE_KEY=privkey.pem
|
||||||
FULL_CHAIN=fullchain.pem
|
FULL_CHAIN=fullchain.pem
|
||||||
|
|
||||||
PORT=[5000] default value is 5000
|
PORT=[5000] default value is 5000
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
ACCESS_TOKEN_SECRET=<secret>
|
ACCESS_TOKEN_SECRET=<secret>
|
||||||
REFRESH_TOKEN_SECRET=<secret>
|
REFRESH_TOKEN_SECRET=<secret>
|
||||||
AUTH_CODE_SECRET=<secret>
|
AUTH_CODE_SECRET=<secret>
|
||||||
SESSION_SECRET=<secret>
|
|
||||||
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
|
||||||
|
|
||||||
SAS_PATH=/opt/sas/sas9/SASHome/SASFoundation/9.4/sas
|
SAS_PATH=/opt/sas/sas9/SASHome/SASFoundation/9.4/sas
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"img-src": ["'self'", "data:"],
|
|
||||||
"script-src": ["'self'", "'unsafe-inline'"],
|
|
||||||
"script-src-attr": ["'self'", "'unsafe-inline'"]
|
|
||||||
}
|
|
||||||
499
api/package-lock.json
generated
499
api/package-lock.json
generated
@@ -8,23 +8,19 @@
|
|||||||
"name": "api",
|
"name": "api",
|
||||||
"version": "0.0.2",
|
"version": "0.0.2",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sasjs/core": "^4.23.1",
|
"@sasjs/core": "^4.19.0",
|
||||||
"@sasjs/utils": "2.42.1",
|
"@sasjs/utils": "2.42.1",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"connect-mongo": "^4.6.0",
|
|
||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"csurf": "^1.11.0",
|
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"express-session": "^1.17.2",
|
|
||||||
"helmet": "^5.0.2",
|
|
||||||
"joi": "^17.4.2",
|
"joi": "^17.4.2",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"mongoose": "^6.0.12",
|
"mongoose": "^6.0.12",
|
||||||
"mongoose-sequence": "^5.3.1",
|
"mongoose-sequence": "^5.3.1",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"multer": "^1.4.3",
|
"multer": "^1.4.3",
|
||||||
"swagger-ui-express": "4.3.0"
|
"swagger-ui-express": "^4.1.6"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"api": "build/src/server.js"
|
"api": "build/src/server.js"
|
||||||
@@ -33,9 +29,7 @@
|
|||||||
"@types/bcryptjs": "^2.4.2",
|
"@types/bcryptjs": "^2.4.2",
|
||||||
"@types/cookie-parser": "^1.4.2",
|
"@types/cookie-parser": "^1.4.2",
|
||||||
"@types/cors": "^2.8.12",
|
"@types/cors": "^2.8.12",
|
||||||
"@types/csurf": "^1.11.2",
|
|
||||||
"@types/express": "^4.17.12",
|
"@types/express": "^4.17.12",
|
||||||
"@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/mongoose-sequence": "^3.0.6",
|
"@types/mongoose-sequence": "^3.0.6",
|
||||||
@@ -49,7 +43,7 @@
|
|||||||
"jest": "^27.0.6",
|
"jest": "^27.0.6",
|
||||||
"mongodb-memory-server": "^8.0.0",
|
"mongodb-memory-server": "^8.0.0",
|
||||||
"nodemon": "^2.0.7",
|
"nodemon": "^2.0.7",
|
||||||
"pkg": "5.6.0",
|
"pkg": "5.5.2",
|
||||||
"prettier": "^2.3.1",
|
"prettier": "^2.3.1",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"supertest": "^6.1.3",
|
"supertest": "^6.1.3",
|
||||||
@@ -1385,9 +1379,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sasjs/core": {
|
"node_modules/@sasjs/core": {
|
||||||
"version": "4.23.1",
|
"version": "4.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.23.1.tgz",
|
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.19.0.tgz",
|
||||||
"integrity": "sha512-9d6yEPJRRvPLMUkpyaiQ62SXNMMyt2l815jxWgFjnVOxKeUQv9TPyZqZ0FpmWdVe6EY8dv8GLlyaBpOLDnY6Vg=="
|
"integrity": "sha512-vG2YHJveQUQqN0YBhapXb8y+Qp4OniHzRedlqKRxyL0Pc+kwXx5co4Vo+dcOI5/MX0p+8oERP2aCR77s4FEUJg=="
|
||||||
},
|
},
|
||||||
"node_modules/@sasjs/utils": {
|
"node_modules/@sasjs/utils": {
|
||||||
"version": "2.42.1",
|
"version": "2.42.1",
|
||||||
@@ -1839,15 +1833,6 @@
|
|||||||
"integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==",
|
"integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/csurf": {
|
|
||||||
"version": "1.11.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/csurf/-/csurf-1.11.2.tgz",
|
|
||||||
"integrity": "sha512-9bc98EnwmC1S0aSJiA8rWwXtgXtXHHOQOsGHptImxFgqm6CeH+mIOunHRg6+/eg2tlmDMX3tY7XrWxo2M/nUNQ==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@types/express-serve-static-core": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/express": {
|
"node_modules/@types/express": {
|
||||||
"version": "4.17.12",
|
"version": "4.17.12",
|
||||||
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.12.tgz",
|
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.12.tgz",
|
||||||
@@ -1871,15 +1856,6 @@
|
|||||||
"@types/range-parser": "*"
|
"@types/range-parser": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/express-session": {
|
|
||||||
"version": "1.17.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.4.tgz",
|
|
||||||
"integrity": "sha512-7cNlSI8+oOBUHTfPXMwDxF/Lchx5aJ3ho7+p9jJZYVg9dVDJFh3qdMXmJtRsysnvS+C6x46k9DRYmrmCkE+MVg==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@types/express": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/fs-extra": {
|
"node_modules/@types/fs-extra": {
|
||||||
"version": "9.0.13",
|
"version": "9.0.13",
|
||||||
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz",
|
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz",
|
||||||
@@ -2471,17 +2447,6 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/asn1.js": {
|
|
||||||
"version": "5.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz",
|
|
||||||
"integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==",
|
|
||||||
"dependencies": {
|
|
||||||
"bn.js": "^4.0.0",
|
|
||||||
"inherits": "^2.0.1",
|
|
||||||
"minimalistic-assert": "^1.0.0",
|
|
||||||
"safer-buffer": "^2.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/async": {
|
"node_modules/async": {
|
||||||
"version": "2.6.4",
|
"version": "2.6.4",
|
||||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
|
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
|
||||||
@@ -2709,11 +2674,6 @@
|
|||||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/bn.js": {
|
|
||||||
"version": "4.12.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
|
||||||
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
|
|
||||||
},
|
|
||||||
"node_modules/body-parser": {
|
"node_modules/body-parser": {
|
||||||
"version": "1.19.0",
|
"version": "1.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
|
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
|
||||||
@@ -2995,20 +2955,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001340",
|
"version": "1.0.30001243",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001340.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001243.tgz",
|
||||||
"integrity": "sha512-jUNz+a9blQTQVu4uFcn17uAD8IDizPzQkIKh3LCJfg9BkyIqExYYdyc/ZSlWUSKb8iYiXxKsxbv4zYSvkqjrxw==",
|
"integrity": "sha512-vNxw9mkTBtkmLFnJRv/2rhs1yufpDfCkBZexG3Y0xdOH2Z/eE/85E4Dl5j1YUN34nZVsSp6vVRFQRrez9wJMRA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": {
|
||||||
{
|
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
"url": "https://opencollective.com/browserslist"
|
"url": "https://opencollective.com/browserslist"
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "tidelift",
|
|
||||||
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
|
|
||||||
}
|
}
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"node_modules/chalk": {
|
"node_modules/chalk": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
@@ -3284,42 +3238,6 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/connect-mongo": {
|
|
||||||
"version": "4.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/connect-mongo/-/connect-mongo-4.6.0.tgz",
|
|
||||||
"integrity": "sha512-8new4Z7NLP3CGP65Aw6ls3xDBeKVvHRSh39CXuDZTQsvpeeU9oNMzfFgvqmHqZ6gWpxIl663RyoVEmCAGf1yOg==",
|
|
||||||
"dependencies": {
|
|
||||||
"debug": "^4.3.1",
|
|
||||||
"kruptein": "^3.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"mongodb": "^4.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/connect-mongo/node_modules/debug": {
|
|
||||||
"version": "4.3.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
|
||||||
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"ms": "2.1.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"supports-color": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/connect-mongo/node_modules/ms": {
|
|
||||||
"version": "2.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
|
||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
|
||||||
},
|
|
||||||
"node_modules/consola": {
|
"node_modules/consola": {
|
||||||
"version": "2.15.0",
|
"version": "2.15.0",
|
||||||
"resolved": "https://registry.npmjs.org/consola/-/consola-2.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/consola/-/consola-2.15.0.tgz",
|
||||||
@@ -3444,19 +3362,6 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/csrf": {
|
|
||||||
"version": "3.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/csrf/-/csrf-3.1.0.tgz",
|
|
||||||
"integrity": "sha512-uTqEnCvWRk042asU6JtapDTcJeeailFy4ydOQS28bj1hcLnYRiqi8SsD2jS412AY1I/4qdOwWZun774iqywf9w==",
|
|
||||||
"dependencies": {
|
|
||||||
"rndm": "1.2.0",
|
|
||||||
"tsscmp": "1.0.6",
|
|
||||||
"uid-safe": "2.1.5"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/cssom": {
|
"node_modules/cssom": {
|
||||||
"version": "0.4.4",
|
"version": "0.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz",
|
||||||
@@ -3481,40 +3386,6 @@
|
|||||||
"integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==",
|
"integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/csurf": {
|
|
||||||
"version": "1.11.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/csurf/-/csurf-1.11.0.tgz",
|
|
||||||
"integrity": "sha512-UCtehyEExKTxgiu8UHdGvHj4tnpE/Qctue03Giq5gPgMQ9cg/ciod5blZQ5a4uCEenNQjxyGuzygLdKUmee/bQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"cookie": "0.4.0",
|
|
||||||
"cookie-signature": "1.0.6",
|
|
||||||
"csrf": "3.1.0",
|
|
||||||
"http-errors": "~1.7.3"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.8.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/csurf/node_modules/http-errors": {
|
|
||||||
"version": "1.7.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz",
|
|
||||||
"integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==",
|
|
||||||
"dependencies": {
|
|
||||||
"depd": "~1.1.2",
|
|
||||||
"inherits": "2.0.4",
|
|
||||||
"setprototypeof": "1.1.1",
|
|
||||||
"statuses": ">= 1.5.0 < 2",
|
|
||||||
"toidentifier": "1.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/csurf/node_modules/inherits": {
|
|
||||||
"version": "2.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
|
||||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
|
||||||
},
|
|
||||||
"node_modules/csv-stringify": {
|
"node_modules/csv-stringify": {
|
||||||
"version": "5.6.5",
|
"version": "5.6.5",
|
||||||
"resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-5.6.5.tgz",
|
"resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-5.6.5.tgz",
|
||||||
@@ -4156,59 +4027,6 @@
|
|||||||
"node": ">= 0.10.0"
|
"node": ">= 0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/express-session": {
|
|
||||||
"version": "1.17.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.2.tgz",
|
|
||||||
"integrity": "sha512-mPcYcLA0lvh7D4Oqr5aNJFMtBMKPLl++OKKxkHzZ0U0oDq1rpKBnkR5f5vCHR26VeArlTOEF9td4x5IjICksRQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"cookie": "0.4.1",
|
|
||||||
"cookie-signature": "1.0.6",
|
|
||||||
"debug": "2.6.9",
|
|
||||||
"depd": "~2.0.0",
|
|
||||||
"on-headers": "~1.0.2",
|
|
||||||
"parseurl": "~1.3.3",
|
|
||||||
"safe-buffer": "5.2.1",
|
|
||||||
"uid-safe": "~2.1.5"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.8.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/express-session/node_modules/cookie": {
|
|
||||||
"version": "0.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
|
|
||||||
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/express-session/node_modules/depd": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/express-session/node_modules/safe-buffer": {
|
|
||||||
"version": "5.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
|
||||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "patreon",
|
|
||||||
"url": "https://www.patreon.com/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "consulting",
|
|
||||||
"url": "https://feross.org/support"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/fast-glob": {
|
"node_modules/fast-glob": {
|
||||||
"version": "3.2.11",
|
"version": "3.2.11",
|
||||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
|
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
|
||||||
@@ -4824,14 +4642,6 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/helmet": {
|
|
||||||
"version": "5.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/helmet/-/helmet-5.0.2.tgz",
|
|
||||||
"integrity": "sha512-QWlwUZZ8BtlvwYVTSDTBChGf8EOcQ2LkGMnQJxSzD1mUu8CCjXJZq/BXP8eWw4kikRnzlhtYo3lCk0ucmYA3Vg==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/html-encoding-sniffer": {
|
"node_modules/html-encoding-sniffer": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz",
|
||||||
@@ -7018,17 +6828,6 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/kruptein": {
|
|
||||||
"version": "3.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/kruptein/-/kruptein-3.0.4.tgz",
|
|
||||||
"integrity": "sha512-614v+4fgOkcw98lI7rMO9HZ+Y2cK6MGYcR/NSVhRXcClUb72LTAf2NibAh8CKSjalY81rfrrjLQgb8TW9RP03Q==",
|
|
||||||
"dependencies": {
|
|
||||||
"asn1.js": "^5.4.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/latest-version": {
|
"node_modules/latest-version": {
|
||||||
"version": "5.1.0",
|
"version": "5.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz",
|
||||||
@@ -7296,11 +7095,6 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/minimalistic-assert": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
|
|
||||||
},
|
|
||||||
"node_modules/minimatch": {
|
"node_modules/minimatch": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||||
@@ -7339,6 +7133,7 @@
|
|||||||
"version": "4.1.4",
|
"version": "4.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.1.4.tgz",
|
||||||
"integrity": "sha512-Cv/sk8on/tpvvqbEvR1h03mdyNdyvvO+WhtFlL4jrZ+DSsN/oSQHVqmJQI/sBCqqbOArFcYCAYDfyzqFwV4GSQ==",
|
"integrity": "sha512-Cv/sk8on/tpvvqbEvR1h03mdyNdyvvO+WhtFlL4jrZ+DSsN/oSQHVqmJQI/sBCqqbOArFcYCAYDfyzqFwV4GSQ==",
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bson": "^4.5.4",
|
"bson": "^4.5.4",
|
||||||
"denque": "^2.0.1",
|
"denque": "^2.0.1",
|
||||||
@@ -8176,9 +7971,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/pkg": {
|
"node_modules/pkg": {
|
||||||
"version": "5.6.0",
|
"version": "5.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/pkg/-/pkg-5.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/pkg/-/pkg-5.5.2.tgz",
|
||||||
"integrity": "sha512-mHrAVSQWmHA41RnUmRpC7pK9lNnMfdA16CF3cqOI22a8LZxOQzF7M8YWtA2nfs+d7I0MTDXOtkDsAsFXeCpYjg==",
|
"integrity": "sha512-pD0UB2ud01C6pVv2wpGsTYJrXI/bnvGRYvMLd44wFzA1p+A2jrlTGFPAYa7YEYzmitXhx23PqalaG1eUEnSwcA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "7.16.2",
|
"@babel/parser": "7.16.2",
|
||||||
@@ -8190,7 +7985,7 @@
|
|||||||
"into-stream": "^6.0.0",
|
"into-stream": "^6.0.0",
|
||||||
"minimist": "^1.2.5",
|
"minimist": "^1.2.5",
|
||||||
"multistream": "^4.1.0",
|
"multistream": "^4.1.0",
|
||||||
"pkg-fetch": "3.3.0",
|
"pkg-fetch": "3.2.6",
|
||||||
"prebuild-install": "6.1.4",
|
"prebuild-install": "6.1.4",
|
||||||
"progress": "^2.0.3",
|
"progress": "^2.0.3",
|
||||||
"resolve": "^1.20.0",
|
"resolve": "^1.20.0",
|
||||||
@@ -8222,9 +8017,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/pkg-fetch": {
|
"node_modules/pkg-fetch": {
|
||||||
"version": "3.3.0",
|
"version": "3.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/pkg-fetch/-/pkg-fetch-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/pkg-fetch/-/pkg-fetch-3.2.6.tgz",
|
||||||
"integrity": "sha512-xJnIZ1KP+8rNN+VLafwu4tEeV4m8IkFBDdCFqmAJz9K1aiXEtbARmdbEe6HlXWGSVuShSHjFXpfkKRkDBQ5kiA==",
|
"integrity": "sha512-Q8fx6SIT022g0cdSE4Axv/xpfHeltspo2gg1KsWRinLQZOTRRAtOOaEFghA1F3jJ8FVsh8hGrL/Pb6Ea5XHIFw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chalk": "^4.1.2",
|
"chalk": "^4.1.2",
|
||||||
@@ -8281,9 +8076,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/pkg-fetch/node_modules/semver": {
|
"node_modules/pkg-fetch/node_modules/semver": {
|
||||||
"version": "7.3.7",
|
"version": "7.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
|
||||||
"integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
|
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lru-cache": "^6.0.0"
|
"lru-cache": "^6.0.0"
|
||||||
@@ -8572,14 +8367,6 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/random-bytes": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
|
|
||||||
"integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/range-parser": {
|
"node_modules/range-parser": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||||
@@ -8760,11 +8547,6 @@
|
|||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rndm": {
|
|
||||||
"version": "1.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/rndm/-/rndm-1.2.0.tgz",
|
|
||||||
"integrity": "sha1-8z/pz7Urv9UgqhgyO8ZdsRCht2w="
|
|
||||||
},
|
|
||||||
"node_modules/run-parallel": {
|
"node_modules/run-parallel": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
||||||
@@ -9440,11 +9222,11 @@
|
|||||||
"integrity": "sha512-WvfPSfAAMlE/sKS6YkW47nX/hA7StmhYnAHc6wWCXNL0oclwLj6UXv0hQCkLnDgvebi0MEV40SJJpVjKUgH1IQ=="
|
"integrity": "sha512-WvfPSfAAMlE/sKS6YkW47nX/hA7StmhYnAHc6wWCXNL0oclwLj6UXv0hQCkLnDgvebi0MEV40SJJpVjKUgH1IQ=="
|
||||||
},
|
},
|
||||||
"node_modules/swagger-ui-express": {
|
"node_modules/swagger-ui-express": {
|
||||||
"version": "4.3.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.2.0.tgz",
|
||||||
"integrity": "sha512-jN46SEEe9EoXa3ZgZoKgnSF6z0w3tnM1yqhO4Y+Q4iZVc8JOQB960EZpIAz6rNROrDApVDwcMHR0mhlnc/5Omw==",
|
"integrity": "sha512-znrHTwh9UpvsjqgWopA4noIet7mi7UGuIYZ465YfUDKQ5Dpas0jxnkfUKCo+0aB17YCBv26AhIjiQYDV4uvJFA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"swagger-ui-dist": ">=4.1.3"
|
"swagger-ui-dist": ">3.52.5"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= v0.10.32"
|
"node": ">= v0.10.32"
|
||||||
@@ -9750,14 +9532,6 @@
|
|||||||
"yarn": ">=1.9.4"
|
"yarn": ">=1.9.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tsscmp": {
|
|
||||||
"version": "1.0.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz",
|
|
||||||
"integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.6.x"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/tunnel-agent": {
|
"node_modules/tunnel-agent": {
|
||||||
"version": "0.6.0",
|
"version": "0.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||||
@@ -9852,17 +9626,6 @@
|
|||||||
"node": ">=0.8.0"
|
"node": ">=0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/uid-safe": {
|
|
||||||
"version": "2.1.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
|
|
||||||
"integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
|
|
||||||
"dependencies": {
|
|
||||||
"random-bytes": "~1.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/unbox-primitive": {
|
"node_modules/unbox-primitive": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz",
|
||||||
@@ -11364,9 +11127,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@sasjs/core": {
|
"@sasjs/core": {
|
||||||
"version": "4.23.1",
|
"version": "4.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.23.1.tgz",
|
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.19.0.tgz",
|
||||||
"integrity": "sha512-9d6yEPJRRvPLMUkpyaiQ62SXNMMyt2l815jxWgFjnVOxKeUQv9TPyZqZ0FpmWdVe6EY8dv8GLlyaBpOLDnY6Vg=="
|
"integrity": "sha512-vG2YHJveQUQqN0YBhapXb8y+Qp4OniHzRedlqKRxyL0Pc+kwXx5co4Vo+dcOI5/MX0p+8oERP2aCR77s4FEUJg=="
|
||||||
},
|
},
|
||||||
"@sasjs/utils": {
|
"@sasjs/utils": {
|
||||||
"version": "2.42.1",
|
"version": "2.42.1",
|
||||||
@@ -11762,15 +11525,6 @@
|
|||||||
"integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==",
|
"integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/csurf": {
|
|
||||||
"version": "1.11.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/csurf/-/csurf-1.11.2.tgz",
|
|
||||||
"integrity": "sha512-9bc98EnwmC1S0aSJiA8rWwXtgXtXHHOQOsGHptImxFgqm6CeH+mIOunHRg6+/eg2tlmDMX3tY7XrWxo2M/nUNQ==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"@types/express-serve-static-core": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@types/express": {
|
"@types/express": {
|
||||||
"version": "4.17.12",
|
"version": "4.17.12",
|
||||||
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.12.tgz",
|
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.12.tgz",
|
||||||
@@ -11794,15 +11548,6 @@
|
|||||||
"@types/range-parser": "*"
|
"@types/range-parser": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/express-session": {
|
|
||||||
"version": "1.17.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.4.tgz",
|
|
||||||
"integrity": "sha512-7cNlSI8+oOBUHTfPXMwDxF/Lchx5aJ3ho7+p9jJZYVg9dVDJFh3qdMXmJtRsysnvS+C6x46k9DRYmrmCkE+MVg==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"@types/express": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@types/fs-extra": {
|
"@types/fs-extra": {
|
||||||
"version": "9.0.13",
|
"version": "9.0.13",
|
||||||
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz",
|
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz",
|
||||||
@@ -12314,17 +12059,6 @@
|
|||||||
"is-string": "^1.0.7"
|
"is-string": "^1.0.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"asn1.js": {
|
|
||||||
"version": "5.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz",
|
|
||||||
"integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==",
|
|
||||||
"requires": {
|
|
||||||
"bn.js": "^4.0.0",
|
|
||||||
"inherits": "^2.0.1",
|
|
||||||
"minimalistic-assert": "^1.0.0",
|
|
||||||
"safer-buffer": "^2.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"async": {
|
"async": {
|
||||||
"version": "2.6.4",
|
"version": "2.6.4",
|
||||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
|
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
|
||||||
@@ -12500,11 +12234,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bn.js": {
|
|
||||||
"version": "4.12.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
|
||||||
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
|
|
||||||
},
|
|
||||||
"body-parser": {
|
"body-parser": {
|
||||||
"version": "1.19.0",
|
"version": "1.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
|
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
|
||||||
@@ -12718,9 +12447,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"caniuse-lite": {
|
"caniuse-lite": {
|
||||||
"version": "1.0.30001340",
|
"version": "1.0.30001243",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001340.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001243.tgz",
|
||||||
"integrity": "sha512-jUNz+a9blQTQVu4uFcn17uAD8IDizPzQkIKh3LCJfg9BkyIqExYYdyc/ZSlWUSKb8iYiXxKsxbv4zYSvkqjrxw==",
|
"integrity": "sha512-vNxw9mkTBtkmLFnJRv/2rhs1yufpDfCkBZexG3Y0xdOH2Z/eE/85E4Dl5j1YUN34nZVsSp6vVRFQRrez9wJMRA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"chalk": {
|
"chalk": {
|
||||||
@@ -12952,30 +12681,6 @@
|
|||||||
"xdg-basedir": "^4.0.0"
|
"xdg-basedir": "^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"connect-mongo": {
|
|
||||||
"version": "4.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/connect-mongo/-/connect-mongo-4.6.0.tgz",
|
|
||||||
"integrity": "sha512-8new4Z7NLP3CGP65Aw6ls3xDBeKVvHRSh39CXuDZTQsvpeeU9oNMzfFgvqmHqZ6gWpxIl663RyoVEmCAGf1yOg==",
|
|
||||||
"requires": {
|
|
||||||
"debug": "^4.3.1",
|
|
||||||
"kruptein": "^3.0.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"debug": {
|
|
||||||
"version": "4.3.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
|
||||||
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
|
||||||
"requires": {
|
|
||||||
"ms": "2.1.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ms": {
|
|
||||||
"version": "2.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
|
||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"consola": {
|
"consola": {
|
||||||
"version": "2.15.0",
|
"version": "2.15.0",
|
||||||
"resolved": "https://registry.npmjs.org/consola/-/consola-2.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/consola/-/consola-2.15.0.tgz",
|
||||||
@@ -13078,16 +12783,6 @@
|
|||||||
"integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==",
|
"integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"csrf": {
|
|
||||||
"version": "3.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/csrf/-/csrf-3.1.0.tgz",
|
|
||||||
"integrity": "sha512-uTqEnCvWRk042asU6JtapDTcJeeailFy4ydOQS28bj1hcLnYRiqi8SsD2jS412AY1I/4qdOwWZun774iqywf9w==",
|
|
||||||
"requires": {
|
|
||||||
"rndm": "1.2.0",
|
|
||||||
"tsscmp": "1.0.6",
|
|
||||||
"uid-safe": "2.1.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"cssom": {
|
"cssom": {
|
||||||
"version": "0.4.4",
|
"version": "0.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz",
|
||||||
@@ -13111,36 +12806,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"csurf": {
|
|
||||||
"version": "1.11.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/csurf/-/csurf-1.11.0.tgz",
|
|
||||||
"integrity": "sha512-UCtehyEExKTxgiu8UHdGvHj4tnpE/Qctue03Giq5gPgMQ9cg/ciod5blZQ5a4uCEenNQjxyGuzygLdKUmee/bQ==",
|
|
||||||
"requires": {
|
|
||||||
"cookie": "0.4.0",
|
|
||||||
"cookie-signature": "1.0.6",
|
|
||||||
"csrf": "3.1.0",
|
|
||||||
"http-errors": "~1.7.3"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"http-errors": {
|
|
||||||
"version": "1.7.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz",
|
|
||||||
"integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==",
|
|
||||||
"requires": {
|
|
||||||
"depd": "~1.1.2",
|
|
||||||
"inherits": "2.0.4",
|
|
||||||
"setprototypeof": "1.1.1",
|
|
||||||
"statuses": ">= 1.5.0 < 2",
|
|
||||||
"toidentifier": "1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"inherits": {
|
|
||||||
"version": "2.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
|
||||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"csv-stringify": {
|
"csv-stringify": {
|
||||||
"version": "5.6.5",
|
"version": "5.6.5",
|
||||||
"resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-5.6.5.tgz",
|
"resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-5.6.5.tgz",
|
||||||
@@ -13638,38 +13303,6 @@
|
|||||||
"vary": "~1.1.2"
|
"vary": "~1.1.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"express-session": {
|
|
||||||
"version": "1.17.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.2.tgz",
|
|
||||||
"integrity": "sha512-mPcYcLA0lvh7D4Oqr5aNJFMtBMKPLl++OKKxkHzZ0U0oDq1rpKBnkR5f5vCHR26VeArlTOEF9td4x5IjICksRQ==",
|
|
||||||
"requires": {
|
|
||||||
"cookie": "0.4.1",
|
|
||||||
"cookie-signature": "1.0.6",
|
|
||||||
"debug": "2.6.9",
|
|
||||||
"depd": "~2.0.0",
|
|
||||||
"on-headers": "~1.0.2",
|
|
||||||
"parseurl": "~1.3.3",
|
|
||||||
"safe-buffer": "5.2.1",
|
|
||||||
"uid-safe": "~2.1.5"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"cookie": {
|
|
||||||
"version": "0.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
|
|
||||||
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA=="
|
|
||||||
},
|
|
||||||
"depd": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
|
|
||||||
},
|
|
||||||
"safe-buffer": {
|
|
||||||
"version": "5.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
|
||||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fast-glob": {
|
"fast-glob": {
|
||||||
"version": "3.2.11",
|
"version": "3.2.11",
|
||||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
|
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
|
||||||
@@ -14141,11 +13774,6 @@
|
|||||||
"integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==",
|
"integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"helmet": {
|
|
||||||
"version": "5.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/helmet/-/helmet-5.0.2.tgz",
|
|
||||||
"integrity": "sha512-QWlwUZZ8BtlvwYVTSDTBChGf8EOcQ2LkGMnQJxSzD1mUu8CCjXJZq/BXP8eWw4kikRnzlhtYo3lCk0ucmYA3Vg=="
|
|
||||||
},
|
|
||||||
"html-encoding-sniffer": {
|
"html-encoding-sniffer": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz",
|
||||||
@@ -15781,14 +15409,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
|
||||||
"integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="
|
"integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="
|
||||||
},
|
},
|
||||||
"kruptein": {
|
|
||||||
"version": "3.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/kruptein/-/kruptein-3.0.4.tgz",
|
|
||||||
"integrity": "sha512-614v+4fgOkcw98lI7rMO9HZ+Y2cK6MGYcR/NSVhRXcClUb72LTAf2NibAh8CKSjalY81rfrrjLQgb8TW9RP03Q==",
|
|
||||||
"requires": {
|
|
||||||
"asn1.js": "^5.4.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"latest-version": {
|
"latest-version": {
|
||||||
"version": "5.1.0",
|
"version": "5.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz",
|
||||||
@@ -15995,11 +15615,6 @@
|
|||||||
"integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==",
|
"integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"minimalistic-assert": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
|
|
||||||
},
|
|
||||||
"minimatch": {
|
"minimatch": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||||
@@ -16029,6 +15644,7 @@
|
|||||||
"version": "4.1.4",
|
"version": "4.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.1.4.tgz",
|
||||||
"integrity": "sha512-Cv/sk8on/tpvvqbEvR1h03mdyNdyvvO+WhtFlL4jrZ+DSsN/oSQHVqmJQI/sBCqqbOArFcYCAYDfyzqFwV4GSQ==",
|
"integrity": "sha512-Cv/sk8on/tpvvqbEvR1h03mdyNdyvvO+WhtFlL4jrZ+DSsN/oSQHVqmJQI/sBCqqbOArFcYCAYDfyzqFwV4GSQ==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"bson": "^4.5.4",
|
"bson": "^4.5.4",
|
||||||
"denque": "^2.0.1",
|
"denque": "^2.0.1",
|
||||||
@@ -16655,9 +16271,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pkg": {
|
"pkg": {
|
||||||
"version": "5.6.0",
|
"version": "5.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/pkg/-/pkg-5.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/pkg/-/pkg-5.5.2.tgz",
|
||||||
"integrity": "sha512-mHrAVSQWmHA41RnUmRpC7pK9lNnMfdA16CF3cqOI22a8LZxOQzF7M8YWtA2nfs+d7I0MTDXOtkDsAsFXeCpYjg==",
|
"integrity": "sha512-pD0UB2ud01C6pVv2wpGsTYJrXI/bnvGRYvMLd44wFzA1p+A2jrlTGFPAYa7YEYzmitXhx23PqalaG1eUEnSwcA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/parser": "7.16.2",
|
"@babel/parser": "7.16.2",
|
||||||
@@ -16669,7 +16285,7 @@
|
|||||||
"into-stream": "^6.0.0",
|
"into-stream": "^6.0.0",
|
||||||
"minimist": "^1.2.5",
|
"minimist": "^1.2.5",
|
||||||
"multistream": "^4.1.0",
|
"multistream": "^4.1.0",
|
||||||
"pkg-fetch": "3.3.0",
|
"pkg-fetch": "3.2.6",
|
||||||
"prebuild-install": "6.1.4",
|
"prebuild-install": "6.1.4",
|
||||||
"progress": "^2.0.3",
|
"progress": "^2.0.3",
|
||||||
"resolve": "^1.20.0",
|
"resolve": "^1.20.0",
|
||||||
@@ -16726,9 +16342,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pkg-fetch": {
|
"pkg-fetch": {
|
||||||
"version": "3.3.0",
|
"version": "3.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/pkg-fetch/-/pkg-fetch-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/pkg-fetch/-/pkg-fetch-3.2.6.tgz",
|
||||||
"integrity": "sha512-xJnIZ1KP+8rNN+VLafwu4tEeV4m8IkFBDdCFqmAJz9K1aiXEtbARmdbEe6HlXWGSVuShSHjFXpfkKRkDBQ5kiA==",
|
"integrity": "sha512-Q8fx6SIT022g0cdSE4Axv/xpfHeltspo2gg1KsWRinLQZOTRRAtOOaEFghA1F3jJ8FVsh8hGrL/Pb6Ea5XHIFw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"chalk": "^4.1.2",
|
"chalk": "^4.1.2",
|
||||||
@@ -16770,9 +16386,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"semver": {
|
"semver": {
|
||||||
"version": "7.3.7",
|
"version": "7.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
|
||||||
"integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
|
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"lru-cache": "^6.0.0"
|
"lru-cache": "^6.0.0"
|
||||||
@@ -16939,11 +16555,6 @@
|
|||||||
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
|
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"random-bytes": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
|
|
||||||
"integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs="
|
|
||||||
},
|
|
||||||
"range-parser": {
|
"range-parser": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||||
@@ -17081,11 +16692,6 @@
|
|||||||
"glob": "^7.1.3"
|
"glob": "^7.1.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rndm": {
|
|
||||||
"version": "1.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/rndm/-/rndm-1.2.0.tgz",
|
|
||||||
"integrity": "sha1-8z/pz7Urv9UgqhgyO8ZdsRCht2w="
|
|
||||||
},
|
|
||||||
"run-parallel": {
|
"run-parallel": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
||||||
@@ -17607,11 +17213,11 @@
|
|||||||
"integrity": "sha512-WvfPSfAAMlE/sKS6YkW47nX/hA7StmhYnAHc6wWCXNL0oclwLj6UXv0hQCkLnDgvebi0MEV40SJJpVjKUgH1IQ=="
|
"integrity": "sha512-WvfPSfAAMlE/sKS6YkW47nX/hA7StmhYnAHc6wWCXNL0oclwLj6UXv0hQCkLnDgvebi0MEV40SJJpVjKUgH1IQ=="
|
||||||
},
|
},
|
||||||
"swagger-ui-express": {
|
"swagger-ui-express": {
|
||||||
"version": "4.3.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.2.0.tgz",
|
||||||
"integrity": "sha512-jN46SEEe9EoXa3ZgZoKgnSF6z0w3tnM1yqhO4Y+Q4iZVc8JOQB960EZpIAz6rNROrDApVDwcMHR0mhlnc/5Omw==",
|
"integrity": "sha512-znrHTwh9UpvsjqgWopA4noIet7mi7UGuIYZ465YfUDKQ5Dpas0jxnkfUKCo+0aB17YCBv26AhIjiQYDV4uvJFA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"swagger-ui-dist": ">=4.1.3"
|
"swagger-ui-dist": ">3.52.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"symbol-tree": {
|
"symbol-tree": {
|
||||||
@@ -17823,11 +17429,6 @@
|
|||||||
"@tsoa/runtime": "^3.13.0"
|
"@tsoa/runtime": "^3.13.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tsscmp": {
|
|
||||||
"version": "1.0.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz",
|
|
||||||
"integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA=="
|
|
||||||
},
|
|
||||||
"tunnel-agent": {
|
"tunnel-agent": {
|
||||||
"version": "0.6.0",
|
"version": "0.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||||
@@ -17894,14 +17495,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"uid-safe": {
|
|
||||||
"version": "2.1.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
|
|
||||||
"integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
|
|
||||||
"requires": {
|
|
||||||
"random-bytes": "~1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"unbox-primitive": {
|
"unbox-primitive": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz",
|
||||||
|
|||||||
@@ -47,31 +47,25 @@
|
|||||||
},
|
},
|
||||||
"author": "4GL Ltd",
|
"author": "4GL Ltd",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sasjs/core": "^4.23.1",
|
"@sasjs/core": "^4.19.0",
|
||||||
"@sasjs/utils": "2.42.1",
|
"@sasjs/utils": "2.42.1",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"connect-mongo": "^4.6.0",
|
|
||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"csurf": "^1.11.0",
|
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"express-session": "^1.17.2",
|
|
||||||
"helmet": "^5.0.2",
|
|
||||||
"joi": "^17.4.2",
|
"joi": "^17.4.2",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"mongoose": "^6.0.12",
|
"mongoose": "^6.0.12",
|
||||||
"mongoose-sequence": "^5.3.1",
|
"mongoose-sequence": "^5.3.1",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"multer": "^1.4.3",
|
"multer": "^1.4.3",
|
||||||
"swagger-ui-express": "4.3.0"
|
"swagger-ui-express": "^4.1.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bcryptjs": "^2.4.2",
|
"@types/bcryptjs": "^2.4.2",
|
||||||
"@types/cookie-parser": "^1.4.2",
|
"@types/cookie-parser": "^1.4.2",
|
||||||
"@types/cors": "^2.8.12",
|
"@types/cors": "^2.8.12",
|
||||||
"@types/csurf": "^1.11.2",
|
|
||||||
"@types/express": "^4.17.12",
|
"@types/express": "^4.17.12",
|
||||||
"@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/mongoose-sequence": "^3.0.6",
|
"@types/mongoose-sequence": "^3.0.6",
|
||||||
@@ -85,7 +79,7 @@
|
|||||||
"jest": "^27.0.6",
|
"jest": "^27.0.6",
|
||||||
"mongodb-memory-server": "^8.0.0",
|
"mongodb-memory-server": "^8.0.0",
|
||||||
"nodemon": "^2.0.7",
|
"nodemon": "^2.0.7",
|
||||||
"pkg": "5.6.0",
|
"pkg": "5.5.2",
|
||||||
"prettier": "^2.3.1",
|
"prettier": "^2.3.1",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"supertest": "^6.1.3",
|
"supertest": "^6.1.3",
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
window.onload = function () {
|
|
||||||
// Build a system
|
|
||||||
var url = window.location.search.match(/url=([^&]+)/)
|
|
||||||
if (url && url.length > 1) {
|
|
||||||
url = decodeURIComponent(url[1])
|
|
||||||
} else {
|
|
||||||
url = window.location.origin
|
|
||||||
}
|
|
||||||
var options = {
|
|
||||||
customOptions: {
|
|
||||||
url: '/swagger.yaml',
|
|
||||||
requestInterceptor: function (request) {
|
|
||||||
request.credentials = 'include'
|
|
||||||
var cookie = document.cookie
|
|
||||||
var startIndex = cookie.indexOf('XSRF-TOKEN')
|
|
||||||
var csrf = cookie.slice(startIndex + 11).split('; ')[0]
|
|
||||||
request.headers['X-XSRF-TOKEN'] = csrf
|
|
||||||
return request
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
url = options.swaggerUrl || url
|
|
||||||
var urls = options.swaggerUrls
|
|
||||||
var customOptions = options.customOptions
|
|
||||||
var spec1 = options.swaggerDoc
|
|
||||||
var swaggerOptions = {
|
|
||||||
spec: spec1,
|
|
||||||
url: url,
|
|
||||||
urls: urls,
|
|
||||||
dom_id: '#swagger-ui',
|
|
||||||
deepLinking: true,
|
|
||||||
presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset],
|
|
||||||
plugins: [SwaggerUIBundle.plugins.DownloadUrl],
|
|
||||||
layout: 'StandaloneLayout'
|
|
||||||
}
|
|
||||||
for (var attrname in customOptions) {
|
|
||||||
swaggerOptions[attrname] = customOptions[attrname]
|
|
||||||
}
|
|
||||||
var ui = SwaggerUIBundle(swaggerOptions)
|
|
||||||
|
|
||||||
if (customOptions.oauth) {
|
|
||||||
ui.initOAuth(customOptions.oauth)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (customOptions.authAction) {
|
|
||||||
ui.authActions.authorize(customOptions.authAction)
|
|
||||||
}
|
|
||||||
|
|
||||||
window.ui = ui
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
const inputElement = document.getElementById('fileId')
|
|
||||||
|
|
||||||
document.getElementById('uploadButton').addEventListener('click', function () {
|
|
||||||
inputElement.click()
|
|
||||||
})
|
|
||||||
|
|
||||||
inputElement.addEventListener(
|
|
||||||
'change',
|
|
||||||
function () {
|
|
||||||
const fileList = this.files /* now you can work with the file list */
|
|
||||||
|
|
||||||
updateFileUploadMessage('Requesting ...')
|
|
||||||
|
|
||||||
const file = fileList[0]
|
|
||||||
const formData = new FormData()
|
|
||||||
|
|
||||||
formData.append('file', file)
|
|
||||||
|
|
||||||
axios
|
|
||||||
.post('/SASjsApi/drive/deploy/upload', formData)
|
|
||||||
.then((res) => res.data)
|
|
||||||
.then((data) => {
|
|
||||||
return (
|
|
||||||
data.message +
|
|
||||||
'\nstreamServiceName: ' +
|
|
||||||
data.streamServiceName +
|
|
||||||
'\nrefreshing page once alert box closes.'
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.then((message) => {
|
|
||||||
alert(message)
|
|
||||||
location.reload()
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
alert(error.response.data)
|
|
||||||
resetFileUpload()
|
|
||||||
updateFileUploadMessage('Upload New App')
|
|
||||||
})
|
|
||||||
},
|
|
||||||
false
|
|
||||||
)
|
|
||||||
|
|
||||||
function updateFileUploadMessage(message) {
|
|
||||||
document.getElementById('uploadMessage').innerHTML = message
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetFileUpload() {
|
|
||||||
inputElement.value = null
|
|
||||||
}
|
|
||||||
3
api/public/axios.min.js
vendored
3
api/public/axios.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -5,6 +5,36 @@ components:
|
|||||||
requestBodies: {}
|
requestBodies: {}
|
||||||
responses: {}
|
responses: {}
|
||||||
schemas:
|
schemas:
|
||||||
|
AuthorizeResponse:
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type: string
|
||||||
|
description: 'Authorization code'
|
||||||
|
example: someRandomCryptoString
|
||||||
|
required:
|
||||||
|
- code
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
AuthorizePayload:
|
||||||
|
properties:
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
description: 'Username for user'
|
||||||
|
example: secretuser
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
description: 'Password for user'
|
||||||
|
example: secretpassword
|
||||||
|
clientId:
|
||||||
|
type: string
|
||||||
|
description: 'Client ID'
|
||||||
|
example: clientID1
|
||||||
|
required:
|
||||||
|
- username
|
||||||
|
- password
|
||||||
|
- clientId
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
TokenResponse:
|
TokenResponse:
|
||||||
properties:
|
properties:
|
||||||
accessToken:
|
accessToken:
|
||||||
@@ -47,41 +77,6 @@ components:
|
|||||||
- userId
|
- userId
|
||||||
type: object
|
type: object
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
LoginPayload:
|
|
||||||
properties:
|
|
||||||
username:
|
|
||||||
type: string
|
|
||||||
description: 'Username for user'
|
|
||||||
example: secretuser
|
|
||||||
password:
|
|
||||||
type: string
|
|
||||||
description: 'Password for user'
|
|
||||||
example: secretpassword
|
|
||||||
required:
|
|
||||||
- username
|
|
||||||
- password
|
|
||||||
type: object
|
|
||||||
additionalProperties: false
|
|
||||||
AuthorizeResponse:
|
|
||||||
properties:
|
|
||||||
code:
|
|
||||||
type: string
|
|
||||||
description: 'Authorization code'
|
|
||||||
example: someRandomCryptoString
|
|
||||||
required:
|
|
||||||
- code
|
|
||||||
type: object
|
|
||||||
additionalProperties: false
|
|
||||||
AuthorizePayload:
|
|
||||||
properties:
|
|
||||||
clientId:
|
|
||||||
type: string
|
|
||||||
description: 'Client ID'
|
|
||||||
example: clientID1
|
|
||||||
required:
|
|
||||||
- clientId
|
|
||||||
type: object
|
|
||||||
additionalProperties: false
|
|
||||||
ClientPayload:
|
ClientPayload:
|
||||||
properties:
|
properties:
|
||||||
clientId:
|
clientId:
|
||||||
@@ -415,6 +410,14 @@ components:
|
|||||||
- description
|
- description
|
||||||
type: object
|
type: object
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
|
ExecuteReturnJsonPayload:
|
||||||
|
properties:
|
||||||
|
_program:
|
||||||
|
type: string
|
||||||
|
description: 'Location of SAS program'
|
||||||
|
example: /Public/somefolder/some.file
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
InfoResponse:
|
InfoResponse:
|
||||||
properties:
|
properties:
|
||||||
mode:
|
mode:
|
||||||
@@ -434,14 +437,6 @@ components:
|
|||||||
- protocol
|
- protocol
|
||||||
type: object
|
type: object
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
ExecuteReturnJsonPayload:
|
|
||||||
properties:
|
|
||||||
_program:
|
|
||||||
type: string
|
|
||||||
description: 'Location of SAS program'
|
|
||||||
example: /Public/somefolder/some.file
|
|
||||||
type: object
|
|
||||||
additionalProperties: false
|
|
||||||
securitySchemes:
|
securitySchemes:
|
||||||
bearerAuth:
|
bearerAuth:
|
||||||
type: http
|
type: http
|
||||||
@@ -455,6 +450,30 @@ info:
|
|||||||
name: '4GL Ltd'
|
name: '4GL Ltd'
|
||||||
openapi: 3.0.0
|
openapi: 3.0.0
|
||||||
paths:
|
paths:
|
||||||
|
/SASjsApi/auth/authorize:
|
||||||
|
post:
|
||||||
|
operationId: Authorize
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Ok
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/AuthorizeResponse'
|
||||||
|
examples:
|
||||||
|
'Example 1':
|
||||||
|
value: {code: someRandomCryptoString}
|
||||||
|
summary: 'Accept a valid username/password, plus a CLIENT_ID, and return an AUTH_CODE'
|
||||||
|
tags:
|
||||||
|
- Auth
|
||||||
|
security: []
|
||||||
|
parameters: []
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/AuthorizePayload'
|
||||||
/SASjsApi/auth/token:
|
/SASjsApi/auth/token:
|
||||||
post:
|
post:
|
||||||
operationId: Token
|
operationId: Token
|
||||||
@@ -512,86 +531,6 @@ paths:
|
|||||||
-
|
-
|
||||||
bearerAuth: []
|
bearerAuth: []
|
||||||
parameters: []
|
parameters: []
|
||||||
/:
|
|
||||||
get:
|
|
||||||
operationId: Home
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Ok
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
summary: 'Render index.html'
|
|
||||||
tags:
|
|
||||||
- Web
|
|
||||||
security: []
|
|
||||||
parameters: []
|
|
||||||
/SASLogon/login:
|
|
||||||
post:
|
|
||||||
operationId: Login
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Ok
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
properties:
|
|
||||||
user: {properties: {displayName: {type: string}, username: {type: string}}, required: [displayName, username], type: object}
|
|
||||||
loggedIn: {type: boolean}
|
|
||||||
required:
|
|
||||||
- user
|
|
||||||
- loggedIn
|
|
||||||
type: object
|
|
||||||
summary: 'Accept a valid username/password'
|
|
||||||
tags:
|
|
||||||
- Web
|
|
||||||
security: []
|
|
||||||
parameters: []
|
|
||||||
requestBody:
|
|
||||||
required: true
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/LoginPayload'
|
|
||||||
/SASLogon/authorize:
|
|
||||||
post:
|
|
||||||
operationId: Authorize
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Ok
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/AuthorizeResponse'
|
|
||||||
examples:
|
|
||||||
'Example 1':
|
|
||||||
value: {code: someRandomCryptoString}
|
|
||||||
summary: 'Accept a valid username/password, plus a CLIENT_ID, and return an AUTH_CODE'
|
|
||||||
tags:
|
|
||||||
- Web
|
|
||||||
security: []
|
|
||||||
parameters: []
|
|
||||||
requestBody:
|
|
||||||
required: true
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/AuthorizePayload'
|
|
||||||
/logout:
|
|
||||||
get:
|
|
||||||
operationId: Logout
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Ok
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema: {}
|
|
||||||
summary: 'Accept a valid username/password'
|
|
||||||
tags:
|
|
||||||
- Web
|
|
||||||
security: []
|
|
||||||
parameters: []
|
|
||||||
/SASjsApi/client:
|
/SASjsApi/client:
|
||||||
post:
|
post:
|
||||||
operationId: CreateClient
|
operationId: CreateClient
|
||||||
@@ -1238,24 +1177,6 @@ paths:
|
|||||||
format: double
|
format: double
|
||||||
type: number
|
type: number
|
||||||
example: '6789'
|
example: '6789'
|
||||||
/SASjsApi/info:
|
|
||||||
get:
|
|
||||||
operationId: Info
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Ok
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/InfoResponse'
|
|
||||||
examples:
|
|
||||||
'Example 1':
|
|
||||||
value: {mode: desktop, cors: enable, whiteList: ['http://example.com', 'http://example2.com'], protocol: http}
|
|
||||||
summary: 'Get server info (mode, cors, whiteList, protocol).'
|
|
||||||
tags:
|
|
||||||
- Info
|
|
||||||
security: []
|
|
||||||
parameters: []
|
|
||||||
/SASjsApi/session:
|
/SASjsApi/session:
|
||||||
get:
|
get:
|
||||||
operationId: Session
|
operationId: Session
|
||||||
@@ -1338,6 +1259,24 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/ExecuteReturnJsonPayload'
|
$ref: '#/components/schemas/ExecuteReturnJsonPayload'
|
||||||
|
/SASjsApi/info:
|
||||||
|
get:
|
||||||
|
operationId: Info
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Ok
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/InfoResponse'
|
||||||
|
examples:
|
||||||
|
'Example 1':
|
||||||
|
value: {mode: desktop, cors: enable, whiteList: ['http://example.com', 'http://example2.com'], protocol: http}
|
||||||
|
summary: 'Get server info (mode, cors, whiteList, protocol).'
|
||||||
|
tags:
|
||||||
|
- Info
|
||||||
|
security: []
|
||||||
|
parameters: []
|
||||||
servers:
|
servers:
|
||||||
-
|
-
|
||||||
url: /
|
url: /
|
||||||
@@ -1369,6 +1308,3 @@ tags:
|
|||||||
-
|
-
|
||||||
name: CODE
|
name: CODE
|
||||||
description: 'Operations on SAS code'
|
description: 'Operations on SAS code'
|
||||||
-
|
|
||||||
name: Web
|
|
||||||
description: 'Operations on Web'
|
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import express, { ErrorRequestHandler } from 'express'
|
import express, { ErrorRequestHandler } from 'express'
|
||||||
import csrf from 'csurf'
|
|
||||||
import session from 'express-session'
|
|
||||||
import MongoStore from 'connect-mongo'
|
|
||||||
import morgan from 'morgan'
|
import morgan from 'morgan'
|
||||||
import cookieParser from 'cookie-parser'
|
import cookieParser from 'cookie-parser'
|
||||||
import dotenv from 'dotenv'
|
import dotenv from 'dotenv'
|
||||||
import cors from 'cors'
|
import cors from 'cors'
|
||||||
import helmet from 'helmet'
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
connectDB,
|
connectDB,
|
||||||
@@ -17,54 +13,13 @@ import {
|
|||||||
setProcessVariables,
|
setProcessVariables,
|
||||||
setupFolders
|
setupFolders
|
||||||
} from './utils'
|
} from './utils'
|
||||||
import { getEnvCSPDirectives } from './utils/parseHelmetConfig'
|
|
||||||
|
|
||||||
dotenv.config()
|
dotenv.config()
|
||||||
|
|
||||||
const app = express()
|
const app = express()
|
||||||
|
|
||||||
app.use(cookieParser())
|
const { MODE, CORS, WHITELIST } = process.env
|
||||||
app.use(morgan('tiny'))
|
|
||||||
|
|
||||||
const { MODE, CORS, WHITELIST, PROTOCOL, HELMET_CSP_CONFIG_PATH, HELMET_COEP } =
|
|
||||||
process.env
|
|
||||||
|
|
||||||
export const cookieOptions = {
|
|
||||||
secure: PROTOCOL === 'https',
|
|
||||||
httpOnly: true,
|
|
||||||
maxAge: 24 * 60 * 60 * 1000 // 24 hours
|
|
||||||
}
|
|
||||||
|
|
||||||
const cspConfigJson: { [key: string]: string[] | null } = getEnvCSPDirectives(
|
|
||||||
HELMET_CSP_CONFIG_PATH
|
|
||||||
)
|
|
||||||
const coepFlag =
|
|
||||||
HELMET_COEP === 'true' || HELMET_COEP === undefined ? true : false
|
|
||||||
if (PROTOCOL === 'http') cspConfigJson['upgrade-insecure-requests'] = null
|
|
||||||
|
|
||||||
/***********************************
|
|
||||||
* CSRF Protection *
|
|
||||||
***********************************/
|
|
||||||
export const csrfProtection = csrf({ cookie: cookieOptions })
|
|
||||||
|
|
||||||
/***********************************
|
|
||||||
* Handle security and origin *
|
|
||||||
***********************************/
|
|
||||||
app.use(
|
|
||||||
helmet({
|
|
||||||
contentSecurityPolicy: {
|
|
||||||
directives: {
|
|
||||||
...helmet.contentSecurityPolicy.getDefaultDirectives(),
|
|
||||||
...cspConfigJson
|
|
||||||
}
|
|
||||||
},
|
|
||||||
crossOriginEmbedderPolicy: coepFlag
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
/***********************************
|
|
||||||
* Enabling CORS *
|
|
||||||
***********************************/
|
|
||||||
if (MODE?.trim() !== 'server' || CORS?.trim() === 'enable') {
|
if (MODE?.trim() !== 'server' || CORS?.trim() === 'enable') {
|
||||||
const whiteList: string[] = []
|
const whiteList: string[] = []
|
||||||
WHITELIST?.split(' ')
|
WHITELIST?.split(' ')
|
||||||
@@ -79,39 +34,12 @@ if (MODE?.trim() !== 'server' || CORS?.trim() === 'enable') {
|
|||||||
app.use(cors({ credentials: true, origin: whiteList }))
|
app.use(cors({ credentials: true, origin: whiteList }))
|
||||||
}
|
}
|
||||||
|
|
||||||
/***********************************
|
app.use(cookieParser())
|
||||||
* DB Connection & *
|
app.use(morgan('tiny'))
|
||||||
* Express Sessions *
|
|
||||||
* With Mongo Store *
|
|
||||||
***********************************/
|
|
||||||
if (MODE?.trim() === 'server') {
|
|
||||||
let store: MongoStore | undefined
|
|
||||||
|
|
||||||
// NOTE: when exporting app.js as agent for supertest
|
|
||||||
// we should exclude connecting to the real database
|
|
||||||
if (process.env.NODE_ENV !== 'test') {
|
|
||||||
const clientPromise = connectDB().then((conn) => conn!.getClient() as any)
|
|
||||||
|
|
||||||
store = MongoStore.create({ clientPromise, collectionName: 'sessions' })
|
|
||||||
}
|
|
||||||
|
|
||||||
app.use(
|
|
||||||
session({
|
|
||||||
secret: process.env.SESSION_SECRET as string,
|
|
||||||
saveUninitialized: false, // don't create session until something stored
|
|
||||||
resave: false, //don't save session if unmodified
|
|
||||||
store,
|
|
||||||
cookie: cookieOptions
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
app.use(express.json({ limit: '100mb' }))
|
app.use(express.json({ limit: '100mb' }))
|
||||||
app.use(express.static(path.join(__dirname, '../public')))
|
app.use(express.static(path.join(__dirname, '../public')))
|
||||||
|
|
||||||
const onError: ErrorRequestHandler = (err, req, res, next) => {
|
const onError: ErrorRequestHandler = (err, req, res, next) => {
|
||||||
if (err.code === 'EBADCSRFTOKEN')
|
|
||||||
return res.status(400).send('Invalid CSRF token!')
|
|
||||||
|
|
||||||
console.error(err.stack)
|
console.error(err.stack)
|
||||||
res.status(500).send('Something broke!')
|
res.status(500).send('Something broke!')
|
||||||
}
|
}
|
||||||
@@ -133,5 +61,6 @@ export default setProcessVariables().then(async () => {
|
|||||||
|
|
||||||
app.use(onError)
|
app.use(onError)
|
||||||
|
|
||||||
|
await connectDB()
|
||||||
return app
|
return app
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { Security, Route, Tags, Example, Post, Body, Query, Hidden } from 'tsoa'
|
import { Security, Route, Tags, Example, Post, Body, Query, Hidden } from 'tsoa'
|
||||||
import jwt from 'jsonwebtoken'
|
import jwt from 'jsonwebtoken'
|
||||||
|
import User from '../model/User'
|
||||||
import { InfoJWT } from '../types'
|
import { InfoJWT } from '../types'
|
||||||
import {
|
import {
|
||||||
generateAccessToken,
|
generateAccessToken,
|
||||||
|
generateAuthCode,
|
||||||
generateRefreshToken,
|
generateRefreshToken,
|
||||||
removeTokensInDB,
|
removeTokensInDB,
|
||||||
saveTokensInDB
|
saveTokensInDB
|
||||||
@@ -22,6 +24,20 @@ export class AuthController {
|
|||||||
static deleteCode = (userId: number, clientId: string) =>
|
static deleteCode = (userId: number, clientId: string) =>
|
||||||
delete AuthController.authCodes[userId][clientId]
|
delete AuthController.authCodes[userId][clientId]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Accept a valid username/password, plus a CLIENT_ID, and return an AUTH_CODE
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Example<AuthorizeResponse>({
|
||||||
|
code: 'someRandomCryptoString'
|
||||||
|
})
|
||||||
|
@Post('/authorize')
|
||||||
|
public async authorize(
|
||||||
|
@Body() body: AuthorizePayload
|
||||||
|
): Promise<AuthorizeResponse> {
|
||||||
|
return authorize(body)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Accepts client/auth code and returns access/refresh tokens
|
* @summary Accepts client/auth code and returns access/refresh tokens
|
||||||
*
|
*
|
||||||
@@ -62,6 +78,30 @@ export class AuthController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const authorize = async (data: any): Promise<AuthorizeResponse> => {
|
||||||
|
const { username, password, clientId } = data
|
||||||
|
|
||||||
|
// Authenticate User
|
||||||
|
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.')
|
||||||
|
|
||||||
|
// generate authorization code against clientId
|
||||||
|
const userInfo: InfoJWT = {
|
||||||
|
clientId,
|
||||||
|
userId: user.id
|
||||||
|
}
|
||||||
|
const code = AuthController.saveCode(
|
||||||
|
user.id,
|
||||||
|
clientId,
|
||||||
|
generateAuthCode(userInfo)
|
||||||
|
)
|
||||||
|
|
||||||
|
return { code }
|
||||||
|
}
|
||||||
|
|
||||||
const token = async (data: any): Promise<TokenResponse> => {
|
const token = async (data: any): Promise<TokenResponse> => {
|
||||||
const { clientId, code } = data
|
const { clientId, code } = data
|
||||||
|
|
||||||
@@ -99,6 +139,32 @@ const logout = async (userInfo: InfoJWT) => {
|
|||||||
await removeTokensInDB(userInfo.userId, userInfo.clientId)
|
await removeTokensInDB(userInfo.userId, userInfo.clientId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface AuthorizePayload {
|
||||||
|
/**
|
||||||
|
* Username for user
|
||||||
|
* @example "secretuser"
|
||||||
|
*/
|
||||||
|
username: string
|
||||||
|
/**
|
||||||
|
* Password for user
|
||||||
|
* @example "secretpassword"
|
||||||
|
*/
|
||||||
|
password: string
|
||||||
|
/**
|
||||||
|
* Client ID
|
||||||
|
* @example "clientID1"
|
||||||
|
*/
|
||||||
|
clientId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AuthorizeResponse {
|
||||||
|
/**
|
||||||
|
* Authorization code
|
||||||
|
* @example "someRandomCryptoString"
|
||||||
|
*/
|
||||||
|
code: string
|
||||||
|
}
|
||||||
|
|
||||||
interface TokenPayload {
|
interface TokenPayload {
|
||||||
/**
|
/**
|
||||||
* Client ID
|
* Client ID
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Request, Security, Route, Tags, Post, Body } from 'tsoa'
|
|||||||
import { ExecuteReturnJson, ExecutionController } from './internal'
|
import { ExecuteReturnJson, ExecutionController } from './internal'
|
||||||
import { PreProgramVars } from '../types'
|
import { PreProgramVars } from '../types'
|
||||||
import { ExecuteReturnJsonResponse } from '.'
|
import { ExecuteReturnJsonResponse } from '.'
|
||||||
import { getPreProgramVariables, parseLogToArray } from '../utils'
|
import { parseLogToArray } from '../utils'
|
||||||
|
|
||||||
interface ExecuteSASCodePayload {
|
interface ExecuteSASCodePayload {
|
||||||
/**
|
/**
|
||||||
@@ -56,3 +56,16 @@ const executeSASCode = async (req: any, { code }: ExecuteSASCodePayload) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getPreProgramVariables = (req: any): PreProgramVars => {
|
||||||
|
const host = req.get('host')
|
||||||
|
const protocol = req.protocol + '://'
|
||||||
|
const { user, accessToken } = req
|
||||||
|
return {
|
||||||
|
username: user.username,
|
||||||
|
userId: user.userId,
|
||||||
|
displayName: user.displayName,
|
||||||
|
serverUrl: protocol + host,
|
||||||
|
accessToken
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ export * from './client'
|
|||||||
export * from './code'
|
export * from './code'
|
||||||
export * from './drive'
|
export * from './drive'
|
||||||
export * from './group'
|
export * from './group'
|
||||||
export * from './info'
|
|
||||||
export * from './session'
|
export * from './session'
|
||||||
export * from './stp'
|
export * from './stp'
|
||||||
export * from './user'
|
export * from './user'
|
||||||
export * from './web'
|
export * from './info'
|
||||||
|
|||||||
@@ -25,8 +25,9 @@ export class InfoController {
|
|||||||
const response = {
|
const response = {
|
||||||
mode: process.env.MODE ?? 'desktop',
|
mode: process.env.MODE ?? 'desktop',
|
||||||
cors:
|
cors:
|
||||||
process.env.CORS ||
|
process.env.CORS ?? process.env.MODE === 'server'
|
||||||
(process.env.MODE === 'server' ? 'disable' : 'enable'),
|
? 'disable'
|
||||||
|
: 'enable',
|
||||||
whiteList:
|
whiteList:
|
||||||
process.env.WHITELIST?.split(' ')?.filter((url) => !!url) ?? [],
|
process.env.WHITELIST?.split(' ')?.filter((url) => !!url) ?? [],
|
||||||
protocol: process.env.PROTOCOL ?? 'http'
|
protocol: process.env.PROTOCOL ?? 'http'
|
||||||
|
|||||||
@@ -75,12 +75,12 @@ 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, 'accessToken.txt')
|
||||||
|
|
||||||
await createFile(weboutPath, '')
|
await createFile(weboutPath, '')
|
||||||
await createFile(
|
await createFile(
|
||||||
tokenFile,
|
tokenFile,
|
||||||
preProgramVariables?.httpHeaders.join('\n') ?? ''
|
preProgramVariables?.accessToken ?? 'accessToken'
|
||||||
)
|
)
|
||||||
|
|
||||||
const varStatments = Object.keys(vars).reduce(
|
const varStatments = Object.keys(vars).reduce(
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export class SessionController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const session = (req: any) => ({
|
const session = (req: any) => ({
|
||||||
id: req.user.userId,
|
id: req.user.id,
|
||||||
username: req.user.username,
|
username: req.user.username,
|
||||||
displayName: req.user.displayName
|
displayName: req.user.displayName
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ import {
|
|||||||
ExecutionController,
|
ExecutionController,
|
||||||
ExecutionVars
|
ExecutionVars
|
||||||
} from './internal'
|
} from './internal'
|
||||||
|
import { PreProgramVars } from '../types'
|
||||||
import {
|
import {
|
||||||
getPreProgramVariables,
|
|
||||||
getTmpFilesFolderPath,
|
getTmpFilesFolderPath,
|
||||||
HTTPHeaders,
|
HTTPHeaders,
|
||||||
isDebugOn,
|
isDebugOn,
|
||||||
@@ -210,3 +210,16 @@ const executeReturnJson = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getPreProgramVariables = (req: any): PreProgramVars => {
|
||||||
|
const host = req.get('host')
|
||||||
|
const protocol = req.protocol + '://'
|
||||||
|
const { user, accessToken } = req
|
||||||
|
return {
|
||||||
|
username: user.username,
|
||||||
|
userId: user.userId,
|
||||||
|
displayName: user.displayName,
|
||||||
|
serverUrl: protocol + host,
|
||||||
|
accessToken
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,156 +0,0 @@
|
|||||||
import path from 'path'
|
|
||||||
import express from 'express'
|
|
||||||
import { Request, Route, Tags, Post, Body, Get, Example } from 'tsoa'
|
|
||||||
import { readFile } from '@sasjs/utils'
|
|
||||||
|
|
||||||
import User from '../model/User'
|
|
||||||
import Client from '../model/Client'
|
|
||||||
import { getWebBuildFolderPath, generateAuthCode } from '../utils'
|
|
||||||
import { InfoJWT } from '../types'
|
|
||||||
import { AuthController } from './auth'
|
|
||||||
|
|
||||||
@Route('/')
|
|
||||||
@Tags('Web')
|
|
||||||
export class WebController {
|
|
||||||
/**
|
|
||||||
* @summary Render index.html
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Get('/')
|
|
||||||
public async home() {
|
|
||||||
return home()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Accept a valid username/password
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Post('/SASLogon/login')
|
|
||||||
public async login(
|
|
||||||
@Request() req: express.Request,
|
|
||||||
@Body() body: LoginPayload
|
|
||||||
) {
|
|
||||||
return login(req, body)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Accept a valid username/password, plus a CLIENT_ID, and return an AUTH_CODE
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Example<AuthorizeResponse>({
|
|
||||||
code: 'someRandomCryptoString'
|
|
||||||
})
|
|
||||||
@Post('/SASLogon/authorize')
|
|
||||||
public async authorize(
|
|
||||||
@Request() req: express.Request,
|
|
||||||
@Body() body: AuthorizePayload
|
|
||||||
): Promise<AuthorizeResponse> {
|
|
||||||
return authorize(req, body.clientId)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Accept a valid username/password
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Get('/logout')
|
|
||||||
public async logout(@Request() req: express.Request) {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
req.session.destroy(() => {
|
|
||||||
resolve(true)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const home = async () => {
|
|
||||||
const indexHtmlPath = path.join(getWebBuildFolderPath(), 'index.html')
|
|
||||||
|
|
||||||
// Attention! Cannot use fileExists here,
|
|
||||||
// due to limitation after building executable
|
|
||||||
const content = await readFile(indexHtmlPath)
|
|
||||||
|
|
||||||
return content
|
|
||||||
}
|
|
||||||
|
|
||||||
const login = async (
|
|
||||||
req: express.Request,
|
|
||||||
{ username, password }: LoginPayload
|
|
||||||
) => {
|
|
||||||
// Authenticate User
|
|
||||||
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.')
|
|
||||||
|
|
||||||
req.session.loggedIn = true
|
|
||||||
req.session.user = {
|
|
||||||
userId: user.id,
|
|
||||||
clientId: 'web_app',
|
|
||||||
username: user.username,
|
|
||||||
displayName: user.displayName,
|
|
||||||
isAdmin: user.isAdmin,
|
|
||||||
isActive: user.isActive
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
loggedIn: true,
|
|
||||||
user: {
|
|
||||||
username: user.username,
|
|
||||||
displayName: user.displayName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const authorize = async (
|
|
||||||
req: express.Request,
|
|
||||||
clientId: string
|
|
||||||
): Promise<AuthorizeResponse> => {
|
|
||||||
const userId = req.session.user?.userId
|
|
||||||
if (!userId) throw new Error('Invalid userId.')
|
|
||||||
|
|
||||||
const client = await Client.findOne({ clientId })
|
|
||||||
if (!client) throw new Error('Invalid clientId.')
|
|
||||||
|
|
||||||
// generate authorization code against clientId
|
|
||||||
const userInfo: InfoJWT = {
|
|
||||||
clientId,
|
|
||||||
userId
|
|
||||||
}
|
|
||||||
const code = AuthController.saveCode(
|
|
||||||
userId,
|
|
||||||
clientId,
|
|
||||||
generateAuthCode(userInfo)
|
|
||||||
)
|
|
||||||
|
|
||||||
return { code }
|
|
||||||
}
|
|
||||||
|
|
||||||
interface LoginPayload {
|
|
||||||
/**
|
|
||||||
* Username for user
|
|
||||||
* @example "secretuser"
|
|
||||||
*/
|
|
||||||
username: string
|
|
||||||
/**
|
|
||||||
* Password for user
|
|
||||||
* @example "secretpassword"
|
|
||||||
*/
|
|
||||||
password: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AuthorizePayload {
|
|
||||||
/**
|
|
||||||
* Client ID
|
|
||||||
* @example "clientID1"
|
|
||||||
*/
|
|
||||||
clientId: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AuthorizeResponse {
|
|
||||||
/**
|
|
||||||
* Authorization code
|
|
||||||
* @example "someRandomCryptoString"
|
|
||||||
*/
|
|
||||||
code: string
|
|
||||||
}
|
|
||||||
@@ -1,16 +1,7 @@
|
|||||||
import jwt from 'jsonwebtoken'
|
import jwt from 'jsonwebtoken'
|
||||||
import { csrfProtection } from '../app'
|
|
||||||
import { verifyTokenInDB } from '../utils'
|
import { verifyTokenInDB } from '../utils'
|
||||||
|
|
||||||
export const authenticateAccessToken = (req: any, res: any, next: any) => {
|
export const authenticateAccessToken = (req: any, res: any, next: any) => {
|
||||||
// if request is coming from web and has valid session
|
|
||||||
// we can validate the request and check for CSRF Token
|
|
||||||
if (req.session?.loggedIn) {
|
|
||||||
req.user = req.session.user
|
|
||||||
|
|
||||||
return csrfProtection(req, res, next)
|
|
||||||
}
|
|
||||||
|
|
||||||
authenticateToken(
|
authenticateToken(
|
||||||
req,
|
req,
|
||||||
res,
|
res,
|
||||||
@@ -52,7 +43,9 @@ const authenticateToken = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const authHeader = req.headers['authorization']
|
const authHeader = req.headers['authorization']
|
||||||
const token = authHeader?.split(' ')[1]
|
const token =
|
||||||
|
authHeader?.split(' ')[1] ??
|
||||||
|
(tokenType === 'accessToken' ? req.cookies.accessToken : '')
|
||||||
if (!token) return res.sendStatus(401)
|
if (!token) return res.sendStatus(401)
|
||||||
|
|
||||||
jwt.verify(token, key, async (err: any, data: any) => {
|
jwt.verify(token, key, async (err: any, data: any) => {
|
||||||
|
|||||||
@@ -1,26 +1,63 @@
|
|||||||
import express from 'express'
|
import express from 'express'
|
||||||
|
|
||||||
import { AuthController } from '../../controllers/'
|
import { AuthController } from '../../controllers/'
|
||||||
|
import Client from '../../model/Client'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
authenticateAccessToken,
|
authenticateAccessToken,
|
||||||
authenticateRefreshToken
|
authenticateRefreshToken
|
||||||
} from '../../middlewares'
|
} from '../../middlewares'
|
||||||
|
|
||||||
import { authorizeValidation, tokenValidation } from '../../utils'
|
import {
|
||||||
|
authorizeValidation,
|
||||||
|
getDesktopFields,
|
||||||
|
tokenValidation
|
||||||
|
} from '../../utils'
|
||||||
import { InfoJWT } from '../../types'
|
import { InfoJWT } from '../../types'
|
||||||
|
|
||||||
const authRouter = express.Router()
|
const authRouter = express.Router()
|
||||||
const controller = new AuthController()
|
|
||||||
|
const clientIDs = new Set()
|
||||||
|
|
||||||
|
export const populateClients = async () => {
|
||||||
|
const result = await Client.find()
|
||||||
|
clientIDs.clear()
|
||||||
|
result.forEach((r) => {
|
||||||
|
clientIDs.add(r.clientId)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
authRouter.post('/authorize', async (req, res) => {
|
||||||
|
const { error, value: body } = authorizeValidation(req.body)
|
||||||
|
if (error) return res.status(400).send(error.details[0].message)
|
||||||
|
|
||||||
|
const { clientId } = body
|
||||||
|
|
||||||
|
// Verify client ID
|
||||||
|
if (!clientIDs.has(clientId)) {
|
||||||
|
return res.status(403).send('Invalid clientId.')
|
||||||
|
}
|
||||||
|
|
||||||
|
const controller = new AuthController()
|
||||||
|
try {
|
||||||
|
const response = await controller.authorize(body)
|
||||||
|
|
||||||
|
res.send(response)
|
||||||
|
} catch (err: any) {
|
||||||
|
res.status(403).send(err.toString())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
const controller = new AuthController()
|
||||||
try {
|
try {
|
||||||
const response = await controller.token(body)
|
const response = await controller.token(body)
|
||||||
|
const { accessToken } = response
|
||||||
|
|
||||||
res.send(response)
|
res.cookie('accessToken', accessToken).send(response)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
res.status(403).send(err.toString())
|
res.status(403).send(err.toString())
|
||||||
}
|
}
|
||||||
@@ -29,6 +66,7 @@ authRouter.post('/token', async (req, res) => {
|
|||||||
authRouter.post('/refresh', authenticateRefreshToken, async (req: any, res) => {
|
authRouter.post('/refresh', authenticateRefreshToken, async (req: any, res) => {
|
||||||
const userInfo: InfoJWT = req.user
|
const userInfo: InfoJWT = req.user
|
||||||
|
|
||||||
|
const controller = new AuthController()
|
||||||
try {
|
try {
|
||||||
const response = await controller.refresh(userInfo)
|
const response = await controller.refresh(userInfo)
|
||||||
|
|
||||||
@@ -41,6 +79,7 @@ authRouter.post('/refresh', authenticateRefreshToken, async (req: any, res) => {
|
|||||||
authRouter.delete('/logout', authenticateAccessToken, async (req: any, res) => {
|
authRouter.delete('/logout', authenticateAccessToken, async (req: any, res) => {
|
||||||
const userInfo: InfoJWT = req.user
|
const userInfo: InfoJWT = req.user
|
||||||
|
|
||||||
|
const controller = new AuthController()
|
||||||
try {
|
try {
|
||||||
await controller.logout(userInfo)
|
await controller.logout(userInfo)
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|||||||
@@ -36,22 +36,12 @@ router.use('/group', desktopRestrict, groupRouter)
|
|||||||
router.use('/stp', authenticateAccessToken, stpRouter)
|
router.use('/stp', authenticateAccessToken, stpRouter)
|
||||||
router.use('/code', authenticateAccessToken, codeRouter)
|
router.use('/code', authenticateAccessToken, codeRouter)
|
||||||
router.use('/user', desktopRestrict, userRouter)
|
router.use('/user', desktopRestrict, userRouter)
|
||||||
|
|
||||||
router.use(
|
router.use(
|
||||||
'/',
|
'/',
|
||||||
swaggerUi.serve,
|
swaggerUi.serve,
|
||||||
swaggerUi.setup(undefined, {
|
swaggerUi.setup(undefined, {
|
||||||
swaggerOptions: {
|
swaggerOptions: {
|
||||||
url: '/swagger.yaml',
|
url: '/swagger.yaml'
|
||||||
requestInterceptor: (request: any) => {
|
|
||||||
request.credentials = 'include'
|
|
||||||
|
|
||||||
const cookie = document.cookie
|
|
||||||
const startIndex = cookie.indexOf('XSRF-TOKEN')
|
|
||||||
const csrf = cookie.slice(startIndex + 11).split('; ')[0]
|
|
||||||
request.headers['X-XSRF-TOKEN'] = csrf
|
|
||||||
return request
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
ClientController,
|
ClientController,
|
||||||
AuthController
|
AuthController
|
||||||
} from '../../../controllers/'
|
} from '../../../controllers/'
|
||||||
|
import { populateClients } from '../auth'
|
||||||
import { InfoJWT } from '../../../types'
|
import { InfoJWT } from '../../../types'
|
||||||
import {
|
import {
|
||||||
generateAccessToken,
|
generateAccessToken,
|
||||||
@@ -41,6 +42,7 @@ describe('auth', () => {
|
|||||||
mongoServer = await MongoMemoryServer.create()
|
mongoServer = await MongoMemoryServer.create()
|
||||||
con = await mongoose.connect(mongoServer.getUri())
|
con = await mongoose.connect(mongoServer.getUri())
|
||||||
await clientController.createClient({ clientId, clientSecret })
|
await clientController.createClient({ clientId, clientSecret })
|
||||||
|
await populateClients()
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
@@ -49,6 +51,114 @@ describe('auth', () => {
|
|||||||
await mongoServer.stop()
|
await mongoServer.stop()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('authorize', () => {
|
||||||
|
afterEach(async () => {
|
||||||
|
const collections = mongoose.connection.collections
|
||||||
|
const collection = collections['users']
|
||||||
|
await collection.deleteMany({})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should respond with authorization code', async () => {
|
||||||
|
await userController.createUser(user)
|
||||||
|
|
||||||
|
const res = await request(app)
|
||||||
|
.post('/SASjsApi/auth/authorize')
|
||||||
|
.send({
|
||||||
|
username: user.username,
|
||||||
|
password: user.password,
|
||||||
|
clientId
|
||||||
|
})
|
||||||
|
.expect(200)
|
||||||
|
|
||||||
|
expect(res.body).toHaveProperty('code')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should respond with Bad Request if username is missing', async () => {
|
||||||
|
const res = await request(app)
|
||||||
|
.post('/SASjsApi/auth/authorize')
|
||||||
|
.send({
|
||||||
|
password: user.password,
|
||||||
|
clientId
|
||||||
|
})
|
||||||
|
.expect(400)
|
||||||
|
|
||||||
|
expect(res.text).toEqual(`"username" is required`)
|
||||||
|
expect(res.body).toEqual({})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should respond with Bad Request if password is missing', async () => {
|
||||||
|
const res = await request(app)
|
||||||
|
.post('/SASjsApi/auth/authorize')
|
||||||
|
.send({
|
||||||
|
username: user.username,
|
||||||
|
clientId
|
||||||
|
})
|
||||||
|
.expect(400)
|
||||||
|
|
||||||
|
expect(res.text).toEqual(`"password" is required`)
|
||||||
|
expect(res.body).toEqual({})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should respond with Bad Request if clientId is missing', async () => {
|
||||||
|
const res = await request(app)
|
||||||
|
.post('/SASjsApi/auth/authorize')
|
||||||
|
.send({
|
||||||
|
username: user.username,
|
||||||
|
password: user.password
|
||||||
|
})
|
||||||
|
.expect(400)
|
||||||
|
|
||||||
|
expect(res.text).toEqual(`"clientId" is required`)
|
||||||
|
expect(res.body).toEqual({})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should respond with Forbidden if username is incorrect', async () => {
|
||||||
|
const res = await request(app)
|
||||||
|
.post('/SASjsApi/auth/authorize')
|
||||||
|
.send({
|
||||||
|
username: user.username,
|
||||||
|
password: user.password,
|
||||||
|
clientId
|
||||||
|
})
|
||||||
|
.expect(403)
|
||||||
|
|
||||||
|
expect(res.text).toEqual('Error: Username is not found.')
|
||||||
|
expect(res.body).toEqual({})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should respond with Forbidden if password is incorrect', async () => {
|
||||||
|
await userController.createUser(user)
|
||||||
|
|
||||||
|
const res = await request(app)
|
||||||
|
.post('/SASjsApi/auth/authorize')
|
||||||
|
.send({
|
||||||
|
username: user.username,
|
||||||
|
password: 'WrongPassword',
|
||||||
|
clientId
|
||||||
|
})
|
||||||
|
.expect(403)
|
||||||
|
|
||||||
|
expect(res.text).toEqual('Error: Invalid password.')
|
||||||
|
expect(res.body).toEqual({})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should respond with Forbidden if clientId is incorrect', async () => {
|
||||||
|
await userController.createUser(user)
|
||||||
|
|
||||||
|
const res = await request(app)
|
||||||
|
.post('/SASjsApi/auth/authorize')
|
||||||
|
.send({
|
||||||
|
username: user.username,
|
||||||
|
password: user.password,
|
||||||
|
clientId: 'WrongClientID'
|
||||||
|
})
|
||||||
|
.expect(403)
|
||||||
|
|
||||||
|
expect(res.text).toEqual('Invalid clientId.')
|
||||||
|
expect(res.body).toEqual({})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('token', () => {
|
describe('token', () => {
|
||||||
const userInfo: InfoJWT = {
|
const userInfo: InfoJWT = {
|
||||||
clientId,
|
clientId,
|
||||||
|
|||||||
@@ -1,182 +0,0 @@
|
|||||||
import { Express } from 'express'
|
|
||||||
import mongoose, { Mongoose } from 'mongoose'
|
|
||||||
import { MongoMemoryServer } from 'mongodb-memory-server'
|
|
||||||
import request from 'supertest'
|
|
||||||
import appPromise from '../../../app'
|
|
||||||
import { UserController, ClientController } from '../../../controllers/'
|
|
||||||
|
|
||||||
const clientId = 'someclientID'
|
|
||||||
const clientSecret = 'someclientSecret'
|
|
||||||
const user = {
|
|
||||||
id: 1234,
|
|
||||||
displayName: 'Test User',
|
|
||||||
username: 'testUsername',
|
|
||||||
password: '87654321',
|
|
||||||
isAdmin: false,
|
|
||||||
isActive: true
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('web', () => {
|
|
||||||
let app: Express
|
|
||||||
let con: Mongoose
|
|
||||||
let mongoServer: MongoMemoryServer
|
|
||||||
const userController = new UserController()
|
|
||||||
const clientController = new ClientController()
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
app = await appPromise
|
|
||||||
|
|
||||||
mongoServer = await MongoMemoryServer.create()
|
|
||||||
con = await mongoose.connect(mongoServer.getUri())
|
|
||||||
await clientController.createClient({ clientId, clientSecret })
|
|
||||||
})
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await con.connection.dropDatabase()
|
|
||||||
await con.connection.close()
|
|
||||||
await mongoServer.stop()
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('home', () => {
|
|
||||||
it('should respond with CSRF Token', async () => {
|
|
||||||
await request(app)
|
|
||||||
.get('/')
|
|
||||||
.expect(
|
|
||||||
'set-cookie',
|
|
||||||
/_csrf=.*; Max-Age=86400000; Path=\/; HttpOnly,XSRF-TOKEN=.*; Path=\//
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('SASLogon/login', () => {
|
|
||||||
let csrfToken: string
|
|
||||||
let cookies: string
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
;({ csrfToken, cookies } = 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('Cookie', cookies)
|
|
||||||
.set('x-xsrf-token', csrfToken)
|
|
||||||
.send({
|
|
||||||
username: user.username,
|
|
||||||
password: user.password
|
|
||||||
})
|
|
||||||
.expect(200)
|
|
||||||
|
|
||||||
expect(res.body.loggedIn).toBeTruthy()
|
|
||||||
expect(res.body.user).toEqual({
|
|
||||||
username: user.username,
|
|
||||||
displayName: user.displayName
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('SASLogon/authorize', () => {
|
|
||||||
let csrfToken: string
|
|
||||||
let cookies: string
|
|
||||||
let authCookies: string
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
;({ csrfToken, cookies } = await getCSRF(app))
|
|
||||||
|
|
||||||
await userController.createUser(user)
|
|
||||||
|
|
||||||
const credentials = {
|
|
||||||
username: user.username,
|
|
||||||
password: user.password
|
|
||||||
}
|
|
||||||
|
|
||||||
;({ cookies: authCookies } = await performLogin(
|
|
||||||
app,
|
|
||||||
credentials,
|
|
||||||
cookies,
|
|
||||||
csrfToken
|
|
||||||
))
|
|
||||||
})
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
const collections = mongoose.connection.collections
|
|
||||||
const collection = collections['users']
|
|
||||||
await collection.deleteMany({})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should respond with authorization code', async () => {
|
|
||||||
const res = await request(app)
|
|
||||||
.post('/SASLogon/authorize')
|
|
||||||
.set('Cookie', [authCookies, cookies].join('; '))
|
|
||||||
.set('x-xsrf-token', csrfToken)
|
|
||||||
.send({ clientId })
|
|
||||||
|
|
||||||
expect(res.body).toHaveProperty('code')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should respond with Bad Request if clientId is missing', async () => {
|
|
||||||
const res = await request(app)
|
|
||||||
.post('/SASLogon/authorize')
|
|
||||||
.set('Cookie', [authCookies, cookies].join('; '))
|
|
||||||
.set('x-xsrf-token', csrfToken)
|
|
||||||
.send({})
|
|
||||||
.expect(400)
|
|
||||||
|
|
||||||
expect(res.text).toEqual(`"clientId" is required`)
|
|
||||||
expect(res.body).toEqual({})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should respond with Forbidden if clientId is incorrect', async () => {
|
|
||||||
const res = await request(app)
|
|
||||||
.post('/SASLogon/authorize')
|
|
||||||
.set('Cookie', [authCookies, cookies].join('; '))
|
|
||||||
.set('x-xsrf-token', csrfToken)
|
|
||||||
.send({
|
|
||||||
clientId: 'WrongClientID'
|
|
||||||
})
|
|
||||||
.expect(403)
|
|
||||||
|
|
||||||
expect(res.text).toEqual('Error: Invalid clientId.')
|
|
||||||
expect(res.body).toEqual({})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const getCSRF = async (app: Express) => {
|
|
||||||
// make request to get CSRF
|
|
||||||
const { header } = await request(app).get('/')
|
|
||||||
const cookies = header['set-cookie'].join()
|
|
||||||
|
|
||||||
console.log('cookies', cookies)
|
|
||||||
const csrfToken = extractCSRF(cookies)
|
|
||||||
return { csrfToken, cookies }
|
|
||||||
}
|
|
||||||
|
|
||||||
const performLogin = async (
|
|
||||||
app: Express,
|
|
||||||
credentials: { username: string; password: string },
|
|
||||||
cookies: string,
|
|
||||||
csrfToken: string
|
|
||||||
) => {
|
|
||||||
const { header } = await request(app)
|
|
||||||
.post('/SASLogon/login')
|
|
||||||
.set('Cookie', cookies)
|
|
||||||
.set('x-xsrf-token', csrfToken)
|
|
||||||
.send(credentials)
|
|
||||||
|
|
||||||
const newCookies: string = header['set-cookie'].join()
|
|
||||||
return { cookies: newCookies }
|
|
||||||
}
|
|
||||||
|
|
||||||
const extractCSRF = (cookies: string) =>
|
|
||||||
/_csrf=(.*); Max-Age=86400000; Path=\/; HttpOnly,XSRF-TOKEN=(.*); Path=\//.exec(
|
|
||||||
cookies
|
|
||||||
)![2]
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { AppStreamConfig } from '../../types'
|
import { AppStreamConfig } from '../../types'
|
||||||
|
import { script } from './script'
|
||||||
import { style } from './style'
|
import { style } from './style'
|
||||||
|
|
||||||
const defaultAppLogo = '/sasjs-logo.svg'
|
const defaultAppLogo = '/sasjs-logo.svg'
|
||||||
@@ -38,7 +39,6 @@ export const appStreamHtml = (appStreamConfig: AppStreamConfig) => `
|
|||||||
<span id="uploadMessage">Upload New App</span>
|
<span id="uploadMessage">Upload New App</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<script src="/axios.min.js"></script>
|
${script}
|
||||||
<script src="/app-streams-script.js"></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>`
|
</html>`
|
||||||
|
|||||||
@@ -7,11 +7,9 @@ import { appStreamHtml } from './appStreamHtml'
|
|||||||
|
|
||||||
const router = express.Router()
|
const router = express.Router()
|
||||||
|
|
||||||
router.get('/', async (req, res) => {
|
router.get('/', async (_, res) => {
|
||||||
const content = appStreamHtml(process.appStreamConfig)
|
const content = appStreamHtml(process.appStreamConfig)
|
||||||
|
|
||||||
res.cookie('XSRF-TOKEN', req.csrfToken())
|
|
||||||
|
|
||||||
return res.send(content)
|
return res.send(content)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
58
api/src/routes/appStream/script.ts
Normal file
58
api/src/routes/appStream/script.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
export const script = `<script>
|
||||||
|
const inputElement = document.getElementById('fileId')
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById('uploadButton')
|
||||||
|
.addEventListener('click', function () {
|
||||||
|
inputElement.click()
|
||||||
|
})
|
||||||
|
|
||||||
|
inputElement.addEventListener(
|
||||||
|
'change',
|
||||||
|
function () {
|
||||||
|
const fileList = this.files /* now you can work with the file list */
|
||||||
|
|
||||||
|
updateFileUploadMessage('Requesting ...')
|
||||||
|
|
||||||
|
const file = fileList[0]
|
||||||
|
const formData = new FormData()
|
||||||
|
|
||||||
|
formData.append('file', file)
|
||||||
|
fetch('/SASjsApi/drive/deploy/upload', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
})
|
||||||
|
.then(async (res) => {
|
||||||
|
const { status, ok } = res
|
||||||
|
if (status === 200 && ok) {
|
||||||
|
const data = await res.json()
|
||||||
|
return (
|
||||||
|
data.message +
|
||||||
|
'\\nstreamServiceName: ' +
|
||||||
|
data.streamServiceName +
|
||||||
|
'\\nrefreshing page once alert box closes.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
throw await res.text()
|
||||||
|
})
|
||||||
|
.then((message) => {
|
||||||
|
alert(message)
|
||||||
|
location.reload()
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
alert(error)
|
||||||
|
resetFileUpload()
|
||||||
|
updateFileUploadMessage('Upload New App')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
false
|
||||||
|
)
|
||||||
|
|
||||||
|
function updateFileUploadMessage(message) {
|
||||||
|
document.getElementById('uploadMessage').innerHTML = message
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetFileUpload() {
|
||||||
|
inputElement.value = null
|
||||||
|
}
|
||||||
|
</script>`
|
||||||
@@ -4,16 +4,13 @@ import webRouter from './web'
|
|||||||
import apiRouter from './api'
|
import apiRouter from './api'
|
||||||
import appStreamRouter from './appStream'
|
import appStreamRouter from './appStream'
|
||||||
|
|
||||||
import { csrfProtection } from '../app'
|
|
||||||
|
|
||||||
export const setupRoutes = (app: Express) => {
|
export const setupRoutes = (app: Express) => {
|
||||||
|
app.use('/', webRouter)
|
||||||
app.use('/SASjsApi', apiRouter)
|
app.use('/SASjsApi', apiRouter)
|
||||||
|
|
||||||
app.use('/AppStream', csrfProtection, function (req, res, next) {
|
app.use('/AppStream', function (req, res, next) {
|
||||||
// this needs to be a function to hook on
|
// this needs to be a function to hook on
|
||||||
// whatever the current router is
|
// whatever the current router is
|
||||||
appStreamRouter(req, res, next)
|
appStreamRouter(req, res, next)
|
||||||
})
|
})
|
||||||
|
|
||||||
app.use('/', csrfProtection, webRouter)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,59 +1,37 @@
|
|||||||
|
import { readFile } from '@sasjs/utils'
|
||||||
import express from 'express'
|
import express from 'express'
|
||||||
import { WebController } from '../../controllers/web'
|
import path from 'path'
|
||||||
import { authenticateAccessToken } from '../../middlewares'
|
import { getWebBuildFolderPath } from '../../utils'
|
||||||
import { authorizeValidation, loginWebValidation } from '../../utils'
|
|
||||||
|
|
||||||
const webRouter = express.Router()
|
const webRouter = express.Router()
|
||||||
const controller = new WebController()
|
|
||||||
|
|
||||||
webRouter.get('/', async (req, res) => {
|
const jsCodeForDesktopMode = `
|
||||||
let response
|
<script>
|
||||||
|
localStorage.setItem('accessToken', JSON.stringify('accessToken'))
|
||||||
|
localStorage.setItem('refreshToken', JSON.stringify('refreshToken'))
|
||||||
|
</script>`
|
||||||
|
|
||||||
|
const jsCodeForServerMode = `
|
||||||
|
<script>
|
||||||
|
localStorage.setItem('CLIENT_ID', '${process.env.CLIENT_ID}')
|
||||||
|
</script>`
|
||||||
|
|
||||||
|
webRouter.get('/', async (_, res) => {
|
||||||
|
let content: string
|
||||||
try {
|
try {
|
||||||
response = await controller.home()
|
const indexHtmlPath = path.join(getWebBuildFolderPath(), 'index.html')
|
||||||
|
content = await readFile(indexHtmlPath)
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
response = 'Web Build is not present'
|
return res.send('Web Build is not present')
|
||||||
} finally {
|
|
||||||
res.cookie('XSRF-TOKEN', req.csrfToken())
|
|
||||||
|
|
||||||
return res.send(response)
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
webRouter.post('/SASLogon/login', async (req, res) => {
|
const { MODE } = process.env
|
||||||
const { error, value: body } = loginWebValidation(req.body)
|
const codeToInject =
|
||||||
if (error) return res.status(400).send(error.details[0].message)
|
MODE?.trim() === 'server' ? jsCodeForServerMode : jsCodeForDesktopMode
|
||||||
|
const injectedContent = content.replace('</head>', `${codeToInject}</head>`)
|
||||||
|
|
||||||
try {
|
res.setHeader('Content-Type', 'text/html')
|
||||||
const response = await controller.login(req, body)
|
return res.send(injectedContent)
|
||||||
res.send(response)
|
|
||||||
} catch (err: any) {
|
|
||||||
res.status(403).send(err.toString())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
webRouter.post(
|
|
||||||
'/SASLogon/authorize',
|
|
||||||
authenticateAccessToken,
|
|
||||||
async (req, res) => {
|
|
||||||
const { error, value: body } = authorizeValidation(req.body)
|
|
||||||
if (error) return res.status(400).send(error.details[0].message)
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await controller.authorize(req, body)
|
|
||||||
res.send(response)
|
|
||||||
} catch (err: any) {
|
|
||||||
res.status(403).send(err.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
webRouter.get('/logout', async (req, res) => {
|
|
||||||
try {
|
|
||||||
await controller.logout(req)
|
|
||||||
res.status(200).send('OK!')
|
|
||||||
} catch (err: any) {
|
|
||||||
res.status(403).send(err.toString())
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export default webRouter
|
export default webRouter
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ export interface PreProgramVars {
|
|||||||
userId: number
|
userId: number
|
||||||
displayName: string
|
displayName: string
|
||||||
serverUrl: string
|
serverUrl: string
|
||||||
httpHeaders: string[]
|
accessToken: string
|
||||||
}
|
}
|
||||||
|
|||||||
8
api/src/types/Process.d.ts
vendored
Normal file
8
api/src/types/Process.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
declare namespace NodeJS {
|
||||||
|
export interface Process {
|
||||||
|
sasLoc: string
|
||||||
|
driveLoc: string
|
||||||
|
sessionController?: import('../controllers/internal').SessionController
|
||||||
|
appStreamConfig: import('./').AppStreamConfig
|
||||||
|
}
|
||||||
|
}
|
||||||
17
api/src/types/Request.ts
Normal file
17
api/src/types/Request.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { MacroVars } from '@sasjs/utils'
|
||||||
|
|
||||||
|
export interface ExecutionQuery {
|
||||||
|
_program: string
|
||||||
|
macroVars?: MacroVars
|
||||||
|
_debug?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FileQuery {
|
||||||
|
filePath: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isExecutionQuery = (arg: any): arg is ExecutionQuery =>
|
||||||
|
arg && !Array.isArray(arg) && typeof arg._program === 'string'
|
||||||
|
|
||||||
|
export const isFileQuery = (arg: any): arg is FileQuery =>
|
||||||
|
arg && !Array.isArray(arg) && typeof arg.filePath === 'string'
|
||||||
@@ -3,5 +3,6 @@ export * from './AppStreamConfig'
|
|||||||
export * from './Execution'
|
export * from './Execution'
|
||||||
export * from './InfoJWT'
|
export * from './InfoJWT'
|
||||||
export * from './PreProgramVars'
|
export * from './PreProgramVars'
|
||||||
|
export * from './Request'
|
||||||
export * from './Session'
|
export * from './Session'
|
||||||
export * from './TreeNode'
|
export * from './TreeNode'
|
||||||
|
|||||||
14
api/src/types/system/express-session.d.ts
vendored
14
api/src/types/system/express-session.d.ts
vendored
@@ -1,14 +0,0 @@
|
|||||||
import express from 'express'
|
|
||||||
declare module 'express-session' {
|
|
||||||
interface SessionData {
|
|
||||||
loggedIn: boolean
|
|
||||||
user: {
|
|
||||||
userId: number
|
|
||||||
clientId: string
|
|
||||||
username: string
|
|
||||||
displayName: string
|
|
||||||
isAdmin: boolean
|
|
||||||
isActive: boolean
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1
api/src/types/system/global.d.ts
vendored
1
api/src/types/system/global.d.ts
vendored
@@ -1 +0,0 @@
|
|||||||
import 'jest-extended'
|
|
||||||
8
api/src/types/system/process.d.ts
vendored
8
api/src/types/system/process.d.ts
vendored
@@ -1,8 +0,0 @@
|
|||||||
declare namespace NodeJS {
|
|
||||||
export interface Process {
|
|
||||||
sasLoc: string
|
|
||||||
driveLoc: string
|
|
||||||
sessionController?: import('../../controllers/internal').SessionController
|
|
||||||
appStreamConfig: import('../').AppStreamConfig
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,28 @@
|
|||||||
import mongoose from 'mongoose'
|
import mongoose from 'mongoose'
|
||||||
|
import { populateClients } from '../routes/api/auth'
|
||||||
import { seedDB } from './seedDB'
|
import { seedDB } from './seedDB'
|
||||||
|
|
||||||
export const connectDB = async () => {
|
export const connectDB = async () => {
|
||||||
try {
|
// NOTE: when exporting app.js as agent for supertest
|
||||||
await mongoose.connect(process.env.DB_CONNECT as string)
|
// we should exclude connecting to the real database
|
||||||
} catch (err) {
|
if (process.env.NODE_ENV === 'test') {
|
||||||
throw new Error('Unable to connect to DB!')
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Connected to DB!')
|
const { MODE } = process.env
|
||||||
|
|
||||||
|
if (MODE?.trim() !== 'server') {
|
||||||
|
console.log('Running in Destop Mode, no DB to connect.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mongoose.connect(process.env.DB_CONNECT as string, async (err) => {
|
||||||
|
if (err) throw err
|
||||||
|
|
||||||
|
console.log('Connected to db!')
|
||||||
|
|
||||||
await seedDB()
|
await seedDB()
|
||||||
|
|
||||||
return mongoose.connection
|
await populateClients()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
import { PreProgramVars } from '../types'
|
|
||||||
|
|
||||||
export const getPreProgramVariables = (req: any): PreProgramVars => {
|
|
||||||
const host = req.get('host')
|
|
||||||
const protocol = req.protocol + '://'
|
|
||||||
const { user, accessToken } = req
|
|
||||||
const csrfToken = req.headers['x-xsrf-token']
|
|
||||||
const sessionId = req.cookies['connect.sid']
|
|
||||||
const { _csrf } = req.cookies
|
|
||||||
|
|
||||||
const httpHeaders: string[] = []
|
|
||||||
|
|
||||||
if (accessToken) httpHeaders.push(`Authorization: Bearer ${accessToken}`)
|
|
||||||
if (csrfToken) httpHeaders.push(`x-xsrf-token: ${csrfToken}`)
|
|
||||||
|
|
||||||
const cookies: string[] = []
|
|
||||||
if (sessionId) cookies.push(`connect.sid=${sessionId}`)
|
|
||||||
if (_csrf) cookies.push(`_csrf=${_csrf}`)
|
|
||||||
|
|
||||||
if (cookies.length) httpHeaders.push(`cookie: ${cookies.join('; ')}`)
|
|
||||||
|
|
||||||
return {
|
|
||||||
username: user.username,
|
|
||||||
userId: user.userId,
|
|
||||||
displayName: user.displayName,
|
|
||||||
serverUrl: protocol + host,
|
|
||||||
httpHeaders
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,7 +8,6 @@ export * from './generateAuthCode'
|
|||||||
export * from './generateRefreshToken'
|
export * from './generateRefreshToken'
|
||||||
export * from './getCertificates'
|
export * from './getCertificates'
|
||||||
export * from './getDesktopFields'
|
export * from './getDesktopFields'
|
||||||
export * from './getPreProgramVariables'
|
|
||||||
export * from './isDebugOn'
|
export * from './isDebugOn'
|
||||||
export * from './parseLogToArray'
|
export * from './parseLogToArray'
|
||||||
export * from './removeTokensInDB'
|
export * from './removeTokensInDB'
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
import path from 'path'
|
|
||||||
import fs from 'fs'
|
|
||||||
|
|
||||||
export const getEnvCSPDirectives = (
|
|
||||||
HELMET_CSP_CONFIG_PATH: string | undefined
|
|
||||||
) => {
|
|
||||||
let cspConfigJson = {
|
|
||||||
'img-src': ["'self'", 'data:'],
|
|
||||||
'script-src': ["'self'", "'unsafe-inline'"],
|
|
||||||
'script-src-attr': ["'self'", "'unsafe-inline'"]
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
typeof HELMET_CSP_CONFIG_PATH === 'string' &&
|
|
||||||
HELMET_CSP_CONFIG_PATH.length > 0
|
|
||||||
) {
|
|
||||||
const cspConfigPath = path.join(process.cwd(), HELMET_CSP_CONFIG_PATH)
|
|
||||||
|
|
||||||
try {
|
|
||||||
let file = fs.readFileSync(cspConfigPath).toString()
|
|
||||||
|
|
||||||
try {
|
|
||||||
cspConfigJson = JSON.parse(file)
|
|
||||||
} catch (e) {
|
|
||||||
console.error(
|
|
||||||
'Parsing Content Security Policy JSON config failed. Make sure it is valid json'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Error reading HELMET CSP config file', e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return cspConfigJson
|
|
||||||
}
|
|
||||||
@@ -5,14 +5,10 @@ const passwordSchema = Joi.string().min(6).max(1024)
|
|||||||
|
|
||||||
export const blockFileRegex = /\.(exe|sh|htaccess)$/i
|
export const blockFileRegex = /\.(exe|sh|htaccess)$/i
|
||||||
|
|
||||||
export const loginWebValidation = (data: any): Joi.ValidationResult =>
|
|
||||||
Joi.object({
|
|
||||||
username: usernameSchema.required(),
|
|
||||||
password: passwordSchema.required()
|
|
||||||
}).validate(data)
|
|
||||||
|
|
||||||
export const authorizeValidation = (data: any): Joi.ValidationResult =>
|
export const authorizeValidation = (data: any): Joi.ValidationResult =>
|
||||||
Joi.object({
|
Joi.object({
|
||||||
|
username: usernameSchema.required(),
|
||||||
|
password: passwordSchema.required(),
|
||||||
clientId: Joi.string().required()
|
clientId: Joi.string().required()
|
||||||
}).validate(data)
|
}).validate(data)
|
||||||
|
|
||||||
|
|||||||
@@ -46,10 +46,6 @@
|
|||||||
{
|
{
|
||||||
"name": "CODE",
|
"name": "CODE",
|
||||||
"description": "Operations on SAS code"
|
"description": "Operations on SAS code"
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Web",
|
|
||||||
"description": "Operations on Web"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"yaml": true,
|
"yaml": true,
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "server",
|
"name": "server",
|
||||||
"version": "0.0.75",
|
"version": "0.0.58",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "server",
|
"name": "server",
|
||||||
"version": "0.0.75",
|
"version": "0.0.58",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"prettier": "^2.3.1",
|
"prettier": "^2.3.1",
|
||||||
"standard-version": "^9.3.2"
|
"standard-version": "^9.3.2"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "server",
|
"name": "server",
|
||||||
"version": "0.0.75",
|
"version": "0.0.58",
|
||||||
"description": "NodeJS wrapper for calling the SAS binary executable",
|
"description": "NodeJS wrapper for calling the SAS binary executable",
|
||||||
"repository": "https://github.com/sasjs/server",
|
"repository": "https://github.com/sasjs/server",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,3 +1,2 @@
|
|||||||
### Get current user's info via session ID
|
### Get current user's info via access token
|
||||||
GET http://localhost:5000/SASjsApi/session
|
GET http://localhost:5000/SASjsApi/session
|
||||||
cookie: connect.sid=s:G2DeFdKuWhnmTOsTHmTWrxAXPx2P6TLD.JyNLxfACC1w3NlFQFfL5chyxtrqbPYmS6iButRc1goE
|
|
||||||
@@ -1 +1,2 @@
|
|||||||
PORT_API=[place sasjs server port] default value is 5000
|
PORT_API=[place sasjs server port] default value is 5000
|
||||||
|
CLIENT_ID=<place clientId here>
|
||||||
384
web/package-lock.json
generated
384
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.4.1",
|
"@emotion/react": "^11.4.1",
|
||||||
"@emotion/styled": "^11.3.0",
|
"@emotion/styled": "^11.3.0",
|
||||||
|
"@monaco-editor/react": "^4.3.1",
|
||||||
"@mui/icons-material": "^5.0.3",
|
"@mui/icons-material": "^5.0.3",
|
||||||
"@mui/lab": "^5.0.0-alpha.50",
|
"@mui/lab": "^5.0.0-alpha.50",
|
||||||
"@mui/material": "^5.0.3",
|
"@mui/material": "^5.0.3",
|
||||||
@@ -20,11 +21,9 @@
|
|||||||
"@types/node": "^12.20.28",
|
"@types/node": "^12.20.28",
|
||||||
"@types/react": "^17.0.27",
|
"@types/react": "^17.0.27",
|
||||||
"axios": "^0.24.0",
|
"axios": "^0.24.0",
|
||||||
"monaco-editor": "^0.33.0",
|
"jwt-decode": "3.1.2",
|
||||||
"monaco-editor-webpack-plugin": "^7.0.1",
|
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-monaco-editor": "^0.48.0",
|
|
||||||
"react-router-dom": "^5.3.0"
|
"react-router-dom": "^5.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -10,17 +10,19 @@ import Drive from './containers/Drive'
|
|||||||
import Studio from './containers/Studio'
|
import Studio from './containers/Studio'
|
||||||
|
|
||||||
import { AppContext } from './context/appContext'
|
import { AppContext } from './context/appContext'
|
||||||
import AuthCode from './containers/AuthCode'
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const appContext = useContext(AppContext)
|
const appContext = useContext(AppContext)
|
||||||
|
|
||||||
if (!appContext.loggedIn) {
|
if (!appContext.tokens) {
|
||||||
return (
|
return (
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<HashRouter>
|
<HashRouter>
|
||||||
<Header />
|
<Header />
|
||||||
<Switch>
|
<Switch>
|
||||||
|
<Route exact path="/SASjsLogon">
|
||||||
|
<Login getCodeOnly />
|
||||||
|
</Route>
|
||||||
<Route path="/">
|
<Route path="/">
|
||||||
<Login />
|
<Login />
|
||||||
</Route>
|
</Route>
|
||||||
@@ -45,7 +47,7 @@ function App() {
|
|||||||
<Studio />
|
<Studio />
|
||||||
</Route>
|
</Route>
|
||||||
<Route exact path="/SASjsLogon">
|
<Route exact path="/SASjsLogon">
|
||||||
<AuthCode />
|
<Login getCodeOnly />
|
||||||
</Route>
|
</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
</HashRouter>
|
</HashRouter>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import OpenInNewIcon from '@mui/icons-material/OpenInNew'
|
import OpenInNewIcon from '@mui/icons-material/OpenInNew'
|
||||||
|
|
||||||
import Username from './username'
|
import UserName from './userName'
|
||||||
import { AppContext } from '../context/appContext'
|
import { AppContext } from '../context/appContext'
|
||||||
|
|
||||||
const NODE_ENV = process.env.NODE_ENV
|
const NODE_ENV = process.env.NODE_ENV
|
||||||
@@ -113,8 +113,8 @@ const Header = (props: any) => {
|
|||||||
justifyContent: 'flex-end'
|
justifyContent: 'flex-end'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Username
|
<UserName
|
||||||
username={appContext.displayName || appContext.username}
|
userName={appContext.displayName}
|
||||||
onClickHandler={handleMenu}
|
onClickHandler={handleMenu}
|
||||||
/>
|
/>
|
||||||
<Menu
|
<Menu
|
||||||
|
|||||||
@@ -1,38 +1,98 @@
|
|||||||
import axios from 'axios'
|
|
||||||
import React, { useState, useContext } from 'react'
|
import React, { useState, useContext } from 'react'
|
||||||
|
import { useLocation } from 'react-router-dom'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import { CssBaseline, Box, TextField, Button } from '@mui/material'
|
import { CssBaseline, Box, TextField, Button, Typography } from '@mui/material'
|
||||||
import { AppContext } from '../context/appContext'
|
import { AppContext } from '../context/appContext'
|
||||||
|
|
||||||
const login = async (payload: { username: string; password: string }) =>
|
const headers = {
|
||||||
axios.post('/SASLogon/login', payload).then((res) => res.data)
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
const NODE_ENV = process.env.NODE_ENV
|
||||||
|
const PORT_API = process.env.PORT_API
|
||||||
|
const baseUrl =
|
||||||
|
NODE_ENV === 'development' ? `http://localhost:${PORT_API ?? 5000}` : ''
|
||||||
|
|
||||||
const Login = () => {
|
const getAuthCode = async (credentials: any) => {
|
||||||
|
return fetch(`${baseUrl}/SASjsApi/auth/authorize`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers,
|
||||||
|
body: JSON.stringify(credentials)
|
||||||
|
}).then(async (response) => {
|
||||||
|
const resText = await response.text()
|
||||||
|
if (response.status !== 200) throw resText
|
||||||
|
|
||||||
|
return JSON.parse(resText)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const getTokens = async (payload: any) => {
|
||||||
|
return fetch(`${baseUrl}/SASjsApi/auth/token`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers,
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
}).then((data) => data.json())
|
||||||
|
}
|
||||||
|
|
||||||
|
const Login = ({ getCodeOnly }: any) => {
|
||||||
|
const location = useLocation()
|
||||||
const appContext = useContext(AppContext)
|
const appContext = useContext(AppContext)
|
||||||
const [username, setUsername] = useState('')
|
const [username, setUserName] = useState('')
|
||||||
const [password, setPassword] = useState('')
|
const [password, setPassword] = useState('')
|
||||||
const [errorMessage, setErrorMessage] = useState('')
|
const [errorMessage, setErrorMessage] = useState('')
|
||||||
|
let error: boolean
|
||||||
|
const [displayCode, setDisplayCode] = useState(null)
|
||||||
|
|
||||||
const handleSubmit = async (e: any) => {
|
const handleSubmit = async (e: any) => {
|
||||||
|
error = false
|
||||||
setErrorMessage('')
|
setErrorMessage('')
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
let clientId = process.env.CLIENT_ID ?? localStorage.getItem('CLIENT_ID')
|
||||||
|
|
||||||
const { loggedIn, user } = await login({
|
if (getCodeOnly) {
|
||||||
|
const params = new URLSearchParams(location.search)
|
||||||
|
const responseType = params.get('response_type')
|
||||||
|
if (responseType === 'code') clientId = params.get('client_id')
|
||||||
|
}
|
||||||
|
|
||||||
|
const { code } = await getAuthCode({
|
||||||
|
clientId,
|
||||||
username,
|
username,
|
||||||
password
|
password
|
||||||
}).catch((err: any) => {
|
}).catch((err: string) => {
|
||||||
setErrorMessage(err.response.data)
|
error = true
|
||||||
|
setErrorMessage(err)
|
||||||
return {}
|
return {}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (loggedIn) {
|
if (!error) {
|
||||||
appContext.setLoggedIn?.(loggedIn)
|
if (getCodeOnly) return setDisplayCode(code)
|
||||||
appContext.setUsername?.(user.username)
|
|
||||||
appContext.setDisplayName?.(user.displayName)
|
const { accessToken, refreshToken } = await getTokens({
|
||||||
|
clientId,
|
||||||
|
code
|
||||||
|
})
|
||||||
|
|
||||||
|
if (appContext.setTokens) appContext.setTokens(accessToken, refreshToken)
|
||||||
|
if (appContext.setUserName) appContext.setUserName(username)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (displayCode) {
|
||||||
|
return (
|
||||||
|
<Box className="main">
|
||||||
|
<CssBaseline />
|
||||||
|
<br />
|
||||||
|
<h2>Authorization Code</h2>
|
||||||
|
<Typography m={2} p={3} style={{ overflowWrap: 'anywhere' }}>
|
||||||
|
{displayCode}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
className="main"
|
className="main"
|
||||||
@@ -45,12 +105,19 @@ const Login = () => {
|
|||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<br />
|
<br />
|
||||||
<h2 style={{ width: 'auto' }}>Welcome to SASjs Server!</h2>
|
<h2 style={{ width: 'auto' }}>Welcome to SASjs Server!</h2>
|
||||||
|
{getCodeOnly && (
|
||||||
|
<p style={{ width: 'auto' }}>
|
||||||
|
Provide credentials to get authorization code.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<br />
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
id="username"
|
id="username"
|
||||||
label="Username"
|
label="Username"
|
||||||
type="text"
|
type="text"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
onChange={(e: any) => setUsername(e.target.value)}
|
onChange={(e: any) => setUserName(e.target.value)}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
@@ -62,11 +129,7 @@ const Login = () => {
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
{errorMessage && <span>{errorMessage}</span>}
|
{errorMessage && <span>{errorMessage}</span>}
|
||||||
<Button
|
<Button type="submit" variant="outlined" disabled={!appContext.setTokens}>
|
||||||
type="submit"
|
|
||||||
variant="outlined"
|
|
||||||
disabled={!appContext.setLoggedIn}
|
|
||||||
>
|
|
||||||
Submit
|
Submit
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
94
web/src/components/useTokens.ts
Normal file
94
web/src/components/useTokens.ts
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
export default function useTokens() {
|
||||||
|
const getTokens = () => {
|
||||||
|
const accessToken = localStorage.getItem('accessToken')
|
||||||
|
const refreshToken = localStorage.getItem('refreshToken')
|
||||||
|
|
||||||
|
if (accessToken && refreshToken) {
|
||||||
|
setAxiosRequestHeader(accessToken)
|
||||||
|
return { accessToken, refreshToken }
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const [tokens, setTokens] = useState(getTokens())
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (tokens === undefined) {
|
||||||
|
localStorage.removeItem('accessToken')
|
||||||
|
localStorage.removeItem('refreshToken')
|
||||||
|
}
|
||||||
|
}, [tokens])
|
||||||
|
setAxiosResponse(setTokens)
|
||||||
|
|
||||||
|
const saveTokens = (accessToken: string, refreshToken: string) => {
|
||||||
|
localStorage.setItem('accessToken', accessToken)
|
||||||
|
localStorage.setItem('refreshToken', refreshToken)
|
||||||
|
setAxiosRequestHeader(accessToken)
|
||||||
|
setTokens({ accessToken, refreshToken })
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
setTokens: saveTokens,
|
||||||
|
tokens
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const NODE_ENV = process.env.NODE_ENV
|
||||||
|
const PORT_API = process.env.PORT_API
|
||||||
|
const baseUrl =
|
||||||
|
NODE_ENV === 'development' ? `http://localhost:${PORT_API ?? 5000}` : ''
|
||||||
|
|
||||||
|
const isAbsoluteURLRegex = /^(?:\w+:)\/\//
|
||||||
|
|
||||||
|
const setAxiosRequestHeader = (accessToken: string) => {
|
||||||
|
axios.interceptors.request.use(function (config) {
|
||||||
|
if (baseUrl && !isAbsoluteURLRegex.test(config.url as string)) {
|
||||||
|
config.url = baseUrl + config.url
|
||||||
|
}
|
||||||
|
config.headers!['Authorization'] = `Bearer ${accessToken}`
|
||||||
|
config.withCredentials = true
|
||||||
|
|
||||||
|
return config
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const setAxiosResponse = (setTokens: Function) => {
|
||||||
|
// Add a response interceptor
|
||||||
|
axios.interceptors.response.use(
|
||||||
|
function (response) {
|
||||||
|
// Any status code that lie within the range of 2xx cause this function to trigger
|
||||||
|
return response
|
||||||
|
},
|
||||||
|
async function (error) {
|
||||||
|
if (error.response?.status === 401) {
|
||||||
|
// refresh token
|
||||||
|
// const { accessToken, refreshToken: newRefresh } = await refreshMyToken(
|
||||||
|
// refreshToken
|
||||||
|
// )
|
||||||
|
|
||||||
|
// if (accessToken && newRefresh) {
|
||||||
|
// setTokens(accessToken, newRefresh)
|
||||||
|
// error.config.headers['Authorization'] = 'Bearer ' + accessToken
|
||||||
|
// error.config.baseURL = undefined
|
||||||
|
|
||||||
|
// return axios.request(error.config)
|
||||||
|
// }
|
||||||
|
setTokens(undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// const refreshMyToken = async (refreshToken: string) => {
|
||||||
|
// return fetch('http://localhost:5000/SASjsApi/auth/refresh', {
|
||||||
|
// method: 'POST',
|
||||||
|
// headers: {
|
||||||
|
// Authorization: `Bearer ${refreshToken}`
|
||||||
|
// }
|
||||||
|
// }).then((data) => data.json())
|
||||||
|
// }
|
||||||
@@ -2,7 +2,7 @@ import React from 'react'
|
|||||||
import { Typography, IconButton } from '@mui/material'
|
import { Typography, IconButton } from '@mui/material'
|
||||||
import AccountCircle from '@mui/icons-material/AccountCircle'
|
import AccountCircle from '@mui/icons-material/AccountCircle'
|
||||||
|
|
||||||
const Username = (props: any) => {
|
const UserName = (props: any) => {
|
||||||
return (
|
return (
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label="account of current user"
|
aria-label="account of current user"
|
||||||
@@ -21,10 +21,10 @@ const Username = (props: any) => {
|
|||||||
<AccountCircle></AccountCircle>
|
<AccountCircle></AccountCircle>
|
||||||
)}
|
)}
|
||||||
<Typography variant="h6" sx={{ color: 'white', padding: '0 8px' }}>
|
<Typography variant="h6" sx={{ color: 'white', padding: '0 8px' }}>
|
||||||
{props.username}
|
{props.userName}
|
||||||
</Typography>
|
</Typography>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Username
|
export default UserName
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
import axios from 'axios'
|
|
||||||
import React, { useEffect, useState } from 'react'
|
|
||||||
import { useLocation } from 'react-router-dom'
|
|
||||||
|
|
||||||
import { CssBaseline, Box, Typography } from '@mui/material'
|
|
||||||
|
|
||||||
const getAuthCode = async (credentials: any) =>
|
|
||||||
axios.post('/SASLogon/authorize', credentials).then((res) => res.data)
|
|
||||||
|
|
||||||
const AuthCode = () => {
|
|
||||||
const location = useLocation()
|
|
||||||
const [displayCode, setDisplayCode] = useState(null)
|
|
||||||
const [errorMessage, setErrorMessage] = useState('')
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
requestAuthCode()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const requestAuthCode = async () => {
|
|
||||||
setErrorMessage('')
|
|
||||||
|
|
||||||
const params = new URLSearchParams(location.search)
|
|
||||||
|
|
||||||
const responseType = params.get('response_type')
|
|
||||||
if (responseType !== 'code')
|
|
||||||
return setErrorMessage('response type is not support')
|
|
||||||
|
|
||||||
const clientId = params.get('client_id')
|
|
||||||
if (!clientId) return setErrorMessage('clientId is not provided')
|
|
||||||
|
|
||||||
setErrorMessage('Fetching auth code... ')
|
|
||||||
const { code } = await getAuthCode({
|
|
||||||
clientId
|
|
||||||
})
|
|
||||||
.then((res) => {
|
|
||||||
setErrorMessage('')
|
|
||||||
return res
|
|
||||||
})
|
|
||||||
.catch((err: any) => {
|
|
||||||
setErrorMessage(err.response.data)
|
|
||||||
return { code: null }
|
|
||||||
})
|
|
||||||
return setDisplayCode(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box className="main">
|
|
||||||
<CssBaseline />
|
|
||||||
<br />
|
|
||||||
<h2>Authorization Code</h2>
|
|
||||||
{displayCode && (
|
|
||||||
<Typography m={2} p={3} style={{ overflowWrap: 'anywhere' }}>
|
|
||||||
{displayCode}
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
{errorMessage && <Typography>{errorMessage}</Typography>}
|
|
||||||
|
|
||||||
<br />
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AuthCode
|
|
||||||
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'
|
|||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
|
||||||
import Editor from 'react-monaco-editor'
|
import Editor from '@monaco-editor/react'
|
||||||
|
|
||||||
import Box from '@mui/material/Box'
|
import Box from '@mui/material/Box'
|
||||||
import Paper from '@mui/material/Paper'
|
import Paper from '@mui/material/Paper'
|
||||||
@@ -125,7 +125,6 @@ const Main = (props: Props) => {
|
|||||||
{!isLoading && props?.selectedFilePath && editMode && (
|
{!isLoading && props?.selectedFilePath && editMode && (
|
||||||
<Editor
|
<Editor
|
||||||
height="95%"
|
height="95%"
|
||||||
language="sas"
|
|
||||||
value={fileContent}
|
value={fileContent}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
if (val) setFileContent(val)
|
if (val) setFileContent(val)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import axios from 'axios'
|
|||||||
import Box from '@mui/material/Box'
|
import Box from '@mui/material/Box'
|
||||||
import { Button, Paper, Stack, Tab, Tooltip } from '@mui/material'
|
import { Button, Paper, Stack, Tab, Tooltip } from '@mui/material'
|
||||||
import { makeStyles } from '@mui/styles'
|
import { makeStyles } from '@mui/styles'
|
||||||
import Editor, { EditorDidMount } from 'react-monaco-editor'
|
import Editor, { OnMount } from '@monaco-editor/react'
|
||||||
import { useLocation } from 'react-router-dom'
|
import { useLocation } from 'react-router-dom'
|
||||||
import { TabContext, TabList, TabPanel } from '@mui/lab'
|
import { TabContext, TabList, TabPanel } from '@mui/lab'
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ const Studio = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const editorRef = useRef(null as any)
|
const editorRef = useRef(null as any)
|
||||||
const handleEditorDidMount: EditorDidMount = (editor) => {
|
const handleEditorDidMount: OnMount = (editor) => {
|
||||||
editor.focus()
|
editor.focus()
|
||||||
editorRef.current = editor
|
editorRef.current = editor
|
||||||
}
|
}
|
||||||
@@ -141,7 +141,6 @@ const Studio = () => {
|
|||||||
<Tooltip title="CTRL+ENTER will also run SAS code">
|
<Tooltip title="CTRL+ENTER will also run SAS code">
|
||||||
<Button onClick={handleRunBtnClick} className={classes.runButton}>
|
<Button onClick={handleRunBtnClick} className={classes.runButton}>
|
||||||
<img
|
<img
|
||||||
alt=""
|
|
||||||
draggable="false"
|
draggable="false"
|
||||||
style={{ width: '25px' }}
|
style={{ width: '25px' }}
|
||||||
src="/running-sas.png"
|
src="/running-sas.png"
|
||||||
@@ -162,9 +161,8 @@ const Studio = () => {
|
|||||||
>
|
>
|
||||||
<Editor
|
<Editor
|
||||||
height="98%"
|
height="98%"
|
||||||
language="sas"
|
|
||||||
value={fileContent}
|
value={fileContent}
|
||||||
editorDidMount={handleEditorDidMount}
|
onMount={handleEditorDidMount}
|
||||||
options={{ readOnly: ctrlPressed }}
|
options={{ readOnly: ctrlPressed }}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
if (val) setFileContent(val)
|
if (val) setFileContent(val)
|
||||||
|
|||||||
@@ -7,73 +7,147 @@ import React, {
|
|||||||
useCallback,
|
useCallback,
|
||||||
ReactNode
|
ReactNode
|
||||||
} from 'react'
|
} from 'react'
|
||||||
|
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
import jwt_decode from 'jwt-decode'
|
||||||
|
|
||||||
|
const NODE_ENV = process.env.NODE_ENV
|
||||||
|
const PORT_API = process.env.PORT_API
|
||||||
|
const baseUrl =
|
||||||
|
NODE_ENV === 'development' ? `http://localhost:${PORT_API ?? 5000}` : ''
|
||||||
|
|
||||||
|
const isAbsoluteURLRegex = /^(?:\w+:)\/\//
|
||||||
|
|
||||||
|
const setAxiosRequestHeader = (accessToken: string) => {
|
||||||
|
axios.interceptors.request.use(function (config) {
|
||||||
|
if (baseUrl && !isAbsoluteURLRegex.test(config.url as string)) {
|
||||||
|
config.url = baseUrl + config.url
|
||||||
|
}
|
||||||
|
console.log('axios.interceptors.request.use', accessToken)
|
||||||
|
config.headers!['Authorization'] = `Bearer ${accessToken}`
|
||||||
|
config.withCredentials = true
|
||||||
|
|
||||||
|
return config
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const setAxiosResponse = (setTokens: Function) => {
|
||||||
|
// Add a response interceptor
|
||||||
|
axios.interceptors.response.use(
|
||||||
|
function (response) {
|
||||||
|
// Any status code that lie within the range of 2xx cause this function to trigger
|
||||||
|
return response
|
||||||
|
},
|
||||||
|
async function (error) {
|
||||||
|
if (error.response?.status === 401) {
|
||||||
|
// refresh token
|
||||||
|
// const { accessToken, refreshToken: newRefresh } = await refreshMyToken(
|
||||||
|
// refreshToken
|
||||||
|
// )
|
||||||
|
|
||||||
|
// if (accessToken && newRefresh) {
|
||||||
|
// setTokens(accessToken, newRefresh)
|
||||||
|
// error.config.headers['Authorization'] = 'Bearer ' + accessToken
|
||||||
|
// error.config.baseURL = undefined
|
||||||
|
|
||||||
|
// return axios.request(error.config)
|
||||||
|
// }
|
||||||
|
console.log(53)
|
||||||
|
setTokens(undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTokens = () => {
|
||||||
|
const accessToken = localStorage.getItem('accessToken')
|
||||||
|
const refreshToken = localStorage.getItem('refreshToken')
|
||||||
|
|
||||||
|
if (accessToken && refreshToken) {
|
||||||
|
setAxiosRequestHeader(accessToken)
|
||||||
|
return { accessToken, refreshToken }
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
interface AppContextProps {
|
interface AppContextProps {
|
||||||
checkingSession: boolean
|
userName: string
|
||||||
loggedIn: boolean
|
|
||||||
setLoggedIn: Dispatch<SetStateAction<boolean>> | null
|
|
||||||
username: string
|
|
||||||
setUsername: Dispatch<SetStateAction<string>> | null
|
|
||||||
displayName: string
|
displayName: string
|
||||||
setDisplayName: Dispatch<SetStateAction<string>> | null
|
setUserName: Dispatch<SetStateAction<string>> | null
|
||||||
|
tokens?: { accessToken: string; refreshToken: string }
|
||||||
|
setTokens: ((accessToken: string, refreshToken: string) => void) | null
|
||||||
logout: (() => void) | null
|
logout: (() => void) | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AppContext = createContext<AppContextProps>({
|
export const AppContext = createContext<AppContextProps>({
|
||||||
checkingSession: false,
|
userName: '',
|
||||||
loggedIn: false,
|
|
||||||
setLoggedIn: null,
|
|
||||||
username: '',
|
|
||||||
setUsername: null,
|
|
||||||
displayName: '',
|
displayName: '',
|
||||||
setDisplayName: null,
|
tokens: getTokens(),
|
||||||
|
setUserName: null,
|
||||||
|
setTokens: null,
|
||||||
logout: null
|
logout: null
|
||||||
})
|
})
|
||||||
|
|
||||||
const AppContextProvider = (props: { children: ReactNode }) => {
|
const AppContextProvider = (props: { children: ReactNode }) => {
|
||||||
const { children } = props
|
const { children } = props
|
||||||
const [checkingSession, setCheckingSession] = useState(false)
|
const [userName, setUserName] = useState('')
|
||||||
const [loggedIn, setLoggedIn] = useState(false)
|
|
||||||
const [username, setUsername] = useState('')
|
|
||||||
const [displayName, setDisplayName] = useState('')
|
const [displayName, setDisplayName] = useState('')
|
||||||
|
const [tokens, setTokens] = useState(getTokens())
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCheckingSession(true)
|
setAxiosResponse(setTokens)
|
||||||
|
|
||||||
axios
|
|
||||||
.get('/SASjsApi/session')
|
|
||||||
.then((res) => res.data)
|
|
||||||
.then((data: any) => {
|
|
||||||
setCheckingSession(false)
|
|
||||||
setLoggedIn(true)
|
|
||||||
setUsername(data.username)
|
|
||||||
setDisplayName(data.displayName)
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
setLoggedIn(false)
|
|
||||||
axios.get('/') // get CSRF TOKEN
|
|
||||||
})
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const logout = useCallback(() => {
|
useEffect(() => {
|
||||||
axios.get('/logout').then(() => {
|
if (tokens === undefined) {
|
||||||
setLoggedIn(false)
|
localStorage.removeItem('accessToken')
|
||||||
setUsername('')
|
localStorage.removeItem('refreshToken')
|
||||||
|
setUserName('')
|
||||||
setDisplayName('')
|
setDisplayName('')
|
||||||
|
} else {
|
||||||
|
const decoded: any = jwt_decode(tokens.accessToken)
|
||||||
|
if (decoded.userId) {
|
||||||
|
axios
|
||||||
|
.get(`/SASjsApi/user/${decoded.userId}`)
|
||||||
|
.then((res: any) => {
|
||||||
|
if (res.data && res.data?.displayName) {
|
||||||
|
setDisplayName(res.data.displayName)
|
||||||
|
} else if (res.data && res.data?.username) {
|
||||||
|
setDisplayName(res.data.username)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [tokens])
|
||||||
|
|
||||||
|
const saveTokens = useCallback(
|
||||||
|
(accessToken: string, refreshToken: string) => {
|
||||||
|
localStorage.setItem('accessToken', accessToken)
|
||||||
|
localStorage.setItem('refreshToken', refreshToken)
|
||||||
|
setAxiosRequestHeader(accessToken)
|
||||||
|
setTokens({ accessToken, refreshToken })
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
const logout = useCallback(() => {
|
||||||
|
setUserName('')
|
||||||
|
setTokens(undefined)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppContext.Provider
|
<AppContext.Provider
|
||||||
value={{
|
value={{
|
||||||
checkingSession,
|
userName,
|
||||||
loggedIn,
|
|
||||||
setLoggedIn,
|
|
||||||
username,
|
|
||||||
setUsername,
|
|
||||||
displayName,
|
displayName,
|
||||||
setDisplayName,
|
setUserName,
|
||||||
|
tokens,
|
||||||
|
setTokens: saveTokens,
|
||||||
logout
|
logout
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -4,18 +4,6 @@ import './index.css'
|
|||||||
import App from './App'
|
import App from './App'
|
||||||
import AppContextProvider from './context/appContext'
|
import AppContextProvider from './context/appContext'
|
||||||
|
|
||||||
import axios from 'axios'
|
|
||||||
|
|
||||||
const NODE_ENV = process.env.NODE_ENV
|
|
||||||
const PORT_API = process.env.PORT_API
|
|
||||||
const baseUrl =
|
|
||||||
NODE_ENV === 'development' ? `http://localhost:${PORT_API ?? 5000}` : ''
|
|
||||||
|
|
||||||
axios.defaults = Object.assign(axios.defaults, {
|
|
||||||
withCredentials: true,
|
|
||||||
baseURL: baseUrl
|
|
||||||
})
|
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<AppContextProvider>
|
<AppContextProvider>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import MonacoWebpackPlugin from 'monaco-editor-webpack-plugin'
|
|
||||||
import { Configuration } from 'webpack'
|
import { Configuration } from 'webpack'
|
||||||
import HtmlWebpackPlugin from 'html-webpack-plugin'
|
import HtmlWebpackPlugin from 'html-webpack-plugin'
|
||||||
import CopyPlugin from 'copy-webpack-plugin'
|
import CopyPlugin from 'copy-webpack-plugin'
|
||||||
@@ -54,8 +53,7 @@ const config: Configuration = {
|
|||||||
new CopyPlugin({
|
new CopyPlugin({
|
||||||
patterns: [{ from: 'public' }]
|
patterns: [{ from: 'public' }]
|
||||||
}),
|
}),
|
||||||
new dotenv(),
|
new dotenv()
|
||||||
new MonacoWebpackPlugin()
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user