From d47ed6d0e8e1e7772e6d0856968b7c9aab1eb7b9 Mon Sep 17 00:00:00 2001 From: munja Date: Fri, 29 Apr 2022 13:28:34 +0100 Subject: [PATCH 001/163] chore(release): 0.0.59 --- CHANGELOG.md | 14 ++++++++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index afde321..77acb6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,20 @@ 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.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) diff --git a/package-lock.json b/package-lock.json index 158ac4c..c6a5bac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "server", - "version": "0.0.58", + "version": "0.0.59", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "server", - "version": "0.0.58", + "version": "0.0.59", "devDependencies": { "prettier": "^2.3.1", "standard-version": "^9.3.2" diff --git a/package.json b/package.json index 7a48911..4d1ce46 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "server", - "version": "0.0.58", + "version": "0.0.59", "description": "NodeJS wrapper for calling the SAS binary executable", "repository": "https://github.com/sasjs/server", "scripts": { From b060ad1b8e0bbc61c20dc25be553bba4cc4d2716 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Sat, 30 Apr 2022 05:04:27 +0500 Subject: [PATCH 002/163] fix: added CSRF check for granting access via session authentication --- api/package-lock.json | 14 ++++++++++++++ api/package.json | 1 + api/src/app.ts | 6 ++++++ api/src/middlewares/authenticateToken.ts | 7 ++++++- api/src/routes/web/index.ts | 9 ++++++++- api/src/routes/web/web.ts | 5 ----- web/src/components/login.tsx | 8 +------- web/src/context/appContext.tsx | 1 + 8 files changed, 37 insertions(+), 14 deletions(-) diff --git a/api/package-lock.json b/api/package-lock.json index a0c9dd0..a3b8311 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -17,6 +17,7 @@ "csurf": "^1.11.0", "express": "^4.17.1", "express-session": "^1.17.2", + "helmet": "^5.0.2", "joi": "^17.4.2", "jsonwebtoken": "^8.5.1", "mongoose": "^6.0.12", @@ -4817,6 +4818,14 @@ "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": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", @@ -14126,6 +14135,11 @@ "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", "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": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", diff --git a/api/package.json b/api/package.json index 4587d7c..c0c201e 100644 --- a/api/package.json +++ b/api/package.json @@ -56,6 +56,7 @@ "csurf": "^1.11.0", "express": "^4.17.1", "express-session": "^1.17.2", + "helmet": "^5.0.2", "joi": "^17.4.2", "jsonwebtoken": "^8.5.1", "mongoose": "^6.0.12", diff --git a/api/src/app.ts b/api/src/app.ts index c7263d1..9c44224 100644 --- a/api/src/app.ts +++ b/api/src/app.ts @@ -7,6 +7,7 @@ import morgan from 'morgan' import cookieParser from 'cookie-parser' import dotenv from 'dotenv' import cors from 'cors' +import helmet from 'helmet' import { connectDB, @@ -37,6 +38,11 @@ export const cookieOptions = { ***********************************/ export const csrfProtection = csrf({ cookie: cookieOptions }) +/*********************************** + * Handle security and origin * + ***********************************/ +app.use(helmet()) + /*********************************** * Enabling CORS * ***********************************/ diff --git a/api/src/middlewares/authenticateToken.ts b/api/src/middlewares/authenticateToken.ts index 4d97583..716c1fd 100644 --- a/api/src/middlewares/authenticateToken.ts +++ b/api/src/middlewares/authenticateToken.ts @@ -1,11 +1,16 @@ import jwt from 'jsonwebtoken' +import { csrfProtection } from '../app' import { verifyTokenInDB } from '../utils' 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 next() + + return csrfProtection(req, res, next) } + authenticateToken( req, res, diff --git a/api/src/routes/web/index.ts b/api/src/routes/web/index.ts index 98b5a85..6f04db4 100644 --- a/api/src/routes/web/index.ts +++ b/api/src/routes/web/index.ts @@ -4,6 +4,13 @@ import webRouter from './web' const router = express.Router() -router.use('/', csrfProtection, webRouter) +router.use(csrfProtection) + +router.use(function (req, res, next) { + res.cookie('XSRF-TOKEN', req.csrfToken()) + next() +}) + +router.use('/', webRouter) export default router diff --git a/api/src/routes/web/web.ts b/api/src/routes/web/web.ts index 696b459..f9889b0 100644 --- a/api/src/routes/web/web.ts +++ b/api/src/routes/web/web.ts @@ -14,11 +14,6 @@ webRouter.get('/', async (_, res) => { return res.send('Web Build is not present') }) -webRouter.get('/form', function (req, res) { - // pass the csrfToken to the view - res.send({ csrfToken: req.csrfToken() }) -}) - webRouter.post('/login', async (req, res) => { const { error, value: body } = loginWebValidation(req.body) if (error) return res.status(400).send(error.details[0].message) diff --git a/web/src/components/login.tsx b/web/src/components/login.tsx index 7d8bfcd..cca13e2 100644 --- a/web/src/components/login.tsx +++ b/web/src/components/login.tsx @@ -10,13 +10,7 @@ const getAuthCode = async (credentials: any) => axios.post('/SASjsApi/auth/authorize', credentials).then((res) => res.data) const login = async (payload: { username: string; password: string }) => - axios.get('/form').then((res1) => - axios - .post('/login', payload, { - headers: { 'csrf-token': res1.data.csrfToken } - }) - .then((res2) => res2.data) - ) + axios.post('/login', payload).then((res) => res.data) const Login = ({ getCodeOnly }: any) => { const location = useLocation() diff --git a/web/src/context/appContext.tsx b/web/src/context/appContext.tsx index dea218e..6e5b72e 100644 --- a/web/src/context/appContext.tsx +++ b/web/src/context/appContext.tsx @@ -52,6 +52,7 @@ const AppContextProvider = (props: { children: ReactNode }) => { }) .catch(() => { setLoggedIn(false) + axios.get('/') // get CSRF TOKEN }) }, []) From b4b60c69cf67a42f4797f7f1afe68b7a5eec2998 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Sat, 30 Apr 2022 06:32:24 +0500 Subject: [PATCH 003/163] fix: setting CSRF Token for only rendering SPA --- api/src/routes/web/index.ts | 5 ----- api/src/routes/web/web.ts | 7 +++++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/api/src/routes/web/index.ts b/api/src/routes/web/index.ts index 6f04db4..e9d91cd 100644 --- a/api/src/routes/web/index.ts +++ b/api/src/routes/web/index.ts @@ -6,11 +6,6 @@ const router = express.Router() router.use(csrfProtection) -router.use(function (req, res, next) { - res.cookie('XSRF-TOKEN', req.csrfToken()) - next() -}) - router.use('/', webRouter) export default router diff --git a/api/src/routes/web/web.ts b/api/src/routes/web/web.ts index f9889b0..0a84af9 100644 --- a/api/src/routes/web/web.ts +++ b/api/src/routes/web/web.ts @@ -6,10 +6,13 @@ import { getWebBuildFolderPath, loginWebValidation } from '../../utils' const webRouter = express.Router() -webRouter.get('/', async (_, res) => { +webRouter.get('/', async (req, res) => { const indexHtmlPath = path.join(getWebBuildFolderPath(), 'index.html') - if (await fileExists(indexHtmlPath)) return res.sendFile(indexHtmlPath) + if (await fileExists(indexHtmlPath)) { + res.cookie('XSRF-TOKEN', req.csrfToken()) + return res.sendFile(indexHtmlPath) + } return res.send('Web Build is not present') }) From 09611cb4162ecf07ac1c798c9ee5f656c7817084 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Sat, 30 Apr 2022 18:53:44 +0000 Subject: [PATCH 004/163] chore(release): 0.0.60 --- CHANGELOG.md | 8 ++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77acb6f..312dec5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ 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.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) diff --git a/package-lock.json b/package-lock.json index c6a5bac..63391a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "server", - "version": "0.0.59", + "version": "0.0.60", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "server", - "version": "0.0.59", + "version": "0.0.60", "devDependencies": { "prettier": "^2.3.1", "standard-version": "^9.3.2" diff --git a/package.json b/package.json index 4d1ce46..1b0c565 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "server", - "version": "0.0.59", + "version": "0.0.60", "description": "NodeJS wrapper for calling the SAS binary executable", "repository": "https://github.com/sasjs/server", "scripts": { From 60a2a4fe32391ec1bc9ba03f02c45c7589adb668 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Sun, 1 May 2022 01:59:04 +0500 Subject: [PATCH 005/163] chore: bumped pkg version --- api/package-lock.json | 42 +++++++++++++++++++++--------------------- api/package.json | 2 +- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/api/package-lock.json b/api/package-lock.json index a3b8311..bb2bb39 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -49,7 +49,7 @@ "jest": "^27.0.6", "mongodb-memory-server": "^8.0.0", "nodemon": "^2.0.7", - "pkg": "5.5.2", + "pkg": "5.6.0", "prettier": "^2.3.1", "rimraf": "^3.0.2", "supertest": "^6.1.3", @@ -8170,9 +8170,9 @@ } }, "node_modules/pkg": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/pkg/-/pkg-5.5.2.tgz", - "integrity": "sha512-pD0UB2ud01C6pVv2wpGsTYJrXI/bnvGRYvMLd44wFzA1p+A2jrlTGFPAYa7YEYzmitXhx23PqalaG1eUEnSwcA==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pkg/-/pkg-5.6.0.tgz", + "integrity": "sha512-mHrAVSQWmHA41RnUmRpC7pK9lNnMfdA16CF3cqOI22a8LZxOQzF7M8YWtA2nfs+d7I0MTDXOtkDsAsFXeCpYjg==", "dev": true, "dependencies": { "@babel/parser": "7.16.2", @@ -8184,7 +8184,7 @@ "into-stream": "^6.0.0", "minimist": "^1.2.5", "multistream": "^4.1.0", - "pkg-fetch": "3.2.6", + "pkg-fetch": "3.3.0", "prebuild-install": "6.1.4", "progress": "^2.0.3", "resolve": "^1.20.0", @@ -8216,9 +8216,9 @@ } }, "node_modules/pkg-fetch": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/pkg-fetch/-/pkg-fetch-3.2.6.tgz", - "integrity": "sha512-Q8fx6SIT022g0cdSE4Axv/xpfHeltspo2gg1KsWRinLQZOTRRAtOOaEFghA1F3jJ8FVsh8hGrL/Pb6Ea5XHIFw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/pkg-fetch/-/pkg-fetch-3.3.0.tgz", + "integrity": "sha512-xJnIZ1KP+8rNN+VLafwu4tEeV4m8IkFBDdCFqmAJz9K1aiXEtbARmdbEe6HlXWGSVuShSHjFXpfkKRkDBQ5kiA==", "dev": true, "dependencies": { "chalk": "^4.1.2", @@ -8275,9 +8275,9 @@ } }, "node_modules/pkg-fetch/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -16649,9 +16649,9 @@ } }, "pkg": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/pkg/-/pkg-5.5.2.tgz", - "integrity": "sha512-pD0UB2ud01C6pVv2wpGsTYJrXI/bnvGRYvMLd44wFzA1p+A2jrlTGFPAYa7YEYzmitXhx23PqalaG1eUEnSwcA==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pkg/-/pkg-5.6.0.tgz", + "integrity": "sha512-mHrAVSQWmHA41RnUmRpC7pK9lNnMfdA16CF3cqOI22a8LZxOQzF7M8YWtA2nfs+d7I0MTDXOtkDsAsFXeCpYjg==", "dev": true, "requires": { "@babel/parser": "7.16.2", @@ -16663,7 +16663,7 @@ "into-stream": "^6.0.0", "minimist": "^1.2.5", "multistream": "^4.1.0", - "pkg-fetch": "3.2.6", + "pkg-fetch": "3.3.0", "prebuild-install": "6.1.4", "progress": "^2.0.3", "resolve": "^1.20.0", @@ -16720,9 +16720,9 @@ } }, "pkg-fetch": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/pkg-fetch/-/pkg-fetch-3.2.6.tgz", - "integrity": "sha512-Q8fx6SIT022g0cdSE4Axv/xpfHeltspo2gg1KsWRinLQZOTRRAtOOaEFghA1F3jJ8FVsh8hGrL/Pb6Ea5XHIFw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/pkg-fetch/-/pkg-fetch-3.3.0.tgz", + "integrity": "sha512-xJnIZ1KP+8rNN+VLafwu4tEeV4m8IkFBDdCFqmAJz9K1aiXEtbARmdbEe6HlXWGSVuShSHjFXpfkKRkDBQ5kiA==", "dev": true, "requires": { "chalk": "^4.1.2", @@ -16764,9 +16764,9 @@ "dev": true }, "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", "dev": true, "requires": { "lru-cache": "^6.0.0" diff --git a/api/package.json b/api/package.json index c0c201e..2657b7e 100644 --- a/api/package.json +++ b/api/package.json @@ -85,7 +85,7 @@ "jest": "^27.0.6", "mongodb-memory-server": "^8.0.0", "nodemon": "^2.0.7", - "pkg": "5.5.2", + "pkg": "5.6.0", "prettier": "^2.3.1", "rimraf": "^3.0.2", "supertest": "^6.1.3", From 57b63db9cb4b73f00a3b3d066db8f28d16479acb Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Sun, 1 May 2022 01:59:12 +0500 Subject: [PATCH 006/163] chore(release): 0.0.61 --- CHANGELOG.md | 8 ++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 312dec5..515baaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ 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.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) diff --git a/package-lock.json b/package-lock.json index 63391a1..b065837 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "server", - "version": "0.0.60", + "version": "0.0.61", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "server", - "version": "0.0.60", + "version": "0.0.61", "devDependencies": { "prettier": "^2.3.1", "standard-version": "^9.3.2" diff --git a/package.json b/package.json index 1b0c565..3cc68a3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "server", - "version": "0.0.60", + "version": "0.0.61", "description": "NodeJS wrapper for calling the SAS binary executable", "repository": "https://github.com/sasjs/server", "scripts": { From 2c77317bb9dbb14b0034dc9474bbdf0c781072f9 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Sun, 1 May 2022 02:07:55 +0500 Subject: [PATCH 007/163] chore: release using node LTS --- .github/workflows/release.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 73e1aed..c9b599f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,9 +8,19 @@ on: jobs: release: runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [lts/*] + steps: - 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 working-directory: ./web From 6139e7bff6693cc405d38946d68beeed9ddfbfb9 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Sun, 1 May 2022 02:08:03 +0500 Subject: [PATCH 008/163] chore(release): 0.0.62 --- CHANGELOG.md | 2 ++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 515baaa..0755e9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ 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.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) diff --git a/package-lock.json b/package-lock.json index b065837..415dc94 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "server", - "version": "0.0.61", + "version": "0.0.62", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "server", - "version": "0.0.61", + "version": "0.0.62", "devDependencies": { "prettier": "^2.3.1", "standard-version": "^9.3.2" diff --git a/package.json b/package.json index 3cc68a3..0820b30 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "server", - "version": "0.0.61", + "version": "0.0.62", "description": "NodeJS wrapper for calling the SAS binary executable", "repository": "https://github.com/sasjs/server", "scripts": { From 5689169ce48279584d0a0297328e4325e1bfd1ec Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Sun, 1 May 2022 02:10:17 +0500 Subject: [PATCH 009/163] chore: syntax fix for workflow --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c9b599f..5a28fb4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,7 +15,7 @@ jobs: steps: - name: Checkout - - uses: actions/checkout@v2 + uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v2 From 6e7f28a6f8cd0bf1617e476f359b435db68e3afc Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Sun, 1 May 2022 02:10:24 +0500 Subject: [PATCH 010/163] chore(release): 0.0.63 --- CHANGELOG.md | 2 ++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0755e9d..759e241 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ 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.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) diff --git a/package-lock.json b/package-lock.json index 415dc94..bbc7b10 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "server", - "version": "0.0.62", + "version": "0.0.63", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "server", - "version": "0.0.62", + "version": "0.0.63", "devDependencies": { "prettier": "^2.3.1", "standard-version": "^9.3.2" diff --git a/package.json b/package.json index 0820b30..855003f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "server", - "version": "0.0.62", + "version": "0.0.63", "description": "NodeJS wrapper for calling the SAS binary executable", "repository": "https://github.com/sasjs/server", "scripts": { From 7b39cc06d358f5ffecb87955040c4eb0fcc7469e Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Sun, 1 May 2022 02:28:50 +0500 Subject: [PATCH 011/163] fix: removed fileExists for serving web --- api/src/routes/web/web.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/api/src/routes/web/web.ts b/api/src/routes/web/web.ts index 0a84af9..289c02b 100644 --- a/api/src/routes/web/web.ts +++ b/api/src/routes/web/web.ts @@ -1,6 +1,6 @@ import path from 'path' import express from 'express' -import { fileExists } from '@sasjs/utils' +import { readFile } from '@sasjs/utils' import { WebController } from '../../controllers/web' import { getWebBuildFolderPath, loginWebValidation } from '../../utils' @@ -9,12 +9,16 @@ const webRouter = express.Router() webRouter.get('/', async (req, res) => { const indexHtmlPath = path.join(getWebBuildFolderPath(), 'index.html') - if (await fileExists(indexHtmlPath)) { - res.cookie('XSRF-TOKEN', req.csrfToken()) - return res.sendFile(indexHtmlPath) - } + try { + // Attention! Cannot use fileExists here, due to limitation after building executable + const content = await readFile(indexHtmlPath) - return res.send('Web Build is not present') + res.cookie('XSRF-TOKEN', req.csrfToken()) + res.setHeader('Content-Type', 'text/html') + return res.send(content) + } catch (_) { + return res.send('Web Build is not present') + } }) webRouter.post('/login', async (req, res) => { From 6d34206bbcc9e286155269a4da17dabeac69da94 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Sun, 1 May 2022 02:28:57 +0500 Subject: [PATCH 012/163] chore(release): 0.0.64 --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 759e241..bf18ae5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ 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.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) diff --git a/package-lock.json b/package-lock.json index bbc7b10..20bd15f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "server", - "version": "0.0.63", + "version": "0.0.64", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "server", - "version": "0.0.63", + "version": "0.0.64", "devDependencies": { "prettier": "^2.3.1", "standard-version": "^9.3.2" diff --git a/package.json b/package.json index 855003f..1afdd85 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "server", - "version": "0.0.63", + "version": "0.0.64", "description": "NodeJS wrapper for calling the SAS binary executable", "repository": "https://github.com/sasjs/server", "scripts": { From 5aaac24080362d6ce0c5d1157798a9343f40ae2a Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Sun, 1 May 2022 06:07:17 +0500 Subject: [PATCH 013/163] fix: consume swagger api with CSRF --- api/package-lock.json | 18 +++++++++--------- api/package.json | 2 +- api/public/swagger.yaml | 15 +++++++++++++++ api/src/controllers/web.ts | 26 ++++++++++++++++++++++++++ api/src/routes/api/index.ts | 12 +++++++++++- api/src/routes/web/web.ts | 19 +++++-------------- 6 files changed, 67 insertions(+), 25 deletions(-) diff --git a/api/package-lock.json b/api/package-lock.json index bb2bb39..f925f4b 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -24,7 +24,7 @@ "mongoose-sequence": "^5.3.1", "morgan": "^1.10.0", "multer": "^1.4.3", - "swagger-ui-express": "^4.1.6" + "swagger-ui-express": "4.3.0" }, "bin": { "api": "build/src/server.js" @@ -9434,11 +9434,11 @@ "integrity": "sha512-WvfPSfAAMlE/sKS6YkW47nX/hA7StmhYnAHc6wWCXNL0oclwLj6UXv0hQCkLnDgvebi0MEV40SJJpVjKUgH1IQ==" }, "node_modules/swagger-ui-express": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.2.0.tgz", - "integrity": "sha512-znrHTwh9UpvsjqgWopA4noIet7mi7UGuIYZ465YfUDKQ5Dpas0jxnkfUKCo+0aB17YCBv26AhIjiQYDV4uvJFA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.3.0.tgz", + "integrity": "sha512-jN46SEEe9EoXa3ZgZoKgnSF6z0w3tnM1yqhO4Y+Q4iZVc8JOQB960EZpIAz6rNROrDApVDwcMHR0mhlnc/5Omw==", "dependencies": { - "swagger-ui-dist": ">3.52.5" + "swagger-ui-dist": ">=4.1.3" }, "engines": { "node": ">= v0.10.32" @@ -17601,11 +17601,11 @@ "integrity": "sha512-WvfPSfAAMlE/sKS6YkW47nX/hA7StmhYnAHc6wWCXNL0oclwLj6UXv0hQCkLnDgvebi0MEV40SJJpVjKUgH1IQ==" }, "swagger-ui-express": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.2.0.tgz", - "integrity": "sha512-znrHTwh9UpvsjqgWopA4noIet7mi7UGuIYZ465YfUDKQ5Dpas0jxnkfUKCo+0aB17YCBv26AhIjiQYDV4uvJFA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.3.0.tgz", + "integrity": "sha512-jN46SEEe9EoXa3ZgZoKgnSF6z0w3tnM1yqhO4Y+Q4iZVc8JOQB960EZpIAz6rNROrDApVDwcMHR0mhlnc/5Omw==", "requires": { - "swagger-ui-dist": ">3.52.5" + "swagger-ui-dist": ">=4.1.3" } }, "symbol-tree": { diff --git a/api/package.json b/api/package.json index 2657b7e..bc78d95 100644 --- a/api/package.json +++ b/api/package.json @@ -63,7 +63,7 @@ "mongoose-sequence": "^5.3.1", "morgan": "^1.10.0", "multer": "^1.4.3", - "swagger-ui-express": "^4.1.6" + "swagger-ui-express": "4.3.0" }, "devDependencies": { "@types/bcryptjs": "^2.4.2", diff --git a/api/public/swagger.yaml b/api/public/swagger.yaml index 0cee9b9..ed3b794 100644 --- a/api/public/swagger.yaml +++ b/api/public/swagger.yaml @@ -465,6 +465,21 @@ info: name: '4GL Ltd' openapi: 3.0.0 paths: + /: + get: + operationId: Home + responses: + '200': + description: Ok + content: + application/json: + schema: + type: string + summary: 'Render index.html' + tags: + - Web + security: [] + parameters: [] /login: post: operationId: Login diff --git a/api/src/controllers/web.ts b/api/src/controllers/web.ts index 605d8e8..d11ecad 100644 --- a/api/src/controllers/web.ts +++ b/api/src/controllers/web.ts @@ -1,10 +1,23 @@ +import path from 'path' import express from 'express' import { Request, Route, Tags, Post, Body, Get } from 'tsoa' +import { readFile } from '@sasjs/utils' + import User from '../model/User' +import { getWebBuildFolderPath } from '../utils' @Route('/') @Tags('Web') export class WebController { + /** + * @summary Render index.html + * + */ + @Get('/') + public async home(@Request() req: express.Request) { + return home(req) + } + /** * @summary Accept a valid username/password * @@ -31,6 +44,19 @@ export class WebController { } } +const home = async (req: express.Request) => { + const indexHtmlPath = path.join(getWebBuildFolderPath(), 'index.html') + + // Attention! Cannot use fileExists here, + // due to limitation after building executable + const content = await readFile(indexHtmlPath) + + req.res?.cookie('XSRF-TOKEN', req.csrfToken()) + req.res?.setHeader('Content-Type', 'text/html') + + return content +} + const login = async ( req: express.Request, { username, password }: LoginPayload diff --git a/api/src/routes/api/index.ts b/api/src/routes/api/index.ts index 7b249a9..cfe44ce 100644 --- a/api/src/routes/api/index.ts +++ b/api/src/routes/api/index.ts @@ -36,12 +36,22 @@ router.use('/group', desktopRestrict, groupRouter) router.use('/stp', authenticateAccessToken, stpRouter) router.use('/code', authenticateAccessToken, codeRouter) router.use('/user', desktopRestrict, userRouter) + router.use( '/', swaggerUi.serve, swaggerUi.setup(undefined, { 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 + } } }) ) diff --git a/api/src/routes/web/web.ts b/api/src/routes/web/web.ts index 289c02b..6a86829 100644 --- a/api/src/routes/web/web.ts +++ b/api/src/routes/web/web.ts @@ -1,21 +1,14 @@ -import path from 'path' import express from 'express' -import { readFile } from '@sasjs/utils' import { WebController } from '../../controllers/web' -import { getWebBuildFolderPath, loginWebValidation } from '../../utils' +import { loginWebValidation } from '../../utils' const webRouter = express.Router() +const controller = new WebController() webRouter.get('/', async (req, res) => { - const indexHtmlPath = path.join(getWebBuildFolderPath(), 'index.html') - try { - // Attention! Cannot use fileExists here, due to limitation after building executable - const content = await readFile(indexHtmlPath) - - res.cookie('XSRF-TOKEN', req.csrfToken()) - res.setHeader('Content-Type', 'text/html') - return res.send(content) + const response = await controller.home(req) + return res.send(response) } catch (_) { return res.send('Web Build is not present') } @@ -25,7 +18,6 @@ webRouter.post('/login', async (req, res) => { const { error, value: body } = loginWebValidation(req.body) if (error) return res.status(400).send(error.details[0].message) - const controller = new WebController() try { const response = await controller.login(req, body) res.send(response) @@ -35,10 +27,9 @@ webRouter.post('/login', async (req, res) => { }) webRouter.get('/logout', async (req, res) => { - const controller = new WebController() try { await controller.logout(req) - res.status(200).send() + res.status(200).send('OK!') } catch (err: any) { res.status(400).send(err.toString()) } From 6adeeefcf57eff3149e7519ceac08bcba64b2eec Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Sun, 1 May 2022 11:36:26 +0000 Subject: [PATCH 014/163] chore(release): 0.0.65 --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf18ae5..ae5fb0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ 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.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) diff --git a/package-lock.json b/package-lock.json index 20bd15f..2e5f76f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "server", - "version": "0.0.64", + "version": "0.0.65", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "server", - "version": "0.0.64", + "version": "0.0.65", "devDependencies": { "prettier": "^2.3.1", "standard-version": "^9.3.2" diff --git a/package.json b/package.json index 1afdd85..dce07e5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "server", - "version": "0.0.64", + "version": "0.0.65", "description": "NodeJS wrapper for calling the SAS binary executable", "repository": "https://github.com/sasjs/server", "scripts": { From e2a97fcb7c54a57a7ca118677cfce93fe9430d8f Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Sun, 1 May 2022 23:31:48 +0500 Subject: [PATCH 015/163] fix: added swagger ui init file manually --- api/public/SASjsApi/swagger-ui-init.js | 50 ++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 api/public/SASjsApi/swagger-ui-init.js diff --git a/api/public/SASjsApi/swagger-ui-init.js b/api/public/SASjsApi/swagger-ui-init.js new file mode 100644 index 0000000..2dbf1fb --- /dev/null +++ b/api/public/SASjsApi/swagger-ui-init.js @@ -0,0 +1,50 @@ +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 +} From 5f29dec16fb905eba1497b01063d94fb8112450f Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Sun, 1 May 2022 23:31:59 +0500 Subject: [PATCH 016/163] chore(release): 0.0.66 --- CHANGELOG.md | 8 ++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae5fb0a..c66a9ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ 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.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) diff --git a/package-lock.json b/package-lock.json index 2e5f76f..a7bcda4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "server", - "version": "0.0.65", + "version": "0.0.66", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "server", - "version": "0.0.65", + "version": "0.0.66", "devDependencies": { "prettier": "^2.3.1", "standard-version": "^9.3.2" diff --git a/package.json b/package.json index dce07e5..4d50847 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "server", - "version": "0.0.65", + "version": "0.0.66", "description": "NodeJS wrapper for calling the SAS binary executable", "repository": "https://github.com/sasjs/server", "scripts": { From 35cba9761165504020901d744c53cd89379aa14f Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Mon, 2 May 2022 03:40:14 +0500 Subject: [PATCH 017/163] chore: commented helmet middleware --- api/src/app.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/src/app.ts b/api/src/app.ts index 9c44224..9ea2a53 100644 --- a/api/src/app.ts +++ b/api/src/app.ts @@ -41,7 +41,8 @@ export const csrfProtection = csrf({ cookie: cookieOptions }) /*********************************** * Handle security and origin * ***********************************/ -app.use(helmet()) +// TODO: fix monaco loader from npm package before enabling helmet +// app.use(helmet()) /*********************************** * Enabling CORS * From 238aa1006f7891b7e597bccbf8be10a1201a20bf Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Mon, 2 May 2022 03:41:07 +0500 Subject: [PATCH 018/163] chore(release): 0.0.67 --- CHANGELOG.md | 2 ++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c66a9ca..5d44b0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ 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.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) diff --git a/package-lock.json b/package-lock.json index a7bcda4..b51d1da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "server", - "version": "0.0.66", + "version": "0.0.67", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "server", - "version": "0.0.66", + "version": "0.0.67", "devDependencies": { "prettier": "^2.3.1", "standard-version": "^9.3.2" diff --git a/package.json b/package.json index 4d50847..71ad95b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "server", - "version": "0.0.66", + "version": "0.0.67", "description": "NodeJS wrapper for calling the SAS binary executable", "repository": "https://github.com/sasjs/server", "scripts": { From 2548c82dfe1149e62a570a00546dddd9e30049b1 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Mon, 2 May 2022 05:57:03 +0500 Subject: [PATCH 019/163] fix: using monaco editor locally --- api/src/app.ts | 3 +- web/package-lock.json | 370 +++++++++------------------- web/package.json | 3 +- web/src/containers/Drive/main.tsx | 3 +- web/src/containers/Studio/index.tsx | 8 +- web/webpack.common.ts | 4 +- 6 files changed, 126 insertions(+), 265 deletions(-) diff --git a/api/src/app.ts b/api/src/app.ts index 9ea2a53..9c44224 100644 --- a/api/src/app.ts +++ b/api/src/app.ts @@ -41,8 +41,7 @@ export const csrfProtection = csrf({ cookie: cookieOptions }) /*********************************** * Handle security and origin * ***********************************/ -// TODO: fix monaco loader from npm package before enabling helmet -// app.use(helmet()) +app.use(helmet()) /*********************************** * Enabling CORS * diff --git a/web/package-lock.json b/web/package-lock.json index 7b9516c..06dc2f1 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -10,7 +10,6 @@ "dependencies": { "@emotion/react": "^11.4.1", "@emotion/styled": "^11.3.0", - "@monaco-editor/react": "^4.3.1", "@mui/icons-material": "^5.0.3", "@mui/lab": "^5.0.0-alpha.50", "@mui/material": "^5.0.3", @@ -22,8 +21,10 @@ "@types/node": "^12.20.28", "@types/react": "^17.0.27", "axios": "^0.24.0", + "monaco-editor-webpack-plugin": "^7.0.1", "react": "^17.0.2", "react-dom": "^17.0.2", + "react-monaco-editor": "^0.48.0", "react-router-dom": "^5.3.0" }, "devDependencies": { @@ -2279,31 +2280,6 @@ "node": ">=8" } }, - "node_modules/@monaco-editor/loader": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.2.0.tgz", - "integrity": "sha512-cJVCG/T/KxXgzYnjKqyAgsKDbH9mGLjcXxN6AmwumBwa2rVFkwvGcUj1RJtD0ko4XqLqJxwqsN/Z/KURB5f1OQ==", - "dependencies": { - "state-local": "^1.0.6" - }, - "peerDependencies": { - "monaco-editor": ">= 0.21.0 < 1" - } - }, - "node_modules/@monaco-editor/react": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.3.1.tgz", - "integrity": "sha512-f+0BK1PP/W5I50hHHmwf11+Ea92E5H1VZXs+wvKplWUWOfyMa1VVwqkJrXjRvbcqHL+XdIGYWhWNdi4McEvnZg==", - "dependencies": { - "@monaco-editor/loader": "^1.2.0", - "prop-types": "^15.7.2" - }, - "peerDependencies": { - "monaco-editor": ">= 0.25.0 < 1", - "react": "^16.8.0 || ^17.0.0", - "react-dom": "^16.8.0 || ^17.0.0" - } - }, "node_modules/@mui/core": { "version": "5.0.0-alpha.54", "resolved": "https://registry.npmjs.org/@mui/core/-/core-5.0.0-alpha.54.tgz", @@ -3065,7 +3041,6 @@ "version": "7.29.0", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.29.0.tgz", "integrity": "sha512-VNcvioYDH8/FxaeTKkM4/TiTwt6pBV9E3OfGmvaw8tPl0rrHCJ4Ll15HRT+pMiFAf/MLQvAzC+6RzUMEL9Ceng==", - "dev": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -3075,7 +3050,6 @@ "version": "3.7.3", "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.3.tgz", "integrity": "sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g==", - "dev": true, "dependencies": { "@types/eslint": "*", "@types/estree": "*" @@ -3084,8 +3058,7 @@ "node_modules/@types/estree": { "version": "0.0.50", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", - "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==", - "dev": true + "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==" }, "node_modules/@types/express": { "version": "4.17.13", @@ -3265,8 +3238,7 @@ "node_modules/@types/json-schema": { "version": "7.0.9", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", - "dev": true + "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==" }, "node_modules/@types/json5": { "version": "0.0.29", @@ -3761,7 +3733,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", - "dev": true, "dependencies": { "@webassemblyjs/helper-numbers": "1.11.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.1" @@ -3770,26 +3741,22 @@ "node_modules/@webassemblyjs/floating-point-hex-parser": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", - "dev": true + "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==" }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", - "dev": true + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==" }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", - "dev": true + "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==" }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", - "dev": true, "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.11.1", "@webassemblyjs/helper-api-error": "1.11.1", @@ -3799,14 +3766,12 @@ "node_modules/@webassemblyjs/helper-wasm-bytecode": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", - "dev": true + "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==" }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", - "dev": true, "dependencies": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -3818,7 +3783,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", - "dev": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } @@ -3827,7 +3791,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", - "dev": true, "dependencies": { "@xtuc/long": "4.2.2" } @@ -3835,14 +3798,12 @@ "node_modules/@webassemblyjs/utf8": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", - "dev": true + "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==" }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", - "dev": true, "dependencies": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -3858,7 +3819,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", - "dev": true, "dependencies": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.1", @@ -3871,7 +3831,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", - "dev": true, "dependencies": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -3883,7 +3842,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", - "dev": true, "dependencies": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-api-error": "1.11.1", @@ -3897,7 +3855,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", - "dev": true, "dependencies": { "@webassemblyjs/ast": "1.11.1", "@xtuc/long": "4.2.2" @@ -3942,14 +3899,12 @@ "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" }, "node_modules/@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" }, "node_modules/accepts": { "version": "1.3.7", @@ -4003,7 +3958,6 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -4058,7 +4012,6 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, "peerDependencies": { "ajv": "^6.9.1" } @@ -4466,7 +4419,6 @@ "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true, "engines": { "node": "*" } @@ -4592,8 +4544,7 @@ "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, "node_modules/buffer-indexof": { "version": "1.1.1", @@ -4711,7 +4662,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true, "engines": { "node": ">=6.0" } @@ -5721,7 +5671,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true, "engines": { "node": ">= 4" } @@ -5739,7 +5688,6 @@ "version": "5.9.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.0.tgz", "integrity": "sha512-weDYmzbBygL7HzGGS26M3hGQx68vehdEg6VUmqSOaFzXExFqlnKuSvsEJCVGQHScS8CQMbrAqftT+AzzHNt/YA==", - "dev": true, "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -5814,8 +5762,7 @@ "node_modules/es-module-lexer": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", - "dev": true + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==" }, "node_modules/es-to-primitive": { "version": "1.2.1", @@ -6267,7 +6214,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -6280,7 +6226,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, "engines": { "node": ">=4.0" } @@ -6589,7 +6534,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, "dependencies": { "estraverse": "^5.2.0" }, @@ -6601,7 +6545,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, "engines": { "node": ">=4.0" } @@ -6634,7 +6577,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, "engines": { "node": ">=0.8.x" } @@ -6710,8 +6652,7 @@ "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-glob": { "version": "3.2.7", @@ -6732,8 +6673,7 @@ "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "node_modules/fast-levenshtein": { "version": "2.0.6", @@ -7061,8 +7001,7 @@ "node_modules/glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" }, "node_modules/globals": { "version": "11.12.0", @@ -7095,8 +7034,7 @@ "node_modules/graceful-fs": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", - "dev": true + "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" }, "node_modules/handle-thing": { "version": "2.0.1", @@ -8032,8 +7970,7 @@ "node_modules/json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", @@ -8043,8 +7980,7 @@ "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -8216,7 +8152,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==", - "dev": true, "engines": { "node": ">=6.11.5" } @@ -8225,7 +8160,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", - "dev": true, "dependencies": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", @@ -8356,8 +8290,7 @@ "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" }, "node_modules/merge2": { "version": "1.4.1", @@ -8406,7 +8339,6 @@ "version": "1.51.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -8415,7 +8347,6 @@ "version": "2.1.34", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", - "dev": true, "dependencies": { "mime-db": "1.51.0" }, @@ -8489,11 +8420,23 @@ } }, "node_modules/monaco-editor": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.30.1.tgz", - "integrity": "sha512-B/y4+b2O5G2gjuxIFtCE2EkM17R2NM7/3F8x0qcPsqy4V83bitJTIO4TIeZpYlzu/xy6INiY/+84BEm6+7Cmzg==", + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.33.0.tgz", + "integrity": "sha512-VcRWPSLIUEgQJQIE0pVT8FcGBIgFoxz7jtqctE+IiCxWugD0DwgyQBcZBhdSrdMC84eumoqMZsGl2GTreOzwqw==", "peer": true }, + "node_modules/monaco-editor-webpack-plugin": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-7.0.1.tgz", + "integrity": "sha512-M8qIqizltrPlIbrb73cZdTWfU9sIsUVFvAZkL3KGjAHmVWEJ0hZKa/uad14JuOckc0GwnCaoGHvMoYtJjVyCzw==", + "dependencies": { + "loader-utils": "^2.0.2" + }, + "peerDependencies": { + "monaco-editor": ">= 0.31.0", + "webpack": "^4.5.0 || 5.x" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -8548,8 +8491,7 @@ "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, "node_modules/no-case": { "version": "3.0.4", @@ -9240,13 +9182,13 @@ "dev": true }, "node_modules/prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", - "react-is": "^16.8.1" + "react-is": "^16.13.1" } }, "node_modules/prop-types/node_modules/react-is": { @@ -9271,7 +9213,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, "engines": { "node": ">=6" } @@ -9309,7 +9250,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, "dependencies": { "safe-buffer": "^5.1.0" } @@ -9377,6 +9317,19 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "node_modules/react-monaco-editor": { + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/react-monaco-editor/-/react-monaco-editor-0.48.0.tgz", + "integrity": "sha512-fD3ww76aIGJynTa6QZqM9hbRHCQe8v20+W2ofTjLzx8P1FTdoWoPPR6AIqCTMc1xjOrmGJVWFYFp5U6z5C04KQ==", + "dependencies": { + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "@types/react": "^17.x", + "monaco-editor": "^0.33.0", + "react": "^17.x" + } + }, "node_modules/react-router": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.1.tgz", @@ -10110,7 +10063,6 @@ "version": "0.5.20", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", - "dev": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -10120,7 +10072,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -10169,11 +10120,6 @@ "node": ">= 6" } }, - "node_modules/state-local": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz", - "integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==" - }, "node_modules/statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", @@ -10343,7 +10289,6 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, "engines": { "node": ">=6" } @@ -10681,7 +10626,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, "dependencies": { "punycode": "^2.1.0" } @@ -10758,7 +10702,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz", "integrity": "sha512-x0t0JuydIo8qCNctdDrn1OzH/qDzk2+rdCOC3YzumZ42fiMqmQ7T3xQurykYMhYfHaPHTp4ZxAx2NfUo1K6QaA==", - "dev": true, "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -10780,7 +10723,6 @@ "version": "5.64.3", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.64.3.tgz", "integrity": "sha512-XF6/IL9Bw2PPQioiR1UYA8Bs4tX3QXJtSelezKECdLFeSFzWoe44zqTzPW5N+xI3fACaRl2/G3sNA4WYHD7Iww==", - "dev": true, "dependencies": { "@types/eslint-scope": "^3.7.0", "@types/estree": "^0.0.50", @@ -11197,7 +11139,6 @@ "version": "8.7.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", - "dev": true, "bin": { "acorn": "bin/acorn" }, @@ -11209,7 +11150,6 @@ "version": "1.8.0", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true, "peerDependencies": { "acorn": "^8" } @@ -11217,14 +11157,12 @@ "node_modules/webpack/node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, "node_modules/webpack/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -11233,7 +11171,6 @@ "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -11247,7 +11184,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dev": true, "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -11265,7 +11201,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, "dependencies": { "randombytes": "^2.1.0" } @@ -11274,7 +11209,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -11283,7 +11217,6 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -11298,7 +11231,6 @@ "version": "5.10.0", "resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz", "integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==", - "dev": true, "dependencies": { "commander": "^2.20.0", "source-map": "~0.7.2", @@ -11323,7 +11255,6 @@ "version": "5.3.1", "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.1.tgz", "integrity": "sha512-GvlZdT6wPQKbDNW/GDQzZFg/j4vKU96yl2q6mcUkzKOgW4gwf1Z8cZToUCrz31XHlPWH8MVb1r2tFtdDtTGJ7g==", - "dev": true, "dependencies": { "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", @@ -11357,7 +11288,6 @@ "version": "0.7.3", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true, "engines": { "node": ">= 8" } @@ -11366,7 +11296,6 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true, "engines": { "node": ">=10.13.0" } @@ -12996,23 +12925,6 @@ } } }, - "@monaco-editor/loader": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.2.0.tgz", - "integrity": "sha512-cJVCG/T/KxXgzYnjKqyAgsKDbH9mGLjcXxN6AmwumBwa2rVFkwvGcUj1RJtD0ko4XqLqJxwqsN/Z/KURB5f1OQ==", - "requires": { - "state-local": "^1.0.6" - } - }, - "@monaco-editor/react": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.3.1.tgz", - "integrity": "sha512-f+0BK1PP/W5I50hHHmwf11+Ea92E5H1VZXs+wvKplWUWOfyMa1VVwqkJrXjRvbcqHL+XdIGYWhWNdi4McEvnZg==", - "requires": { - "@monaco-editor/loader": "^1.2.0", - "prop-types": "^15.7.2" - } - }, "@mui/core": { "version": "5.0.0-alpha.54", "resolved": "https://registry.npmjs.org/@mui/core/-/core-5.0.0-alpha.54.tgz", @@ -13500,7 +13412,6 @@ "version": "7.29.0", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.29.0.tgz", "integrity": "sha512-VNcvioYDH8/FxaeTKkM4/TiTwt6pBV9E3OfGmvaw8tPl0rrHCJ4Ll15HRT+pMiFAf/MLQvAzC+6RzUMEL9Ceng==", - "dev": true, "requires": { "@types/estree": "*", "@types/json-schema": "*" @@ -13510,7 +13421,6 @@ "version": "3.7.3", "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.3.tgz", "integrity": "sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g==", - "dev": true, "requires": { "@types/eslint": "*", "@types/estree": "*" @@ -13519,8 +13429,7 @@ "@types/estree": { "version": "0.0.50", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", - "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==", - "dev": true + "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==" }, "@types/express": { "version": "4.17.13", @@ -13675,8 +13584,7 @@ "@types/json-schema": { "version": "7.0.9", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", - "dev": true + "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==" }, "@types/json5": { "version": "0.0.29", @@ -14033,7 +13941,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", - "dev": true, "requires": { "@webassemblyjs/helper-numbers": "1.11.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.1" @@ -14042,26 +13949,22 @@ "@webassemblyjs/floating-point-hex-parser": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", - "dev": true + "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==" }, "@webassemblyjs/helper-api-error": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", - "dev": true + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==" }, "@webassemblyjs/helper-buffer": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", - "dev": true + "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==" }, "@webassemblyjs/helper-numbers": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", - "dev": true, "requires": { "@webassemblyjs/floating-point-hex-parser": "1.11.1", "@webassemblyjs/helper-api-error": "1.11.1", @@ -14071,14 +13974,12 @@ "@webassemblyjs/helper-wasm-bytecode": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", - "dev": true + "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==" }, "@webassemblyjs/helper-wasm-section": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", - "dev": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -14090,7 +13991,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", - "dev": true, "requires": { "@xtuc/ieee754": "^1.2.0" } @@ -14099,7 +13999,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", - "dev": true, "requires": { "@xtuc/long": "4.2.2" } @@ -14107,14 +14006,12 @@ "@webassemblyjs/utf8": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", - "dev": true + "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==" }, "@webassemblyjs/wasm-edit": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", - "dev": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -14130,7 +14027,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", - "dev": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.1", @@ -14143,7 +14039,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", - "dev": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -14155,7 +14050,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", - "dev": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-api-error": "1.11.1", @@ -14169,7 +14063,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", - "dev": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@xtuc/long": "4.2.2" @@ -14201,14 +14094,12 @@ "@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" }, "@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" }, "accepts": { "version": "1.3.7", @@ -14248,7 +14139,6 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -14289,7 +14179,6 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, "requires": {} }, "ansi-html-community": { @@ -14604,8 +14493,7 @@ "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" }, "binary-extensions": { "version": "2.2.0", @@ -14708,8 +14596,7 @@ "buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, "buffer-indexof": { "version": "1.1.1", @@ -14795,8 +14682,7 @@ "chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==" }, "clean-css": { "version": "5.2.4", @@ -15559,8 +15445,7 @@ "emojis-list": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==" }, "encodeurl": { "version": "1.0.2", @@ -15572,7 +15457,6 @@ "version": "5.9.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.0.tgz", "integrity": "sha512-weDYmzbBygL7HzGGS26M3hGQx68vehdEg6VUmqSOaFzXExFqlnKuSvsEJCVGQHScS8CQMbrAqftT+AzzHNt/YA==", - "dev": true, "requires": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -15629,8 +15513,7 @@ "es-module-lexer": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", - "dev": true + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==" }, "es-to-primitive": { "version": "1.2.1", @@ -16082,7 +15965,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, "requires": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -16091,8 +15973,7 @@ "estraverse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" } } }, @@ -16201,7 +16082,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, "requires": { "estraverse": "^5.2.0" } @@ -16209,8 +16089,7 @@ "estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" }, "esutils": { "version": "2.0.3", @@ -16233,8 +16112,7 @@ "events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" }, "express": { "version": "4.17.1", @@ -16306,8 +16184,7 @@ "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-glob": { "version": "3.2.7", @@ -16325,8 +16202,7 @@ "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "fast-levenshtein": { "version": "2.0.6", @@ -16572,8 +16448,7 @@ "glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" }, "globals": { "version": "11.12.0", @@ -16597,8 +16472,7 @@ "graceful-fs": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", - "dev": true + "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" }, "handle-thing": { "version": "2.0.1", @@ -17266,8 +17140,7 @@ "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" }, "json-parse-even-better-errors": { "version": "2.3.1", @@ -17277,8 +17150,7 @@ "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -17427,14 +17299,12 @@ "loader-runner": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", - "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==", - "dev": true + "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==" }, "loader-utils": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", - "dev": true, "requires": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", @@ -17540,8 +17410,7 @@ "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" }, "merge2": { "version": "1.4.1", @@ -17574,14 +17443,12 @@ "mime-db": { "version": "1.51.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", - "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", - "dev": true + "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==" }, "mime-types": { "version": "2.1.34", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", - "dev": true, "requires": { "mime-db": "1.51.0" } @@ -17636,11 +17503,19 @@ } }, "monaco-editor": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.30.1.tgz", - "integrity": "sha512-B/y4+b2O5G2gjuxIFtCE2EkM17R2NM7/3F8x0qcPsqy4V83bitJTIO4TIeZpYlzu/xy6INiY/+84BEm6+7Cmzg==", + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.33.0.tgz", + "integrity": "sha512-VcRWPSLIUEgQJQIE0pVT8FcGBIgFoxz7jtqctE+IiCxWugD0DwgyQBcZBhdSrdMC84eumoqMZsGl2GTreOzwqw==", "peer": true }, + "monaco-editor-webpack-plugin": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-7.0.1.tgz", + "integrity": "sha512-M8qIqizltrPlIbrb73cZdTWfU9sIsUVFvAZkL3KGjAHmVWEJ0hZKa/uad14JuOckc0GwnCaoGHvMoYtJjVyCzw==", + "requires": { + "loader-utils": "^2.0.2" + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -17683,8 +17558,7 @@ "neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, "no-case": { "version": "3.0.4", @@ -18203,13 +18077,13 @@ "dev": true }, "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "requires": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", - "react-is": "^16.8.1" + "react-is": "^16.13.1" }, "dependencies": { "react-is": { @@ -18232,8 +18106,7 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "qs": { "version": "6.7.0", @@ -18251,7 +18124,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, "requires": { "safe-buffer": "^5.1.0" } @@ -18306,6 +18178,14 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "react-monaco-editor": { + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/react-monaco-editor/-/react-monaco-editor-0.48.0.tgz", + "integrity": "sha512-fD3ww76aIGJynTa6QZqM9hbRHCQe8v20+W2ofTjLzx8P1FTdoWoPPR6AIqCTMc1xjOrmGJVWFYFp5U6z5C04KQ==", + "requires": { + "prop-types": "^15.8.1" + } + }, "react-router": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.1.tgz", @@ -18881,7 +18761,6 @@ "version": "0.5.20", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", - "dev": true, "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -18890,8 +18769,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" } } }, @@ -18935,11 +18813,6 @@ } } }, - "state-local": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz", - "integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==" - }, "statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", @@ -19057,8 +18930,7 @@ "tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==" }, "text-table": { "version": "0.2.0", @@ -19307,7 +19179,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, "requires": { "punycode": "^2.1.0" } @@ -19377,7 +19248,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz", "integrity": "sha512-x0t0JuydIo8qCNctdDrn1OzH/qDzk2+rdCOC3YzumZ42fiMqmQ7T3xQurykYMhYfHaPHTp4ZxAx2NfUo1K6QaA==", - "dev": true, "requires": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -19396,7 +19266,6 @@ "version": "5.64.3", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.64.3.tgz", "integrity": "sha512-XF6/IL9Bw2PPQioiR1UYA8Bs4tX3QXJtSelezKECdLFeSFzWoe44zqTzPW5N+xI3fACaRl2/G3sNA4WYHD7Iww==", - "dev": true, "requires": { "@types/eslint-scope": "^3.7.0", "@types/estree": "^0.0.50", @@ -19427,33 +19296,28 @@ "acorn": { "version": "8.7.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", - "dev": true + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==" }, "acorn-import-assertions": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true, "requires": {} }, "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, "jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, "requires": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -19464,7 +19328,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dev": true, "requires": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -19475,7 +19338,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, "requires": { "randombytes": "^2.1.0" } @@ -19483,14 +19345,12 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, "requires": { "has-flag": "^4.0.0" } @@ -19499,7 +19359,6 @@ "version": "5.10.0", "resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz", "integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==", - "dev": true, "requires": { "commander": "^2.20.0", "source-map": "~0.7.2", @@ -19509,8 +19368,7 @@ "source-map": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" } } }, @@ -19518,7 +19376,6 @@ "version": "5.3.1", "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.1.tgz", "integrity": "sha512-GvlZdT6wPQKbDNW/GDQzZFg/j4vKU96yl2q6mcUkzKOgW4gwf1Z8cZToUCrz31XHlPWH8MVb1r2tFtdDtTGJ7g==", - "dev": true, "requires": { "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", @@ -19530,8 +19387,7 @@ "webpack-sources": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==" } } }, diff --git a/web/package.json b/web/package.json index 67ba5f3..6fe05ff 100644 --- a/web/package.json +++ b/web/package.json @@ -9,7 +9,6 @@ "dependencies": { "@emotion/react": "^11.4.1", "@emotion/styled": "^11.3.0", - "@monaco-editor/react": "^4.3.1", "@mui/icons-material": "^5.0.3", "@mui/lab": "^5.0.0-alpha.50", "@mui/material": "^5.0.3", @@ -21,8 +20,10 @@ "@types/node": "^12.20.28", "@types/react": "^17.0.27", "axios": "^0.24.0", + "monaco-editor-webpack-plugin": "^7.0.1", "react": "^17.0.2", "react-dom": "^17.0.2", + "react-monaco-editor": "^0.48.0", "react-router-dom": "^5.3.0" }, "devDependencies": { diff --git a/web/src/containers/Drive/main.tsx b/web/src/containers/Drive/main.tsx index 0a92e2f..cd44e90 100644 --- a/web/src/containers/Drive/main.tsx +++ b/web/src/containers/Drive/main.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react' import { Link } from 'react-router-dom' import axios from 'axios' -import Editor from '@monaco-editor/react' +import Editor from 'react-monaco-editor' import Box from '@mui/material/Box' import Paper from '@mui/material/Paper' @@ -125,6 +125,7 @@ const Main = (props: Props) => { {!isLoading && props?.selectedFilePath && editMode && ( { if (val) setFileContent(val) diff --git a/web/src/containers/Studio/index.tsx b/web/src/containers/Studio/index.tsx index d2fc8ce..c043dd0 100644 --- a/web/src/containers/Studio/index.tsx +++ b/web/src/containers/Studio/index.tsx @@ -4,7 +4,7 @@ import axios from 'axios' import Box from '@mui/material/Box' import { Button, Paper, Stack, Tab, Tooltip } from '@mui/material' import { makeStyles } from '@mui/styles' -import Editor, { OnMount } from '@monaco-editor/react' +import Editor, { EditorDidMount } from 'react-monaco-editor' import { useLocation } from 'react-router-dom' import { TabContext, TabList, TabPanel } from '@mui/lab' @@ -42,7 +42,7 @@ const Studio = () => { } const editorRef = useRef(null as any) - const handleEditorDidMount: OnMount = (editor) => { + const handleEditorDidMount: EditorDidMount = (editor) => { editor.focus() editorRef.current = editor } @@ -141,6 +141,7 @@ const Studio = () => { + + + ) } From 53bf68a6aff44bb7b2f40d40d6554809253a01a8 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Mon, 23 May 2022 21:14:37 +0500 Subject: [PATCH 056/163] feat(env): added new env variable LOG_FORMAT_MORGAN --- README.md | 4 +++ api/.env.example | 2 ++ api/src/app.ts | 14 +++++++--- api/src/utils/verifyEnvVariables.ts | 40 +++++++++++++++++++++++++---- 4 files changed, 52 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 87aa620..a3a2024 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,10 @@ HELMET_COEP= # } HELMET_CSP_CONFIG_PATH=./csp.config.json +# LOG_FORMAT_MORGAN options: [combined|common|dev|short|tiny] default: `common` +# Docs: https://www.npmjs.com/package/morgan#predefined-formats +LOG_FORMAT_MORGAN= + ``` ## Persisting the Session diff --git a/api/.env.example b/api/.env.example index 163af41..ab6de3e 100644 --- a/api/.env.example +++ b/api/.env.example @@ -19,3 +19,5 @@ DB_CONNECT=mongodb+srv://:@/?retryWr SAS_PATH=/opt/sas/sas9/SASHome/SASFoundation/9.4/sas SASJS_ROOT=./sasjs_root + +LOG_FORMAT_MORGAN=common \ No newline at end of file diff --git a/api/src/app.ts b/api/src/app.ts index a15b08b..33a1e27 100644 --- a/api/src/app.ts +++ b/api/src/app.ts @@ -37,10 +37,18 @@ if (verifyEnvVariables()) { const app = express() app.use(cookieParser()) -app.use(morgan('tiny')) -const { MODE, CORS, WHITELIST, PROTOCOL, HELMET_CSP_CONFIG_PATH, HELMET_COEP } = - process.env +const { + MODE, + CORS, + WHITELIST, + PROTOCOL, + HELMET_CSP_CONFIG_PATH, + HELMET_COEP, + LOG_FORMAT_MORGAN +} = process.env + +app.use(morgan(LOG_FORMAT_MORGAN as string)) export const cookieOptions = { secure: PROTOCOL === ProtocolType.HTTPS, diff --git a/api/src/utils/verifyEnvVariables.ts b/api/src/utils/verifyEnvVariables.ts index d4eb91e..9d82069 100644 --- a/api/src/utils/verifyEnvVariables.ts +++ b/api/src/utils/verifyEnvVariables.ts @@ -18,6 +18,14 @@ export enum HelmetCoepType { FALSE = 'false' } +export enum LOG_FORMAT_MORGANType { + Combined = 'combined', + Common = 'common', + Dev = 'dev', + Short = 'short', + tiny = 'tiny' +} + export enum ReturnCode { Success, InvalidEnv @@ -36,6 +44,8 @@ export const verifyEnvVariables = (): ReturnCode => { errors.push(...verifyHELMET_COEP()) + errors.push(...verifyLOG_FORMAT_MORGAN()) + if (errors.length) { process.logger?.error( `Invalid environment variable(s) provided: \n${errors.join('\n')}` @@ -173,9 +183,29 @@ const verifyHELMET_COEP = (): string[] => { return errors } -const DEFAULTS = { - MODE: 'desktop', - PROTOCOL: 'http', - PORT: '5000', - HELMET_COEP: 'true' +const verifyLOG_FORMAT_MORGAN = (): string[] => { + const errors: string[] = [] + const { LOG_FORMAT_MORGAN } = process.env + + if (LOG_FORMAT_MORGAN) { + const logFormatMorganTypes = Object.values(LOG_FORMAT_MORGANType) + if ( + !logFormatMorganTypes.includes(LOG_FORMAT_MORGAN as LOG_FORMAT_MORGANType) + ) + errors.push( + `- LOG_FORMAT_MORGAN '${LOG_FORMAT_MORGAN}'\n - valid options ${logFormatMorganTypes}` + ) + LOG_FORMAT_MORGAN + } else { + process.env.LOG_FORMAT_MORGAN = DEFAULTS.LOG_FORMAT_MORGAN + } + return errors +} + +const DEFAULTS = { + MODE: ModeType.Desktop, + PROTOCOL: ProtocolType.HTTP, + PORT: '5000', + HELMET_COEP: HelmetCoepType.TRUE, + LOG_FORMAT_MORGAN: LOG_FORMAT_MORGANType.Common } From 1fed5ea6ac465ef1c74990cfacbb08d80325a06c Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 23 May 2022 21:22:02 +0000 Subject: [PATCH 057/163] chore(release): 0.1.0 [skip ci] # [0.1.0](https://github.com/sasjs/server/compare/v0.0.77...v0.1.0) (2022-05-23) ### Bug Fixes * issue174 + issue175 + issue146 ([80b33c7](https://github.com/sasjs/server/commit/80b33c7a18c1b7727316ffeca71658346733e935)) * **web:** click to copy + notification ([f37f8e9](https://github.com/sasjs/server/commit/f37f8e95d1a85e00ceca2413dbb5e1f3f3f72255)) ### Features * **env:** added new env variable LOG_FORMAT_MORGAN ([53bf68a](https://github.com/sasjs/server/commit/53bf68a6aff44bb7b2f40d40d6554809253a01a8)) --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ffbc99a..cc237a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +# [0.1.0](https://github.com/sasjs/server/compare/v0.0.77...v0.1.0) (2022-05-23) + + +### Bug Fixes + +* issue174 + issue175 + issue146 ([80b33c7](https://github.com/sasjs/server/commit/80b33c7a18c1b7727316ffeca71658346733e935)) +* **web:** click to copy + notification ([f37f8e9](https://github.com/sasjs/server/commit/f37f8e95d1a85e00ceca2413dbb5e1f3f3f72255)) + + +### Features + +* **env:** added new env variable LOG_FORMAT_MORGAN ([53bf68a](https://github.com/sasjs/server/commit/53bf68a6aff44bb7b2f40d40d6554809253a01a8)) + ## [0.0.77](https://github.com/sasjs/server/compare/v0.0.76...v0.0.77) (2022-05-16) From 2a7223ad7d6b8f3d4682447fd25d9426a7c79ac3 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Tue, 24 May 2022 21:12:32 +0500 Subject: [PATCH 058/163] feat(api): added autoexec + major type setting changes --- .gitignore | 1 + api/public/swagger.yaml | 7 ++ api/src/controllers/code.ts | 9 ++- api/src/controllers/internal/Execution.ts | 4 + .../internal/FileUploadController.ts | 9 ++- api/src/controllers/session.ts | 8 +- api/src/controllers/stp.ts | 7 +- api/src/controllers/user.ts | 77 +++++++++++-------- api/src/controllers/web.ts | 3 +- api/src/middlewares/authenticateToken.ts | 31 +++++--- api/src/middlewares/desktop.ts | 6 +- api/src/middlewares/verifyAdmin.ts | 4 +- api/src/middlewares/verifyAdminIfNeeded.ts | 6 +- api/src/model/User.ts | 9 +++ api/src/routes/api/auth.ts | 14 +++- api/src/routes/api/group.ts | 22 ++++-- api/src/routes/api/spec/user.spec.ts | 26 ++++++- api/src/routes/api/stp.ts | 2 +- api/src/routes/api/user.ts | 16 ++-- api/src/types/RequestUser.ts | 9 +++ api/src/types/index.ts | 1 + api/src/types/system/express-session.d.ts | 9 +-- api/src/types/system/express.d.ts | 7 ++ api/src/utils/getPreProgramVariables.ts | 9 ++- api/src/utils/validation.ts | 6 +- api/src/utils/verifyTokenInDB.ts | 24 +++++- 26 files changed, 227 insertions(+), 99 deletions(-) create mode 100644 api/src/types/RequestUser.ts create mode 100644 api/src/types/system/express.d.ts diff --git a/.gitignore b/.gitignore index 7595311..9c567ce 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ node_modules/ .DS_Store .env* sas/ +sasjs_root/ tmp/ build/ sasjsbuild/ diff --git a/api/public/swagger.yaml b/api/public/swagger.yaml index 923115f..eba92d4 100644 --- a/api/public/swagger.yaml +++ b/api/public/swagger.yaml @@ -323,6 +323,8 @@ components: type: boolean isAdmin: type: boolean + autoExec: + type: string required: - id - displayName @@ -352,6 +354,10 @@ components: type: boolean description: 'Account should be active or not, defaults to true' example: 'true' + autoExec: + type: string + description: 'User-specific auto-exec code' + example: '' required: - displayName - username @@ -989,6 +995,7 @@ paths: application/json: schema: $ref: '#/components/schemas/UserDetailsResponse' + description: 'Only Admin or user itself will get user autoExec code.' summary: 'Get user properties - such as group memberships, userName, displayName.' tags: - User diff --git a/api/src/controllers/code.ts b/api/src/controllers/code.ts index 80d1837..d4b18fd 100644 --- a/api/src/controllers/code.ts +++ b/api/src/controllers/code.ts @@ -1,7 +1,6 @@ import express from 'express' import { Request, Security, Route, Tags, Post, Body } from 'tsoa' import { ExecuteReturnJson, ExecutionController } from './internal' -import { PreProgramVars } from '../types' import { ExecuteReturnJsonResponse } from '.' import { getPreProgramVariables, parseLogToArray } from '../utils' @@ -30,14 +29,18 @@ export class CodeController { } } -const executeSASCode = async (req: any, { code }: ExecuteSASCodePayload) => { +const executeSASCode = async ( + req: express.Request, + { code }: ExecuteSASCodePayload +) => { + const { user } = req try { const { webout, log, httpHeaders } = (await new ExecutionController().executeProgram( code, getPreProgramVariables(req), { ...req.query, _debug: 131 }, - undefined, + { userAutoExec: user?.autoExec }, true )) as ExecuteReturnJson diff --git a/api/src/controllers/internal/Execution.ts b/api/src/controllers/internal/Execution.ts index 4d4df15..9de586f 100644 --- a/api/src/controllers/internal/Execution.ts +++ b/api/src/controllers/internal/Execution.ts @@ -119,6 +119,10 @@ filename _webout "${weboutPath}" mod; /* dynamic user-provided vars */ ${preProgramVarStatments} +/* user auto exec starts */ +${otherArgs?.userAutoExec} +/* user auto exec ends */ + /* actual job code */ ${program}` diff --git a/api/src/controllers/internal/FileUploadController.ts b/api/src/controllers/internal/FileUploadController.ts index 762d3ee..9593b68 100644 --- a/api/src/controllers/internal/FileUploadController.ts +++ b/api/src/controllers/internal/FileUploadController.ts @@ -1,14 +1,15 @@ +import { Request, RequestHandler } from 'express' import multer from 'multer' import { uuidv4 } from '@sasjs/utils' import { getSessionController } from '.' export class FileUploadController { private storage = multer.diskStorage({ - destination: function (req: any, file: any, cb: any) { + destination: function (req: Request, file: any, cb: any) { //Sending the intercepted files to the sessions subfolder - cb(null, req.sasSession.path) + cb(null, req.sasSession?.path) }, - filename: function (req: any, file: any, cb: any) { + filename: function (req: Request, file: any, cb: any) { //req_file prefix + unique hash added to sas request files cb(null, `req_file_${uuidv4().replace(/-/gm, '')}`) } @@ -18,7 +19,7 @@ export class FileUploadController { //It will intercept request and generate unique uuid to be used as a subfolder name //that will store the files uploaded - public preUploadMiddleware = async (req: any, res: any, next: any) => { + public preUploadMiddleware: RequestHandler = async (req, res, next) => { let session const sessionController = getSessionController() diff --git a/api/src/controllers/session.ts b/api/src/controllers/session.ts index 92a977a..0a3562a 100644 --- a/api/src/controllers/session.ts +++ b/api/src/controllers/session.ts @@ -23,8 +23,8 @@ export class SessionController { } } -const session = (req: any) => ({ - id: req.user.userId, - username: req.user.username, - displayName: req.user.displayName +const session = (req: express.Request) => ({ + id: req.user!.userId, + username: req.user!.username, + displayName: req.user!.displayName }) diff --git a/api/src/controllers/stp.ts b/api/src/controllers/stp.ts index b938629..6a5d378 100644 --- a/api/src/controllers/stp.ts +++ b/api/src/controllers/stp.ts @@ -26,6 +26,7 @@ import { makeFilesNamesMap, parseLogToArray } from '../utils' +import { MulterFile } from '../types/Upload' interface ExecuteReturnJsonPayload { /** @@ -167,7 +168,7 @@ const executeReturnRaw = async ( } const executeReturnJson = async ( - req: any, + req: express.Request, _program: string ): Promise => { const sasCodePath = @@ -175,7 +176,9 @@ const executeReturnJson = async ( .join(getFilesFolder(), _program) .replace(new RegExp('/', 'g'), path.sep) + '.sas' - const filesNamesMap = req.files?.length ? makeFilesNamesMap(req.files) : null + const filesNamesMap = req.files?.length + ? makeFilesNamesMap(req.files as MulterFile[]) + : null try { const { webout, log, httpHeaders } = diff --git a/api/src/controllers/user.ts b/api/src/controllers/user.ts index 9aa66b9..9a875c7 100644 --- a/api/src/controllers/user.ts +++ b/api/src/controllers/user.ts @@ -1,3 +1,4 @@ +import express from 'express' import { Security, Route, @@ -10,7 +11,8 @@ import { Patch, Delete, Body, - Hidden + Hidden, + Request } from 'tsoa' import User, { UserPayload } from '../model/User' @@ -27,6 +29,7 @@ interface UserDetailsResponse { username: string isActive: boolean isAdmin: boolean + autoExec?: string } @Security('bearerAuth') @@ -73,13 +76,19 @@ export class UserController { } /** + * Only Admin or user itself will get user autoExec code. * @summary Get user properties - such as group memberships, userName, displayName. * @param userId The user's identifier * @example userId 1234 */ @Get('{userId}') - public async getUser(@Path() userId: number): Promise { - return getUser(userId) + public async getUser( + @Request() req: express.Request, + @Path() userId: number + ): Promise { + const { user } = req + const getAutoExec = user!.isAdmin || user!.userId == userId + return getUser(userId, getAutoExec) } /** @@ -123,7 +132,7 @@ const getAllUsers = async (): Promise => .exec() const createUser = async (data: UserPayload): Promise => { - const { displayName, username, password, isAdmin, isActive } = data + const { displayName, username, password, isAdmin, isActive, autoExec } = data // Checking if user is already in the database const usernameExist = await User.findOne({ username }) @@ -138,7 +147,8 @@ const createUser = async (data: UserPayload): Promise => { username, password: hashPassword, isAdmin, - isActive + isActive, + autoExec }) const savedUser = await user.save() @@ -148,38 +158,42 @@ const createUser = async (data: UserPayload): Promise => { displayName: savedUser.displayName, username: savedUser.username, isActive: savedUser.isActive, - isAdmin: savedUser.isAdmin + isAdmin: savedUser.isAdmin, + autoExec: savedUser.autoExec } } -const getUser = async (id: number): Promise => { +const getUser = async ( + id: number, + getAutoExec: boolean +): Promise => { const user = await User.findOne({ id }) - .select({ - _id: 0, - id: 1, - username: 1, - displayName: 1, - isAdmin: 1, - isActive: 1 - }) - .exec() + if (!user) throw new Error('User is not found.') - return user + return { + id: user.id, + displayName: user.displayName, + username: user.username, + isActive: user.isActive, + isAdmin: user.isAdmin, + autoExec: getAutoExec ? user.autoExec : undefined + } } const updateUser = async ( id: number, - data: UserPayload + data: Partial ): Promise => { - const { displayName, username, password, isAdmin, isActive } = data + const { displayName, username, password, isAdmin, isActive, autoExec } = data - const params: any = { displayName, isAdmin, isActive } + const params: any = { displayName, isAdmin, isActive, autoExec } if (username) { // Checking if user is already in the database const usernameExist = await User.findOne({ username }) - if (usernameExist?.id != id) throw new Error('Username already exists.') + if (usernameExist && usernameExist.id != id) + throw new Error('Username already exists.') params.username = username } @@ -189,18 +203,17 @@ const updateUser = async ( } const updatedUser = await User.findOneAndUpdate({ id }, params, { new: true }) - .select({ - _id: 0, - id: 1, - username: 1, - displayName: 1, - isAdmin: 1, - isActive: 1 - }) - .exec() - if (!updatedUser) throw new Error('Unable to update user') - return updatedUser + if (!updatedUser) throw new Error(`Unable to find user with id: ${id}`) + + return { + id: updatedUser.id, + username: updatedUser.username, + displayName: updatedUser.displayName, + isAdmin: updatedUser.isAdmin, + isActive: updatedUser.isActive, + autoExec: updatedUser.autoExec + } } const deleteUser = async ( diff --git a/api/src/controllers/web.ts b/api/src/controllers/web.ts index 350eef2..9b72d08 100644 --- a/api/src/controllers/web.ts +++ b/api/src/controllers/web.ts @@ -90,7 +90,8 @@ const login = async ( username: user.username, displayName: user.displayName, isAdmin: user.isAdmin, - isActive: user.isActive + isActive: user.isActive, + autoExec: user.autoExec } return { diff --git a/api/src/middlewares/authenticateToken.ts b/api/src/middlewares/authenticateToken.ts index 716c1fd..032972f 100644 --- a/api/src/middlewares/authenticateToken.ts +++ b/api/src/middlewares/authenticateToken.ts @@ -1,14 +1,27 @@ +import { RequestHandler, Request, Response, NextFunction } from 'express' import jwt from 'jsonwebtoken' import { csrfProtection } from '../app' -import { verifyTokenInDB } from '../utils' +import { fetchLatestAutoExec, verifyTokenInDB } from '../utils' -export const authenticateAccessToken = (req: any, res: any, next: any) => { +export const authenticateAccessToken: RequestHandler = async ( + req, + res, + next +) => { // 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 + if (req.session.user) { + const user = await fetchLatestAutoExec(req.session.user) - return csrfProtection(req, res, next) + if (user) { + if (user.isActive) { + req.user = user + return csrfProtection(req, res, next) + } else return res.sendStatus(401) + } + } + return res.sendStatus(401) } authenticateToken( @@ -20,7 +33,7 @@ export const authenticateAccessToken = (req: any, res: any, next: any) => { ) } -export const authenticateRefreshToken = (req: any, res: any, next: any) => { +export const authenticateRefreshToken: RequestHandler = (req, res, next) => { authenticateToken( req, res, @@ -31,16 +44,16 @@ export const authenticateRefreshToken = (req: any, res: any, next: any) => { } const authenticateToken = ( - req: any, - res: any, - next: any, + req: Request, + res: Response, + next: NextFunction, key: string, tokenType: 'accessToken' | 'refreshToken' ) => { const { MODE } = process.env if (MODE?.trim() !== 'server') { req.user = { - userId: '1234', + userId: 1234, clientId: 'desktopModeClientId', username: 'desktopModeUsername', displayName: 'desktopModeDisplayName', diff --git a/api/src/middlewares/desktop.ts b/api/src/middlewares/desktop.ts index 3444adf..8a6f80f 100644 --- a/api/src/middlewares/desktop.ts +++ b/api/src/middlewares/desktop.ts @@ -1,11 +1,13 @@ -export const desktopRestrict = (req: any, res: any, next: any) => { +import { RequestHandler } from 'express' + +export const desktopRestrict: RequestHandler = (req, res, next) => { const { MODE } = process.env if (MODE?.trim() !== 'server') return res.status(403).send('Not Allowed while in Desktop Mode.') next() } -export const desktopUsername = (req: any, res: any, next: any) => { +export const desktopUsername: RequestHandler = (req, res, next) => { const { MODE } = process.env if (MODE?.trim() !== 'server') return res.status(200).send({ diff --git a/api/src/middlewares/verifyAdmin.ts b/api/src/middlewares/verifyAdmin.ts index 0488b9e..4ca26f7 100644 --- a/api/src/middlewares/verifyAdmin.ts +++ b/api/src/middlewares/verifyAdmin.ts @@ -1,4 +1,6 @@ -export const verifyAdmin = (req: any, res: any, next: any) => { +import { RequestHandler } from 'express' + +export const verifyAdmin: RequestHandler = (req, res, next) => { const { MODE } = process.env if (MODE?.trim() !== 'server') return next() diff --git a/api/src/middlewares/verifyAdminIfNeeded.ts b/api/src/middlewares/verifyAdminIfNeeded.ts index d657e50..d126f3c 100644 --- a/api/src/middlewares/verifyAdminIfNeeded.ts +++ b/api/src/middlewares/verifyAdminIfNeeded.ts @@ -1,8 +1,10 @@ -export const verifyAdminIfNeeded = (req: any, res: any, next: any) => { +import { RequestHandler } from 'express' + +export const verifyAdminIfNeeded: RequestHandler = (req, res, next) => { const { user } = req const userId = parseInt(req.params.userId) - if (!user.isAdmin && user.userId !== userId) { + if (!user?.isAdmin && user?.userId !== userId) { return res.status(401).send('Admin account required') } next() diff --git a/api/src/model/User.ts b/api/src/model/User.ts index 61521b5..b16719a 100644 --- a/api/src/model/User.ts +++ b/api/src/model/User.ts @@ -27,12 +27,18 @@ export interface UserPayload { * @example "true" */ isActive?: boolean + /** + * User-specific auto-exec code + * @example "" + */ + autoExec?: string } interface IUserDocument extends UserPayload, Document { id: number isAdmin: boolean isActive: boolean + autoExec: string groups: Schema.Types.ObjectId[] tokens: [{ [key: string]: string }] } @@ -66,6 +72,9 @@ const userSchema = new Schema({ type: Boolean, default: true }, + autoExec: { + type: String + }, groups: [{ type: Schema.Types.ObjectId, ref: 'Group' }], tokens: [ { diff --git a/api/src/routes/api/auth.ts b/api/src/routes/api/auth.ts index d463b22..34a45df 100644 --- a/api/src/routes/api/auth.ts +++ b/api/src/routes/api/auth.ts @@ -26,8 +26,11 @@ authRouter.post('/token', async (req, res) => { } }) -authRouter.post('/refresh', authenticateRefreshToken, async (req: any, res) => { - const userInfo: InfoJWT = req.user +authRouter.post('/refresh', authenticateRefreshToken, async (req, res) => { + const userInfo: InfoJWT = { + userId: req.user!.userId!, + clientId: req.user!.clientId! + } try { const response = await controller.refresh(userInfo) @@ -38,8 +41,11 @@ authRouter.post('/refresh', authenticateRefreshToken, async (req: any, res) => { } }) -authRouter.delete('/logout', authenticateAccessToken, async (req: any, res) => { - const userInfo: InfoJWT = req.user +authRouter.delete('/logout', authenticateAccessToken, async (req, res) => { + const userInfo: InfoJWT = { + userId: req.user!.userId!, + clientId: req.user!.clientId! + } try { await controller.logout(userInfo) diff --git a/api/src/routes/api/group.ts b/api/src/routes/api/group.ts index fc02289..8164262 100644 --- a/api/src/routes/api/group.ts +++ b/api/src/routes/api/group.ts @@ -33,12 +33,12 @@ groupRouter.get('/', authenticateAccessToken, async (req, res) => { } }) -groupRouter.get('/:groupId', authenticateAccessToken, async (req: any, res) => { +groupRouter.get('/:groupId', authenticateAccessToken, async (req, res) => { const { groupId } = req.params const controller = new GroupController() try { - const response = await controller.getGroup(groupId) + const response = await controller.getGroup(parseInt(groupId)) res.send(response) } catch (err: any) { res.status(403).send(err.toString()) @@ -49,12 +49,15 @@ groupRouter.post( '/:groupId/:userId', authenticateAccessToken, verifyAdmin, - async (req: any, res) => { + async (req, res) => { const { groupId, userId } = req.params const controller = new GroupController() try { - const response = await controller.addUserToGroup(groupId, userId) + const response = await controller.addUserToGroup( + parseInt(groupId), + parseInt(userId) + ) res.send(response) } catch (err: any) { res.status(403).send(err.toString()) @@ -66,12 +69,15 @@ groupRouter.delete( '/:groupId/:userId', authenticateAccessToken, verifyAdmin, - async (req: any, res) => { + async (req, res) => { const { groupId, userId } = req.params const controller = new GroupController() try { - const response = await controller.removeUserFromGroup(groupId, userId) + const response = await controller.removeUserFromGroup( + parseInt(groupId), + parseInt(userId) + ) res.send(response) } catch (err: any) { res.status(403).send(err.toString()) @@ -83,12 +89,12 @@ groupRouter.delete( '/:groupId', authenticateAccessToken, verifyAdmin, - async (req: any, res) => { + async (req, res) => { const { groupId } = req.params const controller = new GroupController() try { - await controller.deleteGroup(groupId) + await controller.deleteGroup(parseInt(groupId)) res.status(200).send('Group Deleted!') } catch (err: any) { res.status(403).send(err.toString()) diff --git a/api/src/routes/api/spec/user.spec.ts b/api/src/routes/api/spec/user.spec.ts index 9289a5e..64510a2 100644 --- a/api/src/routes/api/spec/user.spec.ts +++ b/api/src/routes/api/spec/user.spec.ts @@ -19,7 +19,8 @@ const user = { username: 'testUsername', password: '87654321', isAdmin: false, - isActive: true + isActive: true, + autoExec: 'some sas code for auto exec;' } const controller = new UserController() @@ -64,6 +65,7 @@ describe('user', () => { expect(res.body.displayName).toEqual(user.displayName) expect(res.body.isAdmin).toEqual(user.isAdmin) expect(res.body.isActive).toEqual(user.isActive) + expect(res.body.autoExec).toEqual(user.autoExec) }) it('should respond with Unauthorized if access token is not present', async () => { @@ -360,7 +362,25 @@ describe('user', () => { await deleteAllUsers() }) - it('should respond with user', async () => { + it('should respond with user autoExec when same user requests', async () => { + const dbUser = await controller.createUser(user) + const userId = dbUser.id + const accessToken = await generateAndSaveToken(userId) + + const res = await request(app) + .get(`/SASjsApi/user/${userId}`) + .auth(accessToken, { type: 'bearer' }) + .send() + .expect(200) + + expect(res.body.username).toEqual(user.username) + expect(res.body.displayName).toEqual(user.displayName) + expect(res.body.isAdmin).toEqual(user.isAdmin) + expect(res.body.isActive).toEqual(user.isActive) + expect(res.body.autoExec).toEqual(user.autoExec) + }) + + it('should respond with user autoExec when admin user requests', async () => { const dbUser = await controller.createUser(user) const userId = dbUser.id @@ -374,6 +394,7 @@ describe('user', () => { expect(res.body.displayName).toEqual(user.displayName) expect(res.body.isAdmin).toEqual(user.isAdmin) expect(res.body.isActive).toEqual(user.isActive) + expect(res.body.autoExec).toEqual(user.autoExec) }) it('should respond with user when access token is not of an admin account', async () => { @@ -395,6 +416,7 @@ describe('user', () => { expect(res.body.displayName).toEqual(user.displayName) expect(res.body.isAdmin).toEqual(user.isAdmin) expect(res.body.isActive).toEqual(user.isActive) + expect(res.body.autoExec).toBeUndefined() }) it('should respond with Unauthorized if access token is not present', async () => { diff --git a/api/src/routes/api/stp.ts b/api/src/routes/api/stp.ts index 425208e..c759a9d 100644 --- a/api/src/routes/api/stp.ts +++ b/api/src/routes/api/stp.ts @@ -34,7 +34,7 @@ stpRouter.post( '/execute', fileUploadController.preUploadMiddleware, fileUploadController.getMulterUploadObject().any(), - async (req: any, res: any) => { + async (req, res: any) => { const { error: errQ, value: query } = executeProgramRawValidation(req.query) const { error: errB, value: body } = executeProgramRawValidation(req.body) diff --git a/api/src/routes/api/user.ts b/api/src/routes/api/user.ts index e4d8e9a..fcd6d5e 100644 --- a/api/src/routes/api/user.ts +++ b/api/src/routes/api/user.ts @@ -36,12 +36,12 @@ userRouter.get('/', authenticateAccessToken, async (req, res) => { } }) -userRouter.get('/:userId', authenticateAccessToken, async (req: any, res) => { +userRouter.get('/:userId', authenticateAccessToken, async (req, res) => { const { userId } = req.params const controller = new UserController() try { - const response = await controller.getUser(userId) + const response = await controller.getUser(req, parseInt(userId)) res.send(response) } catch (err: any) { res.status(403).send(err.toString()) @@ -52,17 +52,17 @@ userRouter.patch( '/:userId', authenticateAccessToken, verifyAdminIfNeeded, - async (req: any, res) => { + async (req, res) => { const { user } = req const { userId } = req.params // only an admin can update `isActive` and `isAdmin` fields - const { error, value: body } = updateUserValidation(req.body, user.isAdmin) + const { error, value: body } = updateUserValidation(req.body, user!.isAdmin) if (error) return res.status(400).send(error.details[0].message) const controller = new UserController() try { - const response = await controller.updateUser(userId, body) + const response = await controller.updateUser(parseInt(userId), body) res.send(response) } catch (err: any) { res.status(403).send(err.toString()) @@ -74,17 +74,17 @@ userRouter.delete( '/:userId', authenticateAccessToken, verifyAdminIfNeeded, - async (req: any, res) => { + async (req, res) => { const { user } = req const { userId } = req.params // only an admin can delete user without providing password - const { error, value: data } = deleteUserValidation(req.body, user.isAdmin) + const { error, value: data } = deleteUserValidation(req.body, user!.isAdmin) if (error) return res.status(400).send(error.details[0].message) const controller = new UserController() try { - await controller.deleteUser(userId, data, user.isAdmin) + await controller.deleteUser(parseInt(userId), data, user!.isAdmin) res.status(200).send('Account Deleted!') } catch (err: any) { res.status(403).send(err.toString()) diff --git a/api/src/types/RequestUser.ts b/api/src/types/RequestUser.ts new file mode 100644 index 0000000..e85507f --- /dev/null +++ b/api/src/types/RequestUser.ts @@ -0,0 +1,9 @@ +export interface RequestUser { + userId: number + clientId: string + username: string + displayName: string + isAdmin: boolean + isActive: boolean + autoExec?: string +} diff --git a/api/src/types/index.ts b/api/src/types/index.ts index 9040575..9e42479 100644 --- a/api/src/types/index.ts +++ b/api/src/types/index.ts @@ -5,3 +5,4 @@ export * from './InfoJWT' export * from './PreProgramVars' export * from './Session' export * from './TreeNode' +export * from './RequestUser' diff --git a/api/src/types/system/express-session.d.ts b/api/src/types/system/express-session.d.ts index e55a990..5b70ed6 100644 --- a/api/src/types/system/express-session.d.ts +++ b/api/src/types/system/express-session.d.ts @@ -2,13 +2,6 @@ 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 - } + user: import('../').RequestUser } } diff --git a/api/src/types/system/express.d.ts b/api/src/types/system/express.d.ts new file mode 100644 index 0000000..7ed4e79 --- /dev/null +++ b/api/src/types/system/express.d.ts @@ -0,0 +1,7 @@ +declare namespace Express { + export interface Request { + accessToken?: string + user?: import('../').RequestUser + sasSession?: import('../').Session + } +} diff --git a/api/src/utils/getPreProgramVariables.ts b/api/src/utils/getPreProgramVariables.ts index 44516d7..34adb14 100644 --- a/api/src/utils/getPreProgramVariables.ts +++ b/api/src/utils/getPreProgramVariables.ts @@ -1,6 +1,7 @@ +import { Request } from 'express' import { PreProgramVars } from '../types' -export const getPreProgramVariables = (req: any): PreProgramVars => { +export const getPreProgramVariables = (req: Request): PreProgramVars => { const host = req.get('host') const protocol = req.protocol + '://' const { user, accessToken } = req @@ -20,9 +21,9 @@ export const getPreProgramVariables = (req: any): PreProgramVars => { if (cookies.length) httpHeaders.push(`cookie: ${cookies.join('; ')}`) return { - username: user.username, - userId: user.userId, - displayName: user.displayName, + username: user!.username, + userId: user!.userId, + displayName: user!.displayName, serverUrl: protocol + host, httpHeaders } diff --git a/api/src/utils/validation.ts b/api/src/utils/validation.ts index ba9898f..70fd850 100644 --- a/api/src/utils/validation.ts +++ b/api/src/utils/validation.ts @@ -35,7 +35,8 @@ export const registerUserValidation = (data: any): Joi.ValidationResult => username: usernameSchema.required(), password: passwordSchema.required(), isAdmin: Joi.boolean(), - isActive: Joi.boolean() + isActive: Joi.boolean(), + autoExec: Joi.string() }).validate(data) export const deleteUserValidation = ( @@ -57,7 +58,8 @@ export const updateUserValidation = ( const validationChecks: any = { displayName: Joi.string().min(6), username: usernameSchema, - password: passwordSchema + password: passwordSchema, + autoExec: Joi.string() } if (isAdmin) { validationChecks.isAdmin = Joi.boolean() diff --git a/api/src/utils/verifyTokenInDB.ts b/api/src/utils/verifyTokenInDB.ts index f8397bb..83c4a26 100644 --- a/api/src/utils/verifyTokenInDB.ts +++ b/api/src/utils/verifyTokenInDB.ts @@ -1,11 +1,30 @@ import User from '../model/User' +import { RequestUser } from '../types' + +export const fetchLatestAutoExec = async ( + reqUser: RequestUser +): Promise => { + const dbUser = await User.findOne({ id: reqUser.userId }) + + if (!dbUser) return undefined + + return { + userId: reqUser.userId, + clientId: reqUser.clientId, + username: dbUser.username, + displayName: dbUser.displayName, + isAdmin: dbUser.isAdmin, + isActive: dbUser.isActive, + autoExec: dbUser.autoExec + } +} export const verifyTokenInDB = async ( userId: number, clientId: string, token: string, tokenType: 'accessToken' | 'refreshToken' -) => { +): Promise => { const dbUser = await User.findOne({ id: userId }) if (!dbUser) return undefined @@ -21,7 +40,8 @@ export const verifyTokenInDB = async ( username: dbUser.username, displayName: dbUser.displayName, isAdmin: dbUser.isAdmin, - isActive: dbUser.isActive + isActive: dbUser.isActive, + autoExec: dbUser.autoExec } : undefined } From 79dc2dba23dc48ec218a973119392a45cb3856b5 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Wed, 25 May 2022 10:44:57 +0500 Subject: [PATCH 059/163] fix(autoexec): usage in case of desktop from file --- api/src/app.ts | 7 +++---- api/src/controllers/code.ts | 15 +++++++++++++-- api/src/utils/file.ts | 6 ++++++ api/src/utils/setupFolders.ts | 11 +++++++++-- 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/api/src/app.ts b/api/src/app.ts index 33a1e27..e423b4f 100644 --- a/api/src/app.ts +++ b/api/src/app.ts @@ -30,9 +30,7 @@ dotenv.config() instantiateLogger() -if (verifyEnvVariables()) { - process.exit(ReturnCode.InvalidEnv) -} +if (verifyEnvVariables()) process.exit(ReturnCode.InvalidEnv) const app = express() @@ -85,7 +83,7 @@ app.use( /*********************************** * Enabling CORS * ***********************************/ -if (MODE === ModeType.Server || CORS === CorsType.ENABLED) { +if (CORS === CorsType.ENABLED) { const whiteList: string[] = [] WHITELIST?.split(' ') ?.filter((url) => !!url) @@ -125,6 +123,7 @@ if (MODE === ModeType.Server) { }) ) } + app.use(express.json({ limit: '100mb' })) app.use(express.static(path.join(__dirname, '../public'))) diff --git a/api/src/controllers/code.ts b/api/src/controllers/code.ts index d4b18fd..a102330 100644 --- a/api/src/controllers/code.ts +++ b/api/src/controllers/code.ts @@ -2,7 +2,13 @@ import express from 'express' import { Request, Security, Route, Tags, Post, Body } from 'tsoa' import { ExecuteReturnJson, ExecutionController } from './internal' import { ExecuteReturnJsonResponse } from '.' -import { getPreProgramVariables, parseLogToArray } from '../utils' +import { + getDesktopUserAutoExecPath, + getPreProgramVariables, + ModeType, + parseLogToArray +} from '../utils' +import { readFile } from '@sasjs/utils' interface ExecuteSASCodePayload { /** @@ -34,13 +40,18 @@ const executeSASCode = async ( { code }: ExecuteSASCodePayload ) => { const { user } = req + const userAutoExec = + process.env.MODE === ModeType.Server + ? user?.autoExec + : await readFile(getDesktopUserAutoExecPath()) + try { const { webout, log, httpHeaders } = (await new ExecutionController().executeProgram( code, getPreProgramVariables(req), { ...req.query, _debug: 131 }, - { userAutoExec: user?.autoExec }, + { userAutoExec }, true )) as ExecuteReturnJson diff --git a/api/src/utils/file.ts b/api/src/utils/file.ts index 007ad6f..7df3d9d 100644 --- a/api/src/utils/file.ts +++ b/api/src/utils/file.ts @@ -1,4 +1,5 @@ import path from 'path' +import { homedir } from 'os' export const apiRoot = path.join(__dirname, '..', '..') export const codebaseRoot = path.join(apiRoot, '..') @@ -13,6 +14,11 @@ export const sasJSCoreMacrosInfo = path.join(sasJSCoreMacros, '.macrolist') export const getWebBuildFolder = () => path.join(codebaseRoot, 'web', 'build') +export const getSasjsHomeFolder = () => path.join(homedir(), '.sasjs-server') + +export const getDesktopUserAutoExecPath = () => + path.join(getSasjsHomeFolder(), 'user-autoexec.sas') + export const getSasjsRootFolder = () => process.driveLoc export const getAppStreamConfigPath = () => diff --git a/api/src/utils/setupFolders.ts b/api/src/utils/setupFolders.ts index 2b71f63..4d66455 100644 --- a/api/src/utils/setupFolders.ts +++ b/api/src/utils/setupFolders.ts @@ -1,7 +1,14 @@ -import { createFolder } from '@sasjs/utils' -import { getFilesFolder } from './file' +import { createFile, createFolder, fileExists } from '@sasjs/utils' +import { getDesktopUserAutoExecPath, getFilesFolder } from './file' +import { ModeType } from './verifyEnvVariables' export const setupFolders = async () => { const drivePath = getFilesFolder() await createFolder(drivePath) + + if (process.env.MODE === ModeType.Desktop) { + if (!(await fileExists(getDesktopUserAutoExecPath()))) { + await createFile(getDesktopUserAutoExecPath(), '') + } + } } From e4239fbcc3fefd6ddcb625a41bdf295e991a20f9 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 25 May 2022 05:52:30 +0000 Subject: [PATCH 060/163] chore(release): 0.2.0 [skip ci] # [0.2.0](https://github.com/sasjs/server/compare/v0.1.0...v0.2.0) (2022-05-25) ### Bug Fixes * **autoexec:** usage in case of desktop from file ([79dc2db](https://github.com/sasjs/server/commit/79dc2dba23dc48ec218a973119392a45cb3856b5)) ### Features * **api:** added autoexec + major type setting changes ([2a7223a](https://github.com/sasjs/server/commit/2a7223ad7d6b8f3d4682447fd25d9426a7c79ac3)) --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc237a1..d84b6b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +# [0.2.0](https://github.com/sasjs/server/compare/v0.1.0...v0.2.0) (2022-05-25) + + +### Bug Fixes + +* **autoexec:** usage in case of desktop from file ([79dc2db](https://github.com/sasjs/server/commit/79dc2dba23dc48ec218a973119392a45cb3856b5)) + + +### Features + +* **api:** added autoexec + major type setting changes ([2a7223a](https://github.com/sasjs/server/commit/2a7223ad7d6b8f3d4682447fd25d9426a7c79ac3)) + # [0.1.0](https://github.com/sasjs/server/compare/v0.0.77...v0.1.0) (2022-05-23) From c275db184e874f0ee3a4f08f2592cfacf1e90742 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Thu, 26 May 2022 04:25:15 +0500 Subject: [PATCH 061/163] feat(web): added profile + edit + autoexec changes --- api/public/swagger.yaml | 4 +- api/src/controllers/internal/Execution.ts | 6 +- api/src/controllers/user.ts | 2 +- api/src/controllers/web.ts | 1 + api/src/model/User.ts | 2 +- api/src/utils/validation.ts | 4 +- web/src/App.tsx | 6 + web/src/components/header.tsx | 28 +++- web/src/components/login.tsx | 3 +- web/src/containers/AuthCode/index.tsx | 4 +- web/src/containers/Settings/index.tsx | 55 ++++++++ web/src/containers/Settings/profile.tsx | 148 ++++++++++++++++++++++ web/src/context/appContext.tsx | 10 +- 13 files changed, 256 insertions(+), 17 deletions(-) create mode 100644 web/src/containers/Settings/index.tsx create mode 100644 web/src/containers/Settings/profile.tsx diff --git a/api/public/swagger.yaml b/api/public/swagger.yaml index eba92d4..1f76c10 100644 --- a/api/public/swagger.yaml +++ b/api/public/swagger.yaml @@ -357,7 +357,7 @@ components: autoExec: type: string description: 'User-specific auto-exec code' - example: '' + example: "" required: - displayName - username @@ -543,7 +543,7 @@ paths: application/json: schema: properties: - user: {properties: {displayName: {type: string}, username: {type: string}}, required: [displayName, username], type: object} + user: {properties: {displayName: {type: string}, username: {type: string}, id: {type: number, format: double}}, required: [displayName, username, id], type: object} loggedIn: {type: boolean} required: - user diff --git a/api/src/controllers/internal/Execution.ts b/api/src/controllers/internal/Execution.ts index 9de586f..b4d8b4c 100644 --- a/api/src/controllers/internal/Execution.ts +++ b/api/src/controllers/internal/Execution.ts @@ -119,9 +119,9 @@ filename _webout "${weboutPath}" mod; /* dynamic user-provided vars */ ${preProgramVarStatments} -/* user auto exec starts */ -${otherArgs?.userAutoExec} -/* user auto exec ends */ +/* user autoexec starts */ +${otherArgs?.userAutoExec ?? ''} +/* user autoexec ends */ /* actual job code */ ${program}` diff --git a/api/src/controllers/user.ts b/api/src/controllers/user.ts index 9a875c7..0c43b3a 100644 --- a/api/src/controllers/user.ts +++ b/api/src/controllers/user.ts @@ -177,7 +177,7 @@ const getUser = async ( username: user.username, isActive: user.isActive, isAdmin: user.isAdmin, - autoExec: getAutoExec ? user.autoExec : undefined + autoExec: getAutoExec ? user.autoExec ?? '' : undefined } } diff --git a/api/src/controllers/web.ts b/api/src/controllers/web.ts index 9b72d08..e64b3a4 100644 --- a/api/src/controllers/web.ts +++ b/api/src/controllers/web.ts @@ -97,6 +97,7 @@ const login = async ( return { loggedIn: true, user: { + id: user.id, username: user.username, displayName: user.displayName } diff --git a/api/src/model/User.ts b/api/src/model/User.ts index b16719a..04d550e 100644 --- a/api/src/model/User.ts +++ b/api/src/model/User.ts @@ -29,7 +29,7 @@ export interface UserPayload { isActive?: boolean /** * User-specific auto-exec code - * @example "" + * @example "" */ autoExec?: string } diff --git a/api/src/utils/validation.ts b/api/src/utils/validation.ts index 70fd850..73ce4e5 100644 --- a/api/src/utils/validation.ts +++ b/api/src/utils/validation.ts @@ -36,7 +36,7 @@ export const registerUserValidation = (data: any): Joi.ValidationResult => password: passwordSchema.required(), isAdmin: Joi.boolean(), isActive: Joi.boolean(), - autoExec: Joi.string() + autoExec: Joi.string().allow('') }).validate(data) export const deleteUserValidation = ( @@ -59,7 +59,7 @@ export const updateUserValidation = ( displayName: Joi.string().min(6), username: usernameSchema, password: passwordSchema, - autoExec: Joi.string() + autoExec: Joi.string().allow('') } if (isAdmin) { validationChecks.isAdmin = Joi.boolean() diff --git a/web/src/App.tsx b/web/src/App.tsx index 92cb547..071a8a7 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -8,9 +8,11 @@ import Header from './components/header' import Home from './components/home' import Drive from './containers/Drive' import Studio from './containers/Studio' +import Settings from './containers/Settings' import { AppContext } from './context/appContext' import AuthCode from './containers/AuthCode' +import { ToastContainer } from 'react-toastify' function App() { const appContext = useContext(AppContext) @@ -44,10 +46,14 @@ function App() { + + + + ) diff --git a/web/src/components/header.tsx b/web/src/components/header.tsx index 7ea5ac9..7288d27 100644 --- a/web/src/components/header.tsx +++ b/web/src/components/header.tsx @@ -1,4 +1,4 @@ -import React, { useState, useContext } from 'react' +import React, { useState, useEffect, useContext } from 'react' import { Link, useHistory, useLocation } from 'react-router-dom' import { @@ -11,6 +11,7 @@ import { MenuItem } from '@mui/material' import OpenInNewIcon from '@mui/icons-material/OpenInNew' +import SettingsIcon from '@mui/icons-material/Settings' import Username from './username' import { AppContext } from '../context/appContext' @@ -20,17 +21,23 @@ const PORT_API = process.env.PORT_API const baseUrl = NODE_ENV === 'development' ? `http://localhost:${PORT_API ?? 5000}` : '' +const validTabs = ['/', '/SASjsDrive', '/SASjsStudio'] + const Header = (props: any) => { const history = useHistory() const { pathname } = useLocation() const appContext = useContext(AppContext) const [tabValue, setTabValue] = useState( - pathname === '/SASjsLogon' ? '/' : pathname + validTabs.includes(pathname) ? pathname : '/' ) const [anchorEl, setAnchorEl] = useState< (EventTarget & HTMLButtonElement) | null >(null) + useEffect(() => { + setTabValue(validTabs.includes(pathname) ? pathname : '/') + }, [pathname]) + const handleMenu = ( event: React.MouseEvent ) => { @@ -46,7 +53,10 @@ const Header = (props: any) => { } const handleLogout = () => { - if (appContext.logout) appContext.logout() + if (appContext.logout) { + handleClose() + appContext.logout() + } } return ( { open={!!anchorEl} onClose={handleClose} > + + + - - ) } diff --git a/web/src/containers/Settings/index.tsx b/web/src/containers/Settings/index.tsx new file mode 100644 index 0000000..7a3f829 --- /dev/null +++ b/web/src/containers/Settings/index.tsx @@ -0,0 +1,55 @@ +import * as React from 'react' + +import { Box, Paper, Tab, styled } from '@mui/material' +import TabContext from '@mui/lab/TabContext' +import TabList from '@mui/lab/TabList' +import TabPanel from '@mui/lab/TabPanel' + +import Profile from './profile' + +const StyledTab = styled(Tab)({ + background: 'black', + margin: '0 5px 5px 0' +}) + +const StyledTabpanel = styled(TabPanel)({ + flexGrow: 1 +}) + +const Settings = () => { + const [value, setValue] = React.useState('profile') + + const handleChange = (event: React.SyntheticEvent, newValue: string) => { + setValue(newValue) + } + + return ( + + + + + + + + + + + + + ) +} + +export default Settings diff --git a/web/src/containers/Settings/profile.tsx b/web/src/containers/Settings/profile.tsx new file mode 100644 index 0000000..7571250 --- /dev/null +++ b/web/src/containers/Settings/profile.tsx @@ -0,0 +1,148 @@ +import React, { useState, useEffect, useContext } from 'react' +import axios from 'axios' +import { + Grid, + CircularProgress, + Card, + CardHeader, + Divider, + CardContent, + TextField, + CardActions, + Button, + FormGroup, + FormControlLabel, + Checkbox +} from '@mui/material' + +import { AppContext } from '../../context/appContext' +import { toast } from 'react-toastify' + +const Profile = () => { + const [isLoading, setIsLoading] = useState(false) + const appContext = useContext(AppContext) + const [user, setUser] = useState({} as any) + + useEffect(() => { + setIsLoading(true) + axios + .get(`/SASjsApi/user/${appContext.userId}`) + .then((res: any) => { + setUser(res.data) + }) + .catch((err) => { + console.log(err) + }) + .finally(() => { + setIsLoading(false) + }) + }, []) + + const handleChange = (event: any) => { + const { name, value } = event.target + + setUser({ ...user, [name]: value }) + } + const handleSubmit = () => { + setIsLoading(true) + axios + .patch(`/SASjsApi/user/${appContext.userId}`, { + username: user.username, + displayName: user.displayName, + autoExec: user.autoExec + }) + .then((res: any) => { + toast.success('User information updated', { + theme: 'dark', + position: toast.POSITION.BOTTOM_RIGHT + }) + }) + .catch((err) => { + toast.error('Failed: ' + err.response?.data || err.text, { + theme: 'dark', + position: toast.POSITION.BOTTOM_RIGHT + }) + }) + .finally(() => { + setIsLoading(false) + }) + } + + return isLoading ? ( + + ) : ( + + + + + + + + + + + + + + + + } + label="isActive" + /> + } + label="isAdmin" + /> + + + + + + + + + + + + + + ) +} + +export default Profile diff --git a/web/src/context/appContext.tsx b/web/src/context/appContext.tsx index 6e5b72e..7620901 100644 --- a/web/src/context/appContext.tsx +++ b/web/src/context/appContext.tsx @@ -13,6 +13,8 @@ interface AppContextProps { checkingSession: boolean loggedIn: boolean setLoggedIn: Dispatch> | null + userId: number + setUserId: Dispatch> | null username: string setUsername: Dispatch> | null displayName: string @@ -24,6 +26,8 @@ export const AppContext = createContext({ checkingSession: false, loggedIn: false, setLoggedIn: null, + userId: 0, + setUserId: null, username: '', setUsername: null, displayName: '', @@ -35,6 +39,7 @@ const AppContextProvider = (props: { children: ReactNode }) => { const { children } = props const [checkingSession, setCheckingSession] = useState(false) const [loggedIn, setLoggedIn] = useState(false) + const [userId, setUserId] = useState(0) const [username, setUsername] = useState('') const [displayName, setDisplayName] = useState('') @@ -46,9 +51,10 @@ const AppContextProvider = (props: { children: ReactNode }) => { .then((res) => res.data) .then((data: any) => { setCheckingSession(false) - setLoggedIn(true) + setUserId(data.id) setUsername(data.username) setDisplayName(data.displayName) + setLoggedIn(true) }) .catch(() => { setLoggedIn(false) @@ -70,6 +76,8 @@ const AppContextProvider = (props: { children: ReactNode }) => { checkingSession, loggedIn, setLoggedIn, + userId, + setUserId, username, setUsername, displayName, From ff1def643606f05857572e26c027c8bd55e8f927 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 25 May 2022 23:29:22 +0000 Subject: [PATCH 062/163] chore(release): 0.3.0 [skip ci] # [0.3.0](https://github.com/sasjs/server/compare/v0.2.0...v0.3.0) (2022-05-25) ### Features * **web:** added profile + edit + autoexec changes ([c275db1](https://github.com/sasjs/server/commit/c275db184e874f0ee3a4f08f2592cfacf1e90742)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d84b6b1..2177a75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [0.3.0](https://github.com/sasjs/server/compare/v0.2.0...v0.3.0) (2022-05-25) + + +### Features + +* **web:** added profile + edit + autoexec changes ([c275db1](https://github.com/sasjs/server/commit/c275db184e874f0ee3a4f08f2592cfacf1e90742)) + # [0.2.0](https://github.com/sasjs/server/compare/v0.1.0...v0.2.0) (2022-05-25) From 7d11cc79161e5a07f6c5392d742ef6b9d8658071 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Thu, 26 May 2022 19:48:59 +0500 Subject: [PATCH 063/163] fix(web): reduced width for autoexec input --- web/src/components/header.tsx | 2 +- web/src/containers/Settings/profile.tsx | 28 ++++++++++++------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/web/src/components/header.tsx b/web/src/components/header.tsx index 7288d27..28e0d57 100644 --- a/web/src/components/header.tsx +++ b/web/src/components/header.tsx @@ -153,7 +153,7 @@ const Header = (props: any) => { color="primary" startIcon={} > - Setting + Settings diff --git a/web/src/containers/Settings/profile.tsx b/web/src/containers/Settings/profile.tsx index 7571250..14d8e09 100644 --- a/web/src/containers/Settings/profile.tsx +++ b/web/src/containers/Settings/profile.tsx @@ -14,9 +14,9 @@ import { FormControlLabel, Checkbox } from '@mui/material' +import { toast } from 'react-toastify' import { AppContext } from '../../context/appContext' -import { toast } from 'react-toastify' const Profile = () => { const [isLoading, setIsLoading] = useState(false) @@ -106,6 +106,19 @@ const Profile = () => { /> + + + + { /> - - - - From 5ad6ee5e0f5d7d6faa45b72215f1d9d55cfc37db Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Thu, 26 May 2022 20:20:02 +0500 Subject: [PATCH 064/163] fix(api): username should be lowercase --- api/src/routes/api/spec/user.spec.ts | 20 +++++++++++++++++--- api/src/routes/api/spec/web.spec.ts | 4 ++-- api/src/utils/validation.ts | 2 +- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/api/src/routes/api/spec/user.spec.ts b/api/src/routes/api/spec/user.spec.ts index 64510a2..4e5e2bf 100644 --- a/api/src/routes/api/spec/user.spec.ts +++ b/api/src/routes/api/spec/user.spec.ts @@ -9,14 +9,14 @@ import { generateAccessToken, saveTokensInDB } from '../../../utils' const clientId = 'someclientID' const adminUser = { displayName: 'Test Admin', - username: 'testAdminUsername', + username: 'testadminusername', password: '12345678', isAdmin: true, isActive: true } const user = { displayName: 'Test User', - username: 'testUsername', + username: 'testusername', password: '87654321', isAdmin: false, isActive: true, @@ -68,6 +68,20 @@ describe('user', () => { expect(res.body.autoExec).toEqual(user.autoExec) }) + it('should respond with new user having username as lowercase', async () => { + const res = await request(app) + .post('/SASjsApi/user') + .auth(adminAccessToken, { type: 'bearer' }) + .send({ ...user, username: user.username.toUpperCase() }) + .expect(200) + + expect(res.body.username).toEqual(user.username) + expect(res.body.displayName).toEqual(user.displayName) + expect(res.body.isAdmin).toEqual(user.isAdmin) + expect(res.body.isActive).toEqual(user.isActive) + expect(res.body.autoExec).toEqual(user.autoExec) + }) + it('should respond with Unauthorized if access token is not present', async () => { const res = await request(app) .post('/SASjsApi/user') @@ -244,7 +258,7 @@ describe('user', () => { const dbUser1 = await controller.createUser(user) const dbUser2 = await controller.createUser({ ...user, - username: 'randomUser' + username: 'randomuser' }) const res = await request(app) diff --git a/api/src/routes/api/spec/web.spec.ts b/api/src/routes/api/spec/web.spec.ts index 12da618..4f8ec30 100644 --- a/api/src/routes/api/spec/web.spec.ts +++ b/api/src/routes/api/spec/web.spec.ts @@ -10,7 +10,7 @@ const clientSecret = 'someclientSecret' const user = { id: 1234, displayName: 'Test User', - username: 'testUsername', + username: 'testusername', password: '87654321', isAdmin: false, isActive: true @@ -77,6 +77,7 @@ describe('web', () => { expect(res.body.loggedIn).toBeTruthy() expect(res.body.user).toEqual({ + id: expect.any(Number), username: user.username, displayName: user.displayName }) @@ -155,7 +156,6 @@ const getCSRF = async (app: Express) => { const { header } = await request(app).get('/') const cookies = header['set-cookie'].join() - console.log('cookies', cookies) const csrfToken = extractCSRF(cookies) return { csrfToken, cookies } } diff --git a/api/src/utils/validation.ts b/api/src/utils/validation.ts index 73ce4e5..33a403f 100644 --- a/api/src/utils/validation.ts +++ b/api/src/utils/validation.ts @@ -1,6 +1,6 @@ import Joi from 'joi' -const usernameSchema = Joi.string().alphanum().min(3).max(16) +const usernameSchema = Joi.string().lowercase().alphanum().min(3).max(16) const passwordSchema = Joi.string().min(6).max(1024) export const blockFileRegex = /\.(exe|sh|htaccess)$/i From b066734398e89d34545c79fdeacac2c73f56f2dc Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 26 May 2022 15:30:45 +0000 Subject: [PATCH 065/163] chore(release): 0.3.1 [skip ci] ## [0.3.1](https://github.com/sasjs/server/compare/v0.3.0...v0.3.1) (2022-05-26) ### Bug Fixes * **api:** username should be lowercase ([5ad6ee5](https://github.com/sasjs/server/commit/5ad6ee5e0f5d7d6faa45b72215f1d9d55cfc37db)) * **web:** reduced width for autoexec input ([7d11cc7](https://github.com/sasjs/server/commit/7d11cc79161e5a07f6c5392d742ef6b9d8658071)) --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2177a75..4a9962c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## [0.3.1](https://github.com/sasjs/server/compare/v0.3.0...v0.3.1) (2022-05-26) + + +### Bug Fixes + +* **api:** username should be lowercase ([5ad6ee5](https://github.com/sasjs/server/commit/5ad6ee5e0f5d7d6faa45b72215f1d9d55cfc37db)) +* **web:** reduced width for autoexec input ([7d11cc7](https://github.com/sasjs/server/commit/7d11cc79161e5a07f6c5392d742ef6b9d8658071)) + # [0.3.0](https://github.com/sasjs/server/compare/v0.2.0...v0.3.0) (2022-05-25) From 2c259fe1de95d84e6929e311aaa6b895e66b42a3 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Fri, 27 May 2022 17:01:14 +0500 Subject: [PATCH 066/163] fix(web): ability to use get/patch User API in desktop mode. --- api/src/controllers/code.ts | 5 ++- api/src/controllers/user.ts | 28 +++++++++++++++++ api/src/middlewares/authenticateToken.ts | 11 +++++-- api/src/middlewares/desktop.ts | 40 ++++++++++++++++++------ api/src/utils/desktopAutoExec.ts | 8 +++++ api/src/utils/index.ts | 1 + 6 files changed, 79 insertions(+), 14 deletions(-) create mode 100644 api/src/utils/desktopAutoExec.ts diff --git a/api/src/controllers/code.ts b/api/src/controllers/code.ts index a102330..a4b1d74 100644 --- a/api/src/controllers/code.ts +++ b/api/src/controllers/code.ts @@ -3,12 +3,11 @@ import { Request, Security, Route, Tags, Post, Body } from 'tsoa' import { ExecuteReturnJson, ExecutionController } from './internal' import { ExecuteReturnJsonResponse } from '.' import { - getDesktopUserAutoExecPath, getPreProgramVariables, + getUserAutoExec, ModeType, parseLogToArray } from '../utils' -import { readFile } from '@sasjs/utils' interface ExecuteSASCodePayload { /** @@ -43,7 +42,7 @@ const executeSASCode = async ( const userAutoExec = process.env.MODE === ModeType.Server ? user?.autoExec - : await readFile(getDesktopUserAutoExecPath()) + : await getUserAutoExec() try { const { webout, log, httpHeaders } = diff --git a/api/src/controllers/user.ts b/api/src/controllers/user.ts index 0c43b3a..cfbce9d 100644 --- a/api/src/controllers/user.ts +++ b/api/src/controllers/user.ts @@ -14,8 +14,10 @@ import { Hidden, Request } from 'tsoa' +import { desktopUser } from '../middlewares' import User, { UserPayload } from '../model/User' +import { getUserAutoExec, updateUserAutoExec, ModeType } from '../utils' export interface UserResponse { id: number @@ -86,6 +88,10 @@ export class UserController { @Request() req: express.Request, @Path() userId: number ): Promise { + const { MODE } = process.env + + if (MODE === ModeType.Desktop) return getDesktopAutoExec() + const { user } = req const getAutoExec = user!.isAdmin || user!.userId == userId return getUser(userId, getAutoExec) @@ -108,6 +114,11 @@ export class UserController { @Path() userId: number, @Body() body: UserPayload ): Promise { + const { MODE } = process.env + + if (MODE === ModeType.Desktop) + return updateDesktopAutoExec(body.autoExec ?? '') + return updateUser(userId, body) } @@ -181,6 +192,14 @@ const getUser = async ( } } +const getDesktopAutoExec = async () => { + return { + ...desktopUser, + id: desktopUser.userId, + autoExec: await getUserAutoExec() + } +} + const updateUser = async ( id: number, data: Partial @@ -216,6 +235,15 @@ const updateUser = async ( } } +const updateDesktopAutoExec = async (autoExec: string) => { + await updateUserAutoExec(autoExec) + return { + ...desktopUser, + id: desktopUser.userId, + autoExec + } +} + const deleteUser = async ( id: number, isAdmin: boolean, diff --git a/api/src/middlewares/authenticateToken.ts b/api/src/middlewares/authenticateToken.ts index 032972f..ced94f9 100644 --- a/api/src/middlewares/authenticateToken.ts +++ b/api/src/middlewares/authenticateToken.ts @@ -1,15 +1,22 @@ import { RequestHandler, Request, Response, NextFunction } from 'express' import jwt from 'jsonwebtoken' import { csrfProtection } from '../app' -import { fetchLatestAutoExec, verifyTokenInDB } from '../utils' +import { fetchLatestAutoExec, ModeType, verifyTokenInDB } from '../utils' +import { desktopUser } from './desktop' export const authenticateAccessToken: RequestHandler = async ( req, res, next ) => { + const { MODE } = process.env + if (MODE === ModeType.Server) { + req.user = desktopUser + return next() + } + // if request is coming from web and has valid session - // we can validate the request and check for CSRF Token + // it can be validated. if (req.session?.loggedIn) { if (req.session.user) { const user = await fetchLatestAutoExec(req.session.user) diff --git a/api/src/middlewares/desktop.ts b/api/src/middlewares/desktop.ts index 8a6f80f..ed94c87 100644 --- a/api/src/middlewares/desktop.ts +++ b/api/src/middlewares/desktop.ts @@ -1,20 +1,42 @@ -import { RequestHandler } from 'express' +import { RequestHandler, Request } from 'express' +import { RequestUser } from '../types' +import { ModeType } from '../utils' + +const regexUser = /^\/SASjsApi\/user\/[0-9]*$/ // /SASjsApi/user/1 + +const allowedInDesktopMode: { [key: string]: RegExp[] } = { + GET: [regexUser], + PATCH: [regexUser] +} + +const reqAllowedInDesktopMode = (request: Request): boolean => { + const { method, originalUrl: url } = request + + return !!allowedInDesktopMode[method]?.find((urlRegex) => urlRegex.test(url)) +} export const desktopRestrict: RequestHandler = (req, res, next) => { const { MODE } = process.env - if (MODE?.trim() !== 'server') - return res.status(403).send('Not Allowed while in Desktop Mode.') + + if (MODE === ModeType.Desktop) { + if (!reqAllowedInDesktopMode(req)) + return res.status(403).send('Not Allowed while in Desktop Mode.') + } next() } export const desktopUsername: RequestHandler = (req, res, next) => { const { MODE } = process.env - if (MODE?.trim() !== 'server') - return res.status(200).send({ - userId: 12345, - username: 'DESKTOPusername', - displayName: 'DESKTOP User' - }) + if (MODE === ModeType.Desktop) return res.status(200).send(desktopUser) next() } + +export const desktopUser: RequestUser = { + userId: 12345, + clientId: 'desktop_app', + username: 'DESKTOPusername', + displayName: 'DESKTOP User', + isAdmin: true, + isActive: true +} diff --git a/api/src/utils/desktopAutoExec.ts b/api/src/utils/desktopAutoExec.ts new file mode 100644 index 0000000..e68159c --- /dev/null +++ b/api/src/utils/desktopAutoExec.ts @@ -0,0 +1,8 @@ +import { createFile, readFile } from '@sasjs/utils' +import { getDesktopUserAutoExecPath } from './file' + +export const getUserAutoExec = async (): Promise => + readFile(getDesktopUserAutoExecPath()) + +export const updateUserAutoExec = async (autoExecContent: string) => + createFile(getDesktopUserAutoExecPath(), autoExecContent) diff --git a/api/src/utils/index.ts b/api/src/utils/index.ts index 16980ec..4bb2022 100644 --- a/api/src/utils/index.ts +++ b/api/src/utils/index.ts @@ -1,6 +1,7 @@ export * from './appStreamConfig' export * from './connectDB' export * from './copySASjsCore' +export * from './desktopAutoExec' export * from './extractHeaders' export * from './file' export * from './generateAccessToken' From 0470239ef1f7e99ffe6336b856145aafe48d9301 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Fri, 27 May 2022 17:35:58 +0500 Subject: [PATCH 067/163] chore: quick fix --- api/src/middlewares/authenticateToken.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/middlewares/authenticateToken.ts b/api/src/middlewares/authenticateToken.ts index ced94f9..90c7027 100644 --- a/api/src/middlewares/authenticateToken.ts +++ b/api/src/middlewares/authenticateToken.ts @@ -10,7 +10,7 @@ export const authenticateAccessToken: RequestHandler = async ( next ) => { const { MODE } = process.env - if (MODE === ModeType.Server) { + if (MODE === ModeType.Desktop) { req.user = desktopUser return next() } From cfddf1fb0c38e863f5df344fdea8c21c4865efa6 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 27 May 2022 19:43:00 +0000 Subject: [PATCH 068/163] chore(release): 0.3.2 [skip ci] ## [0.3.2](https://github.com/sasjs/server/compare/v0.3.1...v0.3.2) (2022-05-27) ### Bug Fixes * **web:** ability to use get/patch User API in desktop mode. ([2c259fe](https://github.com/sasjs/server/commit/2c259fe1de95d84e6929e311aaa6b895e66b42a3)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a9962c..5676ec9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [0.3.2](https://github.com/sasjs/server/compare/v0.3.1...v0.3.2) (2022-05-27) + + +### Bug Fixes + +* **web:** ability to use get/patch User API in desktop mode. ([2c259fe](https://github.com/sasjs/server/commit/2c259fe1de95d84e6929e311aaa6b895e66b42a3)) + ## [0.3.1](https://github.com/sasjs/server/compare/v0.3.0...v0.3.1) (2022-05-26) From 414fb19de3fe4f0cfe98076868476604a390c0c8 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Mon, 30 May 2022 00:32:05 +0500 Subject: [PATCH 069/163] chore: code changes --- api/package.json | 2 +- api/src/routes/api/stp.ts | 9 +++++---- api/src/routes/web/web.ts | 7 ++++--- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/api/package.json b/api/package.json index cf8b3a6..bd6f562 100644 --- a/api/package.json +++ b/api/package.json @@ -96,7 +96,7 @@ }, "nodemonConfig": { "ignore": [ - "tmp/**/*" + "sasjs_root/**/*" ] } } diff --git a/api/src/routes/api/stp.ts b/api/src/routes/api/stp.ts index c759a9d..5ea37db 100644 --- a/api/src/routes/api/stp.ts +++ b/api/src/routes/api/stp.ts @@ -47,10 +47,11 @@ stpRouter.post( query?._program ) - if (response instanceof Buffer) { - res.writeHead(200, (req as any).sasHeaders) - return res.end(response) - } + // TODO: investigate if this code is required + // if (response instanceof Buffer) { + // res.writeHead(200, (req as any).sasHeaders) + // return res.end(response) + // } res.send(response) } catch (err: any) { diff --git a/api/src/routes/web/web.ts b/api/src/routes/web/web.ts index c64478f..c4d817c 100644 --- a/api/src/routes/web/web.ts +++ b/api/src/routes/web/web.ts @@ -1,6 +1,6 @@ import express from 'express' import { WebController } from '../../controllers/web' -import { authenticateAccessToken } from '../../middlewares' +import { authenticateAccessToken, desktopRestrict } from '../../middlewares' import { authorizeValidation, loginWebValidation } from '../../utils' const webRouter = express.Router() @@ -19,7 +19,7 @@ webRouter.get('/', async (req, res) => { } }) -webRouter.post('/SASLogon/login', async (req, res) => { +webRouter.post('/SASLogon/login', desktopRestrict, async (req, res) => { const { error, value: body } = loginWebValidation(req.body) if (error) return res.status(400).send(error.details[0].message) @@ -33,6 +33,7 @@ webRouter.post('/SASLogon/login', async (req, res) => { webRouter.post( '/SASLogon/authorize', + desktopRestrict, authenticateAccessToken, async (req, res) => { const { error, value: body } = authorizeValidation(req.body) @@ -47,7 +48,7 @@ webRouter.post( } ) -webRouter.get('/logout', async (req, res) => { +webRouter.get('/logout', desktopRestrict, async (req, res) => { try { await controller.logout(req) res.status(200).send('OK!') From 12d424acce8108a6f53aefbac01fddcdc5efb48f Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Mon, 30 May 2022 17:12:17 +0500 Subject: [PATCH 070/163] fix: usage of autoexec API in DESKTOP mode --- api/src/middlewares/desktop.ts | 6 ------ api/src/routes/api/index.ts | 3 +-- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/api/src/middlewares/desktop.ts b/api/src/middlewares/desktop.ts index ed94c87..bda4d4e 100644 --- a/api/src/middlewares/desktop.ts +++ b/api/src/middlewares/desktop.ts @@ -25,12 +25,6 @@ export const desktopRestrict: RequestHandler = (req, res, next) => { next() } -export const desktopUsername: RequestHandler = (req, res, next) => { - const { MODE } = process.env - if (MODE === ModeType.Desktop) return res.status(200).send(desktopUser) - - next() -} export const desktopUser: RequestUser = { userId: 12345, diff --git a/api/src/routes/api/index.ts b/api/src/routes/api/index.ts index cfe44ce..1bad1c8 100644 --- a/api/src/routes/api/index.ts +++ b/api/src/routes/api/index.ts @@ -5,7 +5,6 @@ import swaggerUi from 'swagger-ui-express' import { authenticateAccessToken, desktopRestrict, - desktopUsername, verifyAdmin } from '../../middlewares' @@ -22,7 +21,7 @@ import sessionRouter from './session' const router = express.Router() router.use('/info', infoRouter) -router.use('/session', desktopUsername, authenticateAccessToken, sessionRouter) +router.use('/session', authenticateAccessToken, sessionRouter) router.use('/auth', desktopRestrict, authRouter) router.use( '/client', From 73c81a45dc67903a9fd3de8db01bb429cab33e4a Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 30 May 2022 12:18:45 +0000 Subject: [PATCH 071/163] chore(release): 0.3.3 [skip ci] ## [0.3.3](https://github.com/sasjs/server/compare/v0.3.2...v0.3.3) (2022-05-30) ### Bug Fixes * usage of autoexec API in DESKTOP mode ([12d424a](https://github.com/sasjs/server/commit/12d424acce8108a6f53aefbac01fddcdc5efb48f)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5676ec9..c2a550e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [0.3.3](https://github.com/sasjs/server/compare/v0.3.2...v0.3.3) (2022-05-30) + + +### Bug Fixes + +* usage of autoexec API in DESKTOP mode ([12d424a](https://github.com/sasjs/server/commit/12d424acce8108a6f53aefbac01fddcdc5efb48f)) + ## [0.3.2](https://github.com/sasjs/server/compare/v0.3.1...v0.3.2) (2022-05-27) From a8ba378fd1ff374ba025a96fdfae5c6c36954465 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Mon, 30 May 2022 21:08:17 +0500 Subject: [PATCH 072/163] fix(web): system username for DESKTOP mode --- api/src/middlewares/desktop.ts | 5 +++-- web/src/containers/Settings/profile.tsx | 4 +++- web/src/context/appContext.tsx | 17 +++++++++++++++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/api/src/middlewares/desktop.ts b/api/src/middlewares/desktop.ts index bda4d4e..b2935fd 100644 --- a/api/src/middlewares/desktop.ts +++ b/api/src/middlewares/desktop.ts @@ -1,4 +1,5 @@ import { RequestHandler, Request } from 'express' +import { userInfo } from 'os' import { RequestUser } from '../types' import { ModeType } from '../utils' @@ -29,8 +30,8 @@ export const desktopRestrict: RequestHandler = (req, res, next) => { export const desktopUser: RequestUser = { userId: 12345, clientId: 'desktop_app', - username: 'DESKTOPusername', - displayName: 'DESKTOP User', + username: userInfo().username, + displayName: userInfo().username, isAdmin: true, isActive: true } diff --git a/web/src/containers/Settings/profile.tsx b/web/src/containers/Settings/profile.tsx index 14d8e09..06602e7 100644 --- a/web/src/containers/Settings/profile.tsx +++ b/web/src/containers/Settings/profile.tsx @@ -16,7 +16,7 @@ import { } from '@mui/material' import { toast } from 'react-toastify' -import { AppContext } from '../../context/appContext' +import { AppContext, ModeType } from '../../context/appContext' const Profile = () => { const [isLoading, setIsLoading] = useState(false) @@ -89,6 +89,7 @@ const Profile = () => { required value={user.displayName} variant="outlined" + disabled={appContext.mode === ModeType.Desktop} /> @@ -103,6 +104,7 @@ const Profile = () => { required value={user.username} variant="outlined" + disabled={appContext.mode === ModeType.Desktop} /> diff --git a/web/src/context/appContext.tsx b/web/src/context/appContext.tsx index 7620901..44b3db9 100644 --- a/web/src/context/appContext.tsx +++ b/web/src/context/appContext.tsx @@ -9,6 +9,11 @@ import React, { } from 'react' import axios from 'axios' +export enum ModeType { + Server = 'server', + Desktop = 'desktop' +} + interface AppContextProps { checkingSession: boolean loggedIn: boolean @@ -19,6 +24,7 @@ interface AppContextProps { setUsername: Dispatch> | null displayName: string setDisplayName: Dispatch> | null + mode: ModeType logout: (() => void) | null } @@ -32,6 +38,7 @@ export const AppContext = createContext({ setUsername: null, displayName: '', setDisplayName: null, + mode: ModeType.Server, logout: null }) @@ -42,6 +49,7 @@ const AppContextProvider = (props: { children: ReactNode }) => { const [userId, setUserId] = useState(0) const [username, setUsername] = useState('') const [displayName, setDisplayName] = useState('') + const [mode, setMode] = useState(ModeType.Server) useEffect(() => { setCheckingSession(true) @@ -60,6 +68,14 @@ const AppContextProvider = (props: { children: ReactNode }) => { setLoggedIn(false) axios.get('/') // get CSRF TOKEN }) + + axios + .get('/SASjsApi/info') + .then((res) => res.data) + .then((data: any) => { + setMode(data.mode) + }) + .catch(() => {}) }, []) const logout = useCallback(() => { @@ -82,6 +98,7 @@ const AppContextProvider = (props: { children: ReactNode }) => { setUsername, displayName, setDisplayName, + mode, logout }} > From afff27fd216648b4333cb1963b84aa284436b63b Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 30 May 2022 16:12:22 +0000 Subject: [PATCH 073/163] chore(release): 0.3.4 [skip ci] ## [0.3.4](https://github.com/sasjs/server/compare/v0.3.3...v0.3.4) (2022-05-30) ### Bug Fixes * **web:** system username for DESKTOP mode ([a8ba378](https://github.com/sasjs/server/commit/a8ba378fd1ff374ba025a96fdfae5c6c36954465)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2a550e..310f597 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [0.3.4](https://github.com/sasjs/server/compare/v0.3.3...v0.3.4) (2022-05-30) + + +### Bug Fixes + +* **web:** system username for DESKTOP mode ([a8ba378](https://github.com/sasjs/server/commit/a8ba378fd1ff374ba025a96fdfae5c6c36954465)) + ## [0.3.3](https://github.com/sasjs/server/compare/v0.3.2...v0.3.3) (2022-05-30) From 61815f8ae18be132e17c199cd8e3afbcc2fa0b60 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Mon, 30 May 2022 18:02:30 +0000 Subject: [PATCH 074/163] fix: bumping sasjs/core library --- api/package-lock.json | 14 +++++++------- api/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/api/package-lock.json b/api/package-lock.json index 5a4d506..feec31c 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -8,7 +8,7 @@ "name": "api", "version": "0.0.2", "dependencies": { - "@sasjs/core": "^4.23.1", + "@sasjs/core": "^4.27.3", "@sasjs/utils": "2.42.1", "bcryptjs": "^2.4.3", "connect-mongo": "^4.6.0", @@ -1385,9 +1385,9 @@ } }, "node_modules/@sasjs/core": { - "version": "4.23.1", - "resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.23.1.tgz", - "integrity": "sha512-9d6yEPJRRvPLMUkpyaiQ62SXNMMyt2l815jxWgFjnVOxKeUQv9TPyZqZ0FpmWdVe6EY8dv8GLlyaBpOLDnY6Vg==" + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.27.3.tgz", + "integrity": "sha512-8AaPPRGMwhmjw244CDSnTqHXdp/77ZBjIJMgwqw4wTrCf8Vzs2Y5hVihbvAniIGQctZHLMR6X5a3X4ccn9gRjg==" }, "node_modules/@sasjs/utils": { "version": "2.42.1", @@ -11364,9 +11364,9 @@ } }, "@sasjs/core": { - "version": "4.23.1", - "resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.23.1.tgz", - "integrity": "sha512-9d6yEPJRRvPLMUkpyaiQ62SXNMMyt2l815jxWgFjnVOxKeUQv9TPyZqZ0FpmWdVe6EY8dv8GLlyaBpOLDnY6Vg==" + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.27.3.tgz", + "integrity": "sha512-8AaPPRGMwhmjw244CDSnTqHXdp/77ZBjIJMgwqw4wTrCf8Vzs2Y5hVihbvAniIGQctZHLMR6X5a3X4ccn9gRjg==" }, "@sasjs/utils": { "version": "2.42.1", diff --git a/api/package.json b/api/package.json index bd6f562..da52b7d 100644 --- a/api/package.json +++ b/api/package.json @@ -47,7 +47,7 @@ }, "author": "4GL Ltd", "dependencies": { - "@sasjs/core": "^4.23.1", + "@sasjs/core": "^4.27.3", "@sasjs/utils": "2.42.1", "bcryptjs": "^2.4.3", "connect-mongo": "^4.6.0", From 7a932383b4572c626308cdaca4e7c80302ac7875 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 30 May 2022 18:07:50 +0000 Subject: [PATCH 075/163] chore(release): 0.3.5 [skip ci] ## [0.3.5](https://github.com/sasjs/server/compare/v0.3.4...v0.3.5) (2022-05-30) ### Bug Fixes * bumping sasjs/core library ([61815f8](https://github.com/sasjs/server/commit/61815f8ae18be132e17c199cd8e3afbcc2fa0b60)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 310f597..5ce1d9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [0.3.5](https://github.com/sasjs/server/compare/v0.3.4...v0.3.5) (2022-05-30) + + +### Bug Fixes + +* bumping sasjs/core library ([61815f8](https://github.com/sasjs/server/commit/61815f8ae18be132e17c199cd8e3afbcc2fa0b60)) + ## [0.3.4](https://github.com/sasjs/server/compare/v0.3.3...v0.3.4) (2022-05-30) From e6d1989847761fbe562d7861ffa0ee542839b125 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Thu, 2 Jun 2022 04:17:12 +0500 Subject: [PATCH 076/163] fix(appstream): should serve only new files for same app stream name with new deployment --- api/package-lock.json | 68 ++++++++++++++++++++++++++----- api/package.json | 3 +- api/src/routes/appStream/index.ts | 32 +++++++++++++-- api/src/utils/getServerUrl.ts | 15 +++++++ api/src/utils/index.ts | 1 + 5 files changed, 104 insertions(+), 15 deletions(-) create mode 100644 api/src/utils/getServerUrl.ts diff --git a/api/package-lock.json b/api/package-lock.json index 5a4d506..a7c33d7 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -24,7 +24,8 @@ "mongoose-sequence": "^5.3.1", "morgan": "^1.10.0", "multer": "^1.4.3", - "swagger-ui-express": "4.3.0" + "swagger-ui-express": "4.3.0", + "url": "^0.10.3" }, "bin": { "api": "build/src/server.js" @@ -2883,7 +2884,7 @@ "node_modules/busboy": { "version": "0.2.14", "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", - "integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=", + "integrity": "sha512-InWFDomvlkEj+xWLBfU3AvnbVYqeTWmQopiW0tWWEy5yehYm2YkGEc59sUmw/4ty5Zj/b0WHGs1LgecuBSBGrg==", "dependencies": { "dicer": "0.2.5", "readable-stream": "1.1.x" @@ -3677,7 +3678,7 @@ "node_modules/dicer": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", - "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=", + "integrity": "sha512-FDvbtnq7dzlPz0wyYlOExifDEZcu8h+rErEXgfxqmLfRfC/kJidEFh4+effJRO3P0xmfqyPbSMG0LveNRfTKVg==", "dependencies": { "readable-stream": "1.1.x", "streamsearch": "0.1.2" @@ -7590,9 +7591,10 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "node_modules/multer": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.3.tgz", - "integrity": "sha512-np0YLKncuZoTzufbkM6wEKp68EhWJXcU6fq6QqrSwkckd2LlMgd1UqhUJLj6NS/5sZ8dE8LYDWslsltJznnXlg==", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.4.tgz", + "integrity": "sha512-2wY2+xD4udX612aMqMcB8Ws2Voq6NIUPEtD1be6m411T4uDH/VtL9i//xvcyFlTVfRdaBsk7hV5tgrGQqhuBiw==", + "deprecated": "Multer 1.x is affected by CVE-2022-24434. This is fixed in v1.4.4-lts.1 which drops support for versions of Node.js before 6. Please upgrade to at least Node.js 6 and version 1.4.4-lts.1 of Multer. If you need support for older versions of Node.js, we are open to accepting patches that would fix the CVE on the main 1.x release line, whilst maintaining compatibility with Node.js 0.10.", "dependencies": { "append-field": "^1.0.0", "busboy": "^0.2.11", @@ -8552,6 +8554,15 @@ "node": ">=0.6" } }, + "node_modules/querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "engines": { + "node": ">=0.4.x" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -9942,6 +9953,15 @@ "url": "https://github.com/yeoman/update-notifier?sponsor=1" } }, + "node_modules/url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "dependencies": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, "node_modules/url-parse-lax": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", @@ -9954,6 +9974,11 @@ "node": ">=4" } }, + "node_modules/url/node_modules/punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -12629,7 +12654,7 @@ "busboy": { "version": "0.2.14", "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", - "integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=", + "integrity": "sha512-InWFDomvlkEj+xWLBfU3AvnbVYqeTWmQopiW0tWWEy5yehYm2YkGEc59sUmw/4ty5Zj/b0WHGs1LgecuBSBGrg==", "requires": { "dicer": "0.2.5", "readable-stream": "1.1.x" @@ -13267,7 +13292,7 @@ "dicer": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", - "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=", + "integrity": "sha512-FDvbtnq7dzlPz0wyYlOExifDEZcu8h+rErEXgfxqmLfRfC/kJidEFh4+effJRO3P0xmfqyPbSMG0LveNRfTKVg==", "requires": { "readable-stream": "1.1.x", "streamsearch": "0.1.2" @@ -16220,9 +16245,9 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "multer": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.3.tgz", - "integrity": "sha512-np0YLKncuZoTzufbkM6wEKp68EhWJXcU6fq6QqrSwkckd2LlMgd1UqhUJLj6NS/5sZ8dE8LYDWslsltJznnXlg==", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.4.tgz", + "integrity": "sha512-2wY2+xD4udX612aMqMcB8Ws2Voq6NIUPEtD1be6m411T4uDH/VtL9i//xvcyFlTVfRdaBsk7hV5tgrGQqhuBiw==", "requires": { "append-field": "^1.0.0", "busboy": "^0.2.11", @@ -16933,6 +16958,11 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -17963,6 +17993,22 @@ "xdg-basedir": "^4.0.0" } }, + "url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + } + } + }, "url-parse-lax": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", diff --git a/api/package.json b/api/package.json index bd6f562..5b96e8b 100644 --- a/api/package.json +++ b/api/package.json @@ -63,7 +63,8 @@ "mongoose-sequence": "^5.3.1", "morgan": "^1.10.0", "multer": "^1.4.3", - "swagger-ui-express": "4.3.0" + "swagger-ui-express": "4.3.0", + "url": "^0.10.3" }, "devDependencies": { "@types/bcryptjs": "^2.4.2", diff --git a/api/src/routes/appStream/index.ts b/api/src/routes/appStream/index.ts index cf52017..4a607e5 100644 --- a/api/src/routes/appStream/index.ts +++ b/api/src/routes/appStream/index.ts @@ -1,10 +1,16 @@ import path from 'path' -import express from 'express' +import express, { Request } from 'express' import { folderExists } from '@sasjs/utils' -import { addEntryToAppStreamConfig, getFilesFolder } from '../../utils' +import { + addEntryToAppStreamConfig, + getFilesFolder, + getFullUrl +} from '../../utils' import { appStreamHtml } from './appStreamHtml' +const appStreams: { [key: string]: string } = {} + const router = express.Router() router.get('/', async (req, res) => { @@ -44,7 +50,7 @@ export const publishAppStream = async ( streamServiceName = `AppStreamName${appCount + 1}` } - router.use(`/${streamServiceName}`, express.static(pathToDeployment)) + appStreams[streamServiceName] = pathToDeployment addEntryToAppStreamConfig( streamServiceName, @@ -64,4 +70,24 @@ export const publishAppStream = async ( return {} } +router.get(`/*`, function (req: Request, res, next) { + const reqPath = req.path.replace(/^\//, '') + + // Redirecting to url with trailing slash for base appStream URL only + if (reqPath.split('/').length === 1 && !reqPath.endsWith('/')) + return res.redirect(301, `${getFullUrl(req)}/`) + + const appStream = reqPath.split('/')[0] + const appStreamFilesPath = appStreams[appStream] + if (appStreamFilesPath) { + const resourcePath = reqPath.split('/')[1] || 'index.html' + + req.url = resourcePath + + return express.static(appStreamFilesPath)(req, res, next) + } + + return res.send("There's no App Stream available here.") +}) + export default router diff --git a/api/src/utils/getServerUrl.ts b/api/src/utils/getServerUrl.ts new file mode 100644 index 0000000..905217a --- /dev/null +++ b/api/src/utils/getServerUrl.ts @@ -0,0 +1,15 @@ +import express from 'express' +import url from 'url' + +export const getFullUrl = (req: express.Request) => + url.format({ + protocol: req.protocol, + host: req.get('host'), + pathname: req.originalUrl + }) + +export const getServerUrl = (req: express.Request) => + url.format({ + protocol: req.protocol, + host: req.get('x-forwarded-host') || req.get('host') + }) diff --git a/api/src/utils/index.ts b/api/src/utils/index.ts index 4bb2022..09254ba 100644 --- a/api/src/utils/index.ts +++ b/api/src/utils/index.ts @@ -10,6 +10,7 @@ export * from './generateRefreshToken' export * from './getCertificates' export * from './getDesktopFields' export * from './getPreProgramVariables' +export * from './getServerUrl' export * from './instantiateLogger' export * from './isDebugOn' export * from './parseLogToArray' From ad82ee71063b012ea4a0d9c56382bf5fbda69451 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 2 Jun 2022 08:30:39 +0000 Subject: [PATCH 077/163] chore(release): 0.3.6 [skip ci] ## [0.3.6](https://github.com/sasjs/server/compare/v0.3.5...v0.3.6) (2022-06-02) ### Bug Fixes * **appstream:** should serve only new files for same app stream name with new deployment ([e6d1989](https://github.com/sasjs/server/commit/e6d1989847761fbe562d7861ffa0ee542839b125)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ce1d9c..8ba53a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [0.3.6](https://github.com/sasjs/server/compare/v0.3.5...v0.3.6) (2022-06-02) + + +### Bug Fixes + +* **appstream:** should serve only new files for same app stream name with new deployment ([e6d1989](https://github.com/sasjs/server/commit/e6d1989847761fbe562d7861ffa0ee542839b125)) + ## [0.3.5](https://github.com/sasjs/server/compare/v0.3.4...v0.3.5) (2022-05-30) From 194eaec7d4a561468f83bf6efce484909ee532eb Mon Sep 17 00:00:00 2001 From: Sabir Hassan Date: Fri, 3 Jun 2022 17:19:12 +0500 Subject: [PATCH 078/163] fix: add runtimes to global process object --- api/src/utils/setProcessVariables.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/api/src/utils/setProcessVariables.ts b/api/src/utils/setProcessVariables.ts index 4edac7c..22205b3 100644 --- a/api/src/utils/setProcessVariables.ts +++ b/api/src/utils/setProcessVariables.ts @@ -19,6 +19,13 @@ export const setProcessVariables = async () => { process.sasLoc = sasLoc } + const { SASJS_RUNTIMES } = process.env + + const runTimes = SASJS_RUNTIMES + ? SASJS_RUNTIMES.split(',').map((runTime) => runTime.toLowerCase()) + : ['sas'] + process.runTimes = runTimes + const { SASJS_ROOT } = process.env const absPath = getAbsolutePath(SASJS_ROOT ?? 'sasjs_root', process.cwd()) await createFolder(absPath) From 07295aa151175db8c93eeef806fc3b7fde40ac72 Mon Sep 17 00:00:00 2001 From: Sabir Hassan Date: Fri, 3 Jun 2022 17:23:28 +0500 Subject: [PATCH 079/163] feat: conver single session controller to two controller i.e. SASSessionController and JSSessionController --- .../internal/FileUploadController.ts | 45 +++++++++++++---- api/src/controllers/internal/Session.ts | 49 +++++++++++++++++-- api/src/types/system/express.d.ts | 2 +- api/src/types/system/process.d.ts | 4 +- 4 files changed, 83 insertions(+), 17 deletions(-) diff --git a/api/src/controllers/internal/FileUploadController.ts b/api/src/controllers/internal/FileUploadController.ts index 9593b68..9fcf734 100644 --- a/api/src/controllers/internal/FileUploadController.ts +++ b/api/src/controllers/internal/FileUploadController.ts @@ -1,13 +1,15 @@ +import path from 'path' import { Request, RequestHandler } from 'express' import multer from 'multer' -import { uuidv4 } from '@sasjs/utils' -import { getSessionController } from '.' +import { uuidv4, fileExists, readFile } from '@sasjs/utils' +import { getSASSessionController, getJSSessionController } from '.' +import { getFilesFolder } from '../../utils' export class FileUploadController { private storage = multer.diskStorage({ destination: function (req: Request, file: any, cb: any) { //Sending the intercepted files to the sessions subfolder - cb(null, req.sasSession?.path) + cb(null, req.sasjsSession?.path) }, filename: function (req: Request, file: any, cb: any) { //req_file prefix + unique hash added to sas request files @@ -20,15 +22,38 @@ export class FileUploadController { //It will intercept request and generate unique uuid to be used as a subfolder name //that will store the files uploaded public preUploadMiddleware: RequestHandler = async (req, res, next) => { - let session + if (process.runTimes.length === 0) { + throw 'No runtime is specified in environment variables.' + } - const sessionController = getSessionController() - session = await sessionController.getSession() - // marking consumed true, so that it's not available - // as readySession for any other request - session.consumed = true + const programPath = req.query._program as string - req.sasSession = session + for (const runTime of process.runTimes) { + const codePath = + path + .join(getFilesFolder(), programPath) + .replace(new RegExp('/', 'g'), path.sep) + runTime + + if (await fileExists(programPath)) { + const program = await readFile(codePath) + + if (runTime === 'sas') { + const sessionController = getSASSessionController() + const session = await sessionController.getSession() + // marking consumed true, so that it's not available + // as readySession for any other request + session.consumed = true + + req.sasjsSession = session + } else if (runTime === 'js') { + const sessionController = getJSSessionController() + const session = await sessionController.getSession() + req.sasjsSession = session + } else { + throw `${runTime} runtime is not implemented yet.` + } + } + } next() } diff --git a/api/src/controllers/internal/Session.ts b/api/src/controllers/internal/Session.ts index b83d70e..4f7eac4 100644 --- a/api/src/controllers/internal/Session.ts +++ b/api/src/controllers/internal/Session.ts @@ -17,7 +17,7 @@ import { const execFilePromise = promisify(execFile) -export class SessionController { +export class SASSessionController { private sessions: Session[] = [] private getReadySessions = (): Session[] => @@ -152,12 +152,51 @@ ${autoExecContent}` } } -export const getSessionController = (): SessionController => { - if (process.sessionController) return process.sessionController +export class JSSessionController { + public async getSession() { + const sessionId = generateUniqueFileName(generateTimestamp()) + const sessionFolder = path.join(getSessionsFolder(), sessionId) - process.sessionController = new SessionController() + const creationTimeStamp = sessionId.split('-').pop() as string + // death time of session is 15 mins from creation + const deathTimeStamp = ( + parseInt(creationTimeStamp) + + 15 * 60 * 1000 - + 1000 + ).toString() - return process.sessionController + const session: Session = { + id: sessionId, + ready: true, + inUse: true, + consumed: false, + completed: false, + creationTimeStamp, + deathTimeStamp, + path: sessionFolder + } + + const headersPath = path.join(session.path, 'stpsrv_header.txt') + await createFile(headersPath, 'Content-type: application/json') + + return session + } +} + +export const getSASSessionController = (): SASSessionController => { + if (process.sasSessionController) return process.sasSessionController + + process.sasSessionController = new SASSessionController() + + return process.sasSessionController +} + +export const getJSSessionController = (): JSSessionController => { + if (process.jsSessionController) return process.jsSessionController + + process.jsSessionController = new JSSessionController() + + return process.jsSessionController } const autoExecContent = ` diff --git a/api/src/types/system/express.d.ts b/api/src/types/system/express.d.ts index 7ed4e79..d02d220 100644 --- a/api/src/types/system/express.d.ts +++ b/api/src/types/system/express.d.ts @@ -2,6 +2,6 @@ declare namespace Express { export interface Request { accessToken?: string user?: import('../').RequestUser - sasSession?: import('../').Session + sasjsSession?: import('../').Session } } diff --git a/api/src/types/system/process.d.ts b/api/src/types/system/process.d.ts index f7845bc..2d345c1 100644 --- a/api/src/types/system/process.d.ts +++ b/api/src/types/system/process.d.ts @@ -1,8 +1,10 @@ declare namespace NodeJS { export interface Process { + runTimes: string[] sasLoc: string driveLoc: string - sessionController?: import('../../controllers/internal').SessionController + sasSessionController?: import('../../controllers/internal').SASSessionController + jsSessionController?: import('../../controllers/internal').JSSessionController appStreamConfig: import('../').AppStreamConfig logger: import('@sasjs/utils/logger').Logger } From 5df619b3f63571e8e326261d8114869d33881d91 Mon Sep 17 00:00:00 2001 From: Sabir Hassan Date: Fri, 3 Jun 2022 17:24:29 +0500 Subject: [PATCH 080/163] fix: pass _program to execute file without extension --- api/src/controllers/stp.ts | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/api/src/controllers/stp.ts b/api/src/controllers/stp.ts index 6a5d378..d3b01bf 100644 --- a/api/src/controllers/stp.ts +++ b/api/src/controllers/stp.ts @@ -131,15 +131,11 @@ const executeReturnRaw = async ( _program: string ): Promise => { const query = req.query as ExecutionVars - const sasCodePath = - path - .join(getFilesFolder(), _program) - .replace(new RegExp('/', 'g'), path.sep) + '.sas' try { const { result, httpHeaders } = (await new ExecutionController().executeFile( - sasCodePath, + _program, getPreProgramVariables(req), query )) as ExecuteReturnRaw @@ -171,11 +167,6 @@ const executeReturnJson = async ( req: express.Request, _program: string ): Promise => { - const sasCodePath = - path - .join(getFilesFolder(), _program) - .replace(new RegExp('/', 'g'), path.sep) + '.sas' - const filesNamesMap = req.files?.length ? makeFilesNamesMap(req.files as MulterFile[]) : null @@ -183,12 +174,12 @@ const executeReturnJson = async ( try { const { webout, log, httpHeaders } = (await new ExecutionController().executeFile( - sasCodePath, + _program, getPreProgramVariables(req), { ...req.query, ...req.body }, { filesNamesMap: filesNamesMap }, true, - req.sasSession + req.sasjsSession )) as ExecuteReturnJson let weboutRes: string | IRecordOfAny = webout From c58666eb81514de500519e7b96c1981778ec149b Mon Sep 17 00:00:00 2001 From: Sabir Hassan Date: Fri, 3 Jun 2022 17:26:21 +0500 Subject: [PATCH 081/163] fix: convert single executeProgram method to two methods i.e. executeSASProgram and executeJSProgram --- api/src/controllers/code.ts | 2 +- api/src/controllers/internal/Execution.ts | 152 ++++++++++++++++++++-- 2 files changed, 139 insertions(+), 15 deletions(-) diff --git a/api/src/controllers/code.ts b/api/src/controllers/code.ts index a4b1d74..bff85bb 100644 --- a/api/src/controllers/code.ts +++ b/api/src/controllers/code.ts @@ -46,7 +46,7 @@ const executeSASCode = async ( try { const { webout, log, httpHeaders } = - (await new ExecutionController().executeProgram( + (await new ExecutionController().executeSASProgram( code, getPreProgramVariables(req), { ...req.query, _debug: 131 }, diff --git a/api/src/controllers/internal/Execution.ts b/api/src/controllers/internal/Execution.ts index b4d8b4c..4ec2486 100644 --- a/api/src/controllers/internal/Execution.ts +++ b/api/src/controllers/internal/Execution.ts @@ -1,6 +1,6 @@ import path from 'path' import fs from 'fs' -import { getSessionController } from './' +import { getSASSessionController, getJSSessionController } from './' import { readFile, fileExists, @@ -42,22 +42,45 @@ export class ExecutionController { returnJson?: boolean, session?: Session ) { - if (!(await fileExists(programPath))) - throw 'ExecutionController: SAS file does not exist.' + if (process.runTimes.length === 0) { + throw 'No runtime is specified in environment variables.' + } - const program = await readFile(programPath) + for (const runTime of process.runTimes) { + const codePath = + path + .join(getFilesFolder(), programPath) + .replace(new RegExp('/', 'g'), path.sep) + runTime - return this.executeProgram( - program, - preProgramVariables, - vars, - otherArgs, - returnJson, - session - ) + if (await fileExists(programPath)) { + const program = await readFile(codePath) + + if (runTime === 'sas') { + return this.executeSASProgram( + program, + preProgramVariables, + vars, + otherArgs, + returnJson, + session + ) + } else if (runTime === 'js') { + return this.executeJSProgram( + program, + preProgramVariables, + vars, + otherArgs, + returnJson + ) + } else { + throw `${runTime} runtime is not implemented yet.` + } + } + } + throw 'ExecutionController: Program file does not exist.' } - async executeProgram( + async executeSASProgram( program: string, preProgramVariables: PreProgramVars, vars: ExecutionVars, @@ -65,7 +88,7 @@ export class ExecutionController { returnJson?: boolean, sessionByFileUpload?: Session ): Promise { - const sessionController = getSessionController() + const sessionController = getSASSessionController() const session = sessionByFileUpload ?? (await sessionController.getSession()) @@ -191,6 +214,107 @@ ${program}` } } + async executeJSProgram( + program: string, + preProgramVariables: PreProgramVars, + vars: ExecutionVars, + otherArgs?: any, + returnJson?: boolean, + sessionByFileUpload?: Session + ): Promise { + const sessionController = getJSSessionController() + + const session = + sessionByFileUpload ?? (await sessionController.getSession()) + + const logPath = path.join(session.path, 'log.log') + const headersPath = path.join(session.path, 'stpsrv_header.txt') + const weboutPath = path.join(session.path, 'webout.txt') + const tokenFile = path.join(session.path, 'reqHeaders.txt') + + await createFile( + tokenFile, + preProgramVariables?.httpHeaders.join('\n') ?? '' + ) + + const varStatments = Object.keys(vars).reduce( + (computed: string, key: string) => + `${computed}const ${key} = ${vars[key]};\n`, + '' + ) + + const preProgramVarStatments = ` +const _webout = ${weboutPath} +const _sasjs_tokenfile = ${tokenFile}; +const _sasjs_username = ${preProgramVariables?.username}; +const _sasjs_userid = ${preProgramVariables?.userId}; +const _sasjs_displayname = ${preProgramVariables?.displayName}; +const _metaperson = _sasjs_displayname; +const _metauser = _sasjs_username; +const sasjsprocessmode = 'Stored Program'; +` + + program = ` +/* runtime vars */ +${varStatments} + +/* dynamic user-provided vars */ +${preProgramVarStatments} + +/* actual job code */ +${program}` + + // todo: modify this commented block for js runtime + // if no files are uploaded filesNamesMap will be undefined + // if (otherArgs?.filesNamesMap) { + // const uploadSasCode = await generateFileUploadSasCode( + // otherArgs.filesNamesMap, + // session.path + // ) + + // //If sas code for the file is generated it will be appended to the top of sasCode + // if (uploadSasCode.length > 0) { + // program = `${uploadSasCode}` + program + // } + // } + + const codePath = path.join(session.path, 'code.sas') + await createFile(codePath, program) + + // todo: execute code.js file + + const log = (await fileExists(logPath)) ? await readFile(logPath) : '' + const headersContent = (await fileExists(headersPath)) + ? await readFile(headersPath) + : '' + const httpHeaders: HTTPHeaders = extractHeaders(headersContent) + const fileResponse: boolean = + httpHeaders.hasOwnProperty('content-type') && + !returnJson && // not a POST Request + !isDebugOn(vars) // Debug is not enabled + + const webout = (await fileExists(weboutPath)) + ? fileResponse + ? await readFileBinary(weboutPath) + : await readFile(weboutPath) + : '' + + if (returnJson) { + return { + httpHeaders, + webout, + log: isDebugOn(vars) ? log : undefined + } + } + + return { + httpHeaders, + result: isDebugOn(vars) + ? `${webout}

SAS Log

${log}
` + : webout + } + } + buildDirectoryTree() { const root: TreeNode = { name: 'files', From f561ba4bf09e4836d8d94128022b6817e91ff80e Mon Sep 17 00:00:00 2001 From: Sabir Hassan Date: Sat, 4 Jun 2022 03:12:51 +0500 Subject: [PATCH 082/163] chore: documented sasjs_runtimes env variable in readme file --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index a3a2024..615c95b 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,15 @@ SAS_PATH=/path/to/sas/executable.exe # This location is for SAS WORK, staged files, DRIVE, configuration etc SASJS_ROOT=./sasjs_root +# A comma separated string that defines the available runTimes. +# Priority is given to the runtime that comes first in string. +# Possible options at the moment are sas and js + +# options: [sas,js|js,sas|sas|js] default:sas +SASJS_RUNTIMES= + + + # options: [http|https] default: http PROTOCOL= From 596ada7ca88798d6d71f6845633a006fd22438ea Mon Sep 17 00:00:00 2001 From: Sabir Hassan Date: Sat, 4 Jun 2022 03:16:07 +0500 Subject: [PATCH 083/163] feat: validate sasjs_runtimes env var --- api/src/utils/verifyEnvVariables.ts | 34 ++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/api/src/utils/verifyEnvVariables.ts b/api/src/utils/verifyEnvVariables.ts index 9d82069..469c90f 100644 --- a/api/src/utils/verifyEnvVariables.ts +++ b/api/src/utils/verifyEnvVariables.ts @@ -26,6 +26,11 @@ export enum LOG_FORMAT_MORGANType { tiny = 'tiny' } +export enum SASJSRunTimes { + SAS = 'sas', + JS = 'js' +} + export enum ReturnCode { Success, InvalidEnv @@ -46,6 +51,8 @@ export const verifyEnvVariables = (): ReturnCode => { errors.push(...verifyLOG_FORMAT_MORGAN()) + errors.push(...verifySASJSRunTimes()) + if (errors.length) { process.logger?.error( `Invalid environment variable(s) provided: \n${errors.join('\n')}` @@ -202,10 +209,35 @@ const verifyLOG_FORMAT_MORGAN = (): string[] => { return errors } +const verifySASJSRunTimes = (): string[] => { + const errors: string[] = [] + const { SASJS_RUNTIMES } = process.env + + if (SASJS_RUNTIMES) { + const runTimes = SASJS_RUNTIMES.split(',').map((runTime) => + runTime.toLowerCase() + ) + + const possibleRunTimes = Object.values(SASJSRunTimes) + + runTimes.forEach((runTime) => { + if (!possibleRunTimes.includes(runTime as SASJSRunTimes)) { + errors.push( + `- Invalid '${runTime}' runtime\n - valid options ${possibleRunTimes}` + ) + } + }) + } else { + process.env.SASJS_RUNTIMES = DEFAULTS.SASJS_RUNTIMES + } + return errors +} + const DEFAULTS = { MODE: ModeType.Desktop, PROTOCOL: ProtocolType.HTTP, PORT: '5000', HELMET_COEP: HelmetCoepType.TRUE, - LOG_FORMAT_MORGAN: LOG_FORMAT_MORGANType.Common + LOG_FORMAT_MORGAN: LOG_FORMAT_MORGANType.Common, + SASJS_RUNTIMES: SASJSRunTimes.SAS } From e5a7674fa1a09d043fec93af54a6af5eb2a7232a Mon Sep 17 00:00:00 2001 From: Sabir Hassan Date: Mon, 6 Jun 2022 09:09:21 +0500 Subject: [PATCH 084/163] chore: refactor ExecutionController class to remove code duplications --- api/src/controllers/code.ts | 2 +- api/src/controllers/internal/Execution.ts | 255 +++++++++++----------- 2 files changed, 130 insertions(+), 127 deletions(-) diff --git a/api/src/controllers/code.ts b/api/src/controllers/code.ts index bff85bb..a4b1d74 100644 --- a/api/src/controllers/code.ts +++ b/api/src/controllers/code.ts @@ -46,7 +46,7 @@ const executeSASCode = async ( try { const { webout, log, httpHeaders } = - (await new ExecutionController().executeSASProgram( + (await new ExecutionController().executeProgram( code, getPreProgramVariables(req), { ...req.query, _debug: 131 }, diff --git a/api/src/controllers/internal/Execution.ts b/api/src/controllers/internal/Execution.ts index 4ec2486..b73d8dd 100644 --- a/api/src/controllers/internal/Execution.ts +++ b/api/src/controllers/internal/Execution.ts @@ -1,5 +1,6 @@ import path from 'path' import fs from 'fs' +import { execFileSync } from 'child_process' import { getSASSessionController, getJSSessionController } from './' import { readFile, @@ -15,7 +16,8 @@ import { getFilesFolder, getMacrosFolder, HTTPHeaders, - isDebugOn + isDebugOn, + SASJSRunTimes } from '../../utils' export interface ExecutionVars { @@ -50,45 +52,53 @@ export class ExecutionController { const codePath = path .join(getFilesFolder(), programPath) - .replace(new RegExp('/', 'g'), path.sep) + runTime - - if (await fileExists(programPath)) { + .replace(new RegExp('/', 'g'), path.sep) + + '.' + + runTime + if (await fileExists(codePath)) { const program = await readFile(codePath) if (runTime === 'sas') { - return this.executeSASProgram( + return this.executeProgram( program, preProgramVariables, vars, otherArgs, returnJson, - session + session, + runTime ) } else if (runTime === 'js') { - return this.executeJSProgram( + return this.executeProgram( program, preProgramVariables, vars, otherArgs, - returnJson + returnJson, + session, + runTime ) } else { throw `${runTime} runtime is not implemented yet.` } } } - throw 'ExecutionController: Program file does not exist.' + throw `ExecutionController: ${programPath} file does not exist.` } - async executeSASProgram( + async executeProgram( program: string, preProgramVariables: PreProgramVars, vars: ExecutionVars, otherArgs?: any, returnJson?: boolean, - sessionByFileUpload?: Session + sessionByFileUpload?: Session, + runTime: string = 'sas' ): Promise { - const sessionController = getSASSessionController() + const sessionController = + runTime === SASJSRunTimes.JS + ? getJSSessionController() + : getSASSessionController() const session = sessionByFileUpload ?? (await sessionController.getSession()) @@ -106,6 +116,93 @@ export class ExecutionController { preProgramVariables?.httpHeaders.join('\n') ?? '' ) + if (runTime === SASJSRunTimes.JS) { + program = await this.createJSProgram( + program, + preProgramVariables, + vars, + weboutPath, + tokenFile + ) + + const codePath = path.join(session.path, 'code.js') + + await createFile(codePath, program) + + execFileSync('node', [codePath]) + } else { + program = await this.createSASProgram( + program, + preProgramVariables, + vars, + session, + weboutPath, + tokenFile, + otherArgs + ) + + const codePath = path.join(session.path, 'code.sas') + + // Creating this file in a RUNNING session will break out + // the autoexec loop and actually execute the program + // but - given it will take several milliseconds to create + // (which can mean SAS trying to run a partial program, or + // failing due to file lock) we first create the file THEN + // we rename it. + await createFile(codePath + '.bkp', program) + await moveFile(codePath + '.bkp', codePath) + + // we now need to poll the session status + while (!session.completed) { + await delay(50) + } + } + + const log = (await fileExists(logPath)) ? await readFile(logPath) : '' + const headersContent = (await fileExists(headersPath)) + ? await readFile(headersPath) + : '' + const httpHeaders: HTTPHeaders = extractHeaders(headersContent) + const fileResponse: boolean = + httpHeaders.hasOwnProperty('content-type') && + !returnJson && // not a POST Request + !isDebugOn(vars) // Debug is not enabled + + const webout = (await fileExists(weboutPath)) + ? fileResponse + ? await readFileBinary(weboutPath) + : await readFile(weboutPath) + : '' + + // it should be deleted by scheduleSessionDestroy + session.inUse = false + + if (returnJson) { + return { + httpHeaders, + webout, + log: isDebugOn(vars) || session.crashed ? log : undefined + } + } + + return { + httpHeaders, + result: + isDebugOn(vars) || session.crashed + ? `${webout}

SAS Log

${log}
` + : webout + } + } + + private async createSASProgram( + program: string, + preProgramVariables: PreProgramVars, + vars: ExecutionVars, + session: Session, + weboutPath: string, + tokenFile: string, + otherArgs?: any + ) { const varStatments = Object.keys(vars).reduce( (computed: string, key: string) => `${computed}%let ${key}=${vars[key]};\n`, @@ -161,100 +258,37 @@ ${program}` program = `${uploadSasCode}` + program } } - - const codePath = path.join(session.path, 'code.sas') - - // Creating this file in a RUNNING session will break out - // the autoexec loop and actually execute the program - // but - given it will take several milliseconds to create - // (which can mean SAS trying to run a partial program, or - // failing due to file lock) we first create the file THEN - // we rename it. - await createFile(codePath + '.bkp', program) - await moveFile(codePath + '.bkp', codePath) - - // we now need to poll the session status - while (!session.completed) { - await delay(50) - } - - const log = (await fileExists(logPath)) ? await readFile(logPath) : '' - const headersContent = (await fileExists(headersPath)) - ? await readFile(headersPath) - : '' - const httpHeaders: HTTPHeaders = extractHeaders(headersContent) - const fileResponse: boolean = - httpHeaders.hasOwnProperty('content-type') && - !returnJson && // not a POST Request - !isDebugOn(vars) // Debug is not enabled - - const webout = (await fileExists(weboutPath)) - ? fileResponse - ? await readFileBinary(weboutPath) - : await readFile(weboutPath) - : '' - - // it should be deleted by scheduleSessionDestroy - session.inUse = false - - if (returnJson) { - return { - httpHeaders, - webout, - log: isDebugOn(vars) || session.crashed ? log : undefined - } - } - - return { - httpHeaders, - result: - isDebugOn(vars) || session.crashed - ? `${webout}

SAS Log

${log}
` - : webout - } + return program } - async executeJSProgram( + private async createJSProgram( program: string, preProgramVariables: PreProgramVars, vars: ExecutionVars, - otherArgs?: any, - returnJson?: boolean, - sessionByFileUpload?: Session - ): Promise { - const sessionController = getJSSessionController() - - const session = - sessionByFileUpload ?? (await sessionController.getSession()) - - const logPath = path.join(session.path, 'log.log') - const headersPath = path.join(session.path, 'stpsrv_header.txt') - const weboutPath = path.join(session.path, 'webout.txt') - const tokenFile = path.join(session.path, 'reqHeaders.txt') - - await createFile( - tokenFile, - preProgramVariables?.httpHeaders.join('\n') ?? '' - ) - + weboutPath: string, + tokenFile: string + ) { const varStatments = Object.keys(vars).reduce( (computed: string, key: string) => - `${computed}const ${key} = ${vars[key]};\n`, + `${computed}const ${key} = '${vars[key]}';\n`, '' ) const preProgramVarStatments = ` -const _webout = ${weboutPath} -const _sasjs_tokenfile = ${tokenFile}; -const _sasjs_username = ${preProgramVariables?.username}; -const _sasjs_userid = ${preProgramVariables?.userId}; -const _sasjs_displayname = ${preProgramVariables?.displayName}; +const weboutPath = '${weboutPath}'; +const _sasjs_tokenfile = '${tokenFile}'; +const _sasjs_username = '${preProgramVariables?.username}'; +const _sasjs_userid = '${preProgramVariables?.userId}'; +const _sasjs_displayname = '${preProgramVariables?.displayName}'; const _metaperson = _sasjs_displayname; const _metauser = _sasjs_username; const sasjsprocessmode = 'Stored Program'; ` program = ` +/*require module for writing webout file*/ +const fs = require('fs-extra') + /* runtime vars */ ${varStatments} @@ -262,7 +296,11 @@ ${varStatments} ${preProgramVarStatments} /* actual job code */ -${program}` +${program} + +/* write webout file*/ +fs.promises.writeFile(weboutPath, JSON.stringify(webout)) +` // todo: modify this commented block for js runtime // if no files are uploaded filesNamesMap will be undefined @@ -277,42 +315,7 @@ ${program}` // program = `${uploadSasCode}` + program // } // } - - const codePath = path.join(session.path, 'code.sas') - await createFile(codePath, program) - - // todo: execute code.js file - - const log = (await fileExists(logPath)) ? await readFile(logPath) : '' - const headersContent = (await fileExists(headersPath)) - ? await readFile(headersPath) - : '' - const httpHeaders: HTTPHeaders = extractHeaders(headersContent) - const fileResponse: boolean = - httpHeaders.hasOwnProperty('content-type') && - !returnJson && // not a POST Request - !isDebugOn(vars) // Debug is not enabled - - const webout = (await fileExists(weboutPath)) - ? fileResponse - ? await readFileBinary(weboutPath) - : await readFile(weboutPath) - : '' - - if (returnJson) { - return { - httpHeaders, - webout, - log: isDebugOn(vars) ? log : undefined - } - } - - return { - httpHeaders, - result: isDebugOn(vars) - ? `${webout}

SAS Log

${log}
` - : webout - } + return program } buildDirectoryTree() { From b4443819d42afecebc0f382c58afb9010d4775ef Mon Sep 17 00:00:00 2001 From: Sabir Hassan Date: Mon, 6 Jun 2022 15:19:39 +0500 Subject: [PATCH 085/163] fix: refactor code for session selection in preUploadMiddleware function --- .../internal/FileUploadController.ts | 28 ++++++++----------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/api/src/controllers/internal/FileUploadController.ts b/api/src/controllers/internal/FileUploadController.ts index 9fcf734..9758aea 100644 --- a/api/src/controllers/internal/FileUploadController.ts +++ b/api/src/controllers/internal/FileUploadController.ts @@ -1,9 +1,9 @@ import path from 'path' import { Request, RequestHandler } from 'express' import multer from 'multer' -import { uuidv4, fileExists, readFile } from '@sasjs/utils' +import { uuidv4, fileExists } from '@sasjs/utils' import { getSASSessionController, getJSSessionController } from '.' -import { getFilesFolder } from '../../utils' +import { getFilesFolder, SASJSRunTimes } from '../../utils' export class FileUploadController { private storage = multer.diskStorage({ @@ -22,22 +22,22 @@ export class FileUploadController { //It will intercept request and generate unique uuid to be used as a subfolder name //that will store the files uploaded public preUploadMiddleware: RequestHandler = async (req, res, next) => { - if (process.runTimes.length === 0) { - throw 'No runtime is specified in environment variables.' - } - const programPath = req.query._program as string for (const runTime of process.runTimes) { const codePath = path .join(getFilesFolder(), programPath) - .replace(new RegExp('/', 'g'), path.sep) + runTime + .replace(new RegExp('/', 'g'), path.sep) + + '.' + + runTime - if (await fileExists(programPath)) { - const program = await readFile(codePath) - - if (runTime === 'sas') { + if (await fileExists(codePath)) { + if (runTime === SASJSRunTimes.JS) { + const sessionController = getJSSessionController() + const session = await sessionController.getSession() + req.sasjsSession = session + } else { const sessionController = getSASSessionController() const session = await sessionController.getSession() // marking consumed true, so that it's not available @@ -45,12 +45,6 @@ export class FileUploadController { session.consumed = true req.sasjsSession = session - } else if (runTime === 'js') { - const sessionController = getJSSessionController() - const session = await sessionController.getSession() - req.sasjsSession = session - } else { - throw `${runTime} runtime is not implemented yet.` } } } From dffe6d7121d569e5c7d13023c6ca68d8c901c88e Mon Sep 17 00:00:00 2001 From: Sabir Hassan Date: Mon, 6 Jun 2022 15:23:42 +0500 Subject: [PATCH 086/163] fix: refactor code in executeFile method of session controller --- api/src/controllers/internal/Execution.ts | 26 +++++++++-------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/api/src/controllers/internal/Execution.ts b/api/src/controllers/internal/Execution.ts index b73d8dd..29210af 100644 --- a/api/src/controllers/internal/Execution.ts +++ b/api/src/controllers/internal/Execution.ts @@ -44,10 +44,6 @@ export class ExecutionController { returnJson?: boolean, session?: Session ) { - if (process.runTimes.length === 0) { - throw 'No runtime is specified in environment variables.' - } - for (const runTime of process.runTimes) { const codePath = path @@ -58,17 +54,7 @@ export class ExecutionController { if (await fileExists(codePath)) { const program = await readFile(codePath) - if (runTime === 'sas') { - return this.executeProgram( - program, - preProgramVariables, - vars, - otherArgs, - returnJson, - session, - runTime - ) - } else if (runTime === 'js') { + if (runTime === SASJSRunTimes.JS) { return this.executeProgram( program, preProgramVariables, @@ -79,7 +65,15 @@ export class ExecutionController { runTime ) } else { - throw `${runTime} runtime is not implemented yet.` + return this.executeProgram( + program, + preProgramVariables, + vars, + otherArgs, + returnJson, + session, + runTime + ) } } } From 6d6bda56267babde7b98cf69e32973d56d719f75 Mon Sep 17 00:00:00 2001 From: Sabir Hassan Date: Mon, 6 Jun 2022 17:23:09 +0500 Subject: [PATCH 087/163] fix: refactor code in preUploadMiddleware function --- .../internal/FileUploadController.ts | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/api/src/controllers/internal/FileUploadController.ts b/api/src/controllers/internal/FileUploadController.ts index 9758aea..4e4b832 100644 --- a/api/src/controllers/internal/FileUploadController.ts +++ b/api/src/controllers/internal/FileUploadController.ts @@ -33,19 +33,18 @@ export class FileUploadController { runTime if (await fileExists(codePath)) { + let sessionController if (runTime === SASJSRunTimes.JS) { - const sessionController = getJSSessionController() - const session = await sessionController.getSession() - req.sasjsSession = session + sessionController = getJSSessionController() } else { - const sessionController = getSASSessionController() - const session = await sessionController.getSession() - // marking consumed true, so that it's not available - // as readySession for any other request - session.consumed = true - - req.sasjsSession = session + sessionController = getSASSessionController() } + const session = await sessionController.getSession() + // marking consumed true, so that it's not available + // as readySession for any other request + session.consumed = true + req.sasjsSession = session + break } } From 2c704a544f4e31a8e8e833a9a62ba016bcfa6c7c Mon Sep 17 00:00:00 2001 From: Sabir Hassan Date: Mon, 6 Jun 2022 17:24:19 +0500 Subject: [PATCH 088/163] fix: refactor sas/js session controller classes to inherit from base session controller class --- api/src/controllers/internal/Session.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/api/src/controllers/internal/Session.ts b/api/src/controllers/internal/Session.ts index 4f7eac4..c1d5d40 100644 --- a/api/src/controllers/internal/Session.ts +++ b/api/src/controllers/internal/Session.ts @@ -17,12 +17,14 @@ import { const execFilePromise = promisify(execFile) -export class SASSessionController { - private sessions: Session[] = [] +abstract class SessionController { + protected sessions: Session[] = [] - private getReadySessions = (): Session[] => + protected getReadySessions = (): Session[] => this.sessions.filter((sess: Session) => sess.ready && !sess.consumed) + protected abstract createSession(): Promise + public async getSession() { const readySessions = this.getReadySessions() @@ -34,8 +36,10 @@ export class SASSessionController { return session } +} - private async createSession(): Promise { +export class SASSessionController extends SessionController { + protected async createSession(): Promise { const sessionId = generateUniqueFileName(generateTimestamp()) const sessionFolder = path.join(getSessionsFolder(), sessionId) @@ -152,8 +156,8 @@ ${autoExecContent}` } } -export class JSSessionController { - public async getSession() { +export class JSSessionController extends SessionController { + protected async createSession(): Promise { const sessionId = generateUniqueFileName(generateTimestamp()) const sessionFolder = path.join(getSessionsFolder(), sessionId) @@ -179,6 +183,7 @@ export class JSSessionController { const headersPath = path.join(session.path, 'stpsrv_header.txt') await createFile(headersPath, 'Content-type: application/json') + this.sessions.push(session) return session } } From 9d5a5e051fd821295664ddb3a1fd64629894a44c Mon Sep 17 00:00:00 2001 From: Sabir Hassan Date: Tue, 7 Jun 2022 13:27:18 +0500 Subject: [PATCH 089/163] fix: no need to stringify _webout in preProgramVarStatements, developer should have _webout as string in actual code --- api/src/controllers/internal/Execution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/controllers/internal/Execution.ts b/api/src/controllers/internal/Execution.ts index 29210af..1df3703 100644 --- a/api/src/controllers/internal/Execution.ts +++ b/api/src/controllers/internal/Execution.ts @@ -293,7 +293,7 @@ ${preProgramVarStatments} ${program} /* write webout file*/ -fs.promises.writeFile(weboutPath, JSON.stringify(webout)) +fs.promises.writeFile(weboutPath, _webout) ` // todo: modify this commented block for js runtime From 058b3b00816e582e143953c2f0b8330bde2181b8 Mon Sep 17 00:00:00 2001 From: Sabir Hassan Date: Wed, 8 Jun 2022 02:01:31 +0500 Subject: [PATCH 090/163] feat: configure child process with writeStream to write logs to log file --- api/src/controllers/internal/Execution.ts | 25 +++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/api/src/controllers/internal/Execution.ts b/api/src/controllers/internal/Execution.ts index 1df3703..b246a20 100644 --- a/api/src/controllers/internal/Execution.ts +++ b/api/src/controllers/internal/Execution.ts @@ -1,6 +1,7 @@ import path from 'path' import fs from 'fs' import { execFileSync } from 'child_process' +import { once } from 'stream' import { getSASSessionController, getJSSessionController } from './' import { readFile, @@ -121,9 +122,29 @@ export class ExecutionController { const codePath = path.join(session.path, 'code.js') - await createFile(codePath, program) + try { + await createFile(codePath, program) - execFileSync('node', [codePath]) + // create a stream that will write to console outputs to log file + const writeStream = fs.createWriteStream(logPath) + + // waiting for the open event so that we can have underlying file descriptor + await once(writeStream, 'open') + + execFileSync('node', [codePath], { + stdio: ['ignore', writeStream, writeStream] + }) + + // copy the code.js program to log and end write stream + writeStream.end(program) + + session.completed = true + console.log('session completed', session) + } catch (err: any) { + session.completed = true + session.crashed = err.toString() + console.log('session crashed', session.id, session.crashed) + } } else { program = await this.createSASProgram( program, From 5ab35b02c4417132dddb5a800982f31d0d50ef66 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Thu, 9 Jun 2022 01:16:25 +0500 Subject: [PATCH 091/163] fix(appstream): redirect to relative + nested resource should be accessed --- api/src/routes/appStream/index.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/api/src/routes/appStream/index.ts b/api/src/routes/appStream/index.ts index 4a607e5..3954039 100644 --- a/api/src/routes/appStream/index.ts +++ b/api/src/routes/appStream/index.ts @@ -2,11 +2,7 @@ import path from 'path' import express, { Request } from 'express' import { folderExists } from '@sasjs/utils' -import { - addEntryToAppStreamConfig, - getFilesFolder, - getFullUrl -} from '../../utils' +import { addEntryToAppStreamConfig, getFilesFolder } from '../../utils' import { appStreamHtml } from './appStreamHtml' const appStreams: { [key: string]: string } = {} @@ -73,14 +69,16 @@ export const publishAppStream = async ( router.get(`/*`, function (req: Request, res, next) { const reqPath = req.path.replace(/^\//, '') - // Redirecting to url with trailing slash for base appStream URL only + // Redirecting to url with trailing slash for appStream base URL only if (reqPath.split('/').length === 1 && !reqPath.endsWith('/')) - return res.redirect(301, `${getFullUrl(req)}/`) + // navigating to same url with slash at start + return res.redirect(301, `${reqPath}/`) const appStream = reqPath.split('/')[0] const appStreamFilesPath = appStreams[appStream] if (appStreamFilesPath) { - const resourcePath = reqPath.split('/')[1] || 'index.html' + // resourcePath is without appStream base path + const resourcePath = reqPath.split('/').slice(1).join('/') || 'index.html' req.url = resourcePath From e7babb9f55d5949f2c436d2e309caccd0d7c3934 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 8 Jun 2022 20:21:22 +0000 Subject: [PATCH 092/163] chore(release): 0.3.7 [skip ci] ## [0.3.7](https://github.com/sasjs/server/compare/v0.3.6...v0.3.7) (2022-06-08) ### Bug Fixes * **appstream:** redirect to relative + nested resource should be accessed ([5ab35b0](https://github.com/sasjs/server/commit/5ab35b02c4417132dddb5a800982f31d0d50ef66)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ba53a6..72b89c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [0.3.7](https://github.com/sasjs/server/compare/v0.3.6...v0.3.7) (2022-06-08) + + +### Bug Fixes + +* **appstream:** redirect to relative + nested resource should be accessed ([5ab35b0](https://github.com/sasjs/server/commit/5ab35b02c4417132dddb5a800982f31d0d50ef66)) + ## [0.3.6](https://github.com/sasjs/server/compare/v0.3.5...v0.3.6) (2022-06-02) From 16856165fb292dc9ffa897189ba105bd9f362267 Mon Sep 17 00:00:00 2001 From: Sabir Hassan Date: Thu, 9 Jun 2022 14:54:11 +0500 Subject: [PATCH 093/163] feat: create and inject code for uploaded files to code.js --- api/src/controllers/internal/Execution.ts | 38 ++++++++++++----------- api/src/utils/upload.ts | 34 +++++++++++++++++++- 2 files changed, 53 insertions(+), 19 deletions(-) diff --git a/api/src/controllers/internal/Execution.ts b/api/src/controllers/internal/Execution.ts index b246a20..50620c1 100644 --- a/api/src/controllers/internal/Execution.ts +++ b/api/src/controllers/internal/Execution.ts @@ -14,6 +14,7 @@ import { PreProgramVars, Session, TreeNode } from '../../types' import { extractHeaders, generateFileUploadSasCode, + generateFileUploadJSCode, getFilesFolder, getMacrosFolder, HTTPHeaders, @@ -116,8 +117,10 @@ export class ExecutionController { program, preProgramVariables, vars, + session, weboutPath, - tokenFile + tokenFile, + otherArgs ) const codePath = path.join(session.path, 'code.js') @@ -280,8 +283,10 @@ ${program}` program: string, preProgramVariables: PreProgramVars, vars: ExecutionVars, + session: Session, weboutPath: string, - tokenFile: string + tokenFile: string, + otherArgs?: any ) { const varStatments = Object.keys(vars).reduce( (computed: string, key: string) => @@ -300,10 +305,9 @@ const _metauser = _sasjs_username; const sasjsprocessmode = 'Stored Program'; ` - program = ` -/*require module for writing webout file*/ -const fs = require('fs-extra') + const requiredModules = `const fs = require('fs-extra')` + program = ` /* runtime vars */ ${varStatments} @@ -316,21 +320,19 @@ ${program} /* write webout file*/ fs.promises.writeFile(weboutPath, _webout) ` - - // todo: modify this commented block for js runtime // if no files are uploaded filesNamesMap will be undefined - // if (otherArgs?.filesNamesMap) { - // const uploadSasCode = await generateFileUploadSasCode( - // otherArgs.filesNamesMap, - // session.path - // ) + if (otherArgs?.filesNamesMap) { + const uploadJSCode = await generateFileUploadJSCode( + otherArgs.filesNamesMap, + session.path + ) - // //If sas code for the file is generated it will be appended to the top of sasCode - // if (uploadSasCode.length > 0) { - // program = `${uploadSasCode}` + program - // } - // } - return program + //If js code for the file is generated it will be appended to the top of jsCode + if (uploadJSCode.length > 0) { + program = `${uploadJSCode}\n` + program + } + } + return requiredModules + program } buildDirectoryTree() { diff --git a/api/src/utils/upload.ts b/api/src/utils/upload.ts index 640464c..cc9a47f 100644 --- a/api/src/utils/upload.ts +++ b/api/src/utils/upload.ts @@ -1,5 +1,6 @@ +import path from 'path' import { MulterFile } from '../types/Upload' -import { listFilesInFolder } from '@sasjs/utils' +import { listFilesInFolder, readFileBinary } from '@sasjs/utils' interface FilenameMapSingle { fieldName: string @@ -98,3 +99,34 @@ export const generateFileUploadSasCode = async ( return uploadSasCode } + +/** + * Generates the js code that references uploaded files in the concurrent request + * @param filesNamesMap object that maps hashed file names and original file names + * @param sessionFolder name of the folder that is created for the purpose of files in concurrent request + * @returns generated js code + */ +export const generateFileUploadJSCode = async ( + filesNamesMap: FilenamesMap, + sessionFolder: string +) => { + let uploadCode = '' + let fileCount = 0 + + const sessionFolderList: string[] = await listFilesInFolder(sessionFolder) + sessionFolderList.forEach(async (fileName) => { + if (fileName.includes('req_file')) { + fileCount++ + const filePath = path.join(sessionFolder, fileName) + uploadCode += `\nconst _WEBIN_FILEREF${fileCount} = fs.readFileSync('${filePath}')` + uploadCode += `\nconst _WEBIN_FILENAME${fileCount} = '${filesNamesMap[fileName].originalName}'` + uploadCode += `\nconst _WEBIN_NAME${fileCount} = '${filesNamesMap[fileName].fieldName}'` + } + }) + + if (fileCount) { + uploadCode = `\nconst _WEBIN_FILE_COUNT = ${fileCount}` + uploadCode + } + + return uploadCode +} From 8a617a73ae63233332f5788c90f173d6cd5e1283 Mon Sep 17 00:00:00 2001 From: Mihajlo Date: Mon, 13 Jun 2022 14:01:12 +0200 Subject: [PATCH 094/163] fix: execution controller better error handling --- api/src/controllers/internal/Execution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/controllers/internal/Execution.ts b/api/src/controllers/internal/Execution.ts index b4d8b4c..9c46601 100644 --- a/api/src/controllers/internal/Execution.ts +++ b/api/src/controllers/internal/Execution.ts @@ -43,7 +43,7 @@ export class ExecutionController { session?: Session ) { if (!(await fileExists(programPath))) - throw 'ExecutionController: SAS file does not exist.' + throw `ExecutionController: SAS file (${programPath}) does not exist.` const program = await readFile(programPath) From 3fa2a7e2e32f90050f6b09e30ce3ef725eb0b15f Mon Sep 17 00:00:00 2001 From: Mihajlo Medjedovic Date: Mon, 13 Jun 2022 12:25:06 +0000 Subject: [PATCH 095/163] fix: execution controller error details --- api/src/controllers/internal/Execution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/controllers/internal/Execution.ts b/api/src/controllers/internal/Execution.ts index 9c46601..141942a 100644 --- a/api/src/controllers/internal/Execution.ts +++ b/api/src/controllers/internal/Execution.ts @@ -43,7 +43,7 @@ export class ExecutionController { session?: Session ) { if (!(await fileExists(programPath))) - throw `ExecutionController: SAS file (${programPath}) does not exist.` + throw `The Stored Program at (${vars._program}) does not exist, or you do not have permission to view it.` const program = await readFile(programPath) From 7e6635f40fde16e32e9745e6b20b3a231da50a99 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 13 Jun 2022 12:32:32 +0000 Subject: [PATCH 096/163] chore(release): 0.3.8 [skip ci] ## [0.3.8](https://github.com/sasjs/server/compare/v0.3.7...v0.3.8) (2022-06-13) ### Bug Fixes * execution controller better error handling ([8a617a7](https://github.com/sasjs/server/commit/8a617a73ae63233332f5788c90f173d6cd5e1283)) * execution controller error details ([3fa2a7e](https://github.com/sasjs/server/commit/3fa2a7e2e32f90050f6b09e30ce3ef725eb0b15f)) --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72b89c0..9ded935 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## [0.3.8](https://github.com/sasjs/server/compare/v0.3.7...v0.3.8) (2022-06-13) + + +### Bug Fixes + +* execution controller better error handling ([8a617a7](https://github.com/sasjs/server/commit/8a617a73ae63233332f5788c90f173d6cd5e1283)) +* execution controller error details ([3fa2a7e](https://github.com/sasjs/server/commit/3fa2a7e2e32f90050f6b09e30ce3ef725eb0b15f)) + ## [0.3.7](https://github.com/sasjs/server/compare/v0.3.6...v0.3.7) (2022-06-08) From de9ed15286127f3755168afd2ca6dc61cb1de54a Mon Sep 17 00:00:00 2001 From: Sabir Hassan Date: Mon, 13 Jun 2022 20:51:44 +0500 Subject: [PATCH 097/163] chore: update error message when stored program not found --- api/src/controllers/internal/Execution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/controllers/internal/Execution.ts b/api/src/controllers/internal/Execution.ts index dff5efc..0391f7f 100644 --- a/api/src/controllers/internal/Execution.ts +++ b/api/src/controllers/internal/Execution.ts @@ -79,7 +79,7 @@ export class ExecutionController { } } } - throw `The Stored Program at "${programPath}" does not exist, or you do not have permission to view it.` + throw `ExecutionController: The Stored Program at "${programPath}" does not exist, or you do not have permission to view it.` } async executeProgram( From 8734489cf014aedaca3f325e689493e4fe0b71ca Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Tue, 14 Jun 2022 09:12:41 +0000 Subject: [PATCH 098/163] fix: forcing utf 8 encoding. Closes #76 --- api/src/controllers/internal/Session.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/api/src/controllers/internal/Session.ts b/api/src/controllers/internal/Session.ts index b83d70e..72c8907 100644 --- a/api/src/controllers/internal/Session.ts +++ b/api/src/controllers/internal/Session.ts @@ -93,6 +93,7 @@ ${autoExecContent}` session.path, '-AUTOEXEC', autoExecPath, + '-ENCODING UTF-8', process.platform === 'win32' ? '-nosplash' : '' ]) .then(() => { From e059bee7dcf7df8c1bf7031fd48056671d3a2b6b Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 14 Jun 2022 09:20:37 +0000 Subject: [PATCH 099/163] chore(release): 0.3.9 [skip ci] ## [0.3.9](https://github.com/sasjs/server/compare/v0.3.8...v0.3.9) (2022-06-14) ### Bug Fixes * forcing utf 8 encoding. Closes [#76](https://github.com/sasjs/server/issues/76) ([8734489](https://github.com/sasjs/server/commit/8734489cf014aedaca3f325e689493e4fe0b71ca)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ded935..f0712db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [0.3.9](https://github.com/sasjs/server/compare/v0.3.8...v0.3.9) (2022-06-14) + + +### Bug Fixes + +* forcing utf 8 encoding. Closes [#76](https://github.com/sasjs/server/issues/76) ([8734489](https://github.com/sasjs/server/commit/8734489cf014aedaca3f325e689493e4fe0b71ca)) + ## [0.3.8](https://github.com/sasjs/server/compare/v0.3.7...v0.3.8) (2022-06-13) From 32d372b42fbf56b6c0779e8f704164eaae1c7548 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Tue, 14 Jun 2022 09:49:05 +0000 Subject: [PATCH 100/163] fix: correct syntax for encoding option --- api/src/controllers/internal/Session.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/src/controllers/internal/Session.ts b/api/src/controllers/internal/Session.ts index 72c8907..a120128 100644 --- a/api/src/controllers/internal/Session.ts +++ b/api/src/controllers/internal/Session.ts @@ -93,7 +93,8 @@ ${autoExecContent}` session.path, '-AUTOEXEC', autoExecPath, - '-ENCODING UTF-8', + '-ENCODING', + 'UTF-8', process.platform === 'win32' ? '-nosplash' : '' ]) .then(() => { From 806ea4cb5c76aff1e0120cafe9e70f326f0ac280 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 14 Jun 2022 09:53:53 +0000 Subject: [PATCH 101/163] chore(release): 0.3.10 [skip ci] ## [0.3.10](https://github.com/sasjs/server/compare/v0.3.9...v0.3.10) (2022-06-14) ### Bug Fixes * correct syntax for encoding option ([32d372b](https://github.com/sasjs/server/commit/32d372b42fbf56b6c0779e8f704164eaae1c7548)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0712db..d3ee046 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [0.3.10](https://github.com/sasjs/server/compare/v0.3.9...v0.3.10) (2022-06-14) + + +### Bug Fixes + +* correct syntax for encoding option ([32d372b](https://github.com/sasjs/server/commit/32d372b42fbf56b6c0779e8f704164eaae1c7548)) + ## [0.3.9](https://github.com/sasjs/server/compare/v0.3.8...v0.3.9) (2022-06-14) From c830f44e2977703150f17b436287e48d6ea3cdde Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Tue, 14 Jun 2022 16:48:58 +0500 Subject: [PATCH 102/163] chore: code fixes --- api/src/controllers/internal/Execution.ts | 497 +++++++++--------- .../internal/FileUploadController.ts | 50 +- api/src/controllers/stp.ts | 37 +- api/src/routes/api/stp.ts | 11 +- api/src/types/system/process.d.ts | 2 +- api/src/utils/getRunTimeAndFilePath.ts | 18 + api/src/utils/index.ts | 1 + api/src/utils/setProcessVariables.ts | 13 +- api/src/utils/verifyEnvVariables.ts | 24 +- 9 files changed, 343 insertions(+), 310 deletions(-) create mode 100644 api/src/utils/getRunTimeAndFilePath.ts diff --git a/api/src/controllers/internal/Execution.ts b/api/src/controllers/internal/Execution.ts index 0391f7f..0bc80a7 100644 --- a/api/src/controllers/internal/Execution.ts +++ b/api/src/controllers/internal/Execution.ts @@ -19,7 +19,7 @@ import { getMacrosFolder, HTTPHeaders, isDebugOn, - SASJSRunTimes + RunTimeType } from '../../utils' export interface ExecutionVars { @@ -37,64 +37,56 @@ export interface ExecuteReturnJson { log?: string } -export class ExecutionController { - async executeFile( - programPath: string, - preProgramVariables: PreProgramVars, - vars: ExecutionVars, - otherArgs?: any, - returnJson?: boolean, - session?: Session - ) { - for (const runTime of process.runTimes) { - const codePath = - path - .join(getFilesFolder(), programPath) - .replace(new RegExp('/', 'g'), path.sep) + - '.' + - runTime - if (await fileExists(codePath)) { - const program = await readFile(codePath) +interface ExecuteFileParams { + programPath: string + preProgramVariables: PreProgramVars + vars: ExecutionVars + otherArgs?: any + returnJson?: boolean + session?: Session + runTime: RunTimeType +} - if (runTime === SASJSRunTimes.JS) { - return this.executeProgram( - program, - preProgramVariables, - vars, - otherArgs, - returnJson, - session, - runTime - ) - } else { - return this.executeProgram( - program, - preProgramVariables, - vars, - otherArgs, - returnJson, - session, - runTime - ) - } - } - } - throw `ExecutionController: The Stored Program at "${programPath}" does not exist, or you do not have permission to view it.` +interface ExecuteProgramParams extends Omit { + program: string +} + +export class ExecutionController { + async executeFile({ + programPath, + preProgramVariables, + vars, + otherArgs, + returnJson, + session, + runTime + }: ExecuteFileParams) { + const program = await readFile(programPath) + + return this.executeProgram({ + program, + preProgramVariables, + vars, + otherArgs, + returnJson, + session, + runTime + }) } - async executeProgram( - program: string, - preProgramVariables: PreProgramVars, - vars: ExecutionVars, - otherArgs?: any, - returnJson?: boolean, - sessionByFileUpload?: Session, - runTime: string = 'sas' - ): Promise { + async executeProgram({ + program, + preProgramVariables, + vars, + otherArgs, + returnJson, + session: sessionByFileUpload, + runTime + }: ExecuteProgramParams): Promise { const sessionController = - runTime === SASJSRunTimes.JS - ? getJSSessionController() - : getSASSessionController() + runTime === RunTimeType.SAS + ? getSASSessionController() + : getJSSessionController() const session = sessionByFileUpload ?? (await sessionController.getSession()) @@ -112,69 +104,17 @@ export class ExecutionController { preProgramVariables?.httpHeaders.join('\n') ?? '' ) - if (runTime === SASJSRunTimes.JS) { - program = await this.createJSProgram( - program, - preProgramVariables, - vars, - session, - weboutPath, - tokenFile, - otherArgs - ) - - const codePath = path.join(session.path, 'code.js') - - try { - await createFile(codePath, program) - - // create a stream that will write to console outputs to log file - const writeStream = fs.createWriteStream(logPath) - - // waiting for the open event so that we can have underlying file descriptor - await once(writeStream, 'open') - - execFileSync('node', [codePath], { - stdio: ['ignore', writeStream, writeStream] - }) - - // copy the code.js program to log and end write stream - writeStream.end(program) - - session.completed = true - console.log('session completed', session) - } catch (err: any) { - session.completed = true - session.crashed = err.toString() - console.log('session crashed', session.id, session.crashed) - } - } else { - program = await this.createSASProgram( - program, - preProgramVariables, - vars, - session, - weboutPath, - tokenFile, - otherArgs - ) - - const codePath = path.join(session.path, 'code.sas') - - // Creating this file in a RUNNING session will break out - // the autoexec loop and actually execute the program - // but - given it will take several milliseconds to create - // (which can mean SAS trying to run a partial program, or - // failing due to file lock) we first create the file THEN - // we rename it. - await createFile(codePath + '.bkp', program) - await moveFile(codePath + '.bkp', codePath) - - // we now need to poll the session status - while (!session.completed) { - await delay(50) - } - } + await processProgram( + program, + preProgramVariables, + vars, + session, + weboutPath, + tokenFile, + runTime, + logPath, + otherArgs + ) const log = (await fileExists(logPath)) ? await readFile(logPath) : '' const headersContent = (await fileExists(headersPath)) @@ -212,129 +152,6 @@ export class ExecutionController { } } - private async createSASProgram( - program: string, - preProgramVariables: PreProgramVars, - vars: ExecutionVars, - session: Session, - weboutPath: string, - tokenFile: string, - otherArgs?: any - ) { - const varStatments = Object.keys(vars).reduce( - (computed: string, key: string) => - `${computed}%let ${key}=${vars[key]};\n`, - '' - ) - - const preProgramVarStatments = ` -%let _sasjs_tokenfile=${tokenFile}; -%let _sasjs_username=${preProgramVariables?.username}; -%let _sasjs_userid=${preProgramVariables?.userId}; -%let _sasjs_displayname=${preProgramVariables?.displayName}; -%let _sasjs_apiserverurl=${preProgramVariables?.serverUrl}; -%let _sasjs_apipath=/SASjsApi/stp/execute; -%let _metaperson=&_sasjs_displayname; -%let _metauser=&_sasjs_username; -%let sasjsprocessmode=Stored Program; -%let sasjs_stpsrv_header_loc=%sysfunc(pathname(work))/../stpsrv_header.txt; - -%global SYSPROCESSMODE SYSTCPIPHOSTNAME SYSHOSTINFOLONG; -%macro _sasjs_server_init(); - %if "&SYSPROCESSMODE"="" %then %let SYSPROCESSMODE=&sasjsprocessmode; - %if "&SYSTCPIPHOSTNAME"="" %then %let SYSTCPIPHOSTNAME=&_sasjs_apiserverurl; -%mend; -%_sasjs_server_init() -` - - program = ` -options insert=(SASAUTOS="${getMacrosFolder()}"); - -/* runtime vars */ -${varStatments} -filename _webout "${weboutPath}" mod; - -/* dynamic user-provided vars */ -${preProgramVarStatments} - -/* user autoexec starts */ -${otherArgs?.userAutoExec ?? ''} -/* user autoexec ends */ - -/* actual job code */ -${program}` - - // if no files are uploaded filesNamesMap will be undefined - if (otherArgs?.filesNamesMap) { - const uploadSasCode = await generateFileUploadSasCode( - otherArgs.filesNamesMap, - session.path - ) - - //If sas code for the file is generated it will be appended to the top of sasCode - if (uploadSasCode.length > 0) { - program = `${uploadSasCode}` + program - } - } - return program - } - - private async createJSProgram( - program: string, - preProgramVariables: PreProgramVars, - vars: ExecutionVars, - session: Session, - weboutPath: string, - tokenFile: string, - otherArgs?: any - ) { - const varStatments = Object.keys(vars).reduce( - (computed: string, key: string) => - `${computed}const ${key} = '${vars[key]}';\n`, - '' - ) - - const preProgramVarStatments = ` -const weboutPath = '${weboutPath}'; -const _sasjs_tokenfile = '${tokenFile}'; -const _sasjs_username = '${preProgramVariables?.username}'; -const _sasjs_userid = '${preProgramVariables?.userId}'; -const _sasjs_displayname = '${preProgramVariables?.displayName}'; -const _metaperson = _sasjs_displayname; -const _metauser = _sasjs_username; -const sasjsprocessmode = 'Stored Program'; -` - - const requiredModules = `const fs = require('fs-extra')` - - program = ` -/* runtime vars */ -${varStatments} - -/* dynamic user-provided vars */ -${preProgramVarStatments} - -/* actual job code */ -${program} - -/* write webout file*/ -fs.promises.writeFile(weboutPath, _webout) -` - // if no files are uploaded filesNamesMap will be undefined - if (otherArgs?.filesNamesMap) { - const uploadJSCode = await generateFileUploadJSCode( - otherArgs.filesNamesMap, - session.path - ) - - //If js code for the file is generated it will be appended to the top of jsCode - if (uploadJSCode.length > 0) { - program = `${uploadJSCode}\n` + program - } - } - return requiredModules + program - } - buildDirectoryTree() { const root: TreeNode = { name: 'files', @@ -374,3 +191,201 @@ fs.promises.writeFile(weboutPath, _webout) } const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) + +const createSASProgram = async ( + program: string, + preProgramVariables: PreProgramVars, + vars: ExecutionVars, + session: Session, + weboutPath: string, + tokenFile: string, + otherArgs?: any +) => { + const varStatments = Object.keys(vars).reduce( + (computed: string, key: string) => `${computed}%let ${key}=${vars[key]};\n`, + '' + ) + + const preProgramVarStatments = ` +%let _sasjs_tokenfile=${tokenFile}; +%let _sasjs_username=${preProgramVariables?.username}; +%let _sasjs_userid=${preProgramVariables?.userId}; +%let _sasjs_displayname=${preProgramVariables?.displayName}; +%let _sasjs_apiserverurl=${preProgramVariables?.serverUrl}; +%let _sasjs_apipath=/SASjsApi/stp/execute; +%let _metaperson=&_sasjs_displayname; +%let _metauser=&_sasjs_username; +%let sasjsprocessmode=Stored Program; +%let sasjs_stpsrv_header_loc=%sysfunc(pathname(work))/../stpsrv_header.txt; + +%global SYSPROCESSMODE SYSTCPIPHOSTNAME SYSHOSTINFOLONG; +%macro _sasjs_server_init(); +%if "&SYSPROCESSMODE"="" %then %let SYSPROCESSMODE=&sasjsprocessmode; +%if "&SYSTCPIPHOSTNAME"="" %then %let SYSTCPIPHOSTNAME=&_sasjs_apiserverurl; +%mend; +%_sasjs_server_init() +` + + program = ` +options insert=(SASAUTOS="${getMacrosFolder()}"); + +/* runtime vars */ +${varStatments} +filename _webout "${weboutPath}" mod; + +/* dynamic user-provided vars */ +${preProgramVarStatments} + +/* user autoexec starts */ +${otherArgs?.userAutoExec ?? ''} +/* user autoexec ends */ + +/* actual job code */ +${program}` + + // if no files are uploaded filesNamesMap will be undefined + if (otherArgs?.filesNamesMap) { + const uploadSasCode = await generateFileUploadSasCode( + otherArgs.filesNamesMap, + session.path + ) + + //If sas code for the file is generated it will be appended to the top of sasCode + if (uploadSasCode.length > 0) { + program = `${uploadSasCode}` + program + } + } + return program +} + +const createJSProgram = async ( + program: string, + preProgramVariables: PreProgramVars, + vars: ExecutionVars, + session: Session, + weboutPath: string, + tokenFile: string, + otherArgs?: any +) => { + const varStatments = Object.keys(vars).reduce( + (computed: string, key: string) => + `${computed}const ${key} = '${vars[key]}';\n`, + '' + ) + + const preProgramVarStatments = ` +const weboutPath = '${weboutPath}'; +const _sasjs_tokenfile = '${tokenFile}'; +const _sasjs_username = '${preProgramVariables?.username}'; +const _sasjs_userid = '${preProgramVariables?.userId}'; +const _sasjs_displayname = '${preProgramVariables?.displayName}'; +const _metaperson = _sasjs_displayname; +const _metauser = _sasjs_username; +const sasjsprocessmode = 'Stored Program'; +` + + const requiredModules = `const fs = require('fs-extra')` + + program = ` +/* runtime vars */ +${varStatments} + +/* dynamic user-provided vars */ +${preProgramVarStatments} + +/* actual job code */ +${program} + +/* write webout file*/ +fs.promises.writeFile(weboutPath, _webout) +` + // if no files are uploaded filesNamesMap will be undefined + if (otherArgs?.filesNamesMap) { + const uploadJSCode = await generateFileUploadJSCode( + otherArgs.filesNamesMap, + session.path + ) + + //If js code for the file is generated it will be appended to the top of jsCode + if (uploadJSCode.length > 0) { + program = `${uploadJSCode}\n` + program + } + } + return requiredModules + program +} + +const processProgram = async ( + program: string, + preProgramVariables: PreProgramVars, + vars: ExecutionVars, + session: Session, + weboutPath: string, + tokenFile: string, + runTime: RunTimeType, + logPath: string, + otherArgs?: any +) => { + if (runTime === RunTimeType.JS) { + program = await createJSProgram( + program, + preProgramVariables, + vars, + session, + weboutPath, + tokenFile, + otherArgs + ) + + const codePath = path.join(session.path, 'code.js') + + try { + await createFile(codePath, program) + + // create a stream that will write to console outputs to log file + const writeStream = fs.createWriteStream(logPath) + + // waiting for the open event so that we can have underlying file descriptor + await once(writeStream, 'open') + + execFileSync('node', [codePath], { + stdio: ['ignore', writeStream, writeStream] + }) + + // copy the code.js program to log and end write stream + writeStream.end(program) + + session.completed = true + console.log('session completed', session) + } catch (err: any) { + session.completed = true + session.crashed = err.toString() + console.log('session crashed', session.id, session.crashed) + } + } else { + program = await createSASProgram( + program, + preProgramVariables, + vars, + session, + weboutPath, + tokenFile, + otherArgs + ) + + const codePath = path.join(session.path, 'code.sas') + + // Creating this file in a RUNNING session will break out + // the autoexec loop and actually execute the program + // but - given it will take several milliseconds to create + // (which can mean SAS trying to run a partial program, or + // failing due to file lock) we first create the file THEN + // we rename it. + await createFile(codePath + '.bkp', program) + await moveFile(codePath + '.bkp', codePath) + + // we now need to poll the session status + while (!session.completed) { + await delay(50) + } + } +} diff --git a/api/src/controllers/internal/FileUploadController.ts b/api/src/controllers/internal/FileUploadController.ts index 4e4b832..d267d43 100644 --- a/api/src/controllers/internal/FileUploadController.ts +++ b/api/src/controllers/internal/FileUploadController.ts @@ -1,9 +1,12 @@ -import path from 'path' import { Request, RequestHandler } from 'express' import multer from 'multer' -import { uuidv4, fileExists } from '@sasjs/utils' +import { uuidv4 } from '@sasjs/utils' import { getSASSessionController, getJSSessionController } from '.' -import { getFilesFolder, SASJSRunTimes } from '../../utils' +import { + executeProgramRawValidation, + getRunTimeAndFilePath, + RunTimeType +} from '../../utils' export class FileUploadController { private storage = multer.diskStorage({ @@ -22,31 +25,26 @@ export class FileUploadController { //It will intercept request and generate unique uuid to be used as a subfolder name //that will store the files uploaded public preUploadMiddleware: RequestHandler = async (req, res, next) => { - const programPath = req.query._program as string + const { error: errQ, value: query } = executeProgramRawValidation(req.query) + const { error: errB, value: body } = executeProgramRawValidation(req.body) - for (const runTime of process.runTimes) { - const codePath = - path - .join(getFilesFolder(), programPath) - .replace(new RegExp('/', 'g'), path.sep) + - '.' + - runTime + if (errQ && errB) return res.status(400).send(errB.details[0].message) - if (await fileExists(codePath)) { - let sessionController - if (runTime === SASJSRunTimes.JS) { - sessionController = getJSSessionController() - } else { - sessionController = getSASSessionController() - } - const session = await sessionController.getSession() - // marking consumed true, so that it's not available - // as readySession for any other request - session.consumed = true - req.sasjsSession = session - break - } - } + const programPath = (query?._program ?? body?._program) as string + + const { runTime } = await getRunTimeAndFilePath(programPath) + + const sessionController = + runTime === RunTimeType.SAS + ? getSASSessionController() + : getJSSessionController() + + const session = await sessionController.getSession() + // marking consumed true, so that it's not available + // as readySession for any other request + session.consumed = true + + req.sasjsSession = session next() } diff --git a/api/src/controllers/stp.ts b/api/src/controllers/stp.ts index d3b01bf..624762c 100644 --- a/api/src/controllers/stp.ts +++ b/api/src/controllers/stp.ts @@ -1,5 +1,4 @@ import express from 'express' -import path from 'path' import { Request, Security, @@ -19,12 +18,12 @@ import { } from './internal' import { getPreProgramVariables, - getFilesFolder, HTTPHeaders, isDebugOn, LogLine, makeFilesNamesMap, - parseLogToArray + parseLogToArray, + getRunTimeAndFilePath } from '../utils' import { MulterFile } from '../types/Upload' @@ -132,13 +131,16 @@ const executeReturnRaw = async ( ): Promise => { const query = req.query as ExecutionVars + const { codePath, runTime } = await getRunTimeAndFilePath(_program) + try { const { result, httpHeaders } = - (await new ExecutionController().executeFile( - _program, - getPreProgramVariables(req), - query - )) as ExecuteReturnRaw + (await new ExecutionController().executeFile({ + programPath: codePath, + preProgramVariables: getPreProgramVariables(req), + vars: query, + runTime + })) as ExecuteReturnRaw // Should over-ride response header for debug // on GET request to see entire log rendering on browser. @@ -167,20 +169,23 @@ const executeReturnJson = async ( req: express.Request, _program: string ): Promise => { + const { codePath, runTime } = await getRunTimeAndFilePath(_program) + const filesNamesMap = req.files?.length ? makeFilesNamesMap(req.files as MulterFile[]) : null try { const { webout, log, httpHeaders } = - (await new ExecutionController().executeFile( - _program, - getPreProgramVariables(req), - { ...req.query, ...req.body }, - { filesNamesMap: filesNamesMap }, - true, - req.sasjsSession - )) as ExecuteReturnJson + (await new ExecutionController().executeFile({ + programPath: codePath, + preProgramVariables: getPreProgramVariables(req), + vars: { ...req.query, ...req.body }, + otherArgs: { filesNamesMap: filesNamesMap }, + returnJson: true, + session: req.sasjsSession, + runTime + })) as ExecuteReturnJson let weboutRes: string | IRecordOfAny = webout if (httpHeaders['content-type']?.toLowerCase() === 'application/json') { diff --git a/api/src/routes/api/stp.ts b/api/src/routes/api/stp.ts index 5ea37db..858feb5 100644 --- a/api/src/routes/api/stp.ts +++ b/api/src/routes/api/stp.ts @@ -35,16 +35,17 @@ stpRouter.post( fileUploadController.preUploadMiddleware, fileUploadController.getMulterUploadObject().any(), async (req, res: any) => { - const { error: errQ, value: query } = executeProgramRawValidation(req.query) - const { error: errB, value: body } = executeProgramRawValidation(req.body) + // below validations are moved to preUploadMiddleware + // const { error: errQ, value: query } = executeProgramRawValidation(req.query) + // const { error: errB, value: body } = executeProgramRawValidation(req.body) - if (errQ && errB) return res.status(400).send(errB.details[0].message) + // if (errQ && errB) return res.status(400).send(errB.details[0].message) try { const response = await controller.executeReturnJson( req, - body, - query?._program + req.body, + req.query?._program as string ) // TODO: investigate if this code is required diff --git a/api/src/types/system/process.d.ts b/api/src/types/system/process.d.ts index 2d345c1..e8e83b4 100644 --- a/api/src/types/system/process.d.ts +++ b/api/src/types/system/process.d.ts @@ -1,11 +1,11 @@ declare namespace NodeJS { export interface Process { - runTimes: string[] sasLoc: string driveLoc: string sasSessionController?: import('../../controllers/internal').SASSessionController jsSessionController?: import('../../controllers/internal').JSSessionController appStreamConfig: import('../').AppStreamConfig logger: import('@sasjs/utils/logger').Logger + runTimes: import('../../utils').RunTimeType[] } } diff --git a/api/src/utils/getRunTimeAndFilePath.ts b/api/src/utils/getRunTimeAndFilePath.ts new file mode 100644 index 0000000..f25edf6 --- /dev/null +++ b/api/src/utils/getRunTimeAndFilePath.ts @@ -0,0 +1,18 @@ +import path from 'path' +import { fileExists } from '@sasjs/utils' +import { getFilesFolder } from './file' + +export const getRunTimeAndFilePath = async (programPath: string) => { + for (const runTime of process.runTimes) { + const codePath = + path + .join(getFilesFolder(), programPath) + .replace(new RegExp('/', 'g'), path.sep) + + '.' + + runTime + + if (await fileExists(codePath)) return { codePath, runTime } + } + + throw `The Program at (${programPath}) does not exist.` +} diff --git a/api/src/utils/index.ts b/api/src/utils/index.ts index 09254ba..b520778 100644 --- a/api/src/utils/index.ts +++ b/api/src/utils/index.ts @@ -10,6 +10,7 @@ export * from './generateRefreshToken' export * from './getCertificates' export * from './getDesktopFields' export * from './getPreProgramVariables' +export * from './getRunTimeAndFilePath' export * from './getServerUrl' export * from './instantiateLogger' export * from './isDebugOn' diff --git a/api/src/utils/setProcessVariables.ts b/api/src/utils/setProcessVariables.ts index 22205b3..43725a3 100644 --- a/api/src/utils/setProcessVariables.ts +++ b/api/src/utils/setProcessVariables.ts @@ -1,7 +1,7 @@ import path from 'path' import { createFolder, getAbsolutePath, getRealPath } from '@sasjs/utils' -import { getDesktopFields, ModeType } from '.' +import { getDesktopFields, ModeType, RunTimeType } from '.' export const setProcessVariables = async () => { if (process.env.NODE_ENV === 'test') { @@ -19,18 +19,15 @@ export const setProcessVariables = async () => { process.sasLoc = sasLoc } - const { SASJS_RUNTIMES } = process.env - - const runTimes = SASJS_RUNTIMES - ? SASJS_RUNTIMES.split(',').map((runTime) => runTime.toLowerCase()) - : ['sas'] - process.runTimes = runTimes - const { SASJS_ROOT } = process.env const absPath = getAbsolutePath(SASJS_ROOT ?? 'sasjs_root', process.cwd()) await createFolder(absPath) process.driveLoc = getRealPath(absPath) + const { RUN_TIMES } = process.env + process.runTimes = (RUN_TIMES as string).split(',') as RunTimeType[] + console.log('sasLoc: ', process.sasLoc) console.log('sasDrive: ', process.driveLoc) + console.log('runTimes: ', process.runTimes) } diff --git a/api/src/utils/verifyEnvVariables.ts b/api/src/utils/verifyEnvVariables.ts index 469c90f..e596074 100644 --- a/api/src/utils/verifyEnvVariables.ts +++ b/api/src/utils/verifyEnvVariables.ts @@ -26,7 +26,7 @@ export enum LOG_FORMAT_MORGANType { tiny = 'tiny' } -export enum SASJSRunTimes { +export enum RunTimeType { SAS = 'sas', JS = 'js' } @@ -51,7 +51,7 @@ export const verifyEnvVariables = (): ReturnCode => { errors.push(...verifyLOG_FORMAT_MORGAN()) - errors.push(...verifySASJSRunTimes()) + errors.push(...verifyRUN_TIMES()) if (errors.length) { process.logger?.error( @@ -209,26 +209,24 @@ const verifyLOG_FORMAT_MORGAN = (): string[] => { return errors } -const verifySASJSRunTimes = (): string[] => { +const verifyRUN_TIMES = (): string[] => { const errors: string[] = [] - const { SASJS_RUNTIMES } = process.env + const { RUN_TIMES } = process.env - if (SASJS_RUNTIMES) { - const runTimes = SASJS_RUNTIMES.split(',').map((runTime) => - runTime.toLowerCase() - ) + if (RUN_TIMES) { + const runTimes = RUN_TIMES.split(',') - const possibleRunTimes = Object.values(SASJSRunTimes) + const runTimeTypes = Object.values(RunTimeType) runTimes.forEach((runTime) => { - if (!possibleRunTimes.includes(runTime as SASJSRunTimes)) { + if (!runTimeTypes.includes(runTime.toLowerCase() as RunTimeType)) { errors.push( - `- Invalid '${runTime}' runtime\n - valid options ${possibleRunTimes}` + `- Invalid '${runTime}' runtime\n - valid options ${runTimeTypes}` ) } }) } else { - process.env.SASJS_RUNTIMES = DEFAULTS.SASJS_RUNTIMES + process.env.RUN_TIMES = DEFAULTS.RUN_TIMES } return errors } @@ -239,5 +237,5 @@ const DEFAULTS = { PORT: '5000', HELMET_COEP: HelmetCoepType.TRUE, LOG_FORMAT_MORGAN: LOG_FORMAT_MORGANType.Common, - SASJS_RUNTIMES: SASJSRunTimes.SAS + RUN_TIMES: RunTimeType.SAS } From 8e7c9e671c4f0fcc383bf1434012647a4f0a9b49 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Tue, 14 Jun 2022 17:05:13 +0500 Subject: [PATCH 103/163] chore: quick fix --- api/src/utils/verifyEnvVariables.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/utils/verifyEnvVariables.ts b/api/src/utils/verifyEnvVariables.ts index e596074..c046061 100644 --- a/api/src/utils/verifyEnvVariables.ts +++ b/api/src/utils/verifyEnvVariables.ts @@ -219,7 +219,7 @@ const verifyRUN_TIMES = (): string[] => { const runTimeTypes = Object.values(RunTimeType) runTimes.forEach((runTime) => { - if (!runTimeTypes.includes(runTime.toLowerCase() as RunTimeType)) { + if (!runTimeTypes.includes(runTime as RunTimeType)) { errors.push( `- Invalid '${runTime}' runtime\n - valid options ${runTimeTypes}` ) From e359265c4be90d245b58ed8da5d31cd6b4bae1ae Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Tue, 14 Jun 2022 17:05:40 +0500 Subject: [PATCH 104/163] chore: quick fix --- README.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 615c95b..523bda9 100644 --- a/README.md +++ b/README.md @@ -65,14 +65,6 @@ SAS_PATH=/path/to/sas/executable.exe # This location is for SAS WORK, staged files, DRIVE, configuration etc SASJS_ROOT=./sasjs_root -# A comma separated string that defines the available runTimes. -# Priority is given to the runtime that comes first in string. -# Possible options at the moment are sas and js - -# options: [sas,js|js,sas|sas|js] default:sas -SASJS_RUNTIMES= - - # options: [http|https] default: http PROTOCOL= @@ -138,6 +130,13 @@ HELMET_CSP_CONFIG_PATH=./csp.config.json # Docs: https://www.npmjs.com/package/morgan#predefined-formats LOG_FORMAT_MORGAN= +# A comma separated string that defines the available runTimes. +# Priority is given to the runtime that comes first in string. +# Possible options at the moment are sas and js + +# options: [sas,js|js,sas|sas|js] default:sas +RUN_TIMES= + ``` ## Persisting the Session From aef411a0eac625c33274dfe3e88b6f75115c44d8 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Tue, 14 Jun 2022 22:08:56 +0500 Subject: [PATCH 105/163] feat: new APIs added for GET|PATCH|DELETE of user by username --- api/public/swagger.yaml | 88 +++++++ api/src/controllers/user.ts | 96 ++++++-- api/src/middlewares/verifyAdminIfNeeded.ts | 17 +- api/src/routes/api/spec/user.spec.ts | 259 +++++++++++++++++++++ api/src/routes/api/user.ts | 76 ++++++ api/src/utils/validation.ts | 5 + 6 files changed, 525 insertions(+), 16 deletions(-) diff --git a/api/public/swagger.yaml b/api/public/swagger.yaml index 1f76c10..7e761fe 100644 --- a/api/public/swagger.yaml +++ b/api/public/swagger.yaml @@ -985,6 +985,94 @@ paths: application/json: schema: $ref: '#/components/schemas/UserPayload' + '/SASjsApi/user/by/username/{username}': + get: + operationId: GetUserByUsername + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/UserDetailsResponse' + description: 'Only Admin or user itself will get user autoExec code.' + summary: 'Get user properties - such as group memberships, userName, displayName.' + tags: + - User + security: + - + bearerAuth: [] + parameters: + - + description: 'The User''s username' + in: path + name: username + required: true + schema: + type: string + example: johnSnow01 + patch: + operationId: UpdateUserByUsername + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/UserDetailsResponse' + examples: + 'Example 1': + value: {id: 1234, displayName: 'John Snow', username: johnSnow01, isAdmin: false, isActive: true} + summary: 'Update user properties - such as displayName. Can be performed either by admins, or the user in question.' + tags: + - User + security: + - + bearerAuth: [] + parameters: + - + description: 'The User''s username' + in: path + name: username + required: true + schema: + type: string + example: johnSnow01 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UserPayload' + delete: + operationId: DeleteUserByUsername + responses: + '204': + description: 'No content' + summary: 'Delete a user. Can be performed either by admins, or the user in question.' + tags: + - User + security: + - + bearerAuth: [] + parameters: + - + description: 'The User''s username' + in: path + name: username + required: true + schema: + type: string + example: johnSnow01 + requestBody: + required: true + content: + application/json: + schema: + properties: + password: + type: string + type: object '/SASjsApi/user/{userId}': get: operationId: GetUser diff --git a/api/src/controllers/user.ts b/api/src/controllers/user.ts index cfbce9d..99a8f9b 100644 --- a/api/src/controllers/user.ts +++ b/api/src/controllers/user.ts @@ -77,6 +77,26 @@ export class UserController { return createUser(body) } + /** + * Only Admin or user itself will get user autoExec code. + * @summary Get user properties - such as group memberships, userName, displayName. + * @param username The User's username + * @example username "johnSnow01" + */ + @Get('by/username/{username}') + public async getUserByUsername( + @Request() req: express.Request, + @Path() username: string + ): Promise { + const { MODE } = process.env + + if (MODE === ModeType.Desktop) return getDesktopAutoExec() + + const { user } = req + const getAutoExec = user!.isAdmin || user!.username == username + return getUser({ username }, getAutoExec) + } + /** * Only Admin or user itself will get user autoExec code. * @summary Get user properties - such as group memberships, userName, displayName. @@ -94,7 +114,32 @@ export class UserController { const { user } = req const getAutoExec = user!.isAdmin || user!.userId == userId - return getUser(userId, getAutoExec) + return getUser({ id: userId }, getAutoExec) + } + + /** + * @summary Update user properties - such as displayName. Can be performed either by admins, or the user in question. + * @param username The User's username + * @example username "johnSnow01" + */ + @Example({ + id: 1234, + displayName: 'John Snow', + username: 'johnSnow01', + isAdmin: false, + isActive: true + }) + @Patch('by/username/{username}') + public async updateUserByUsername( + @Path() username: string, + @Body() body: UserPayload + ): Promise { + const { MODE } = process.env + + if (MODE === ModeType.Desktop) + return updateDesktopAutoExec(body.autoExec ?? '') + + return updateUser({ username }, body) } /** @@ -119,7 +164,21 @@ export class UserController { if (MODE === ModeType.Desktop) return updateDesktopAutoExec(body.autoExec ?? '') - return updateUser(userId, body) + return updateUser({ id: userId }, body) + } + + /** + * @summary Delete a user. Can be performed either by admins, or the user in question. + * @param username The User's username + * @example username "johnSnow01" + */ + @Delete('by/username/{username}') + public async deleteUserByUsername( + @Path() username: string, + @Body() body: { password?: string }, + @Query() @Hidden() isAdmin: boolean = false + ) { + return deleteUser({ username }, isAdmin, body) } /** @@ -133,7 +192,7 @@ export class UserController { @Body() body: { password?: string }, @Query() @Hidden() isAdmin: boolean = false ) { - return deleteUser(userId, isAdmin, body) + return deleteUser({ id: userId }, isAdmin, body) } } @@ -174,11 +233,16 @@ const createUser = async (data: UserPayload): Promise => { } } +interface GetUserBy { + id?: number + username?: string +} + const getUser = async ( - id: number, + findBy: GetUserBy, getAutoExec: boolean ): Promise => { - const user = await User.findOne({ id }) + const user = await User.findOne(findBy) if (!user) throw new Error('User is not found.') @@ -201,7 +265,7 @@ const getDesktopAutoExec = async () => { } const updateUser = async ( - id: number, + findBy: GetUserBy, data: Partial ): Promise => { const { displayName, username, password, isAdmin, isActive, autoExec } = data @@ -211,8 +275,13 @@ const updateUser = async ( if (username) { // Checking if user is already in the database const usernameExist = await User.findOne({ username }) - if (usernameExist && usernameExist.id != id) - throw new Error('Username already exists.') + if (usernameExist) { + if ( + (findBy.id && usernameExist.id != findBy.id) || + (findBy.username && usernameExist.username != findBy.username) + ) + throw new Error('Username already exists.') + } params.username = username } @@ -221,9 +290,10 @@ const updateUser = async ( params.password = User.hashPassword(password) } - const updatedUser = await User.findOneAndUpdate({ id }, params, { new: true }) + const updatedUser = await User.findOneAndUpdate(findBy, params, { new: true }) - if (!updatedUser) throw new Error(`Unable to find user with id: ${id}`) + if (!updatedUser) + throw new Error(`Unable to find user with ${findBy.id || findBy.username}`) return { id: updatedUser.id, @@ -245,11 +315,11 @@ const updateDesktopAutoExec = async (autoExec: string) => { } const deleteUser = async ( - id: number, + findBy: GetUserBy, isAdmin: boolean, { password }: { password?: string } ) => { - const user = await User.findOne({ id }) + const user = await User.findOne(findBy) if (!user) throw new Error('User is not found.') if (!isAdmin) { @@ -257,5 +327,5 @@ const deleteUser = async ( if (!validPass) throw new Error('Invalid password.') } - await User.deleteOne({ id }) + await User.deleteOne(findBy) } diff --git a/api/src/middlewares/verifyAdminIfNeeded.ts b/api/src/middlewares/verifyAdminIfNeeded.ts index d126f3c..c9246f6 100644 --- a/api/src/middlewares/verifyAdminIfNeeded.ts +++ b/api/src/middlewares/verifyAdminIfNeeded.ts @@ -1,11 +1,22 @@ import { RequestHandler } from 'express' +// This middleware checks if a non-admin user trying to +// access information of other user export const verifyAdminIfNeeded: RequestHandler = (req, res, next) => { const { user } = req - const userId = parseInt(req.params.userId) - if (!user?.isAdmin && user?.userId !== userId) { - return res.status(401).send('Admin account required') + if (!user?.isAdmin) { + let adminAccountRequired: boolean = true + + if (req.params.userId) { + adminAccountRequired = user?.userId !== parseInt(req.params.userId) + } else if (req.params.username) { + adminAccountRequired = user?.username !== req.params.username + } + + if (adminAccountRequired) + return res.status(401).send('Admin account required') } + next() } diff --git a/api/src/routes/api/spec/user.spec.ts b/api/src/routes/api/spec/user.spec.ts index 4e5e2bf..36913a3 100644 --- a/api/src/routes/api/spec/user.spec.ts +++ b/api/src/routes/api/spec/user.spec.ts @@ -270,6 +270,102 @@ describe('user', () => { expect(res.text).toEqual('Error: Username already exists.') expect(res.body).toEqual({}) }) + + describe('by username', () => { + it('should respond with updated user when admin user requests', async () => { + const dbUser = await controller.createUser(user) + const newDisplayName = 'My new display Name' + + const res = await request(app) + .patch(`/SASjsApi/user/by/username/${user.username}`) + .auth(adminAccessToken, { type: 'bearer' }) + .send({ ...user, displayName: newDisplayName }) + .expect(200) + + expect(res.body.username).toEqual(user.username) + expect(res.body.displayName).toEqual(newDisplayName) + expect(res.body.isAdmin).toEqual(user.isAdmin) + expect(res.body.isActive).toEqual(user.isActive) + }) + + it('should respond with updated user when user himself requests', async () => { + const dbUser = await controller.createUser(user) + const accessToken = await generateAndSaveToken(dbUser.id) + const newDisplayName = 'My new display Name' + + const res = await request(app) + .patch(`/SASjsApi/user/by/username/${user.username}`) + .auth(accessToken, { type: 'bearer' }) + .send({ + displayName: newDisplayName, + username: user.username, + password: user.password + }) + .expect(200) + + expect(res.body.username).toEqual(user.username) + expect(res.body.displayName).toEqual(newDisplayName) + expect(res.body.isAdmin).toEqual(user.isAdmin) + expect(res.body.isActive).toEqual(user.isActive) + }) + + it('should respond with Bad Request, only admin can update isAdmin/isActive', async () => { + const dbUser = await controller.createUser(user) + const accessToken = await generateAndSaveToken(dbUser.id) + const newDisplayName = 'My new display Name' + + await request(app) + .patch(`/SASjsApi/user/by/username/${user.username}`) + .auth(accessToken, { type: 'bearer' }) + .send({ ...user, displayName: newDisplayName }) + .expect(400) + }) + + it('should respond with Unauthorized if access token is not present', async () => { + const res = await request(app) + .patch('/SASjsApi/user/by/username/1234') + .send(user) + .expect(401) + + expect(res.text).toEqual('Unauthorized') + expect(res.body).toEqual({}) + }) + + it('should respond with Unauthorized when access token is not of an admin account or himself', async () => { + const dbUser1 = await controller.createUser(user) + const dbUser2 = await controller.createUser({ + ...user, + username: 'randomUser' + }) + const accessToken = await generateAndSaveToken(dbUser2.id) + + const res = await request(app) + .patch(`/SASjsApi/user/${dbUser1.id}`) + .auth(accessToken, { type: 'bearer' }) + .send(user) + .expect(401) + + expect(res.text).toEqual('Admin account required') + expect(res.body).toEqual({}) + }) + + it('should respond with Forbidden if username is already present', async () => { + const dbUser1 = await controller.createUser(user) + const dbUser2 = await controller.createUser({ + ...user, + username: 'randomuser' + }) + + const res = await request(app) + .patch(`/SASjsApi/user/by/username/${dbUser1.username}`) + .auth(adminAccessToken, { type: 'bearer' }) + .send({ username: dbUser2.username }) + .expect(403) + + expect(res.text).toEqual('Error: Username already exists.') + expect(res.body).toEqual({}) + }) + }) }) describe('delete', () => { @@ -363,6 +459,89 @@ describe('user', () => { expect(res.text).toEqual('Error: Invalid password.') expect(res.body).toEqual({}) }) + + describe('by username', () => { + it('should respond with OK when admin user requests', async () => { + const dbUser = await controller.createUser(user) + + const res = await request(app) + .delete(`/SASjsApi/user/by/username/${dbUser.username}`) + .auth(adminAccessToken, { type: 'bearer' }) + .send() + .expect(200) + + expect(res.body).toEqual({}) + }) + + it('should respond with OK when user himself requests', async () => { + const dbUser = await controller.createUser(user) + const accessToken = await generateAndSaveToken(dbUser.id) + + const res = await request(app) + .delete(`/SASjsApi/user/by/username/${dbUser.username}`) + .auth(accessToken, { type: 'bearer' }) + .send({ password: user.password }) + .expect(200) + + expect(res.body).toEqual({}) + }) + + it('should respond with Bad Request when user himself requests and password is missing', async () => { + const dbUser = await controller.createUser(user) + const accessToken = await generateAndSaveToken(dbUser.id) + + const res = await request(app) + .delete(`/SASjsApi/user/by/username/${dbUser.username}`) + .auth(accessToken, { type: 'bearer' }) + .send() + .expect(400) + + expect(res.text).toEqual(`"password" is required`) + expect(res.body).toEqual({}) + }) + + it('should respond with Unauthorized when access token is not present', async () => { + const res = await request(app) + .delete('/SASjsApi/user/by/username/RandomUsername') + .send(user) + .expect(401) + + expect(res.text).toEqual('Unauthorized') + expect(res.body).toEqual({}) + }) + + it('should respond with Unauthorized when access token is not of an admin account or himself', async () => { + const dbUser1 = await controller.createUser(user) + const dbUser2 = await controller.createUser({ + ...user, + username: 'randomUser' + }) + const accessToken = await generateAndSaveToken(dbUser2.id) + + const res = await request(app) + .delete(`/SASjsApi/user/by/username/${dbUser1.username}`) + .auth(accessToken, { type: 'bearer' }) + .send(user) + .expect(401) + + expect(res.text).toEqual('Admin account required') + expect(res.body).toEqual({}) + }) + + it('should respond with Forbidden when user himself requests and password is incorrect', async () => { + const dbUser = await controller.createUser(user) + const accessToken = await generateAndSaveToken(dbUser.id) + + const res = await request(app) + .delete(`/SASjsApi/user/by/username/${dbUser.username}`) + .auth(accessToken, { type: 'bearer' }) + .send({ password: 'incorrectpassword' }) + .expect(403) + + expect(res.text).toEqual('Error: Invalid password.') + expect(res.body).toEqual({}) + }) + }) }) describe('get', () => { @@ -455,6 +634,86 @@ describe('user', () => { expect(res.text).toEqual('Error: User is not found.') expect(res.body).toEqual({}) }) + + describe('by username', () => { + it('should respond with user autoExec when same user requests', async () => { + const dbUser = await controller.createUser(user) + const userId = dbUser.id + const accessToken = await generateAndSaveToken(userId) + + const res = await request(app) + .get(`/SASjsApi/user/by/username/${dbUser.username}`) + .auth(accessToken, { type: 'bearer' }) + .send() + .expect(200) + + expect(res.body.username).toEqual(user.username) + expect(res.body.displayName).toEqual(user.displayName) + expect(res.body.isAdmin).toEqual(user.isAdmin) + expect(res.body.isActive).toEqual(user.isActive) + expect(res.body.autoExec).toEqual(user.autoExec) + }) + + it('should respond with user autoExec when admin user requests', async () => { + const dbUser = await controller.createUser(user) + + const res = await request(app) + .get(`/SASjsApi/user/by/username/${dbUser.username}`) + .auth(adminAccessToken, { type: 'bearer' }) + .send() + .expect(200) + + expect(res.body.username).toEqual(user.username) + expect(res.body.displayName).toEqual(user.displayName) + expect(res.body.isAdmin).toEqual(user.isAdmin) + expect(res.body.isActive).toEqual(user.isActive) + expect(res.body.autoExec).toEqual(user.autoExec) + }) + + it('should respond with user when access token is not of an admin account', async () => { + const accessToken = await generateSaveTokenAndCreateUser({ + ...user, + username: 'randomUser' + }) + + const dbUser = await controller.createUser(user) + + const res = await request(app) + .get(`/SASjsApi/user/by/username/${dbUser.username}`) + .auth(accessToken, { type: 'bearer' }) + .send() + .expect(200) + + expect(res.body.username).toEqual(user.username) + expect(res.body.displayName).toEqual(user.displayName) + expect(res.body.isAdmin).toEqual(user.isAdmin) + expect(res.body.isActive).toEqual(user.isActive) + expect(res.body.autoExec).toBeUndefined() + }) + + it('should respond with Unauthorized if access token is not present', async () => { + const res = await request(app) + .get('/SASjsApi/user/by/username/randomUsername') + .send() + .expect(401) + + expect(res.text).toEqual('Unauthorized') + expect(res.body).toEqual({}) + }) + + it('should respond with Forbidden if username is incorrect', async () => { + await controller.createUser(user) + + const res = await request(app) + .get('/SASjsApi/user/by/username/randomUsername') + .auth(adminAccessToken, { type: 'bearer' }) + .send() + .expect(403) + + expect(res.text).toEqual('Error: User is not found.') + expect(res.body).toEqual({}) + }) + }) }) describe('getAll', () => { diff --git a/api/src/routes/api/user.ts b/api/src/routes/api/user.ts index fcd6d5e..20ce88c 100644 --- a/api/src/routes/api/user.ts +++ b/api/src/routes/api/user.ts @@ -7,6 +7,7 @@ import { } from '../../middlewares' import { deleteUserValidation, + getUserValidation, registerUserValidation, updateUserValidation } from '../../utils' @@ -36,6 +37,25 @@ userRouter.get('/', authenticateAccessToken, async (req, res) => { } }) +userRouter.get( + '/by/username/:username', + authenticateAccessToken, + async (req, res) => { + const { error, value: params } = getUserValidation(req.params) + if (error) return res.status(400).send(error.details[0].message) + + const { username } = params + + const controller = new UserController() + try { + const response = await controller.getUserByUsername(req, username) + res.send(response) + } catch (err: any) { + res.status(403).send(err.toString()) + } + } +) + userRouter.get('/:userId', authenticateAccessToken, async (req, res) => { const { userId } = req.params @@ -48,6 +68,34 @@ userRouter.get('/:userId', authenticateAccessToken, async (req, res) => { } }) +userRouter.patch( + '/by/username/:username', + authenticateAccessToken, + verifyAdminIfNeeded, + async (req, res) => { + const { user } = req + const { error: errorUsername, value: params } = getUserValidation( + req.params + ) + if (errorUsername) + return res.status(400).send(errorUsername.details[0].message) + + const { username } = params + + // only an admin can update `isActive` and `isAdmin` fields + const { error, value: body } = updateUserValidation(req.body, user!.isAdmin) + if (error) return res.status(400).send(error.details[0].message) + + const controller = new UserController() + try { + const response = await controller.updateUserByUsername(username, body) + res.send(response) + } catch (err: any) { + res.status(403).send(err.toString()) + } + } +) + userRouter.patch( '/:userId', authenticateAccessToken, @@ -70,6 +118,34 @@ userRouter.patch( } ) +userRouter.delete( + '/by/username/:username', + authenticateAccessToken, + verifyAdminIfNeeded, + async (req, res) => { + const { user } = req + const { error: errorUsername, value: params } = getUserValidation( + req.params + ) + if (errorUsername) + return res.status(400).send(errorUsername.details[0].message) + + const { username } = params + + // only an admin can delete user without providing password + const { error, value: data } = deleteUserValidation(req.body, user!.isAdmin) + if (error) return res.status(400).send(error.details[0].message) + + const controller = new UserController() + try { + await controller.deleteUserByUsername(username, data, user!.isAdmin) + res.status(200).send('Account Deleted!') + } catch (err: any) { + res.status(403).send(err.toString()) + } + } +) + userRouter.delete( '/:userId', authenticateAccessToken, diff --git a/api/src/utils/validation.ts b/api/src/utils/validation.ts index 33a403f..3499715 100644 --- a/api/src/utils/validation.ts +++ b/api/src/utils/validation.ts @@ -5,6 +5,11 @@ const passwordSchema = Joi.string().min(6).max(1024) export const blockFileRegex = /\.(exe|sh|htaccess)$/i +export const getUserValidation = (data: any): Joi.ValidationResult => + Joi.object({ + username: usernameSchema.required() + }).validate(data) + export const loginWebValidation = (data: any): Joi.ValidationResult => Joi.object({ username: usernameSchema.required(), From eef3cb270dd7a3fbbc0a96d933cf9a297baf1fc4 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 14 Jun 2022 17:28:50 +0000 Subject: [PATCH 106/163] chore(release): 0.4.0 [skip ci] # [0.4.0](https://github.com/sasjs/server/compare/v0.3.10...v0.4.0) (2022-06-14) ### Features * new APIs added for GET|PATCH|DELETE of user by username ([aef411a](https://github.com/sasjs/server/commit/aef411a0eac625c33274dfe3e88b6f75115c44d8)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3ee046..19abfbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [0.4.0](https://github.com/sasjs/server/compare/v0.3.10...v0.4.0) (2022-06-14) + + +### Features + +* new APIs added for GET|PATCH|DELETE of user by username ([aef411a](https://github.com/sasjs/server/commit/aef411a0eac625c33274dfe3e88b6f75115c44d8)) + ## [0.3.10](https://github.com/sasjs/server/compare/v0.3.9...v0.3.10) (2022-06-14) From e08bbcc5435cbabaee40a41a7fb667d4a1f078e6 Mon Sep 17 00:00:00 2001 From: Sabir Hassan Date: Wed, 15 Jun 2022 15:18:42 +0500 Subject: [PATCH 107/163] fix: add/remove group to User when adding/removing user from group and return group membership on getting user --- api/public/swagger.yaml | 34 ++++++++++++++++------------ api/src/controllers/group.ts | 5 +++- api/src/controllers/user.ts | 13 +++++++++-- api/src/model/User.ts | 24 ++++++++++++++++++++ api/src/routes/api/spec/user.spec.ts | 32 +++++++++++++++++++++++++- 5 files changed, 89 insertions(+), 19 deletions(-) diff --git a/api/public/swagger.yaml b/api/public/swagger.yaml index 7e761fe..dd34dd8 100644 --- a/api/public/swagger.yaml +++ b/api/public/swagger.yaml @@ -310,6 +310,21 @@ components: - displayName type: object additionalProperties: false + GroupResponse: + properties: + groupId: + type: number + format: double + name: + type: string + description: + type: string + required: + - groupId + - name + - description + type: object + additionalProperties: false UserDetailsResponse: properties: id: @@ -325,6 +340,10 @@ components: type: boolean autoExec: type: string + groups: + items: + $ref: '#/components/schemas/GroupResponse' + type: array required: - id - displayName @@ -364,21 +383,6 @@ components: - password type: object additionalProperties: false - GroupResponse: - properties: - groupId: - type: number - format: double - name: - type: string - description: - type: string - required: - - groupId - - name - - description - type: object - additionalProperties: false GroupDetailsResponse: properties: groupId: diff --git a/api/src/controllers/group.ts b/api/src/controllers/group.ts index 44adef2..b3cbb0f 100644 --- a/api/src/controllers/group.ts +++ b/api/src/controllers/group.ts @@ -14,7 +14,7 @@ import Group, { GroupPayload } from '../model/Group' import User from '../model/User' import { UserResponse } from './user' -interface GroupResponse { +export interface GroupResponse { groupId: number name: string description: string @@ -210,6 +210,9 @@ const updateUsersListInGroup = async ( if (!updatedGroup) throw new Error('Unable to update group') + if (action === 'addUser') user.addGroup(group._id) + else user.removeGroup(group._id) + return { groupId: updatedGroup.groupId, name: updatedGroup.name, diff --git a/api/src/controllers/user.ts b/api/src/controllers/user.ts index 99a8f9b..cbb3d81 100644 --- a/api/src/controllers/user.ts +++ b/api/src/controllers/user.ts @@ -18,6 +18,7 @@ import { desktopUser } from '../middlewares' import User, { UserPayload } from '../model/User' import { getUserAutoExec, updateUserAutoExec, ModeType } from '../utils' +import { GroupResponse } from './group' export interface UserResponse { id: number @@ -32,6 +33,7 @@ interface UserDetailsResponse { isActive: boolean isAdmin: boolean autoExec?: string + groups?: GroupResponse[] } @Security('bearerAuth') @@ -242,7 +244,13 @@ const getUser = async ( findBy: GetUserBy, getAutoExec: boolean ): Promise => { - const user = await User.findOne(findBy) + const user = (await User.findOne( + findBy, + `id displayName username isActive isAdmin autoExec -_id` + ).populate( + 'groups', + 'groupId name description -_id' + )) as unknown as UserDetailsResponse if (!user) throw new Error('User is not found.') @@ -252,7 +260,8 @@ const getUser = async ( username: user.username, isActive: user.isActive, isAdmin: user.isAdmin, - autoExec: getAutoExec ? user.autoExec ?? '' : undefined + autoExec: getAutoExec ? user.autoExec ?? '' : undefined, + groups: user.groups } } diff --git a/api/src/model/User.ts b/api/src/model/User.ts index 04d550e..b70807d 100644 --- a/api/src/model/User.ts +++ b/api/src/model/User.ts @@ -45,6 +45,8 @@ interface IUserDocument extends UserPayload, Document { interface IUser extends IUserDocument { comparePassword(password: string): boolean + addGroup(groupObjectId: Schema.Types.ObjectId): Promise + removeGroup(groupObjectId: Schema.Types.ObjectId): Promise } interface IUserModel extends Model { hashPassword(password: string): string @@ -106,6 +108,28 @@ userSchema.method('comparePassword', function (password: string): boolean { if (bcrypt.compareSync(password, this.password)) return true return false }) +userSchema.method( + 'addGroup', + async function (groupObjectId: Schema.Types.ObjectId) { + const groupIdIndex = this.groups.indexOf(groupObjectId) + if (groupIdIndex === -1) { + this.groups.push(groupObjectId) + } + this.markModified('groups') + return this.save() + } +) +userSchema.method( + 'removeGroup', + async function (groupObjectId: Schema.Types.ObjectId) { + const groupIdIndex = this.groups.indexOf(groupObjectId) + if (groupIdIndex > -1) { + this.groups.splice(groupIdIndex, 1) + } + this.markModified('groups') + return this.save() + } +) export const User: IUserModel = model('User', userSchema) diff --git a/api/src/routes/api/spec/user.spec.ts b/api/src/routes/api/spec/user.spec.ts index 36913a3..1c4fd99 100644 --- a/api/src/routes/api/spec/user.spec.ts +++ b/api/src/routes/api/spec/user.spec.ts @@ -3,7 +3,7 @@ import mongoose, { Mongoose } from 'mongoose' import { MongoMemoryServer } from 'mongodb-memory-server' import request from 'supertest' import appPromise from '../../../app' -import { UserController } from '../../../controllers/' +import { UserController, GroupController } from '../../../controllers/' import { generateAccessToken, saveTokensInDB } from '../../../utils' const clientId = 'someclientID' @@ -571,6 +571,7 @@ describe('user', () => { expect(res.body.isAdmin).toEqual(user.isAdmin) expect(res.body.isActive).toEqual(user.isActive) expect(res.body.autoExec).toEqual(user.autoExec) + expect(res.body.groups).toEqual([]) }) it('should respond with user autoExec when admin user requests', async () => { @@ -588,6 +589,7 @@ describe('user', () => { expect(res.body.isAdmin).toEqual(user.isAdmin) expect(res.body.isActive).toEqual(user.isActive) expect(res.body.autoExec).toEqual(user.autoExec) + expect(res.body.groups).toEqual([]) }) it('should respond with user when access token is not of an admin account', async () => { @@ -610,6 +612,34 @@ describe('user', () => { expect(res.body.isAdmin).toEqual(user.isAdmin) expect(res.body.isActive).toEqual(user.isActive) expect(res.body.autoExec).toBeUndefined() + expect(res.body.groups).toEqual([]) + }) + + it('should respond with user along with associated groups', async () => { + const dbUser = await controller.createUser(user) + const userId = dbUser.id + const accessToken = await generateAndSaveToken(userId) + + const group = { + name: 'DCGroup1', + description: 'DC group for testing purposes.' + } + const groupController = new GroupController() + const dbGroup = await groupController.createGroup(group) + await groupController.addUserToGroup(dbGroup.groupId, dbUser.id) + + const res = await request(app) + .get(`/SASjsApi/user/${userId}`) + .auth(accessToken, { type: 'bearer' }) + .send() + .expect(200) + + expect(res.body.username).toEqual(user.username) + expect(res.body.displayName).toEqual(user.displayName) + expect(res.body.isAdmin).toEqual(user.isAdmin) + expect(res.body.isActive).toEqual(user.isActive) + expect(res.body.autoExec).toEqual(user.autoExec) + expect(res.body.groups.length).toBeGreaterThan(0) }) it('should respond with Unauthorized if access token is not present', async () => { From c08cfcbc38c2f873934bfea3c4fbd287ca6ca437 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 15 Jun 2022 10:38:22 +0000 Subject: [PATCH 108/163] chore(release): 0.4.1 [skip ci] ## [0.4.1](https://github.com/sasjs/server/compare/v0.4.0...v0.4.1) (2022-06-15) ### Bug Fixes * add/remove group to User when adding/removing user from group and return group membership on getting user ([e08bbcc](https://github.com/sasjs/server/commit/e08bbcc5435cbabaee40a41a7fb667d4a1f078e6)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19abfbb..f3a72b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [0.4.1](https://github.com/sasjs/server/compare/v0.4.0...v0.4.1) (2022-06-15) + + +### Bug Fixes + +* add/remove group to User when adding/removing user from group and return group membership on getting user ([e08bbcc](https://github.com/sasjs/server/commit/e08bbcc5435cbabaee40a41a7fb667d4a1f078e6)) + # [0.4.0](https://github.com/sasjs/server/compare/v0.3.10...v0.4.0) (2022-06-14) From 81501d17abd9adf3e6611635360ae7a0fdb05d61 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Wed, 15 Jun 2022 16:03:04 +0500 Subject: [PATCH 109/163] chore: code fixes --- api/src/controllers/code.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/api/src/controllers/code.ts b/api/src/controllers/code.ts index a4b1d74..11d8ca6 100644 --- a/api/src/controllers/code.ts +++ b/api/src/controllers/code.ts @@ -6,7 +6,8 @@ import { getPreProgramVariables, getUserAutoExec, ModeType, - parseLogToArray + parseLogToArray, + RunTimeType } from '../utils' interface ExecuteSASCodePayload { @@ -46,13 +47,14 @@ const executeSASCode = async ( try { const { webout, log, httpHeaders } = - (await new ExecutionController().executeProgram( - code, - getPreProgramVariables(req), - { ...req.query, _debug: 131 }, - { userAutoExec }, - true - )) as ExecuteReturnJson + (await new ExecutionController().executeProgram({ + program: code, + preProgramVariables: getPreProgramVariables(req), + vars: { ...req.query, _debug: 131 }, + otherArgs: { userAutoExec }, + returnJson: true, + runTime: RunTimeType.SAS + })) as ExecuteReturnJson return { status: 'success', From 53854d001279462104b24c0e59a8c94ab4938a94 Mon Sep 17 00:00:00 2001 From: Sabir Hassan Date: Wed, 15 Jun 2022 16:18:07 +0500 Subject: [PATCH 110/163] fix: code fixes for executing program from program path including file extension --- api/.env.example | 2 + .../internal/FileUploadController.ts | 12 +++++- api/src/controllers/stp.ts | 8 ++-- api/src/utils/getRunTimeAndFilePath.ts | 37 ++++++++++++++----- 4 files changed, 45 insertions(+), 14 deletions(-) diff --git a/api/.env.example b/api/.env.example index ab6de3e..a21c209 100644 --- a/api/.env.example +++ b/api/.env.example @@ -6,6 +6,8 @@ PROTOCOL=[http|https] default considered as http PRIVATE_KEY=privkey.pem FULL_CHAIN=fullchain.pem +RUN_TIMES=[sas|js|sas,js|js,sas] default considered as sas + PORT=[5000] default value is 5000 HELMET_CSP_CONFIG_PATH=./csp.config.json if omitted HELMET default will be used diff --git a/api/src/controllers/internal/FileUploadController.ts b/api/src/controllers/internal/FileUploadController.ts index d267d43..7c6737c 100644 --- a/api/src/controllers/internal/FileUploadController.ts +++ b/api/src/controllers/internal/FileUploadController.ts @@ -32,7 +32,17 @@ export class FileUploadController { const programPath = (query?._program ?? body?._program) as string - const { runTime } = await getRunTimeAndFilePath(programPath) + let runTime + + try { + ;({ runTime } = await getRunTimeAndFilePath(programPath)) + } catch (err: any) { + res.status(400).send({ + status: 'failure', + message: 'Job execution failed', + error: typeof err === 'object' ? err.toString() : err + }) + } const sessionController = runTime === RunTimeType.SAS diff --git a/api/src/controllers/stp.ts b/api/src/controllers/stp.ts index 624762c..37e3dad 100644 --- a/api/src/controllers/stp.ts +++ b/api/src/controllers/stp.ts @@ -131,9 +131,9 @@ const executeReturnRaw = async ( ): Promise => { const query = req.query as ExecutionVars - const { codePath, runTime } = await getRunTimeAndFilePath(_program) - try { + const { codePath, runTime } = await getRunTimeAndFilePath(_program) + const { result, httpHeaders } = (await new ExecutionController().executeFile({ programPath: codePath, @@ -169,13 +169,13 @@ const executeReturnJson = async ( req: express.Request, _program: string ): Promise => { - const { codePath, runTime } = await getRunTimeAndFilePath(_program) - const filesNamesMap = req.files?.length ? makeFilesNamesMap(req.files as MulterFile[]) : null try { + const { codePath, runTime } = await getRunTimeAndFilePath(_program) + const { webout, log, httpHeaders } = (await new ExecutionController().executeFile({ programPath: codePath, diff --git a/api/src/utils/getRunTimeAndFilePath.ts b/api/src/utils/getRunTimeAndFilePath.ts index f25edf6..e36c931 100644 --- a/api/src/utils/getRunTimeAndFilePath.ts +++ b/api/src/utils/getRunTimeAndFilePath.ts @@ -1,18 +1,37 @@ import path from 'path' import { fileExists } from '@sasjs/utils' import { getFilesFolder } from './file' +import { RunTimeType } from '.' export const getRunTimeAndFilePath = async (programPath: string) => { - for (const runTime of process.runTimes) { - const codePath = - path - .join(getFilesFolder(), programPath) - .replace(new RegExp('/', 'g'), path.sep) + - '.' + - runTime + const ext = path.extname(programPath) + // if program path is provided with extension we should split that into code path and ext as run time + if (ext) { + const runTime = ext.slice(1) + const runTimeTypes = Object.values(RunTimeType) - if (await fileExists(codePath)) return { codePath, runTime } + if (!runTimeTypes.includes(runTime as RunTimeType)) { + throw `The '${runTime}' runtime is not supported.` + } + + const codePath = path + .join(getFilesFolder(), programPath) + .replace(new RegExp('/', 'g'), path.sep) + + if (await fileExists(codePath)) { + return { codePath, runTime: runTime as RunTimeType } + } + } else { + for (const runTime of process.runTimes) { + const codePath = + path + .join(getFilesFolder(), programPath) + .replace(new RegExp('/', 'g'), path.sep) + + '.' + + runTime + + if (await fileExists(codePath)) return { codePath, runTime } + } } - throw `The Program at (${programPath}) does not exist.` } From 73792fb574c90bd280c4324e0b41c6fee7d572b6 Mon Sep 17 00:00:00 2001 From: Mihajlo Date: Wed, 15 Jun 2022 15:51:42 +0200 Subject: [PATCH 111/163] fix: appStream redesign --- api/src/routes/appStream/appStreamHtml.ts | 20 ++++++--- api/src/routes/appStream/style.ts | 55 ++++++++++++++++++++++- 2 files changed, 68 insertions(+), 7 deletions(-) diff --git a/api/src/routes/appStream/appStreamHtml.ts b/api/src/routes/appStream/appStreamHtml.ts index 2635729..b0512ca 100644 --- a/api/src/routes/appStream/appStreamHtml.ts +++ b/api/src/routes/appStream/appStreamHtml.ts @@ -23,13 +23,21 @@ export const appStreamHtml = (appStreamConfig: AppStreamConfig) => ` ${style} -

App Stream

+
+ +

App Stream

+
- ${Object.entries(appStreamConfig) - .map(([streamServiceName, entry]) => - singleAppStreamHtml(streamServiceName, entry.appLoc, entry.streamLogo) - ) - .join('')} + ${Object.entries(appStreamConfig) + .map(([streamServiceName, entry]) => + singleAppStreamHtml( + streamServiceName, + entry.appLoc, + entry.streamLogo + ) + ) + .join('')} + + + + + +
- {/* */} > | null mode: ModeType + runTimes: RunTimeType[] logout: (() => void) | null } @@ -39,6 +45,7 @@ export const AppContext = createContext({ displayName: '', setDisplayName: null, mode: ModeType.Server, + runTimes: [], logout: null }) @@ -50,6 +57,7 @@ const AppContextProvider = (props: { children: ReactNode }) => { const [username, setUsername] = useState('') const [displayName, setDisplayName] = useState('') const [mode, setMode] = useState(ModeType.Server) + const [runTimes, setRunTimes] = useState([]) useEffect(() => { setCheckingSession(true) @@ -74,6 +82,7 @@ const AppContextProvider = (props: { children: ReactNode }) => { .then((res) => res.data) .then((data: any) => { setMode(data.mode) + setRunTimes(data.runTimes) }) .catch(() => {}) }, []) @@ -99,6 +108,7 @@ const AppContextProvider = (props: { children: ReactNode }) => { displayName, setDisplayName, mode, + runTimes, logout }} > From 9023cf33b5fa4b13c2d5e9b80ae307df69c7fc02 Mon Sep 17 00:00:00 2001 From: Sabir Hassan Date: Fri, 17 Jun 2022 23:17:23 +0500 Subject: [PATCH 136/163] fix(Studio): style fix for runtime dropdown --- web/src/containers/Studio/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/containers/Studio/index.tsx b/web/src/containers/Studio/index.tsx index 85b8080..10bd25d 100644 --- a/web/src/containers/Studio/index.tsx +++ b/web/src/containers/Studio/index.tsx @@ -166,7 +166,7 @@ const Studio = () => { RUN
- + From eb397b15c2a499c3d152e5574723a98d858b22f8 Mon Sep 17 00:00:00 2001 From: sabhas Date: Mon, 20 Jun 2022 17:32:28 +0500 Subject: [PATCH 147/163] chore: lint fixes --- web/src/containers/Studio/index.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/web/src/containers/Studio/index.tsx b/web/src/containers/Studio/index.tsx index 4c44995..c05516a 100644 --- a/web/src/containers/Studio/index.tsx +++ b/web/src/containers/Studio/index.tsx @@ -56,7 +56,7 @@ const Studio = () => { }, [appContext.runTimes]) useEffect(() => { - if(runTimes.length) setSelectedRunTime(runTimes[0]) + if (runTimes.length) setSelectedRunTime(runTimes[0]) }, [runTimes]) const handleTabChange = (_e: any, newValue: string) => { @@ -184,7 +184,9 @@ const Studio = () => { onChange={handleChangeRunTime} > {runTimes.map((runTime) => ( - {runTime} + + {runTime} + ))} From 8c48d00d2131f2f8993f0cc2bc484b2033442271 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 20 Jun 2022 12:44:59 +0000 Subject: [PATCH 148/163] chore(release): 0.7.1 [skip ci] ## [0.7.1](https://github.com/sasjs/server/compare/v0.7.0...v0.7.1) (2022-06-20) ### Bug Fixes * default runtime should be sas ([91d29cb](https://github.com/sasjs/server/commit/91d29cb1272c28afbceaf39d1e0a87e17fbfdcd6)) * **Studio:** default selection of runtime fixed ([eb569c7](https://github.com/sasjs/server/commit/eb569c7b827c872ed2c4bc114559b97d87fd2aa0)) * webout path fixed in code.js when running on windows ([99a1107](https://github.com/sasjs/server/commit/99a110736448f66f99a512396b268fc31a3feef0)) --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e412018..a1ba02e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## [0.7.1](https://github.com/sasjs/server/compare/v0.7.0...v0.7.1) (2022-06-20) + + +### Bug Fixes + +* default runtime should be sas ([91d29cb](https://github.com/sasjs/server/commit/91d29cb1272c28afbceaf39d1e0a87e17fbfdcd6)) +* **Studio:** default selection of runtime fixed ([eb569c7](https://github.com/sasjs/server/commit/eb569c7b827c872ed2c4bc114559b97d87fd2aa0)) +* webout path fixed in code.js when running on windows ([99a1107](https://github.com/sasjs/server/commit/99a110736448f66f99a512396b268fc31a3feef0)) + # [0.7.0](https://github.com/sasjs/server/compare/v0.6.1...v0.7.0) (2022-06-19) From f6dc74f16bddafa1de9c83c2f27671a241abdad4 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Mon, 20 Jun 2022 14:38:19 +0000 Subject: [PATCH 149/163] fix: removing UTF-8 options from commandline. There appears to be no reliable way to enforce UTF-8 without additional modifications to the PATH variable to ensure a DBCS instance of SAS. --- api/src/controllers/internal/Session.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/api/src/controllers/internal/Session.ts b/api/src/controllers/internal/Session.ts index 623ec85..dc29866 100644 --- a/api/src/controllers/internal/Session.ts +++ b/api/src/controllers/internal/Session.ts @@ -99,8 +99,6 @@ ${autoExecContent}` session.path, '-AUTOEXEC', autoExecPath, - '-ENCODING', - 'UTF-8', process.platform === 'win32' ? '-nosplash' : '', process.platform === 'win32' ? '-icon' : '', process.platform === 'win32' ? '-nologo' : '' From 620eddb7137c24f88ad3f9c5435206861f144017 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 20 Jun 2022 15:03:27 +0000 Subject: [PATCH 150/163] chore(release): 0.7.2 [skip ci] ## [0.7.2](https://github.com/sasjs/server/compare/v0.7.1...v0.7.2) (2022-06-20) ### Bug Fixes * removing UTF-8 options from commandline. There appears to be no reliable way to enforce ([f6dc74f](https://github.com/sasjs/server/commit/f6dc74f16bddafa1de9c83c2f27671a241abdad4)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1ba02e..db69aaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [0.7.2](https://github.com/sasjs/server/compare/v0.7.1...v0.7.2) (2022-06-20) + + +### Bug Fixes + +* removing UTF-8 options from commandline. There appears to be no reliable way to enforce ([f6dc74f](https://github.com/sasjs/server/commit/f6dc74f16bddafa1de9c83c2f27671a241abdad4)) + ## [0.7.1](https://github.com/sasjs/server/compare/v0.7.0...v0.7.1) (2022-06-20) From 5d5d6ce3265a43af2e22bcd38cda54fafaf7b3ef Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Mon, 20 Jun 2022 15:07:17 +0000 Subject: [PATCH 151/163] fix: path descriptions and defaults --- api/src/utils/getDesktopFields.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/src/utils/getDesktopFields.ts b/api/src/utils/getDesktopFields.ts index 74999fa..972ef78 100644 --- a/api/src/utils/getDesktopFields.ts +++ b/api/src/utils/getDesktopFields.ts @@ -55,7 +55,7 @@ const getSASLocation = async (): Promise => { : '/opt/sas/sas9/SASHome/SASFoundation/9.4/sasexe/sas' const targetName = await getString( - 'Please enter path to SAS executable (absolute path): ', + 'Please enter full path to a SAS executable with UTF-8 encoding: ', validator, defaultLocation ) @@ -75,11 +75,11 @@ const getNodeLocation = async (): Promise => { } const defaultLocation = isWindows() - ? 'C:\\Program Files\\nodejs\\' - : '/usr/local/nodejs/bin' + ? 'C:\\Program Files\\nodejs\\node.exe' + : '/usr/local/nodejs/bin/node.sh' const targetName = await getString( - 'Please enter path to nodejs executable (absolute path): ', + 'Please enter full path to a NodeJS executable: ', validator, defaultLocation ) From 87dbab98f6a1d65736fec445d08a2f48b806484e Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 20 Jun 2022 15:31:20 +0000 Subject: [PATCH 152/163] chore(release): 0.7.3 [skip ci] ## [0.7.3](https://github.com/sasjs/server/compare/v0.7.2...v0.7.3) (2022-06-20) ### Bug Fixes * path descriptions and defaults ([5d5d6ce](https://github.com/sasjs/server/commit/5d5d6ce3265a43af2e22bcd38cda54fafaf7b3ef)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index db69aaa..fd9f17b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [0.7.3](https://github.com/sasjs/server/compare/v0.7.2...v0.7.3) (2022-06-20) + + +### Bug Fixes + +* path descriptions and defaults ([5d5d6ce](https://github.com/sasjs/server/commit/5d5d6ce3265a43af2e22bcd38cda54fafaf7b3ef)) + ## [0.7.2](https://github.com/sasjs/server/compare/v0.7.1...v0.7.2) (2022-06-20) From 2119e9de9ab1e5ce1222658f554ac74f4f35cf4d Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Tue, 21 Jun 2022 03:17:14 +0500 Subject: [PATCH 153/163] feat(certs): ENV variables updated and set CA Root for HTTPS server --- README.md | 3 ++- api/src/controllers/internal/Execution.ts | 19 +++++++++++---- .../internal/FileUploadController.ts | 24 +++++++++++++++---- api/src/controllers/internal/Session.ts | 11 +++++---- .../controllers/internal/createJSProgram.ts | 5 ++-- .../controllers/internal/processProgram.ts | 2 +- api/src/server.ts | 4 ++-- api/src/types/system/process.d.ts | 4 ++-- api/src/utils/getCertificates.ts | 16 +++++++++---- api/src/utils/getDesktopFields.ts | 17 ++++++++----- api/src/utils/setProcessVariables.ts | 11 ++++----- api/src/utils/verifyEnvVariables.ts | 8 +++---- 12 files changed, 81 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 2a4df5b..5d8ca20 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,8 @@ SASV9_OPTIONS= -NOXCMD # ENV variables required for PROTOCOL: `https` PRIVATE_KEY=privkey.pem -FULL_CHAIN=fullchain.pem +CERT_CHAIN=certificate.pem +CA_ROOT=fullchain.pem # ENV variables required for MODE: `server` ACCESS_TOKEN_SECRET= diff --git a/api/src/controllers/internal/Execution.ts b/api/src/controllers/internal/Execution.ts index b742402..60d3508 100644 --- a/api/src/controllers/internal/Execution.ts +++ b/api/src/controllers/internal/Execution.ts @@ -1,6 +1,8 @@ import path from 'path' import fs from 'fs' import { + SASSessionController, + JSSessionController, getSASSessionController, getJSSessionController, processProgram @@ -76,10 +78,19 @@ export class ExecutionController { session: sessionByFileUpload, runTime }: ExecuteProgramParams): Promise { - const sessionController = - runTime === RunTimeType.SAS - ? getSASSessionController() - : getJSSessionController() + let sessionController: SASSessionController | JSSessionController + + switch (runTime) { + case RunTimeType.SAS: + sessionController = getSASSessionController() + break + case RunTimeType.JS: + sessionController = getJSSessionController() + break + + default: + throw new Error('No Runtime is configured1') + } const session = sessionByFileUpload ?? (await sessionController.getSession()) diff --git a/api/src/controllers/internal/FileUploadController.ts b/api/src/controllers/internal/FileUploadController.ts index 7c6737c..92af4e9 100644 --- a/api/src/controllers/internal/FileUploadController.ts +++ b/api/src/controllers/internal/FileUploadController.ts @@ -1,7 +1,12 @@ import { Request, RequestHandler } from 'express' import multer from 'multer' import { uuidv4 } from '@sasjs/utils' -import { getSASSessionController, getJSSessionController } from '.' +import { + SASSessionController, + JSSessionController, + getSASSessionController, + getJSSessionController +} from '.' import { executeProgramRawValidation, getRunTimeAndFilePath, @@ -44,10 +49,19 @@ export class FileUploadController { }) } - const sessionController = - runTime === RunTimeType.SAS - ? getSASSessionController() - : getJSSessionController() + let sessionController: SASSessionController | JSSessionController + + switch (runTime) { + case RunTimeType.SAS: + sessionController = getSASSessionController() + break + case RunTimeType.JS: + sessionController = getJSSessionController() + break + + default: + return res.status(400).send('No Runtime is configured1') + } const session = await sessionController.getSession() // marking consumed true, so that it's not available diff --git a/api/src/controllers/internal/Session.ts b/api/src/controllers/internal/Session.ts index dc29866..4080f16 100644 --- a/api/src/controllers/internal/Session.ts +++ b/api/src/controllers/internal/Session.ts @@ -12,7 +12,8 @@ import { createFile, fileExists, generateTimestamp, - readFile + readFile, + isWindows } from '@sasjs/utils' const execFilePromise = promisify(execFile) @@ -88,7 +89,7 @@ ${autoExecContent}` // Additional windows specific options to avoid the desktop popups. - execFilePromise(process.sasLoc, [ + execFilePromise(process.sasLoc!, [ '-SYSIN', codePath, '-LOG', @@ -99,9 +100,9 @@ ${autoExecContent}` session.path, '-AUTOEXEC', autoExecPath, - process.platform === 'win32' ? '-nosplash' : '', - process.platform === 'win32' ? '-icon' : '', - process.platform === 'win32' ? '-nologo' : '' + isWindows() ? '-nosplash' : '', + isWindows() ? '-icon' : '', + isWindows() ? '-nologo' : '' ]) .then(() => { session.completed = true diff --git a/api/src/controllers/internal/createJSProgram.ts b/api/src/controllers/internal/createJSProgram.ts index 5176dae..b1e041c 100644 --- a/api/src/controllers/internal/createJSProgram.ts +++ b/api/src/controllers/internal/createJSProgram.ts @@ -1,3 +1,4 @@ +import { isWindows } from '@sasjs/utils' import { PreProgramVars, Session } from '../../types' import { generateFileUploadJSCode } from '../../utils' import { ExecutionVars } from './' @@ -20,9 +21,7 @@ export const createJSProgram = async ( const preProgramVarStatments = ` let _webout = ''; const weboutPath = '${ - process.platform === 'win32' - ? weboutPath.replace(/\\/g, '\\\\') - : weboutPath + isWindows() ? weboutPath.replace(/\\/g, '\\\\') : weboutPath }'; const _sasjs_tokenfile = '${tokenFile}'; const _sasjs_username = '${preProgramVariables?.username}'; diff --git a/api/src/controllers/internal/processProgram.ts b/api/src/controllers/internal/processProgram.ts index f9f3f6b..c059797 100644 --- a/api/src/controllers/internal/processProgram.ts +++ b/api/src/controllers/internal/processProgram.ts @@ -40,7 +40,7 @@ export const processProgram = async ( // waiting for the open event so that we can have underlying file descriptor await once(writeStream, 'open') - execFileSync(process.nodeLoc, [codePath], { + execFileSync(process.nodeLoc!, [codePath], { stdio: ['ignore', writeStream, writeStream] }) diff --git a/api/src/server.ts b/api/src/server.ts index 3745169..0743469 100644 --- a/api/src/server.ts +++ b/api/src/server.ts @@ -16,9 +16,9 @@ appPromise.then(async (app) => { ) }) } else { - const { key, cert } = await getCertificates() + const { key, cert, ca } = await getCertificates() - const httpsServer = createServer({ key, cert }, app) + const httpsServer = createServer({ key, cert, ca }, app) httpsServer.listen(sasJsPort, () => { console.log( `⚡️[server]: Server is running at https://localhost:${sasJsPort}` diff --git a/api/src/types/system/process.d.ts b/api/src/types/system/process.d.ts index 45f9572..1e56d75 100644 --- a/api/src/types/system/process.d.ts +++ b/api/src/types/system/process.d.ts @@ -1,7 +1,7 @@ declare namespace NodeJS { export interface Process { - sasLoc: string - nodeLoc: string + sasLoc?: string + nodeLoc?: string driveLoc: string sasSessionController?: import('../../controllers/internal').SASSessionController jsSessionController?: import('../../controllers/internal').JSSessionController diff --git a/api/src/utils/getCertificates.ts b/api/src/utils/getCertificates.ts index e1e96e7..8b59a26 100644 --- a/api/src/utils/getCertificates.ts +++ b/api/src/utils/getCertificates.ts @@ -2,22 +2,30 @@ import path from 'path' import { fileExists, getString, readFile } from '@sasjs/utils' export const getCertificates = async () => { - const { PRIVATE_KEY, FULL_CHAIN } = process.env + const { PRIVATE_KEY, CERT_CHAIN, CA_ROOT } = process.env const keyPath = PRIVATE_KEY ?? (await getFileInput('Private Key (PEM)')) - const certPath = FULL_CHAIN ?? (await getFileInput('Full Chain (PEM)')) + const certPath = CERT_CHAIN ?? (await getFileInput('Certificate Chain (PEM)')) + const caPath = CA_ROOT ?? (await getFileInput('CA ROOT (PEM)')) console.log('keyPath: ', keyPath) console.log('certPath: ', certPath) + console.log('caPath: ', caPath) const key = await readFile(keyPath) const cert = await readFile(certPath) + const ca = await readFile(caPath) - return { key, cert } + return { key, cert, ca } } -const getFileInput = async (filename: string): Promise => { +const getFileInput = async ( + filename: string, + required: boolean = true +): Promise => { const validator = async (filePath: string) => { + if (!required) return true + if (!filePath) return `Path to ${filename} is required.` if (!(await fileExists(path.join(process.cwd(), filePath)))) { diff --git a/api/src/utils/getDesktopFields.ts b/api/src/utils/getDesktopFields.ts index 972ef78..e1e0966 100644 --- a/api/src/utils/getDesktopFields.ts +++ b/api/src/utils/getDesktopFields.ts @@ -1,15 +1,20 @@ import path from 'path' import { getString } from '@sasjs/utils/input' -import { createFolder, fileExists, folderExists } from '@sasjs/utils' - -const isWindows = () => process.platform === 'win32' +import { createFolder, fileExists, folderExists, isWindows } from '@sasjs/utils' +import { RunTimeType } from './verifyEnvVariables' export const getDesktopFields = async () => { const { SAS_PATH, NODE_PATH } = process.env - const sasLoc = SAS_PATH ?? (await getSASLocation()) - const nodeLoc = NODE_PATH ?? (await getNodeLocation()) - // const driveLoc = DRIVE_PATH ?? (await getDriveLocation()) + let sasLoc, nodeLoc + + if (process.runTimes.includes(RunTimeType.SAS)) { + sasLoc = SAS_PATH ?? (await getSASLocation()) + } + + if (process.runTimes.includes(RunTimeType.JS)) { + nodeLoc = NODE_PATH ?? (await getNodeLocation()) + } return { sasLoc, nodeLoc } } diff --git a/api/src/utils/setProcessVariables.ts b/api/src/utils/setProcessVariables.ts index d365ec1..f6f21b0 100644 --- a/api/src/utils/setProcessVariables.ts +++ b/api/src/utils/setProcessVariables.ts @@ -9,11 +9,13 @@ export const setProcessVariables = async () => { return } - const { MODE } = process.env + const { MODE, RUN_TIMES } = process.env + + process.runTimes = (RUN_TIMES?.split(',') as RunTimeType[]) ?? [] if (MODE === ModeType.Server) { - process.sasLoc = process.env.SAS_PATH as string - process.nodeLoc = process.env.NODE_PATH as string + process.sasLoc = process.env.SAS_PATH + process.nodeLoc = process.env.NODE_PATH } else { const { sasLoc, nodeLoc } = await getDesktopFields() @@ -26,9 +28,6 @@ export const setProcessVariables = async () => { await createFolder(absPath) process.driveLoc = getRealPath(absPath) - const { RUN_TIMES } = process.env - process.runTimes = (RUN_TIMES as string).split(',') as RunTimeType[] - console.log('sasLoc: ', process.sasLoc) console.log('sasDrive: ', process.driveLoc) console.log('runTimes: ', process.runTimes) diff --git a/api/src/utils/verifyEnvVariables.ts b/api/src/utils/verifyEnvVariables.ts index d53338f..8ab857f 100644 --- a/api/src/utils/verifyEnvVariables.ts +++ b/api/src/utils/verifyEnvVariables.ts @@ -129,16 +129,16 @@ const verifyPROTOCOL = (): string[] => { } if (process.env.PROTOCOL === ProtocolType.HTTPS) { - const { PRIVATE_KEY, FULL_CHAIN } = process.env + const { PRIVATE_KEY, CERT_CHAIN } = process.env if (!PRIVATE_KEY) errors.push( `- PRIVATE_KEY is required for PROTOCOL '${ProtocolType.HTTPS}'` ) - if (!FULL_CHAIN) + if (!CERT_CHAIN) errors.push( - `- FULL_CHAIN is required for PROTOCOL '${ProtocolType.HTTPS}'` + `- CERT_CHAIN is required for PROTOCOL '${ProtocolType.HTTPS}'` ) } @@ -258,5 +258,5 @@ const DEFAULTS = { PORT: '5000', HELMET_COEP: HelmetCoepType.TRUE, LOG_FORMAT_MORGAN: LOG_FORMAT_MORGANType.Common, - RUN_TIMES: `${RunTimeType.SAS}` + RUN_TIMES: RunTimeType.SAS } From 01e9a1d9e95dc39032e0f048ea78528843a86711 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Tue, 21 Jun 2022 03:26:12 +0500 Subject: [PATCH 154/163] chore: README.md and .env.example updated --- README.md | 8 ++++---- api/.env.example | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 5d8ca20..7bd997a 100644 --- a/README.md +++ b/README.md @@ -99,10 +99,10 @@ SASV9_OPTIONS= -NOXCMD ## Additional Web Server Options # -# ENV variables required for PROTOCOL: `https` -PRIVATE_KEY=privkey.pem -CERT_CHAIN=certificate.pem -CA_ROOT=fullchain.pem +# ENV variables for PROTOCOL: `https` +PRIVATE_KEY=privkey.pem (required) +CERT_CHAIN=certificate.pem (required) +CA_ROOT=fullchain.pem (optional) # ENV variables required for MODE: `server` ACCESS_TOKEN_SECRET= diff --git a/api/.env.example b/api/.env.example index d11bd43..f38b982 100644 --- a/api/.env.example +++ b/api/.env.example @@ -4,7 +4,8 @@ WHITELIST= PROTOCOL=[http|https] default considered as http PRIVATE_KEY=privkey.pem -FULL_CHAIN=fullchain.pem +CERT_CHAIN=certificate.pem +CA_ROOT=fullchain.pem PORT=[5000] default value is 5000 From 3130fbeff0060700929832190e6a458c290722c5 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Tue, 21 Jun 2022 03:41:44 +0500 Subject: [PATCH 155/163] chore: code refactor for getting session controller --- api/src/controllers/internal/Execution.ts | 22 ++------------ .../internal/FileUploadController.ts | 30 +++++++------------ api/src/controllers/internal/Session.ts | 21 +++++++++++-- 3 files changed, 31 insertions(+), 42 deletions(-) diff --git a/api/src/controllers/internal/Execution.ts b/api/src/controllers/internal/Execution.ts index 60d3508..7f3541d 100644 --- a/api/src/controllers/internal/Execution.ts +++ b/api/src/controllers/internal/Execution.ts @@ -1,12 +1,6 @@ import path from 'path' import fs from 'fs' -import { - SASSessionController, - JSSessionController, - getSASSessionController, - getJSSessionController, - processProgram -} from './' +import { getSessionController, processProgram } from './' import { readFile, fileExists, createFile, readFileBinary } from '@sasjs/utils' import { PreProgramVars, Session, TreeNode } from '../../types' import { @@ -78,19 +72,7 @@ export class ExecutionController { session: sessionByFileUpload, runTime }: ExecuteProgramParams): Promise { - let sessionController: SASSessionController | JSSessionController - - switch (runTime) { - case RunTimeType.SAS: - sessionController = getSASSessionController() - break - case RunTimeType.JS: - sessionController = getJSSessionController() - break - - default: - throw new Error('No Runtime is configured1') - } + const sessionController = getSessionController(runTime) const session = sessionByFileUpload ?? (await sessionController.getSession()) diff --git a/api/src/controllers/internal/FileUploadController.ts b/api/src/controllers/internal/FileUploadController.ts index 92af4e9..a06f512 100644 --- a/api/src/controllers/internal/FileUploadController.ts +++ b/api/src/controllers/internal/FileUploadController.ts @@ -1,12 +1,7 @@ import { Request, RequestHandler } from 'express' import multer from 'multer' import { uuidv4 } from '@sasjs/utils' -import { - SASSessionController, - JSSessionController, - getSASSessionController, - getJSSessionController -} from '.' +import { getSessionController } from '.' import { executeProgramRawValidation, getRunTimeAndFilePath, @@ -42,25 +37,22 @@ export class FileUploadController { try { ;({ runTime } = await getRunTimeAndFilePath(programPath)) } catch (err: any) { - res.status(400).send({ + return res.status(400).send({ status: 'failure', message: 'Job execution failed', error: typeof err === 'object' ? err.toString() : err }) } - let sessionController: SASSessionController | JSSessionController - - switch (runTime) { - case RunTimeType.SAS: - sessionController = getSASSessionController() - break - case RunTimeType.JS: - sessionController = getJSSessionController() - break - - default: - return res.status(400).send('No Runtime is configured1') + let sessionController + try { + sessionController = getSessionController(runTime) + } catch (err: any) { + return res.status(400).send({ + status: 'failure', + message: err.message, + error: typeof err === 'object' ? err.toString() : err + }) } const session = await sessionController.getSession() diff --git a/api/src/controllers/internal/Session.ts b/api/src/controllers/internal/Session.ts index 4080f16..3b57f16 100644 --- a/api/src/controllers/internal/Session.ts +++ b/api/src/controllers/internal/Session.ts @@ -5,7 +5,8 @@ import { execFile } from 'child_process' import { getSessionsFolder, generateUniqueFileName, - sysInitCompiledPath + sysInitCompiledPath, + RunTimeType } from '../../utils' import { deleteFolder, @@ -193,7 +194,21 @@ export class JSSessionController extends SessionController { } } -export const getSASSessionController = (): SASSessionController => { +export const getSessionController = ( + runTime: RunTimeType +): SASSessionController | JSSessionController => { + if (runTime === RunTimeType.SAS) { + return getSASSessionController() + } + + if (runTime === RunTimeType.JS) { + return getJSSessionController() + } + + throw new Error('No Runtime is configured') +} + +const getSASSessionController = (): SASSessionController => { if (process.sasSessionController) return process.sasSessionController process.sasSessionController = new SASSessionController() @@ -201,7 +216,7 @@ export const getSASSessionController = (): SASSessionController => { return process.sasSessionController } -export const getJSSessionController = (): JSSessionController => { +const getJSSessionController = (): JSSessionController => { if (process.jsSessionController) return process.jsSessionController process.jsSessionController = new JSSessionController() From bfc5ac6a4ffbdea017ca20104a548c1ea3d2f2e3 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 21 Jun 2022 07:24:26 +0000 Subject: [PATCH 156/163] chore(release): 0.8.0 [skip ci] # [0.8.0](https://github.com/sasjs/server/compare/v0.7.3...v0.8.0) (2022-06-21) ### Features * **certs:** ENV variables updated and set CA Root for HTTPS server ([2119e9d](https://github.com/sasjs/server/commit/2119e9de9ab1e5ce1222658f554ac74f4f35cf4d)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd9f17b..464e4e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [0.8.0](https://github.com/sasjs/server/compare/v0.7.3...v0.8.0) (2022-06-21) + + +### Features + +* **certs:** ENV variables updated and set CA Root for HTTPS server ([2119e9d](https://github.com/sasjs/server/commit/2119e9de9ab1e5ce1222658f554ac74f4f35cf4d)) + ## [0.7.3](https://github.com/sasjs/server/compare/v0.7.2...v0.7.3) (2022-06-20) From 56b20beb8c77b223437c5dca4042ab86a1aa3771 Mon Sep 17 00:00:00 2001 From: Yury Shkoda Date: Tue, 21 Jun 2022 19:07:14 +0300 Subject: [PATCH 157/163] chore(template): added pull request template --- PULL_REQUEST_TEMPLATE.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 PULL_REQUEST_TEMPLATE.md diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..a86ebc1 --- /dev/null +++ b/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,19 @@ +## Issue + +Link any related issue(s) in this section. + +## Intent + +What this PR intends to achieve. + +## Implementation + +What code changes have been made to achieve the intent. + +## Checks + +- [ ] Code is formatted correctly (`npm run lint:fix`). +- [ ] Any new functionality has been unit tested. +- [ ] All unit tests are passing (`npm test`). +- [ ] All CI checks are green. +- [ ] Reviewer is assigned. From 65380be2f3945bae559f1749064845b514447a53 Mon Sep 17 00:00:00 2001 From: Sabir Hassan Date: Wed, 22 Jun 2022 00:24:41 +0500 Subject: [PATCH 158/163] fix: update /logout route to /SASLogon/logout --- api/public/swagger.yaml | 14 +++++++------- api/src/controllers/web.ts | 4 ++-- api/src/routes/web/web.ts | 2 +- web/src/context/appContext.tsx | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/api/public/swagger.yaml b/api/public/swagger.yaml index 6acdb14..d8d6f7d 100644 --- a/api/public/swagger.yaml +++ b/api/public/swagger.yaml @@ -624,7 +624,7 @@ paths: application/json: schema: $ref: '#/components/schemas/AuthorizePayload' - /logout: + /SASLogon/logout: get: operationId: Logout responses: @@ -633,7 +633,7 @@ paths: content: application/json: schema: {} - summary: 'Accept a valid username/password' + summary: 'Destroy the session stored in cookies' tags: - Web security: [] @@ -763,7 +763,7 @@ paths: examples: 'Example 1': value: {status: failure, message: 'Deployment failed!'} - description: "Accepts JSON file and zipped compressed JSON file as well.\r\nCompressed file should only contain one JSON file and should have same name\r\nas of compressed file e.g. deploy.JSON should be compressed to deploy.JSON.zip\r\nAny other file or JSON file in zipped will be ignored!" + description: "Accepts JSON file and zipped compressed JSON file as well.\nCompressed file should only contain one JSON file and should have same name\nas of compressed file e.g. deploy.JSON should be compressed to deploy.JSON.zip\nAny other file or JSON file in zipped will be ignored!" summary: 'Creates/updates files within SASjs Drive using uploaded JSON/compressed JSON file.' tags: - Drive @@ -851,7 +851,7 @@ paths: examples: 'Example 1': value: {status: failure, message: 'File request failed.'} - description: "It's optional to either provide `_filePath` in url as query parameter\r\nOr provide `filePath` in body as form field.\r\nBut it's required to provide else API will respond with Bad Request." + description: "It's optional to either provide `_filePath` in url as query parameter\nOr provide `filePath` in body as form field.\nBut it's required to provide else API will respond with Bad Request." summary: 'Create a file in SASjs Drive' tags: - Drive @@ -902,7 +902,7 @@ paths: examples: 'Example 1': value: {status: failure, message: 'File request failed.'} - description: "It's optional to either provide `_filePath` in url as query parameter\r\nOr provide `filePath` in body as form field.\r\nBut it's required to provide else API will respond with Bad Request." + description: "It's optional to either provide `_filePath` in url as query parameter\nOr provide `filePath` in body as form field.\nBut it's required to provide else API will respond with Bad Request." summary: 'Modify a file in SASjs Drive' tags: - Drive @@ -1454,7 +1454,7 @@ paths: anyOf: - {type: string} - {type: string, format: byte} - description: "Trigger a SAS or JS program using the _program URL parameter.\r\n\r\nAccepts URL parameters and file uploads. For more details, see docs:\r\n\r\nhttps://server.sasjs.io/storedprograms" + description: "Trigger a SAS or JS program using the _program URL parameter.\n\nAccepts URL parameters and file uploads. For more details, see docs:\n\nhttps://server.sasjs.io/storedprograms" summary: 'Execute a Stored Program, returns raw _webout content.' tags: - STP @@ -1482,7 +1482,7 @@ paths: examples: 'Example 1': value: {status: success, _webout: 'webout content', log: [], httpHeaders: {Content-type: application/zip, Cache-Control: 'public, max-age=1000'}} - description: "Trigger a SAS or JS program using the _program URL parameter.\r\n\r\nAccepts URL parameters and file uploads. For more details, see docs:\r\n\r\nhttps://server.sasjs.io/storedprograms\r\n\r\nThe response will be a JSON object with the following root attributes:\r\nlog, webout, headers.\r\n\r\nThe webout attribute will be nested JSON ONLY if the response-header\r\ncontains a content-type of application/json AND it is valid JSON.\r\nOtherwise it will be a stringified version of the webout content." + description: "Trigger a SAS or JS program using the _program URL parameter.\n\nAccepts URL parameters and file uploads. For more details, see docs:\n\nhttps://server.sasjs.io/storedprograms\n\nThe response will be a JSON object with the following root attributes:\nlog, webout, headers.\n\nThe webout attribute will be nested JSON ONLY if the response-header\ncontains a content-type of application/json AND it is valid JSON.\nOtherwise it will be a stringified version of the webout content." summary: 'Execute a Stored Program, return a JSON object' tags: - STP diff --git a/api/src/controllers/web.ts b/api/src/controllers/web.ts index e64b3a4..7cef99d 100644 --- a/api/src/controllers/web.ts +++ b/api/src/controllers/web.ts @@ -49,10 +49,10 @@ export class WebController { } /** - * @summary Accept a valid username/password + * @summary Destroy the session stored in cookies * */ - @Get('/logout') + @Get('/SASLogon/logout') public async logout(@Request() req: express.Request) { return new Promise((resolve) => { req.session.destroy(() => { diff --git a/api/src/routes/web/web.ts b/api/src/routes/web/web.ts index c4d817c..0cd9283 100644 --- a/api/src/routes/web/web.ts +++ b/api/src/routes/web/web.ts @@ -48,7 +48,7 @@ webRouter.post( } ) -webRouter.get('/logout', desktopRestrict, async (req, res) => { +webRouter.get('/SASLogon/logout', desktopRestrict, async (req, res) => { try { await controller.logout(req) res.status(200).send('OK!') diff --git a/web/src/context/appContext.tsx b/web/src/context/appContext.tsx index 52c6a63..a91d8e3 100644 --- a/web/src/context/appContext.tsx +++ b/web/src/context/appContext.tsx @@ -88,7 +88,7 @@ const AppContextProvider = (props: { children: ReactNode }) => { }, []) const logout = useCallback(() => { - axios.get('/logout').then(() => { + axios.get('/SASLogon/logout').then(() => { setLoggedIn(false) setUsername('') setDisplayName('') From 1b5859ee37ae73c419115b9debfd5141a79733de Mon Sep 17 00:00:00 2001 From: Sabir Hassan Date: Wed, 22 Jun 2022 00:25:41 +0500 Subject: [PATCH 159/163] fix: make CA_ROOT optional in getCertificates method --- api/src/utils/getCertificates.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/api/src/utils/getCertificates.ts b/api/src/utils/getCertificates.ts index 8b59a26..6c295e0 100644 --- a/api/src/utils/getCertificates.ts +++ b/api/src/utils/getCertificates.ts @@ -4,17 +4,19 @@ import { fileExists, getString, readFile } from '@sasjs/utils' export const getCertificates = async () => { const { PRIVATE_KEY, CERT_CHAIN, CA_ROOT } = process.env + let ca + const keyPath = PRIVATE_KEY ?? (await getFileInput('Private Key (PEM)')) const certPath = CERT_CHAIN ?? (await getFileInput('Certificate Chain (PEM)')) - const caPath = CA_ROOT ?? (await getFileInput('CA ROOT (PEM)')) + const caPath = CA_ROOT console.log('keyPath: ', keyPath) console.log('certPath: ', certPath) - console.log('caPath: ', caPath) + if (caPath) console.log('caPath: ', caPath) const key = await readFile(keyPath) const cert = await readFile(certPath) - const ca = await readFile(caPath) + if (caPath) ca = await readFile(caPath) return { key, cert, ca } } From ae0fc0c48c53d7a7ee5d94b245f24c7c69ecc5b9 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 21 Jun 2022 20:10:31 +0000 Subject: [PATCH 160/163] chore(release): 0.8.1 [skip ci] ## [0.8.1](https://github.com/sasjs/server/compare/v0.8.0...v0.8.1) (2022-06-21) ### Bug Fixes * make CA_ROOT optional in getCertificates method ([1b5859e](https://github.com/sasjs/server/commit/1b5859ee37ae73c419115b9debfd5141a79733de)) * update /logout route to /SASLogon/logout ([65380be](https://github.com/sasjs/server/commit/65380be2f3945bae559f1749064845b514447a53)) --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 464e4e8..8850b36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## [0.8.1](https://github.com/sasjs/server/compare/v0.8.0...v0.8.1) (2022-06-21) + + +### Bug Fixes + +* make CA_ROOT optional in getCertificates method ([1b5859e](https://github.com/sasjs/server/commit/1b5859ee37ae73c419115b9debfd5141a79733de)) +* update /logout route to /SASLogon/logout ([65380be](https://github.com/sasjs/server/commit/65380be2f3945bae559f1749064845b514447a53)) + # [0.8.0](https://github.com/sasjs/server/compare/v0.7.3...v0.8.0) (2022-06-21) From 5cc85b57f80b13296156811fe966d7b37d45f213 Mon Sep 17 00:00:00 2001 From: Sabir Hassan Date: Wed, 22 Jun 2022 14:24:06 +0500 Subject: [PATCH 161/163] fix: getRuntimeAndFilePath function to handle the scenarion when path is provided with an extension other than runtimes --- api/src/utils/getRunTimeAndFilePath.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/api/src/utils/getRunTimeAndFilePath.ts b/api/src/utils/getRunTimeAndFilePath.ts index e36c931..74ea7b2 100644 --- a/api/src/utils/getRunTimeAndFilePath.ts +++ b/api/src/utils/getRunTimeAndFilePath.ts @@ -6,13 +6,8 @@ import { RunTimeType } from '.' export const getRunTimeAndFilePath = async (programPath: string) => { const ext = path.extname(programPath) // if program path is provided with extension we should split that into code path and ext as run time - if (ext) { + if (ext && Object.values(RunTimeType).includes(ext.slice(1) as RunTimeType)) { const runTime = ext.slice(1) - const runTimeTypes = Object.values(RunTimeType) - - if (!runTimeTypes.includes(runTime as RunTimeType)) { - throw `The '${runTime}' runtime is not supported.` - } const codePath = path .join(getFilesFolder(), programPath) From d3d2ab9a36b1f68a90833f3f855e4e2c0baa1e94 Mon Sep 17 00:00:00 2001 From: Allan Bowe <4420615+allanbowe@users.noreply.github.com> Date: Wed, 22 Jun 2022 11:12:48 +0100 Subject: [PATCH 162/163] Update getRunTimeAndFilePath.ts --- api/src/utils/getRunTimeAndFilePath.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/src/utils/getRunTimeAndFilePath.ts b/api/src/utils/getRunTimeAndFilePath.ts index 74ea7b2..b83cdee 100644 --- a/api/src/utils/getRunTimeAndFilePath.ts +++ b/api/src/utils/getRunTimeAndFilePath.ts @@ -5,7 +5,8 @@ import { RunTimeType } from '.' export const getRunTimeAndFilePath = async (programPath: string) => { const ext = path.extname(programPath) - // if program path is provided with extension we should split that into code path and ext as run time + // If programPath (_program) is provided with a ".sas" or ".js" extension + // we should use that extension to determine the appropriate runTime if (ext && Object.values(RunTimeType).includes(ext.slice(1) as RunTimeType)) { const runTime = ext.slice(1) From 71bcbb913476594ba461595b68be4048c40e2a91 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 22 Jun 2022 10:18:59 +0000 Subject: [PATCH 163/163] chore(release): 0.8.2 [skip ci] ## [0.8.2](https://github.com/sasjs/server/compare/v0.8.1...v0.8.2) (2022-06-22) ### Bug Fixes * getRuntimeAndFilePath function to handle the scenarion when path is provided with an extension other than runtimes ([5cc85b5](https://github.com/sasjs/server/commit/5cc85b57f80b13296156811fe966d7b37d45f213)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8850b36..04caf3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [0.8.2](https://github.com/sasjs/server/compare/v0.8.1...v0.8.2) (2022-06-22) + + +### Bug Fixes + +* getRuntimeAndFilePath function to handle the scenarion when path is provided with an extension other than runtimes ([5cc85b5](https://github.com/sasjs/server/commit/5cc85b57f80b13296156811fe966d7b37d45f213)) + ## [0.8.1](https://github.com/sasjs/server/compare/v0.8.0...v0.8.1) (2022-06-21)