diff --git a/README.md b/README.md index 5049e24..c1aa053 100644 --- a/README.md +++ b/README.md @@ -48,15 +48,22 @@ When launching the app, it will make use of specific environment variables. Thes Example contents of a `.env` file: ``` -# options: [desktop|server] default: `desktop` +# +## Core Settings +# + + +# MODE options: [desktop|server] default: `desktop` +# Desktop mode is single user and designed for workstation use +# Server mode is multi-user and suitable for intranet / internet use MODE= -# options: [disable|enable] default: `disable` for `server` & `enable` for `desktop` -# If enabled, be sure to also configure the WHITELIST of third party servers. -CORS= +# Path to SAS executable (sas.exe / sas.sh) +SAS_PATH=/path/to/sas/executable.exe -# options: space separated urls -WHITELIST= +# Path to working directory +# This location is for SAS WORK, staged files, DRIVE, configuration etc +DRIVE_PATH=/tmp # options: [http|https] default: http PROTOCOL= @@ -65,16 +72,22 @@ PROTOCOL= PORT= -# optional -# for MODE: `desktop`, prompts user -# for MODE: `server` gets value from api/package.json `configuration.sasPath` -SAS_PATH=/path/to/sas/executable.exe +# +## Additional SAS Options +# -# optional -# for MODE: `desktop`, prompts user -# for MODE: `server` defaults to /tmp -DRIVE_PATH=/tmp +# On windows use SAS_OPTIONS and on unix use SASV9_OPTIONS +# Any options set here are automatically applied in the SAS session +# See: https://documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/hostunx/p0wrdmqp8k0oyyn1xbx3bp3qy2wl.htm +# And: https://documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/hostwin/p0drw76qo0gig2n1kcoliekh605k.htm#p09y7hx0grw1gin1giuvrjyx61m6 +SAS_OPTIONS= -NOXCMD +SASV9_OPTIONS= -NOXCMD + + +# +## Additional Web Server Options +# # ENV variables required for PROTOCOL: `https` PRIVATE_KEY=privkey.pem @@ -87,13 +100,30 @@ AUTH_CODE_SECRET= SESSION_SECRET= DB_CONNECT=mongodb+srv://:@/?retryWrites=true&w=majority -# SAS Options -# On windows use SAS_OPTIONS and on unix use SASV9_OPTIONS -# Any options set here are automatically applied in the SAS session -# See: https://documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/hostunx/p0wrdmqp8k0oyyn1xbx3bp3qy2wl.htm -# And: https://documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/hostwin/p0drw76qo0gig2n1kcoliekh605k.htm#p09y7hx0grw1gin1giuvrjyx61m6 -SAS_OPTIONS= -NOXCMD -SASV9_OPTIONS= -NOXCMD +# options: [disable|enable] default: `disable` for `server` & `enable` for `desktop` +# If enabled, be sure to also configure the WHITELIST of third party servers. +CORS= + +# options: space separated urls +WHITELIST= + +# HELMET Cross Origin Embedder Policy +# Sets the Cross-Origin-Embedder-Policy header to require-corp when `true` +# options: [true|false] default: true +# Docs: https://helmetjs.github.io/#reference (`crossOriginEmbedderPolicy`) +HELMET_COEP= + +# HELMET Content Security Policy +# Path to a json file containing HELMET `contentSecurityPolicy` directives +# Docs: https://helmetjs.github.io/#reference +# +# Example config: +# { +# "img-src": ["'self'", "domain.com"], +# "script-src": ["'self'", "'unsafe-inline'"], +# "script-src-attr": ["'self'", "'unsafe-inline'"] +# } +HELMET_CSP_CONFIG_PATH=./csp.config.json ``` diff --git a/api/.env.example b/api/.env.example index 0a4fecc..577112f 100644 --- a/api/.env.example +++ b/api/.env.example @@ -8,6 +8,9 @@ FULL_CHAIN=fullchain.pem PORT=[5000] default value is 5000 +HELMET_CSP_CONFIG_PATH=./csp.config.json if omitted HELMET default will be used +HELMET_COEP=[true|false] if omitted HELMET default will be used + ACCESS_TOKEN_SECRET= REFRESH_TOKEN_SECRET= AUTH_CODE_SECRET= diff --git a/api/csp.config.example.json b/api/csp.config.example.json new file mode 100644 index 0000000..8ca57a5 --- /dev/null +++ b/api/csp.config.example.json @@ -0,0 +1,5 @@ +{ + "img-src": ["'self'", "domen.com"], + "script-src": ["'self'", "'unsafe-inline'"], + "script-src-attr": ["'self'", "'unsafe-inline'"] +} \ No newline at end of file diff --git a/api/src/app.ts b/api/src/app.ts index 18c75ea..262b510 100644 --- a/api/src/app.ts +++ b/api/src/app.ts @@ -17,6 +17,7 @@ import { setProcessVariables, setupFolders } from './utils' +import { getEnvCSPDirectives } from './utils/parseHelmetConfig' dotenv.config() @@ -25,7 +26,8 @@ const app = express() app.use(cookieParser()) app.use(morgan('tiny')) -const { MODE, CORS, WHITELIST, PROTOCOL } = process.env +const { MODE, CORS, WHITELIST, PROTOCOL, HELMET_CSP_CONFIG_PATH, HELMET_COEP } = + process.env export const cookieOptions = { secure: PROTOCOL === 'https', @@ -33,6 +35,10 @@ export const cookieOptions = { maxAge: 24 * 60 * 60 * 1000 // 24 hours } +const cspConfigJson = getEnvCSPDirectives(HELMET_CSP_CONFIG_PATH) +const coepFlag = + HELMET_COEP === 'true' || HELMET_COEP === undefined ? true : false + /*********************************** * CSRF Protection * ***********************************/ @@ -46,9 +52,10 @@ app.use( contentSecurityPolicy: { directives: { ...helmet.contentSecurityPolicy.getDefaultDirectives(), - 'script-src': ["'self'", "'unsafe-inline'"] + ...cspConfigJson } - } + }, + crossOriginEmbedderPolicy: coepFlag }) ) diff --git a/api/src/utils/parseHelmetConfig.ts b/api/src/utils/parseHelmetConfig.ts new file mode 100644 index 0000000..7310431 --- /dev/null +++ b/api/src/utils/parseHelmetConfig.ts @@ -0,0 +1,33 @@ +import path from 'path' +import fs from 'fs' + +export const getEnvCSPDirectives = ( + HELMET_CSP_CONFIG_PATH: string | undefined +) => { + let cspConfigJson = { + 'script-src': ["'self'", "'unsafe-inline'"] + } + + if ( + typeof HELMET_CSP_CONFIG_PATH === 'string' && + HELMET_CSP_CONFIG_PATH.length > 0 + ) { + const cspConfigPath = path.join(process.cwd(), HELMET_CSP_CONFIG_PATH) + + try { + let file = fs.readFileSync(cspConfigPath).toString() + + try { + cspConfigJson = JSON.parse(file) + } catch (e) { + console.error( + 'Parsing Content Security Policy JSON config failed. Make sure it is valid json' + ) + } + } catch (e) { + console.error('Error reading HELMET CSP config file', e) + } + } + + return cspConfigJson +} diff --git a/web/package-lock.json b/web/package-lock.json index 06dc2f1..300af37 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -21,6 +21,7 @@ "@types/node": "^12.20.28", "@types/react": "^17.0.27", "axios": "^0.24.0", + "monaco-editor": "^0.33.0", "monaco-editor-webpack-plugin": "^7.0.1", "react": "^17.0.2", "react-dom": "^17.0.2", @@ -8422,8 +8423,7 @@ "node_modules/monaco-editor": { "version": "0.33.0", "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.33.0.tgz", - "integrity": "sha512-VcRWPSLIUEgQJQIE0pVT8FcGBIgFoxz7jtqctE+IiCxWugD0DwgyQBcZBhdSrdMC84eumoqMZsGl2GTreOzwqw==", - "peer": true + "integrity": "sha512-VcRWPSLIUEgQJQIE0pVT8FcGBIgFoxz7jtqctE+IiCxWugD0DwgyQBcZBhdSrdMC84eumoqMZsGl2GTreOzwqw==" }, "node_modules/monaco-editor-webpack-plugin": { "version": "7.0.1", @@ -17505,8 +17505,7 @@ "monaco-editor": { "version": "0.33.0", "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.33.0.tgz", - "integrity": "sha512-VcRWPSLIUEgQJQIE0pVT8FcGBIgFoxz7jtqctE+IiCxWugD0DwgyQBcZBhdSrdMC84eumoqMZsGl2GTreOzwqw==", - "peer": true + "integrity": "sha512-VcRWPSLIUEgQJQIE0pVT8FcGBIgFoxz7jtqctE+IiCxWugD0DwgyQBcZBhdSrdMC84eumoqMZsGl2GTreOzwqw==" }, "monaco-editor-webpack-plugin": { "version": "7.0.1", diff --git a/web/package.json b/web/package.json index 6fe05ff..6832f0b 100644 --- a/web/package.json +++ b/web/package.json @@ -20,6 +20,7 @@ "@types/node": "^12.20.28", "@types/react": "^17.0.27", "axios": "^0.24.0", + "monaco-editor": "^0.33.0", "monaco-editor-webpack-plugin": "^7.0.1", "react": "^17.0.2", "react-dom": "^17.0.2",