From 80b33c7a18c1b7727316ffeca71658346733e935 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Mon, 23 May 2022 19:24:56 +0500 Subject: [PATCH 1/3] fix: issue174 + issue175 + issue146 --- README.md | 4 +- api/.env.example | 2 +- api/package.json | 3 - api/src/app.ts | 32 ++-- api/src/controllers/drive.ts | 22 +-- api/src/controllers/internal/Execution.ts | 8 +- api/src/controllers/internal/Session.ts | 4 +- api/src/controllers/internal/deploy.ts | 4 +- api/src/controllers/stp.ts | 6 +- api/src/controllers/web.ts | 4 +- api/src/middlewares/multer.ts | 4 +- api/src/routes/api/spec/drive.spec.ts | 30 ++-- api/src/routes/appStream/index.ts | 4 +- api/src/types/system/process.d.ts | 1 + api/src/utils/appStreamConfig.ts | 6 +- api/src/utils/copySASjsCore.ts | 4 +- api/src/utils/file.ts | 26 ++-- api/src/utils/getDesktopFields.ts | 6 +- api/src/utils/index.ts | 2 + api/src/utils/instantiateLogger.ts | 7 + api/src/utils/setProcessVariables.ts | 23 ++- api/src/utils/setupFolders.ts | 4 +- api/src/utils/verifyEnvVariables.ts | 181 ++++++++++++++++++++++ 23 files changed, 289 insertions(+), 98 deletions(-) create mode 100644 api/src/utils/instantiateLogger.ts create mode 100644 api/src/utils/verifyEnvVariables.ts diff --git a/README.md b/README.md index 8caa85c..87aa620 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ SAS_PATH=/path/to/sas/executable.exe # Path to working directory # This location is for SAS WORK, staged files, DRIVE, configuration etc -DRIVE_PATH=/tmp +SASJS_ROOT=./sasjs_root # options: [http|https] default: http PROTOCOL= @@ -147,7 +147,7 @@ Install the npm package [pm2](https://www.npmjs.com/package/pm2) (`npm install p ```bash export SAS_PATH=/opt/sas9/SASHome/SASFoundation/9.4/sasexe/sas export PORT=5001 -export DRIVE_PATH=./tmp +export SASJS_ROOT=./sasjs_root pm2 start api-linux ``` diff --git a/api/.env.example b/api/.env.example index 577112f..163af41 100644 --- a/api/.env.example +++ b/api/.env.example @@ -18,4 +18,4 @@ SESSION_SECRET= DB_CONNECT=mongodb+srv://:@/?retryWrites=true&w=majority SAS_PATH=/opt/sas/sas9/SASHome/SASFoundation/9.4/sas -DRIVE_PATH=./tmp +SASJS_ROOT=./sasjs_root diff --git a/api/package.json b/api/package.json index 663da85..cf8b3a6 100644 --- a/api/package.json +++ b/api/package.json @@ -94,9 +94,6 @@ "tsoa": "3.14.1", "typescript": "^4.3.2" }, - "configuration": { - "sasPath": "/opt/sas/sas9/SASHome/SASFoundation/9.4/sas" - }, "nodemonConfig": { "ignore": [ "tmp/**/*" diff --git a/api/src/app.ts b/api/src/app.ts index ce828dd..a15b08b 100644 --- a/api/src/app.ts +++ b/api/src/app.ts @@ -12,15 +12,28 @@ import helmet from 'helmet' import { connectDB, copySASjsCore, - getWebBuildFolderPath, + CorsType, + getWebBuildFolder, + HelmetCoepType, + instantiateLogger, loadAppStreamConfig, + ModeType, + ProtocolType, + ReturnCode, setProcessVariables, - setupFolders + setupFolders, + verifyEnvVariables } from './utils' import { getEnvCSPDirectives } from './utils/parseHelmetConfig' dotenv.config() +instantiateLogger() + +if (verifyEnvVariables()) { + process.exit(ReturnCode.InvalidEnv) +} + const app = express() app.use(cookieParser()) @@ -30,7 +43,7 @@ const { MODE, CORS, WHITELIST, PROTOCOL, HELMET_CSP_CONFIG_PATH, HELMET_COEP } = process.env export const cookieOptions = { - secure: PROTOCOL === 'https', + secure: PROTOCOL === ProtocolType.HTTPS, httpOnly: true, maxAge: 24 * 60 * 60 * 1000 // 24 hours } @@ -38,9 +51,8 @@ export const cookieOptions = { const cspConfigJson: { [key: string]: string[] | null } = getEnvCSPDirectives( HELMET_CSP_CONFIG_PATH ) -const coepFlag = - HELMET_COEP === 'true' || HELMET_COEP === undefined ? true : false -if (PROTOCOL === 'http') cspConfigJson['upgrade-insecure-requests'] = null +if (PROTOCOL === ProtocolType.HTTP) + cspConfigJson['upgrade-insecure-requests'] = null /*********************************** * CSRF Protection * @@ -58,14 +70,14 @@ app.use( ...cspConfigJson } }, - crossOriginEmbedderPolicy: coepFlag + crossOriginEmbedderPolicy: HELMET_COEP === HelmetCoepType.TRUE }) ) /*********************************** * Enabling CORS * ***********************************/ -if (MODE?.trim() !== 'server' || CORS?.trim() === 'enable') { +if (MODE === ModeType.Server || CORS === CorsType.ENABLED) { const whiteList: string[] = [] WHITELIST?.split(' ') ?.filter((url) => !!url) @@ -84,7 +96,7 @@ if (MODE?.trim() !== 'server' || CORS?.trim() === 'enable') { * Express Sessions * * With Mongo Store * ***********************************/ -if (MODE?.trim() === 'server') { +if (MODE === ModeType.Server) { let store: MongoStore | undefined // NOTE: when exporting app.js as agent for supertest @@ -129,7 +141,7 @@ export default setProcessVariables().then(async () => { // should be served after setting up web route // index.html needs to be injected with some js script. - app.use(express.static(getWebBuildFolderPath())) + app.use(express.static(getWebBuildFolder())) app.use(onError) diff --git a/api/src/controllers/drive.ts b/api/src/controllers/drive.ts index 7f22522..d4eee88 100644 --- a/api/src/controllers/drive.ts +++ b/api/src/controllers/drive.ts @@ -32,7 +32,7 @@ import { import { createFileTree, ExecutionController, getTreeExample } from './internal' import { TreeNode } from '../types' -import { getTmpFilesFolderPath } from '../utils' +import { getFilesFolder } from '../utils' interface DeployPayload { appLoc: string @@ -214,12 +214,12 @@ const getFileTree = () => { } const deploy = async (data: DeployPayload) => { - const driveFilesPath = getTmpFilesFolderPath() + const driveFilesPath = getFilesFolder() const appLocParts = data.appLoc.replace(/^\//, '').split('/') const appLocPath = path - .join(getTmpFilesFolderPath(), ...appLocParts) + .join(getFilesFolder(), ...appLocParts) .replace(new RegExp('/', 'g'), path.sep) if (!appLocPath.includes(driveFilesPath)) { @@ -238,10 +238,10 @@ const deploy = async (data: DeployPayload) => { } const getFile = async (req: express.Request, filePath: string) => { - const driveFilesPath = getTmpFilesFolderPath() + const driveFilesPath = getFilesFolder() const filePathFull = path - .join(getTmpFilesFolderPath(), filePath) + .join(getFilesFolder(), filePath) .replace(new RegExp('/', 'g'), path.sep) if (!filePathFull.includes(driveFilesPath)) { @@ -261,11 +261,11 @@ const getFile = async (req: express.Request, filePath: string) => { } const getFolder = async (folderPath?: string) => { - const driveFilesPath = getTmpFilesFolderPath() + const driveFilesPath = getFilesFolder() if (folderPath) { const folderPathFull = path - .join(getTmpFilesFolderPath(), folderPath) + .join(getFilesFolder(), folderPath) .replace(new RegExp('/', 'g'), path.sep) if (!folderPathFull.includes(driveFilesPath)) { @@ -291,10 +291,10 @@ const getFolder = async (folderPath?: string) => { } const deleteFile = async (filePath: string) => { - const driveFilesPath = getTmpFilesFolderPath() + const driveFilesPath = getFilesFolder() const filePathFull = path - .join(getTmpFilesFolderPath(), filePath) + .join(getFilesFolder(), filePath) .replace(new RegExp('/', 'g'), path.sep) if (!filePathFull.includes(driveFilesPath)) { @@ -314,7 +314,7 @@ const saveFile = async ( filePath: string, multerFile: Express.Multer.File ): Promise => { - const driveFilesPath = getTmpFilesFolderPath() + const driveFilesPath = getFilesFolder() const filePathFull = path .join(driveFilesPath, filePath) @@ -339,7 +339,7 @@ const updateFile = async ( filePath: string, multerFile: Express.Multer.File ): Promise => { - const driveFilesPath = getTmpFilesFolderPath() + const driveFilesPath = getFilesFolder() const filePathFull = path .join(driveFilesPath, filePath) diff --git a/api/src/controllers/internal/Execution.ts b/api/src/controllers/internal/Execution.ts index c7fde8e..4d4df15 100644 --- a/api/src/controllers/internal/Execution.ts +++ b/api/src/controllers/internal/Execution.ts @@ -12,8 +12,8 @@ import { PreProgramVars, Session, TreeNode } from '../../types' import { extractHeaders, generateFileUploadSasCode, - getTmpFilesFolderPath, - getTmpMacrosPath, + getFilesFolder, + getMacrosFolder, HTTPHeaders, isDebugOn } from '../../utils' @@ -110,7 +110,7 @@ export class ExecutionController { ` program = ` -options insert=(SASAUTOS="${getTmpMacrosPath()}"); +options insert=(SASAUTOS="${getMacrosFolder()}"); /* runtime vars */ ${varStatments} @@ -191,7 +191,7 @@ ${program}` const root: TreeNode = { name: 'files', relativePath: '', - absolutePath: getTmpFilesFolderPath(), + absolutePath: getFilesFolder(), children: [] } diff --git a/api/src/controllers/internal/Session.ts b/api/src/controllers/internal/Session.ts index e26d0c7..b83d70e 100644 --- a/api/src/controllers/internal/Session.ts +++ b/api/src/controllers/internal/Session.ts @@ -3,7 +3,7 @@ import { Session } from '../../types' import { promisify } from 'util' import { execFile } from 'child_process' import { - getTmpSessionsFolderPath, + getSessionsFolder, generateUniqueFileName, sysInitCompiledPath } from '../../utils' @@ -37,7 +37,7 @@ export class SessionController { private async createSession(): Promise { const sessionId = generateUniqueFileName(generateTimestamp()) - const sessionFolder = path.join(getTmpSessionsFolderPath(), sessionId) + const sessionFolder = path.join(getSessionsFolder(), sessionId) const creationTimeStamp = sessionId.split('-').pop() as string // death time of session is 15 mins from creation diff --git a/api/src/controllers/internal/deploy.ts b/api/src/controllers/internal/deploy.ts index 05a1996..d061018 100644 --- a/api/src/controllers/internal/deploy.ts +++ b/api/src/controllers/internal/deploy.ts @@ -1,5 +1,5 @@ import path from 'path' -import { getTmpFilesFolderPath } from '../../utils/file' +import { getFilesFolder } from '../../utils/file' import { createFolder, createFile, @@ -17,7 +17,7 @@ export const createFileTree = async ( parentFolders: string[] = [] ) => { const destinationPath = path.join( - getTmpFilesFolderPath(), + getFilesFolder(), path.join(...parentFolders) ) diff --git a/api/src/controllers/stp.ts b/api/src/controllers/stp.ts index 0d25ff0..b938629 100644 --- a/api/src/controllers/stp.ts +++ b/api/src/controllers/stp.ts @@ -19,7 +19,7 @@ import { } from './internal' import { getPreProgramVariables, - getTmpFilesFolderPath, + getFilesFolder, HTTPHeaders, isDebugOn, LogLine, @@ -132,7 +132,7 @@ const executeReturnRaw = async ( const query = req.query as ExecutionVars const sasCodePath = path - .join(getTmpFilesFolderPath(), _program) + .join(getFilesFolder(), _program) .replace(new RegExp('/', 'g'), path.sep) + '.sas' try { @@ -172,7 +172,7 @@ const executeReturnJson = async ( ): Promise => { const sasCodePath = path - .join(getTmpFilesFolderPath(), _program) + .join(getFilesFolder(), _program) .replace(new RegExp('/', 'g'), path.sep) + '.sas' const filesNamesMap = req.files?.length ? makeFilesNamesMap(req.files) : null diff --git a/api/src/controllers/web.ts b/api/src/controllers/web.ts index 70ccf6a..350eef2 100644 --- a/api/src/controllers/web.ts +++ b/api/src/controllers/web.ts @@ -5,7 +5,7 @@ import { readFile } from '@sasjs/utils' import User from '../model/User' import Client from '../model/Client' -import { getWebBuildFolderPath, generateAuthCode } from '../utils' +import { getWebBuildFolder, generateAuthCode } from '../utils' import { InfoJWT } from '../types' import { AuthController } from './auth' @@ -63,7 +63,7 @@ export class WebController { } const home = async () => { - const indexHtmlPath = path.join(getWebBuildFolderPath(), 'index.html') + const indexHtmlPath = path.join(getWebBuildFolder(), 'index.html') // Attention! Cannot use fileExists here, // due to limitation after building executable diff --git a/api/src/middlewares/multer.ts b/api/src/middlewares/multer.ts index 21ab913..7daf339 100644 --- a/api/src/middlewares/multer.ts +++ b/api/src/middlewares/multer.ts @@ -1,13 +1,13 @@ import path from 'path' import { Request } from 'express' import multer, { FileFilterCallback, Options } from 'multer' -import { blockFileRegex, getTmpUploadsPath } from '../utils' +import { blockFileRegex, getUploadsFolder } from '../utils' const fieldNameSize = 300 const fileSize = 104857600 // 100 MB const storage = multer.diskStorage({ - destination: getTmpUploadsPath(), + destination: getUploadsFolder(), filename: function ( _req: Request, file: Express.Multer.File, diff --git a/api/src/routes/api/spec/drive.spec.ts b/api/src/routes/api/spec/drive.spec.ts index dba7098..341bd02 100644 --- a/api/src/routes/api/spec/drive.spec.ts +++ b/api/src/routes/api/spec/drive.spec.ts @@ -21,17 +21,17 @@ import * as fileUtilModules from '../../../utils/file' const timestamp = generateTimestamp() const tmpFolder = path.join(process.cwd(), `tmp-${timestamp}`) jest - .spyOn(fileUtilModules, 'getTmpFolderPath') + .spyOn(fileUtilModules, 'getSasjsRootFolder') .mockImplementation(() => tmpFolder) jest - .spyOn(fileUtilModules, 'getTmpUploadsPath') + .spyOn(fileUtilModules, 'getUploadsFolder') .mockImplementation(() => path.join(tmpFolder, 'uploads')) import appPromise from '../../../app' import { UserController } from '../../../controllers/' import { getTreeExample } from '../../../controllers/internal' import { generateAccessToken, saveTokensInDB } from '../../../utils/' -const { getTmpFilesFolderPath } = fileUtilModules +const { getFilesFolder } = fileUtilModules const clientId = 'someclientID' const user = { @@ -157,10 +157,10 @@ describe('drive', () => { expect(res.text).toEqual( '{"status":"success","message":"Files deployed successfully to @sasjs/server."}' ) - await expect(folderExists(getTmpFilesFolderPath())).resolves.toEqual(true) + await expect(folderExists(getFilesFolder())).resolves.toEqual(true) const testJobFolder = path.join( - getTmpFilesFolderPath(), + getFilesFolder(), 'public', 'jobs', 'extract' @@ -174,7 +174,7 @@ describe('drive', () => { await expect(readFile(testJobFile)).resolves.toEqual(exampleService.code) - await deleteFolder(path.join(getTmpFilesFolderPath(), 'public')) + await deleteFolder(path.join(getFilesFolder(), 'public')) }) }) @@ -192,7 +192,7 @@ describe('drive', () => { }) it('should get a SAS folder on drive having _folderPath as query param', async () => { - const pathToDrive = fileUtilModules.getTmpFilesFolderPath() + const pathToDrive = fileUtilModules.getFilesFolder() const dirLevel1 = 'level1' const dirLevel2 = 'level2' @@ -267,10 +267,7 @@ describe('drive', () => { const fileToCopyPath = path.join(__dirname, 'files', 'sample.sas') const filePath = '/my/path/code.sas' - const pathToCopy = path.join( - fileUtilModules.getTmpFilesFolderPath(), - filePath - ) + const pathToCopy = path.join(fileUtilModules.getFilesFolder(), filePath) await copy(fileToCopyPath, pathToCopy) const res = await request(app) @@ -333,7 +330,7 @@ describe('drive', () => { const pathToUpload = `/my/path/code-${generateTimestamp()}.sas` const pathToCopy = path.join( - fileUtilModules.getTmpFilesFolderPath(), + fileUtilModules.getFilesFolder(), pathToUpload ) await copy(fileToAttachPath, pathToCopy) @@ -445,7 +442,7 @@ describe('drive', () => { const pathToUpload = '/my/path/code.sas' const pathToCopy = path.join( - fileUtilModules.getTmpFilesFolderPath(), + fileUtilModules.getFilesFolder(), pathToUpload ) await copy(fileToAttachPath, pathToCopy) @@ -467,7 +464,7 @@ describe('drive', () => { const pathToUpload = '/my/path/code.sas' const pathToCopy = path.join( - fileUtilModules.getTmpFilesFolderPath(), + fileUtilModules.getFilesFolder(), pathToUpload ) await copy(fileToAttachPath, pathToCopy) @@ -603,10 +600,7 @@ describe('drive', () => { const fileToCopyContent = await readFile(fileToCopyPath) const filePath = '/my/path/code.sas' - const pathToCopy = path.join( - fileUtilModules.getTmpFilesFolderPath(), - filePath - ) + const pathToCopy = path.join(fileUtilModules.getFilesFolder(), filePath) await copy(fileToCopyPath, pathToCopy) const res = await request(app) diff --git a/api/src/routes/appStream/index.ts b/api/src/routes/appStream/index.ts index d0f4249..cf52017 100644 --- a/api/src/routes/appStream/index.ts +++ b/api/src/routes/appStream/index.ts @@ -2,7 +2,7 @@ import path from 'path' import express from 'express' import { folderExists } from '@sasjs/utils' -import { addEntryToAppStreamConfig, getTmpFilesFolderPath } from '../../utils' +import { addEntryToAppStreamConfig, getFilesFolder } from '../../utils' import { appStreamHtml } from './appStreamHtml' const router = express.Router() @@ -22,7 +22,7 @@ export const publishAppStream = async ( streamLogo?: string, addEntryToFile: boolean = true ) => { - const driveFilesPath = getTmpFilesFolderPath() + const driveFilesPath = getFilesFolder() const appLocParts = appLoc.replace(/^\//, '')?.split('/') const appLocPath = path.join(driveFilesPath, ...appLocParts) diff --git a/api/src/types/system/process.d.ts b/api/src/types/system/process.d.ts index 9b03ec6..f7845bc 100644 --- a/api/src/types/system/process.d.ts +++ b/api/src/types/system/process.d.ts @@ -4,5 +4,6 @@ declare namespace NodeJS { driveLoc: string sessionController?: import('../../controllers/internal').SessionController appStreamConfig: import('../').AppStreamConfig + logger: import('@sasjs/utils/logger').Logger } } diff --git a/api/src/utils/appStreamConfig.ts b/api/src/utils/appStreamConfig.ts index 867964d..c293b35 100644 --- a/api/src/utils/appStreamConfig.ts +++ b/api/src/utils/appStreamConfig.ts @@ -2,12 +2,12 @@ import { createFile, fileExists, readFile } from '@sasjs/utils' import { publishAppStream } from '../routes/appStream' import { AppStreamConfig } from '../types' -import { getTmpAppStreamConfigPath } from './file' +import { getAppStreamConfigPath } from './file' export const loadAppStreamConfig = async () => { if (process.env.NODE_ENV === 'test') return - const appStreamConfigPath = getTmpAppStreamConfigPath() + const appStreamConfigPath = getAppStreamConfigPath() const content = (await fileExists(appStreamConfigPath)) ? await readFile(appStreamConfigPath) @@ -63,7 +63,7 @@ export const removeEntryFromAppStreamConfig = (streamServiceName: string) => { } const saveAppStreamConfig = async () => { - const appStreamConfigPath = getTmpAppStreamConfigPath() + const appStreamConfigPath = getAppStreamConfigPath() try { await createFile( diff --git a/api/src/utils/copySASjsCore.ts b/api/src/utils/copySASjsCore.ts index 0352dd0..42e040e 100644 --- a/api/src/utils/copySASjsCore.ts +++ b/api/src/utils/copySASjsCore.ts @@ -7,14 +7,14 @@ import { readFile } from '@sasjs/utils' -import { getTmpMacrosPath, sasJSCoreMacros, sasJSCoreMacrosInfo } from '.' +import { getMacrosFolder, sasJSCoreMacros, sasJSCoreMacrosInfo } from '.' export const copySASjsCore = async () => { if (process.env.NODE_ENV === 'test') return console.log('Copying Macros from container to drive(tmp).') - const macrosDrivePath = getTmpMacrosPath() + const macrosDrivePath = getMacrosFolder() await deleteFolder(macrosDrivePath) await createFolder(macrosDrivePath) diff --git a/api/src/utils/file.ts b/api/src/utils/file.ts index 764be4a..007ad6f 100644 --- a/api/src/utils/file.ts +++ b/api/src/utils/file.ts @@ -11,28 +11,26 @@ export const sysInitCompiledPath = path.join( export const sasJSCoreMacros = path.join(apiRoot, 'sasjscore') export const sasJSCoreMacrosInfo = path.join(sasJSCoreMacros, '.macrolist') -export const getWebBuildFolderPath = () => - path.join(codebaseRoot, 'web', 'build') +export const getWebBuildFolder = () => path.join(codebaseRoot, 'web', 'build') -export const getTmpFolderPath = () => process.driveLoc +export const getSasjsRootFolder = () => process.driveLoc -export const getTmpAppStreamConfigPath = () => - path.join(getTmpFolderPath(), 'appStreamConfig.json') +export const getAppStreamConfigPath = () => + path.join(getSasjsRootFolder(), 'appStreamConfig.json') -export const getTmpMacrosPath = () => path.join(getTmpFolderPath(), 'sasjscore') +export const getMacrosFolder = () => + path.join(getSasjsRootFolder(), 'sasjscore') -export const getTmpUploadsPath = () => path.join(getTmpFolderPath(), 'uploads') +export const getUploadsFolder = () => path.join(getSasjsRootFolder(), 'uploads') -export const getTmpFilesFolderPath = () => - path.join(getTmpFolderPath(), 'files') +export const getFilesFolder = () => path.join(getSasjsRootFolder(), 'files') -export const getTmpLogFolderPath = () => path.join(getTmpFolderPath(), 'logs') +export const getLogFolder = () => path.join(getSasjsRootFolder(), 'logs') -export const getTmpWeboutFolderPath = () => - path.join(getTmpFolderPath(), 'webouts') +export const getWeboutFolder = () => path.join(getSasjsRootFolder(), 'webouts') -export const getTmpSessionsFolderPath = () => - path.join(getTmpFolderPath(), 'sessions') +export const getSessionsFolder = () => + path.join(getSasjsRootFolder(), 'sessions') export const generateUniqueFileName = (fileName: string, extension = '') => [ diff --git a/api/src/utils/getDesktopFields.ts b/api/src/utils/getDesktopFields.ts index 2700145..af499a7 100644 --- a/api/src/utils/getDesktopFields.ts +++ b/api/src/utils/getDesktopFields.ts @@ -5,12 +5,12 @@ import { createFolder, fileExists, folderExists } from '@sasjs/utils' const isWindows = () => process.platform === 'win32' export const getDesktopFields = async () => { - const { SAS_PATH, DRIVE_PATH } = process.env + const { SAS_PATH } = process.env const sasLoc = SAS_PATH ?? (await getSASLocation()) - const driveLoc = DRIVE_PATH ?? (await getDriveLocation()) + // const driveLoc = DRIVE_PATH ?? (await getDriveLocation()) - return { sasLoc, driveLoc } + return { sasLoc } } const getDriveLocation = async (): Promise => { diff --git a/api/src/utils/index.ts b/api/src/utils/index.ts index 77dfc05..16980ec 100644 --- a/api/src/utils/index.ts +++ b/api/src/utils/index.ts @@ -9,6 +9,7 @@ export * from './generateRefreshToken' export * from './getCertificates' export * from './getDesktopFields' export * from './getPreProgramVariables' +export * from './instantiateLogger' export * from './isDebugOn' export * from './parseLogToArray' export * from './removeTokensInDB' @@ -18,4 +19,5 @@ export * from './setProcessVariables' export * from './setupFolders' export * from './upload' export * from './validation' +export * from './verifyEnvVariables' export * from './verifyTokenInDB' diff --git a/api/src/utils/instantiateLogger.ts b/api/src/utils/instantiateLogger.ts new file mode 100644 index 0000000..0317e47 --- /dev/null +++ b/api/src/utils/instantiateLogger.ts @@ -0,0 +1,7 @@ +import { LogLevel, Logger } from '@sasjs/utils/logger' + +export const instantiateLogger = () => { + const logLevel = (process.env.LOG_LEVEL || LogLevel.Info) as LogLevel + const logger = new Logger(logLevel) + process.logger = logger +} diff --git a/api/src/utils/setProcessVariables.ts b/api/src/utils/setProcessVariables.ts index b44f1bd..4edac7c 100644 --- a/api/src/utils/setProcessVariables.ts +++ b/api/src/utils/setProcessVariables.ts @@ -1,30 +1,29 @@ import path from 'path' -import { getAbsolutePath, getRealPath } from '@sasjs/utils' +import { createFolder, getAbsolutePath, getRealPath } from '@sasjs/utils' -import { configuration } from '../../package.json' -import { getDesktopFields } from '.' +import { getDesktopFields, ModeType } from '.' export const setProcessVariables = async () => { if (process.env.NODE_ENV === 'test') { - process.driveLoc = path.join(process.cwd(), 'tmp') + process.driveLoc = path.join(process.cwd(), 'sasjs_root') return } const { MODE } = process.env - if (MODE?.trim() === 'server') { - const { SAS_PATH, DRIVE_PATH } = process.env - - process.sasLoc = SAS_PATH ?? configuration.sasPath - const absPath = getAbsolutePath(DRIVE_PATH ?? 'tmp', process.cwd()) - process.driveLoc = getRealPath(absPath) + if (MODE === ModeType.Server) { + process.sasLoc = process.env.SAS_PATH as string } else { - const { sasLoc, driveLoc } = await getDesktopFields() + const { sasLoc } = await getDesktopFields() process.sasLoc = sasLoc - process.driveLoc = driveLoc } + const { SASJS_ROOT } = process.env + const absPath = getAbsolutePath(SASJS_ROOT ?? 'sasjs_root', process.cwd()) + await createFolder(absPath) + process.driveLoc = getRealPath(absPath) + console.log('sasLoc: ', process.sasLoc) console.log('sasDrive: ', process.driveLoc) } diff --git a/api/src/utils/setupFolders.ts b/api/src/utils/setupFolders.ts index cfe0872..2b71f63 100644 --- a/api/src/utils/setupFolders.ts +++ b/api/src/utils/setupFolders.ts @@ -1,7 +1,7 @@ import { createFolder } from '@sasjs/utils' -import { getTmpFilesFolderPath } from './file' +import { getFilesFolder } from './file' export const setupFolders = async () => { - const drivePath = getTmpFilesFolderPath() + const drivePath = getFilesFolder() await createFolder(drivePath) } diff --git a/api/src/utils/verifyEnvVariables.ts b/api/src/utils/verifyEnvVariables.ts new file mode 100644 index 0000000..d4eb91e --- /dev/null +++ b/api/src/utils/verifyEnvVariables.ts @@ -0,0 +1,181 @@ +export enum ModeType { + Server = 'server', + Desktop = 'desktop' +} + +export enum ProtocolType { + HTTP = 'http', + HTTPS = 'https' +} + +export enum CorsType { + ENABLED = 'enable', + DISABLED = 'disable' +} + +export enum HelmetCoepType { + TRUE = 'true', + FALSE = 'false' +} + +export enum ReturnCode { + Success, + InvalidEnv +} + +export const verifyEnvVariables = (): ReturnCode => { + const errors: string[] = [] + + errors.push(...verifyMODE()) + + errors.push(...verifyPROTOCOL()) + + errors.push(...verifyPORT()) + + errors.push(...verifyCORS()) + + errors.push(...verifyHELMET_COEP()) + + if (errors.length) { + process.logger?.error( + `Invalid environment variable(s) provided: \n${errors.join('\n')}` + ) + return ReturnCode.InvalidEnv + } + + return ReturnCode.Success +} + +const verifyMODE = (): string[] => { + const errors: string[] = [] + const { MODE } = process.env + + if (MODE) { + const modeTypes = Object.values(ModeType) + if (!modeTypes.includes(MODE as ModeType)) + errors.push(`- MODE '${MODE}'\n - valid options ${modeTypes}`) + } else { + process.env.MODE = DEFAULTS.MODE + } + + if (process.env.MODE === ModeType.Server) { + const { + ACCESS_TOKEN_SECRET, + REFRESH_TOKEN_SECRET, + AUTH_CODE_SECRET, + SESSION_SECRET, + DB_CONNECT + } = process.env + + if (!ACCESS_TOKEN_SECRET) + errors.push( + `- ACCESS_TOKEN_SECRET is required for PROTOCOL '${ModeType.Server}'` + ) + + if (!REFRESH_TOKEN_SECRET) + errors.push( + `- REFRESH_TOKEN_SECRET is required for PROTOCOL '${ModeType.Server}'` + ) + + if (!AUTH_CODE_SECRET) + errors.push( + `- AUTH_CODE_SECRET is required for PROTOCOL '${ModeType.Server}'` + ) + + if (!SESSION_SECRET) + errors.push( + `- SESSION_SECRET is required for PROTOCOL '${ModeType.Server}'` + ) + + if (process.env.NODE_ENV !== 'test') + if (!DB_CONNECT) + errors.push( + `- DB_CONNECT is required for PROTOCOL '${ModeType.Server}'` + ) + } + + return errors +} + +const verifyPROTOCOL = (): string[] => { + const errors: string[] = [] + const { PROTOCOL } = process.env + + if (PROTOCOL) { + const protocolTypes = Object.values(ProtocolType) + if (!protocolTypes.includes(PROTOCOL as ProtocolType)) + errors.push(`- PROTOCOL '${PROTOCOL}'\n - valid options ${protocolTypes}`) + } else { + process.env.PROTOCOL = DEFAULTS.PROTOCOL + } + + if (process.env.PROTOCOL === ProtocolType.HTTPS) { + const { PRIVATE_KEY, FULL_CHAIN } = process.env + + if (!PRIVATE_KEY) + errors.push( + `- PRIVATE_KEY is required for PROTOCOL '${ProtocolType.HTTPS}'` + ) + + if (!FULL_CHAIN) + errors.push( + `- FULL_CHAIN is required for PROTOCOL '${ProtocolType.HTTPS}'` + ) + } + + return errors +} + +const verifyCORS = (): string[] => { + const errors: string[] = [] + const { CORS } = process.env + + if (CORS) { + const corsTypes = Object.values(CorsType) + if (!corsTypes.includes(CORS as CorsType)) + errors.push(`- CORS '${CORS}'\n - valid options ${corsTypes}`) + } else { + const { MODE } = process.env + process.env.CORS = + MODE === ModeType.Server ? CorsType.DISABLED : CorsType.ENABLED + } + + return errors +} + +const verifyPORT = (): string[] => { + const errors: string[] = [] + const { PORT } = process.env + + if (PORT) { + if (Number.isNaN(parseInt(PORT))) + errors.push(`- PORT '${PORT}'\n - should be a valid number`) + } else { + process.env.PORT = DEFAULTS.PORT + } + return errors +} + +const verifyHELMET_COEP = (): string[] => { + const errors: string[] = [] + const { HELMET_COEP } = process.env + + if (HELMET_COEP) { + const helmetCoepTypes = Object.values(HelmetCoepType) + if (!helmetCoepTypes.includes(HELMET_COEP as HelmetCoepType)) + errors.push( + `- HELMET_COEP '${HELMET_COEP}'\n - valid options ${helmetCoepTypes}` + ) + HELMET_COEP + } else { + process.env.HELMET_COEP = DEFAULTS.HELMET_COEP + } + return errors +} + +const DEFAULTS = { + MODE: 'desktop', + PROTOCOL: 'http', + PORT: '5000', + HELMET_COEP: 'true' +} From f37f8e95d1a85e00ceca2413dbb5e1f3f3f72255 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Mon, 23 May 2022 20:29:29 +0500 Subject: [PATCH 2/3] fix(web): click to copy + notification --- web/package-lock.json | 90 ++++++++++++++++++++++++++- web/package.json | 5 +- web/src/components/header.tsx | 4 +- web/src/containers/AuthCode/index.tsx | 21 ++++++- 4 files changed, 115 insertions(+), 5 deletions(-) diff --git a/web/package-lock.json b/web/package-lock.json index 300af37..dbfbda5 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -24,9 +24,11 @@ "monaco-editor": "^0.33.0", "monaco-editor-webpack-plugin": "^7.0.1", "react": "^17.0.2", + "react-copy-to-clipboard": "^5.1.0", "react-dom": "^17.0.2", "react-monaco-editor": "^0.48.0", - "react-router-dom": "^5.3.0" + "react-router-dom": "^5.3.0", + "react-toastify": "^9.0.1" }, "devDependencies": { "@babel/core": "^7.16.0", @@ -38,6 +40,7 @@ "@types/dotenv-webpack": "^7.0.3", "@types/prismjs": "^1.16.6", "@types/react": "^17.0.37", + "@types/react-copy-to-clipboard": "^5.0.2", "@types/react-dom": "^17.0.11", "@types/react-router-dom": "^5.3.1", "babel-loader": "^8.2.3", @@ -3296,6 +3299,15 @@ "csstype": "^3.0.2" } }, + "node_modules/@types/react-copy-to-clipboard": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.2.tgz", + "integrity": "sha512-O29AThfxrkUFRsZXjfSWR2yaWo0ppB1yLEnHA+Oh24oNetjBAwTDu1PmolIqdJKzsZiO4J1jn6R6TmO96uBvGg==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/react-dom": { "version": "17.0.11", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.11.tgz", @@ -4863,6 +4875,14 @@ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", "dev": true }, + "node_modules/copy-to-clipboard": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz", + "integrity": "sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==", + "dependencies": { + "toggle-selection": "^1.0.6" + } + }, "node_modules/copy-webpack-plugin": { "version": "10.2.4", "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-10.2.4.tgz", @@ -9299,6 +9319,18 @@ "node": ">=0.10.0" } }, + "node_modules/react-copy-to-clipboard": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz", + "integrity": "sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==", + "dependencies": { + "copy-to-clipboard": "^3.3.1", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": "^15.3.0 || 16 || 17 || 18" + } + }, "node_modules/react-dom": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", @@ -9372,6 +9404,18 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-toastify": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-9.0.1.tgz", + "integrity": "sha512-c2zeZHkCX+WXuItS/JRqQ/8CH8Qm/je+M0rt09xe9fnu5YPJigtNOdD8zX4fwLA093V2am3abkGfOowwpkrwOQ==", + "dependencies": { + "clsx": "^1.1.1" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/react-transition-group": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz", @@ -10335,6 +10379,11 @@ "node": ">=8.0" } }, + "node_modules/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI=" + }, "node_modules/toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", @@ -13641,6 +13690,15 @@ "csstype": "^3.0.2" } }, + "@types/react-copy-to-clipboard": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.2.tgz", + "integrity": "sha512-O29AThfxrkUFRsZXjfSWR2yaWo0ppB1yLEnHA+Oh24oNetjBAwTDu1PmolIqdJKzsZiO4J1jn6R6TmO96uBvGg==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/react-dom": { "version": "17.0.11", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.11.tgz", @@ -14848,6 +14906,14 @@ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", "dev": true }, + "copy-to-clipboard": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz", + "integrity": "sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==", + "requires": { + "toggle-selection": "^1.0.6" + } + }, "copy-webpack-plugin": { "version": "10.2.4", "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-10.2.4.tgz", @@ -18162,6 +18228,15 @@ "object-assign": "^4.1.1" } }, + "react-copy-to-clipboard": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz", + "integrity": "sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==", + "requires": { + "copy-to-clipboard": "^3.3.1", + "prop-types": "^15.8.1" + } + }, "react-dom": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", @@ -18223,6 +18298,14 @@ "tiny-warning": "^1.0.0" } }, + "react-toastify": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-9.0.1.tgz", + "integrity": "sha512-c2zeZHkCX+WXuItS/JRqQ/8CH8Qm/je+M0rt09xe9fnu5YPJigtNOdD8zX4fwLA093V2am3abkGfOowwpkrwOQ==", + "requires": { + "clsx": "^1.1.1" + } + }, "react-transition-group": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz", @@ -18967,6 +19050,11 @@ "is-number": "^7.0.0" } }, + "toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI=" + }, "toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", diff --git a/web/package.json b/web/package.json index 6832f0b..a3a5898 100644 --- a/web/package.json +++ b/web/package.json @@ -23,9 +23,11 @@ "monaco-editor": "^0.33.0", "monaco-editor-webpack-plugin": "^7.0.1", "react": "^17.0.2", + "react-copy-to-clipboard": "^5.1.0", "react-dom": "^17.0.2", "react-monaco-editor": "^0.48.0", - "react-router-dom": "^5.3.0" + "react-router-dom": "^5.3.0", + "react-toastify": "^9.0.1" }, "devDependencies": { "@babel/core": "^7.16.0", @@ -37,6 +39,7 @@ "@types/dotenv-webpack": "^7.0.3", "@types/prismjs": "^1.16.6", "@types/react": "^17.0.37", + "@types/react-copy-to-clipboard": "^5.0.2", "@types/react-dom": "^17.0.11", "@types/react-router-dom": "^5.3.1", "babel-loader": "^8.2.3", diff --git a/web/src/components/header.tsx b/web/src/components/header.tsx index c499ae3..7ea5ac9 100644 --- a/web/src/components/header.tsx +++ b/web/src/components/header.tsx @@ -24,7 +24,9 @@ const Header = (props: any) => { const history = useHistory() const { pathname } = useLocation() const appContext = useContext(AppContext) - const [tabValue, setTabValue] = useState(pathname) + const [tabValue, setTabValue] = useState( + pathname === '/SASjsLogon' ? '/' : pathname + ) const [anchorEl, setAnchorEl] = useState< (EventTarget & HTMLButtonElement) | null >(null) diff --git a/web/src/containers/AuthCode/index.tsx b/web/src/containers/AuthCode/index.tsx index 59c392d..8476bc8 100644 --- a/web/src/containers/AuthCode/index.tsx +++ b/web/src/containers/AuthCode/index.tsx @@ -1,15 +1,18 @@ import axios from 'axios' +import { CopyToClipboard } from 'react-copy-to-clipboard' import React, { useEffect, useState } from 'react' +import { ToastContainer, toast } from 'react-toastify' +import 'react-toastify/dist/ReactToastify.css' import { useLocation } from 'react-router-dom' -import { CssBaseline, Box, Typography } from '@mui/material' +import { CssBaseline, Box, Typography, Button } from '@mui/material' const getAuthCode = async (credentials: any) => axios.post('/SASLogon/authorize', credentials).then((res) => res.data) const AuthCode = () => { const location = useLocation() - const [displayCode, setDisplayCode] = useState(null) + const [displayCode, setDisplayCode] = useState('') const [errorMessage, setErrorMessage] = useState('') useEffect(() => { @@ -56,6 +59,20 @@ const AuthCode = () => { {errorMessage && {errorMessage}}
+ + + toast.info('Code copied to ClipBoard', { + theme: 'dark', + position: toast.POSITION.BOTTOM_RIGHT + }) + } + > + + + + ) } From 53bf68a6aff44bb7b2f40d40d6554809253a01a8 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Mon, 23 May 2022 21:14:37 +0500 Subject: [PATCH 3/3] 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 }