mirror of
https://github.com/sasjs/server.git
synced 2025-12-10 11:24:35 +00:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e10a0554f0 | ||
|
|
337e2eb2a0 | ||
| 66f8e7840b | |||
| 1c9d167f86 | |||
|
|
7e684b54a6 | ||
|
|
aafda2922b | ||
| 418bf41e38 | |||
| 81f0b03b09 | |||
| fe5ae44aab | |||
| 36be3a7d5e | |||
| 6434123401 | |||
|
|
0a6b972c65 | ||
|
|
be11707042 | ||
| 2412622367 | |||
|
|
de3a190a8d | ||
|
|
d5daafc6ed | ||
|
|
b1a2677b8c | ||
|
|
94072c3d24 | ||
|
|
b64c0c12da | ||
|
|
79bc7b0e28 | ||
|
|
fda0e0b57d | ||
|
|
14731e8824 | ||
|
|
258cc35f14 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,8 +5,6 @@ node_modules/
|
||||
.env*
|
||||
sas/
|
||||
sasjs_root/
|
||||
api/mocks/custom/*
|
||||
!api/mocks/custom/.keep
|
||||
tmp/
|
||||
build/
|
||||
sasjsbuild/
|
||||
|
||||
38
CHANGELOG.md
38
CHANGELOG.md
@@ -1,3 +1,41 @@
|
||||
# [0.25.0](https://github.com/sasjs/server/compare/v0.24.0...v0.25.0) (2022-11-02)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Enable DRIVE_LOCATION setting for deploying multiple instances of SASjs Server ([1c9d167](https://github.com/sasjs/server/commit/1c9d167f86bbbb108b96e9bc30efaf8de65d82ff))
|
||||
|
||||
# [0.24.0](https://github.com/sasjs/server/compare/v0.23.4...v0.24.0) (2022-10-28)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* cli mock testing ([6434123](https://github.com/sasjs/server/commit/643412340162e854f31fba2f162d83b7ab1751d8))
|
||||
* mocking sas9 responses with JS STP ([36be3a7](https://github.com/sasjs/server/commit/36be3a7d5e7df79f9a1f3f00c3661b925f462383))
|
||||
|
||||
## [0.23.4](https://github.com/sasjs/server/compare/v0.23.3...v0.23.4) (2022-10-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add action to editor ref for running code ([2412622](https://github.com/sasjs/server/commit/2412622367eb46c40f388e988ae4606a7ec239b2))
|
||||
|
||||
## [0.23.3](https://github.com/sasjs/server/compare/v0.23.2...v0.23.3) (2022-10-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* added domain for session cookies ([94072c3](https://github.com/sasjs/server/commit/94072c3d24a4d0d4c97900dc31bfbf1c9d2559b7))
|
||||
|
||||
## [0.23.2](https://github.com/sasjs/server/compare/v0.23.1...v0.23.2) (2022-10-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* bump in correct place ([14731e8](https://github.com/sasjs/server/commit/14731e8824fa9f3d1daf89fd62f9916d5e3fcae4))
|
||||
* bumping sasjs/score ([258cc35](https://github.com/sasjs/server/commit/258cc35f14cf50f2160f607000c60de27593fd79))
|
||||
* reverting commit ([fda0e0b](https://github.com/sasjs/server/commit/fda0e0b57d56e3b5231e626a8d933343ac0c5cdc))
|
||||
|
||||
## [0.23.1](https://github.com/sasjs/server/compare/v0.23.0...v0.23.1) (2022-10-04)
|
||||
|
||||
|
||||
|
||||
@@ -93,6 +93,10 @@ R_PATH=/usr/bin/Rscript
|
||||
SASJS_ROOT=./sasjs_root
|
||||
|
||||
|
||||
# This location is for files, sasjs packages and appStreamConfig.json
|
||||
DRIVE_LOCATION=./sasjs_root/drive
|
||||
|
||||
|
||||
# options: [http|https] default: http
|
||||
PROTOCOL=
|
||||
|
||||
@@ -103,6 +107,11 @@ PORT=
|
||||
# If not present, mocking function is disabled
|
||||
MOCK_SERVERTYPE=
|
||||
|
||||
# default: /api/mocks
|
||||
# Path to mocking folder, for generic responses, it's sub directories should be: sas9, viya, sasjs
|
||||
# Server will automatically use subdirectory accordingly
|
||||
STATIC_MOCK_LOCATION=
|
||||
|
||||
#
|
||||
## Additional SAS Options
|
||||
#
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
MODE=[desktop|server] default considered as desktop
|
||||
CORS=[disable|enable] default considered as disable for server MODE & enable for desktop MODE
|
||||
ALLOWED_DOMAIN=<just domain e.g. example.com >
|
||||
WHITELIST=<space separated urls, each starting with protocol `http` or `https`>
|
||||
|
||||
PROTOCOL=[http|https] default considered as http
|
||||
@@ -29,6 +30,7 @@ PYTHON_PATH=/usr/bin/python
|
||||
R_PATH=/usr/bin/Rscript
|
||||
|
||||
SASJS_ROOT=./sasjs_root
|
||||
DRIVE_LOCATION=./sasjs_root/drive
|
||||
|
||||
LOG_FORMAT_MORGAN=common
|
||||
LOG_LOCATION=./sasjs_root/logs
|
||||
@@ -9,7 +9,7 @@
|
||||
<div class="content">
|
||||
<form id="credentials" class="minimal" action="/SASLogon/login?service=http%3A%2F%2Flocalhost:5004%2FSASStoredProcess%2Fj_spring_cas_security_check" method="post">
|
||||
<!--form container-->
|
||||
<input type="hidden" name="lt" value="LT-8-WGkt9EXwICBihaVbxGc92opjufTK1D" aria-hidden="true" />
|
||||
<input type="hidden" name="lt" value="validtoken" aria-hidden="true" />
|
||||
<input type="hidden" name="execution" value="e2s1" aria-hidden="true" />
|
||||
<input type="hidden" name="_eventId" value="submit" aria-hidden="true" />
|
||||
|
||||
14
api/package-lock.json
generated
14
api/package-lock.json
generated
@@ -8,7 +8,7 @@
|
||||
"name": "api",
|
||||
"version": "0.0.2",
|
||||
"dependencies": {
|
||||
"@sasjs/core": "^4.31.3",
|
||||
"@sasjs/core": "^4.40.1",
|
||||
"@sasjs/utils": "2.48.1",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"connect-mongo": "^4.6.0",
|
||||
@@ -1394,9 +1394,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sasjs/core": {
|
||||
"version": "4.31.3",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.31.3.tgz",
|
||||
"integrity": "sha512-TpVqWl5bqp3JTQjIg0r4WiQg7Ima5f17eAJILJbdYDdXsnLXlA/Csbb95G7eDPhzWpM3C0NrzKek3yvCMGzXIA=="
|
||||
"version": "4.40.1",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.40.1.tgz",
|
||||
"integrity": "sha512-hVEVnH8tej57Cran/X/iUoDms7EoL+2fwAPvjQMgHBHh8ynsF8aqYBreiRCwbrvdrjBsnmayOVh2RiQLtfHhoQ=="
|
||||
},
|
||||
"node_modules/@sasjs/utils": {
|
||||
"version": "2.48.1",
|
||||
@@ -11135,9 +11135,9 @@
|
||||
}
|
||||
},
|
||||
"@sasjs/core": {
|
||||
"version": "4.31.3",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.31.3.tgz",
|
||||
"integrity": "sha512-TpVqWl5bqp3JTQjIg0r4WiQg7Ima5f17eAJILJbdYDdXsnLXlA/Csbb95G7eDPhzWpM3C0NrzKek3yvCMGzXIA=="
|
||||
"version": "4.40.1",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.40.1.tgz",
|
||||
"integrity": "sha512-hVEVnH8tej57Cran/X/iUoDms7EoL+2fwAPvjQMgHBHh8ynsF8aqYBreiRCwbrvdrjBsnmayOVh2RiQLtfHhoQ=="
|
||||
},
|
||||
"@sasjs/utils": {
|
||||
"version": "2.48.1",
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
},
|
||||
"author": "4GL Ltd",
|
||||
"dependencies": {
|
||||
"@sasjs/core": "^4.31.3",
|
||||
"@sasjs/core": "^4.40.1",
|
||||
"@sasjs/utils": "2.48.1",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"connect-mongo": "^4.6.0",
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { Express } from 'express'
|
||||
import { Express, CookieOptions } from 'express'
|
||||
import mongoose from 'mongoose'
|
||||
import session from 'express-session'
|
||||
import MongoStore from 'connect-mongo'
|
||||
|
||||
import { ModeType } from '../utils'
|
||||
import { cookieOptions } from '../app'
|
||||
import { ModeType, ProtocolType } from '../utils'
|
||||
|
||||
export const configureExpressSession = (app: Express) => {
|
||||
const { MODE } = process.env
|
||||
@@ -19,6 +18,15 @@ export const configureExpressSession = (app: Express) => {
|
||||
})
|
||||
}
|
||||
|
||||
const { PROTOCOL, ALLOWED_DOMAIN } = process.env
|
||||
const cookieOptions: CookieOptions = {
|
||||
secure: PROTOCOL === ProtocolType.HTTPS,
|
||||
httpOnly: true,
|
||||
sameSite: PROTOCOL === ProtocolType.HTTPS ? 'none' : undefined,
|
||||
maxAge: 24 * 60 * 60 * 1000, // 24 hours
|
||||
domain: ALLOWED_DOMAIN?.trim() || undefined
|
||||
}
|
||||
|
||||
app.use(
|
||||
session({
|
||||
secret: process.secrets.SESSION_SECRET,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import path from 'path'
|
||||
import express, { ErrorRequestHandler, CookieOptions } from 'express'
|
||||
import express, { ErrorRequestHandler } from 'express'
|
||||
import cookieParser from 'cookie-parser'
|
||||
import dotenv from 'dotenv'
|
||||
|
||||
@@ -8,10 +8,10 @@ import {
|
||||
getWebBuildFolder,
|
||||
instantiateLogger,
|
||||
loadAppStreamConfig,
|
||||
ProtocolType,
|
||||
ReturnCode,
|
||||
setProcessVariables,
|
||||
setupFolders,
|
||||
setupUserAutoExec,
|
||||
verifyEnvVariables
|
||||
} from './utils'
|
||||
import {
|
||||
@@ -29,15 +29,6 @@ if (verifyEnvVariables()) process.exit(ReturnCode.InvalidEnv)
|
||||
|
||||
const app = express()
|
||||
|
||||
const { PROTOCOL } = process.env
|
||||
|
||||
export const cookieOptions: CookieOptions = {
|
||||
secure: PROTOCOL === ProtocolType.HTTPS,
|
||||
httpOnly: true,
|
||||
sameSite: PROTOCOL === ProtocolType.HTTPS ? 'none' : undefined,
|
||||
maxAge: 24 * 60 * 60 * 1000 // 24 hours
|
||||
}
|
||||
|
||||
const onError: ErrorRequestHandler = (err, req, res, next) => {
|
||||
console.error(err.stack)
|
||||
res.status(500).send('Something broke!')
|
||||
@@ -72,8 +63,12 @@ export default setProcessVariables().then(async () => {
|
||||
// Currently only place we use it is SAS9 Mock - POST /SASLogon/login
|
||||
app.use(express.urlencoded({ extended: true }))
|
||||
|
||||
await setupFolders()
|
||||
await copySASjsCore()
|
||||
await setupUserAutoExec()
|
||||
|
||||
if (process.driveLoc === path.join(process.sasjsRoot, 'drive')) {
|
||||
await setupFolders()
|
||||
await copySASjsCore()
|
||||
}
|
||||
|
||||
// loading these modules after setting up variables due to
|
||||
// multer's usage of process var process.driveLoc
|
||||
|
||||
@@ -28,6 +28,7 @@ interface ExecuteFileParams {
|
||||
returnJson?: boolean
|
||||
session?: Session
|
||||
runTime: RunTimeType
|
||||
forceStringResult?: boolean
|
||||
}
|
||||
|
||||
interface ExecuteProgramParams extends Omit<ExecuteFileParams, 'programPath'> {
|
||||
@@ -42,7 +43,8 @@ export class ExecutionController {
|
||||
otherArgs,
|
||||
returnJson,
|
||||
session,
|
||||
runTime
|
||||
runTime,
|
||||
forceStringResult
|
||||
}: ExecuteFileParams) {
|
||||
const program = await readFile(programPath)
|
||||
|
||||
@@ -53,7 +55,8 @@ export class ExecutionController {
|
||||
otherArgs,
|
||||
returnJson,
|
||||
session,
|
||||
runTime
|
||||
runTime,
|
||||
forceStringResult
|
||||
})
|
||||
}
|
||||
|
||||
@@ -63,7 +66,8 @@ export class ExecutionController {
|
||||
vars,
|
||||
otherArgs,
|
||||
session: sessionByFileUpload,
|
||||
runTime
|
||||
runTime,
|
||||
forceStringResult
|
||||
}: ExecuteProgramParams): Promise<ExecuteReturnRaw> {
|
||||
const sessionController = getSessionController(runTime)
|
||||
|
||||
@@ -104,7 +108,7 @@ export class ExecutionController {
|
||||
const fileResponse: boolean = httpHeaders.hasOwnProperty('content-type')
|
||||
|
||||
const webout = (await fileExists(weboutPath))
|
||||
? fileResponse
|
||||
? fileResponse && !forceStringResult
|
||||
? await readFileBinary(weboutPath)
|
||||
: await readFile(weboutPath)
|
||||
: ''
|
||||
|
||||
@@ -110,17 +110,13 @@ export const processProgram = async (
|
||||
|
||||
// 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(executablePath, [codePath], {
|
||||
stdio: ['ignore', writeStream, writeStream]
|
||||
})
|
||||
|
||||
// copy the code file to log and end write stream
|
||||
writeStream.end(program)
|
||||
|
||||
session.completed = true
|
||||
console.log('session completed', session)
|
||||
} catch (err: any) {
|
||||
|
||||
@@ -2,6 +2,16 @@ import { readFile } from '@sasjs/utils'
|
||||
import express from 'express'
|
||||
import path from 'path'
|
||||
import { Request, Post, Get } from 'tsoa'
|
||||
import dotenv from 'dotenv'
|
||||
import { ExecutionController } from './internal'
|
||||
import {
|
||||
getPreProgramVariables,
|
||||
getRunTimeAndFilePath,
|
||||
makeFilesNamesMap
|
||||
} from '../utils'
|
||||
import { MulterFile } from '../types/Upload'
|
||||
|
||||
dotenv.config()
|
||||
|
||||
export interface Sas9Response {
|
||||
content: string
|
||||
@@ -16,9 +26,17 @@ export interface MockFileRead {
|
||||
|
||||
export class MockSas9Controller {
|
||||
private loggedIn: string | undefined
|
||||
private mocksPath = process.env.STATIC_MOCK_LOCATION || 'mocks'
|
||||
|
||||
@Get('/SASStoredProcess')
|
||||
public async sasStoredProcess(): Promise<Sas9Response> {
|
||||
public async sasStoredProcess(
|
||||
@Request() req: express.Request
|
||||
): Promise<Sas9Response> {
|
||||
const username = req.query._username?.toString() || undefined
|
||||
const password = req.query._password?.toString() || undefined
|
||||
|
||||
if (username && password) this.loggedIn = req.body.username
|
||||
|
||||
if (!this.loggedIn) {
|
||||
return {
|
||||
content: '',
|
||||
@@ -26,17 +44,87 @@ export class MockSas9Controller {
|
||||
}
|
||||
}
|
||||
|
||||
let program = req.query._program?.toString() || undefined
|
||||
const filePath: string[] = program
|
||||
? program.replace('/', '').split('/')
|
||||
: ['generic', 'sas-stored-process']
|
||||
|
||||
if (program) {
|
||||
return await getMockResponseFromFile([
|
||||
process.cwd(),
|
||||
this.mocksPath,
|
||||
'sas9',
|
||||
...filePath
|
||||
])
|
||||
}
|
||||
|
||||
return await getMockResponseFromFile([
|
||||
process.cwd(),
|
||||
'mocks',
|
||||
'generic',
|
||||
'sas9',
|
||||
'sas-stored-process'
|
||||
...filePath
|
||||
])
|
||||
}
|
||||
|
||||
@Get('/SASStoredProcess/do')
|
||||
public async sasStoredProcessDoGet(
|
||||
@Request() req: express.Request
|
||||
): Promise<Sas9Response> {
|
||||
const username = req.query._username?.toString() || undefined
|
||||
const password = req.query._password?.toString() || undefined
|
||||
|
||||
if (username && password) this.loggedIn = username
|
||||
|
||||
if (!this.loggedIn) {
|
||||
return {
|
||||
content: '',
|
||||
redirect: '/SASLogon/login'
|
||||
}
|
||||
}
|
||||
|
||||
const program = req.query._program ?? req.body?._program
|
||||
const filePath: string[] = ['generic', 'sas-stored-process']
|
||||
|
||||
if (program) {
|
||||
const vars = { ...req.query, ...req.body, _requestMethod: req.method }
|
||||
const otherArgs = {}
|
||||
|
||||
try {
|
||||
const { codePath, runTime } = await getRunTimeAndFilePath(
|
||||
program + '.js'
|
||||
)
|
||||
|
||||
const result = await new ExecutionController().executeFile({
|
||||
programPath: codePath,
|
||||
preProgramVariables: getPreProgramVariables(req),
|
||||
vars: vars,
|
||||
otherArgs: otherArgs,
|
||||
runTime,
|
||||
forceStringResult: true
|
||||
})
|
||||
|
||||
return {
|
||||
content: result.result as string
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('err', err)
|
||||
}
|
||||
|
||||
return {
|
||||
content: 'No webout returned.'
|
||||
}
|
||||
}
|
||||
|
||||
return await getMockResponseFromFile([
|
||||
process.cwd(),
|
||||
'mocks',
|
||||
'sas9',
|
||||
...filePath
|
||||
])
|
||||
}
|
||||
|
||||
@Post('/SASStoredProcess/do/')
|
||||
public async sasStoredProcessDo(
|
||||
public async sasStoredProcessDoPost(
|
||||
@Request() req: express.Request
|
||||
): Promise<Sas9Response> {
|
||||
if (!this.loggedIn) {
|
||||
@@ -53,23 +141,38 @@ export class MockSas9Controller {
|
||||
}
|
||||
}
|
||||
|
||||
let program = req.query._program?.toString() || ''
|
||||
program = program.replace('/', '')
|
||||
const program = req.query._program ?? req.body?._program
|
||||
const vars = {
|
||||
...req.query,
|
||||
...req.body,
|
||||
_requestMethod: req.method,
|
||||
_driveLoc: process.driveLoc
|
||||
}
|
||||
const filesNamesMap = req.files?.length
|
||||
? makeFilesNamesMap(req.files as MulterFile[])
|
||||
: null
|
||||
const otherArgs = { filesNamesMap: filesNamesMap }
|
||||
const { codePath, runTime } = await getRunTimeAndFilePath(program + '.js')
|
||||
try {
|
||||
const result = await new ExecutionController().executeFile({
|
||||
programPath: codePath,
|
||||
preProgramVariables: getPreProgramVariables(req),
|
||||
vars: vars,
|
||||
otherArgs: otherArgs,
|
||||
runTime,
|
||||
session: req.sasjsSession,
|
||||
forceStringResult: true
|
||||
})
|
||||
|
||||
const content = await getMockResponseFromFile([
|
||||
process.cwd(),
|
||||
'mocks',
|
||||
...program.split('/')
|
||||
])
|
||||
|
||||
if (content.error) {
|
||||
return content
|
||||
return {
|
||||
content: result.result as string
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('err', err)
|
||||
}
|
||||
|
||||
const parsedContent = parseJsonIfValid(content.content)
|
||||
|
||||
return {
|
||||
content: parsedContent
|
||||
content: 'No webout returned.'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,8 +188,8 @@ export class MockSas9Controller {
|
||||
return await getMockResponseFromFile([
|
||||
process.cwd(),
|
||||
'mocks',
|
||||
'generic',
|
||||
'sas9',
|
||||
'generic',
|
||||
'logged-in'
|
||||
])
|
||||
}
|
||||
@@ -95,21 +198,27 @@ export class MockSas9Controller {
|
||||
return await getMockResponseFromFile([
|
||||
process.cwd(),
|
||||
'mocks',
|
||||
'generic',
|
||||
'sas9',
|
||||
'generic',
|
||||
'login'
|
||||
])
|
||||
}
|
||||
|
||||
@Post('/SASLogon/login')
|
||||
public async loginPost(req: express.Request): Promise<Sas9Response> {
|
||||
if (req.body.lt && req.body.lt !== 'validtoken')
|
||||
return {
|
||||
content: '',
|
||||
redirect: '/SASLogon/login'
|
||||
}
|
||||
|
||||
this.loggedIn = req.body.username
|
||||
|
||||
return await getMockResponseFromFile([
|
||||
process.cwd(),
|
||||
'mocks',
|
||||
'generic',
|
||||
'sas9',
|
||||
'generic',
|
||||
'logged-in'
|
||||
])
|
||||
}
|
||||
@@ -122,8 +231,8 @@ export class MockSas9Controller {
|
||||
return await getMockResponseFromFile([
|
||||
process.cwd(),
|
||||
'mocks',
|
||||
'generic',
|
||||
'sas9',
|
||||
'generic',
|
||||
'public-access-denied'
|
||||
])
|
||||
}
|
||||
@@ -131,8 +240,8 @@ export class MockSas9Controller {
|
||||
return await getMockResponseFromFile([
|
||||
process.cwd(),
|
||||
'mocks',
|
||||
'generic',
|
||||
'sas9',
|
||||
'generic',
|
||||
'logged-out'
|
||||
])
|
||||
}
|
||||
@@ -152,23 +261,6 @@ export class MockSas9Controller {
|
||||
private isPublicAccount = () => this.loggedIn?.toLowerCase() === 'public'
|
||||
}
|
||||
|
||||
/**
|
||||
* If JSON is valid it will be parsed otherwise will return text unaltered
|
||||
* @param content string to be parsed
|
||||
* @returns JSON or string
|
||||
*/
|
||||
const parseJsonIfValid = (content: string) => {
|
||||
let fileContent = ''
|
||||
|
||||
try {
|
||||
fileContent = JSON.parse(content)
|
||||
} catch (err: any) {
|
||||
fileContent = content
|
||||
}
|
||||
|
||||
return fileContent
|
||||
}
|
||||
|
||||
const getMockResponseFromFile = async (
|
||||
filePath: string[]
|
||||
): Promise<MockFileRead> => {
|
||||
|
||||
@@ -15,5 +15,5 @@ export const setupRoutes = (app: Express) => {
|
||||
appStreamRouter(req, res, next)
|
||||
})
|
||||
|
||||
app.use('/', csrfProtection, webRouter)
|
||||
app.use('/', webRouter)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import sas9WebRouter from './sas9-web'
|
||||
import sasViyaWebRouter from './sasviya-web'
|
||||
import webRouter from './web'
|
||||
import { MOCK_SERVERTYPEType } from '../../utils'
|
||||
import { csrfProtection } from '../../middlewares'
|
||||
|
||||
const router = express.Router()
|
||||
|
||||
@@ -18,7 +19,7 @@ switch (MOCK_SERVERTYPE) {
|
||||
break
|
||||
}
|
||||
default: {
|
||||
router.use('/', webRouter)
|
||||
router.use('/', csrfProtection, webRouter)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,25 @@ import express from 'express'
|
||||
import { generateCSRFToken } from '../../middlewares'
|
||||
import { WebController } from '../../controllers'
|
||||
import { MockSas9Controller } from '../../controllers/mock-sas9'
|
||||
import multer from 'multer'
|
||||
import path from 'path'
|
||||
import dotenv from 'dotenv'
|
||||
import { FileUploadController } from '../../controllers/internal'
|
||||
|
||||
dotenv.config()
|
||||
|
||||
const sas9WebRouter = express.Router()
|
||||
const webController = new WebController()
|
||||
// Mock controller must be singleton because it keeps the states
|
||||
// for example `isLoggedIn` and potentially more in future mocks
|
||||
const controller = new MockSas9Controller()
|
||||
const fileUploadController = new FileUploadController()
|
||||
|
||||
const mockPath = process.env.STATIC_MOCK_LOCATION || 'mocks'
|
||||
|
||||
const upload = multer({
|
||||
dest: path.join(process.cwd(), mockPath, 'sas9', 'files-received')
|
||||
})
|
||||
|
||||
sas9WebRouter.get('/', async (req, res) => {
|
||||
let response
|
||||
@@ -27,7 +40,7 @@ sas9WebRouter.get('/', async (req, res) => {
|
||||
})
|
||||
|
||||
sas9WebRouter.get('/SASStoredProcess', async (req, res) => {
|
||||
const response = await controller.sasStoredProcess()
|
||||
const response = await controller.sasStoredProcess(req)
|
||||
|
||||
if (response.redirect) {
|
||||
res.redirect(response.redirect)
|
||||
@@ -41,8 +54,8 @@ sas9WebRouter.get('/SASStoredProcess', async (req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
sas9WebRouter.post('/SASStoredProcess/do/', async (req, res) => {
|
||||
const response = await controller.sasStoredProcessDo(req)
|
||||
sas9WebRouter.get('/SASStoredProcess/do/', async (req, res) => {
|
||||
const response = await controller.sasStoredProcessDoGet(req)
|
||||
|
||||
if (response.redirect) {
|
||||
res.redirect(response.redirect)
|
||||
@@ -56,6 +69,26 @@ sas9WebRouter.post('/SASStoredProcess/do/', async (req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
sas9WebRouter.post(
|
||||
'/SASStoredProcess/do/',
|
||||
fileUploadController.preUploadMiddleware,
|
||||
fileUploadController.getMulterUploadObject().any(),
|
||||
async (req, res) => {
|
||||
const response = await controller.sasStoredProcessDoPost(req)
|
||||
|
||||
if (response.redirect) {
|
||||
res.redirect(response.redirect)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
res.send(response.content)
|
||||
} catch (err: any) {
|
||||
res.status(403).send(err.toString())
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
sas9WebRouter.get('/SASLogon/login', async (req, res) => {
|
||||
const response = await controller.loginGet()
|
||||
|
||||
|
||||
@@ -14,7 +14,10 @@ webRouter.get('/', async (req, res) => {
|
||||
} catch (_) {
|
||||
response = '<html><head></head><body>Web Build is not present</body></html>'
|
||||
} finally {
|
||||
const codeToInject = `<script>document.cookie = 'XSRF-TOKEN=${generateCSRFToken()}; Max-Age=86400; SameSite=Strict; Path=/;'</script>`
|
||||
const { ALLOWED_DOMAIN } = process.env
|
||||
const allowedDomain = ALLOWED_DOMAIN?.trim()
|
||||
const domain = allowedDomain ? ` Domain=${allowedDomain};` : ''
|
||||
const codeToInject = `<script>document.cookie = 'XSRF-TOKEN=${generateCSRFToken()};${domain} Max-Age=86400; SameSite=Strict; Path=/;'</script>`
|
||||
const injectedContent = response?.replace(
|
||||
'</head>',
|
||||
`${codeToInject}</head>`
|
||||
|
||||
1
api/src/types/system/process.d.ts
vendored
1
api/src/types/system/process.d.ts
vendored
@@ -5,6 +5,7 @@ declare namespace NodeJS {
|
||||
pythonLoc?: string
|
||||
rLoc?: string
|
||||
driveLoc: string
|
||||
sasjsRoot: string
|
||||
logsLoc: string
|
||||
logsUUID: string
|
||||
sessionController?: import('../../controllers/internal').SessionController
|
||||
|
||||
@@ -12,7 +12,7 @@ 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).')
|
||||
console.log('Copying Macros from container to drive.')
|
||||
|
||||
const macrosDrivePath = getMacrosFolder()
|
||||
|
||||
|
||||
@@ -20,22 +20,24 @@ export const getSasjsHomeFolder = () => path.join(homedir(), '.sasjs-server')
|
||||
export const getDesktopUserAutoExecPath = () =>
|
||||
path.join(getSasjsHomeFolder(), 'user-autoexec.sas')
|
||||
|
||||
export const getSasjsRootFolder = () => process.driveLoc
|
||||
export const getSasjsRootFolder = () => process.sasjsRoot
|
||||
|
||||
export const getSasjsDriveFolder = () => process.driveLoc
|
||||
|
||||
export const getLogFolder = () => process.logsLoc
|
||||
|
||||
export const getAppStreamConfigPath = () =>
|
||||
path.join(getSasjsRootFolder(), 'appStreamConfig.json')
|
||||
path.join(getSasjsDriveFolder(), 'appStreamConfig.json')
|
||||
|
||||
export const getMacrosFolder = () =>
|
||||
path.join(getSasjsRootFolder(), 'sas', 'sasautos')
|
||||
path.join(getSasjsDriveFolder(), 'sas', 'sasautos')
|
||||
|
||||
export const getPackagesFolder = () =>
|
||||
path.join(getSasjsRootFolder(), 'sas', 'sas_packages')
|
||||
path.join(getSasjsDriveFolder(), 'sas', 'sas_packages')
|
||||
|
||||
export const getUploadsFolder = () => path.join(getSasjsRootFolder(), 'uploads')
|
||||
|
||||
export const getFilesFolder = () => path.join(getSasjsRootFolder(), 'files')
|
||||
export const getFilesFolder = () => path.join(getSasjsDriveFolder(), 'files')
|
||||
|
||||
export const getWeboutFolder = () => path.join(getSasjsRootFolder(), 'webouts')
|
||||
|
||||
|
||||
@@ -18,10 +18,12 @@ export const getPreProgramVariables = (req: Request): PreProgramVars => {
|
||||
|
||||
if (cookies.length) httpHeaders.push(`cookie: ${cookies.join('; ')}`)
|
||||
|
||||
//In desktop mode when mocking mode is enabled, user was undefined.
|
||||
//So this is workaround.
|
||||
return {
|
||||
username: user!.username,
|
||||
userId: user!.userId,
|
||||
displayName: user!.displayName,
|
||||
username: user ? user.username : 'demo',
|
||||
userId: user ? user.userId : 0,
|
||||
displayName: user ? user.displayName : 'demo',
|
||||
serverUrl: protocol + host,
|
||||
httpHeaders
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ export * from './saveTokensInDB'
|
||||
export * from './seedDB'
|
||||
export * from './setProcessVariables'
|
||||
export * from './setupFolders'
|
||||
export * from './setupUserAutoExec'
|
||||
export * from './upload'
|
||||
export * from './validation'
|
||||
export * from './verifyEnvVariables'
|
||||
|
||||
@@ -19,7 +19,8 @@ export const setProcessVariables = async () => {
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
process.driveLoc = path.join(process.cwd(), 'sasjs_root')
|
||||
process.sasjsRoot = path.join(process.cwd(), 'sasjs_root')
|
||||
process.driveLoc = path.join(process.cwd(), 'sasjs_root', 'drive')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -32,7 +33,6 @@ export const setProcessVariables = async () => {
|
||||
process.rLoc = process.env.R_PATH
|
||||
} else {
|
||||
const { sasLoc, nodeLoc, pythonLoc, rLoc } = await getDesktopFields()
|
||||
|
||||
process.sasLoc = sasLoc
|
||||
process.nodeLoc = nodeLoc
|
||||
process.pythonLoc = pythonLoc
|
||||
@@ -42,11 +42,19 @@ export const setProcessVariables = async () => {
|
||||
const { SASJS_ROOT } = process.env
|
||||
const absPath = getAbsolutePath(SASJS_ROOT ?? 'sasjs_root', process.cwd())
|
||||
await createFolder(absPath)
|
||||
process.driveLoc = getRealPath(absPath)
|
||||
process.sasjsRoot = getRealPath(absPath)
|
||||
|
||||
const { DRIVE_LOCATION } = process.env
|
||||
const absDrivePath = getAbsolutePath(
|
||||
DRIVE_LOCATION ?? path.join(process.sasjsRoot, 'drive'),
|
||||
process.cwd()
|
||||
)
|
||||
await createFolder(absDrivePath)
|
||||
process.driveLoc = getRealPath(absDrivePath)
|
||||
|
||||
const { LOG_LOCATION } = process.env
|
||||
const absLogsPath = getAbsolutePath(
|
||||
LOG_LOCATION ?? `sasjs_root${path.sep}logs`,
|
||||
LOG_LOCATION ?? path.join(process.sasjsRoot, 'logs'),
|
||||
process.cwd()
|
||||
)
|
||||
await createFolder(absLogsPath)
|
||||
|
||||
@@ -1,19 +1,7 @@
|
||||
import { createFile, createFolder, fileExists } from '@sasjs/utils'
|
||||
import {
|
||||
getDesktopUserAutoExecPath,
|
||||
getFilesFolder,
|
||||
getPackagesFolder
|
||||
} from './file'
|
||||
import { ModeType } from './verifyEnvVariables'
|
||||
import { createFolder } from '@sasjs/utils'
|
||||
import { getFilesFolder, getPackagesFolder } from './file'
|
||||
|
||||
export const setupFolders = async () => {
|
||||
const drivePath = getFilesFolder()
|
||||
await createFolder(drivePath)
|
||||
await createFolder(getFilesFolder())
|
||||
await createFolder(getPackagesFolder())
|
||||
|
||||
if (process.env.MODE === ModeType.Desktop) {
|
||||
if (!(await fileExists(getDesktopUserAutoExecPath()))) {
|
||||
await createFile(getDesktopUserAutoExecPath(), '')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
api/src/utils/setupUserAutoExec.ts
Normal file
11
api/src/utils/setupUserAutoExec.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { createFile, fileExists } from '@sasjs/utils'
|
||||
import { getDesktopUserAutoExecPath } from './file'
|
||||
import { ModeType } from './verifyEnvVariables'
|
||||
|
||||
export const setupUserAutoExec = async () => {
|
||||
if (process.env.MODE === ModeType.Desktop) {
|
||||
if (!(await fileExists(getDesktopUserAutoExecPath()))) {
|
||||
await createFile(getDesktopUserAutoExecPath(), '')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -267,7 +267,7 @@ const verifyRUN_TIMES = (): string[] => {
|
||||
return errors
|
||||
}
|
||||
|
||||
const verifyExecutablePaths = () => {
|
||||
const verifyExecutablePaths = (): string[] => {
|
||||
const errors: string[] = []
|
||||
const { RUN_TIMES, SAS_PATH, NODE_PATH, PYTHON_PATH, R_PATH, MODE } =
|
||||
process.env
|
||||
|
||||
@@ -48,7 +48,6 @@ const SASjsEditor = ({
|
||||
setTab
|
||||
}: SASjsEditorProps) => {
|
||||
const {
|
||||
ctrlPressed,
|
||||
fileContent,
|
||||
isLoading,
|
||||
log,
|
||||
@@ -64,8 +63,6 @@ const SASjsEditor = ({
|
||||
handleDiffEditorDidMount,
|
||||
handleEditorDidMount,
|
||||
handleFilePathInput,
|
||||
handleKeyDown,
|
||||
handleKeyUp,
|
||||
handleRunBtnClick,
|
||||
handleTabChange,
|
||||
saveFile,
|
||||
@@ -99,7 +96,6 @@ const SASjsEditor = ({
|
||||
original={prevFileContent}
|
||||
value={fileContent}
|
||||
editorDidMount={handleDiffEditorDidMount}
|
||||
options={{ readOnly: ctrlPressed }}
|
||||
onChange={(val) => setFileContent(val)}
|
||||
/>
|
||||
) : (
|
||||
@@ -108,7 +104,6 @@ const SASjsEditor = ({
|
||||
language={getLanguageFromExtension(selectedFileExtension)}
|
||||
value={fileContent}
|
||||
editorDidMount={handleEditorDidMount}
|
||||
options={{ readOnly: ctrlPressed }}
|
||||
onChange={(val) => setFileContent(val)}
|
||||
/>
|
||||
)
|
||||
@@ -176,8 +171,6 @@ const SASjsEditor = ({
|
||||
{fileMenu}
|
||||
</Box>
|
||||
<Paper
|
||||
onKeyUp={handleKeyUp}
|
||||
onKeyDown={handleKeyDown}
|
||||
sx={{
|
||||
height: 'calc(100vh - 170px)',
|
||||
padding: '10px',
|
||||
|
||||
@@ -42,7 +42,6 @@ const useEditor = ({
|
||||
const [prevFileContent, setPrevFileContent] = useStateWithCallback('')
|
||||
const [fileContent, setFileContent] = useState('')
|
||||
const [log, setLog] = useState('')
|
||||
const [ctrlPressed, setCtrlPressed] = useState(false)
|
||||
const [webout, setWebout] = useState('')
|
||||
const [runTimes, setRunTimes] = useState<string[]>([])
|
||||
const [selectedRunTime, setSelectedRunTime] = useState('')
|
||||
@@ -148,53 +147,47 @@ const useEditor = ({
|
||||
const handleRunBtnClick = () =>
|
||||
runCode(getSelection(editorRef.current as any) || fileContent)
|
||||
|
||||
const runCode = (code: string) => {
|
||||
setIsLoading(true)
|
||||
axios
|
||||
.post(`/SASjsApi/code/execute`, {
|
||||
code: programPathInjection(
|
||||
code,
|
||||
selectedFilePath,
|
||||
selectedRunTime as RunTimeType
|
||||
),
|
||||
runTime: selectedRunTime
|
||||
})
|
||||
.then((res: any) => {
|
||||
setWebout(res.data.split(SASJS_LOGS_SEPARATOR)[0] ?? '')
|
||||
setLog(res.data.split(SASJS_LOGS_SEPARATOR)[1] ?? '')
|
||||
setTab('log')
|
||||
const runCode = useCallback(
|
||||
(code: string) => {
|
||||
setIsLoading(true)
|
||||
axios
|
||||
.post(`/SASjsApi/code/execute`, {
|
||||
code: programPathInjection(
|
||||
code,
|
||||
selectedFilePath,
|
||||
selectedRunTime as RunTimeType
|
||||
),
|
||||
runTime: selectedRunTime
|
||||
})
|
||||
.then((res: any) => {
|
||||
setWebout(res.data.split(SASJS_LOGS_SEPARATOR)[0] ?? '')
|
||||
setLog(res.data.split(SASJS_LOGS_SEPARATOR)[1] ?? '')
|
||||
setTab('log')
|
||||
|
||||
// Scroll to bottom of log
|
||||
const logElement = document.getElementById('log')
|
||||
if (logElement) logElement.scrollTop = logElement.scrollHeight
|
||||
})
|
||||
.catch((err) => {
|
||||
setModalTitle('Abort')
|
||||
setModalPayload(
|
||||
typeof err.response.data === 'object'
|
||||
? JSON.stringify(err.response.data)
|
||||
: err.response.data
|
||||
)
|
||||
setOpenModal(true)
|
||||
})
|
||||
.finally(() => setIsLoading(false))
|
||||
}
|
||||
|
||||
const handleKeyDown = (event: any) => {
|
||||
if (event.ctrlKey) {
|
||||
if (event.key === 'v') {
|
||||
setCtrlPressed(false)
|
||||
}
|
||||
|
||||
if (event.key === 'Enter')
|
||||
runCode(getSelection(editorRef.current as any) || fileContent)
|
||||
if (!ctrlPressed) setCtrlPressed(true)
|
||||
}
|
||||
}
|
||||
|
||||
const handleKeyUp = (event: any) => {
|
||||
if (!event.ctrlKey && ctrlPressed) setCtrlPressed(false)
|
||||
}
|
||||
// Scroll to bottom of log
|
||||
const logElement = document.getElementById('log')
|
||||
if (logElement) logElement.scrollTop = logElement.scrollHeight
|
||||
})
|
||||
.catch((err) => {
|
||||
setModalTitle('Abort')
|
||||
setModalPayload(
|
||||
typeof err.response.data === 'object'
|
||||
? JSON.stringify(err.response.data)
|
||||
: err.response.data
|
||||
)
|
||||
setOpenModal(true)
|
||||
})
|
||||
.finally(() => setIsLoading(false))
|
||||
},
|
||||
[
|
||||
selectedFilePath,
|
||||
selectedRunTime,
|
||||
setModalPayload,
|
||||
setModalTitle,
|
||||
setOpenModal,
|
||||
setTab
|
||||
]
|
||||
)
|
||||
|
||||
const handleChangeRunTime = (event: SelectChangeEvent) => {
|
||||
setSelectedRunTime(event.target.value as RunTimeType)
|
||||
@@ -223,7 +216,28 @@ const useEditor = ({
|
||||
if (prevFileContent !== fileContent) return saveFile()
|
||||
}
|
||||
})
|
||||
}, [fileContent, prevFileContent, selectedFilePath, saveFile])
|
||||
|
||||
editorRef.current.addAction({
|
||||
// An unique identifier of the contributed action.
|
||||
id: 'run-code',
|
||||
|
||||
// A label of the action that will be presented to the user.
|
||||
label: 'Run Code',
|
||||
|
||||
// An optional array of keybindings for the action.
|
||||
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter],
|
||||
|
||||
contextMenuGroupId: 'navigation',
|
||||
|
||||
contextMenuOrder: 1,
|
||||
|
||||
// Method that will be executed when the action is triggered.
|
||||
// @param editor The editor instance is passed in as a convenience
|
||||
run: function () {
|
||||
runCode(getSelection(editorRef.current as any) || fileContent)
|
||||
}
|
||||
})
|
||||
}, [fileContent, prevFileContent, selectedFilePath, saveFile, runCode])
|
||||
|
||||
useEffect(() => {
|
||||
setRunTimes(Object.values(appContext.runTimes))
|
||||
@@ -277,7 +291,6 @@ const useEditor = ({
|
||||
}, [selectedFileExtension, runTimes])
|
||||
|
||||
return {
|
||||
ctrlPressed,
|
||||
fileContent,
|
||||
isLoading,
|
||||
log,
|
||||
@@ -293,8 +306,6 @@ const useEditor = ({
|
||||
handleDiffEditorDidMount,
|
||||
handleEditorDidMount,
|
||||
handleFilePathInput,
|
||||
handleKeyDown,
|
||||
handleKeyUp,
|
||||
handleRunBtnClick,
|
||||
handleTabChange,
|
||||
saveFile,
|
||||
|
||||
Reference in New Issue
Block a user