mirror of
https://github.com/sasjs/server.git
synced 2025-12-10 19:34:34 +00:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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*
|
.env*
|
||||||
sas/
|
sas/
|
||||||
sasjs_root/
|
sasjs_root/
|
||||||
api/mocks/custom/*
|
|
||||||
!api/mocks/custom/.keep
|
|
||||||
tmp/
|
tmp/
|
||||||
build/
|
build/
|
||||||
sasjsbuild/
|
sasjsbuild/
|
||||||
|
|||||||
31
CHANGELOG.md
31
CHANGELOG.md
@@ -1,3 +1,34 @@
|
|||||||
|
# [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)
|
## [0.23.1](https://github.com/sasjs/server/compare/v0.23.0...v0.23.1) (2022-10-04)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -103,6 +103,11 @@ PORT=
|
|||||||
# If not present, mocking function is disabled
|
# If not present, mocking function is disabled
|
||||||
MOCK_SERVERTYPE=
|
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
|
## Additional SAS Options
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
MODE=[desktop|server] default considered as desktop
|
MODE=[desktop|server] default considered as desktop
|
||||||
CORS=[disable|enable] default considered as disable for server MODE & enable for desktop MODE
|
CORS=[disable|enable] default considered as disable for server MODE & enable for desktop MODE
|
||||||
|
ALLOWED_DOMAIN=<just domain e.g. example.com >
|
||||||
WHITELIST=<space separated urls, each starting with protocol `http` or `https`>
|
WHITELIST=<space separated urls, each starting with protocol `http` or `https`>
|
||||||
|
|
||||||
PROTOCOL=[http|https] default considered as http
|
PROTOCOL=[http|https] default considered as http
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<div class="content">
|
<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 id="credentials" class="minimal" action="/SASLogon/login?service=http%3A%2F%2Flocalhost:5004%2FSASStoredProcess%2Fj_spring_cas_security_check" method="post">
|
||||||
<!--form container-->
|
<!--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="execution" value="e2s1" aria-hidden="true" />
|
||||||
<input type="hidden" name="_eventId" value="submit" 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",
|
"name": "api",
|
||||||
"version": "0.0.2",
|
"version": "0.0.2",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sasjs/core": "^4.31.3",
|
"@sasjs/core": "^4.40.1",
|
||||||
"@sasjs/utils": "2.48.1",
|
"@sasjs/utils": "2.48.1",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"connect-mongo": "^4.6.0",
|
"connect-mongo": "^4.6.0",
|
||||||
@@ -1394,9 +1394,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sasjs/core": {
|
"node_modules/@sasjs/core": {
|
||||||
"version": "4.31.3",
|
"version": "4.40.1",
|
||||||
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.31.3.tgz",
|
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.40.1.tgz",
|
||||||
"integrity": "sha512-TpVqWl5bqp3JTQjIg0r4WiQg7Ima5f17eAJILJbdYDdXsnLXlA/Csbb95G7eDPhzWpM3C0NrzKek3yvCMGzXIA=="
|
"integrity": "sha512-hVEVnH8tej57Cran/X/iUoDms7EoL+2fwAPvjQMgHBHh8ynsF8aqYBreiRCwbrvdrjBsnmayOVh2RiQLtfHhoQ=="
|
||||||
},
|
},
|
||||||
"node_modules/@sasjs/utils": {
|
"node_modules/@sasjs/utils": {
|
||||||
"version": "2.48.1",
|
"version": "2.48.1",
|
||||||
@@ -11135,9 +11135,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@sasjs/core": {
|
"@sasjs/core": {
|
||||||
"version": "4.31.3",
|
"version": "4.40.1",
|
||||||
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.31.3.tgz",
|
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.40.1.tgz",
|
||||||
"integrity": "sha512-TpVqWl5bqp3JTQjIg0r4WiQg7Ima5f17eAJILJbdYDdXsnLXlA/Csbb95G7eDPhzWpM3C0NrzKek3yvCMGzXIA=="
|
"integrity": "sha512-hVEVnH8tej57Cran/X/iUoDms7EoL+2fwAPvjQMgHBHh8ynsF8aqYBreiRCwbrvdrjBsnmayOVh2RiQLtfHhoQ=="
|
||||||
},
|
},
|
||||||
"@sasjs/utils": {
|
"@sasjs/utils": {
|
||||||
"version": "2.48.1",
|
"version": "2.48.1",
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
},
|
},
|
||||||
"author": "4GL Ltd",
|
"author": "4GL Ltd",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sasjs/core": "^4.31.3",
|
"@sasjs/core": "^4.40.1",
|
||||||
"@sasjs/utils": "2.48.1",
|
"@sasjs/utils": "2.48.1",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"connect-mongo": "^4.6.0",
|
"connect-mongo": "^4.6.0",
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { Express } from 'express'
|
import { Express, CookieOptions } from 'express'
|
||||||
import mongoose from 'mongoose'
|
import mongoose from 'mongoose'
|
||||||
import session from 'express-session'
|
import session from 'express-session'
|
||||||
import MongoStore from 'connect-mongo'
|
import MongoStore from 'connect-mongo'
|
||||||
|
|
||||||
import { ModeType } from '../utils'
|
import { ModeType, ProtocolType } from '../utils'
|
||||||
import { cookieOptions } from '../app'
|
|
||||||
|
|
||||||
export const configureExpressSession = (app: Express) => {
|
export const configureExpressSession = (app: Express) => {
|
||||||
const { MODE } = process.env
|
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(
|
app.use(
|
||||||
session({
|
session({
|
||||||
secret: process.secrets.SESSION_SECRET,
|
secret: process.secrets.SESSION_SECRET,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import express, { ErrorRequestHandler, CookieOptions } from 'express'
|
import express, { ErrorRequestHandler } from 'express'
|
||||||
import cookieParser from 'cookie-parser'
|
import cookieParser from 'cookie-parser'
|
||||||
import dotenv from 'dotenv'
|
import dotenv from 'dotenv'
|
||||||
|
|
||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
getWebBuildFolder,
|
getWebBuildFolder,
|
||||||
instantiateLogger,
|
instantiateLogger,
|
||||||
loadAppStreamConfig,
|
loadAppStreamConfig,
|
||||||
ProtocolType,
|
|
||||||
ReturnCode,
|
ReturnCode,
|
||||||
setProcessVariables,
|
setProcessVariables,
|
||||||
setupFolders,
|
setupFolders,
|
||||||
@@ -29,15 +28,6 @@ if (verifyEnvVariables()) process.exit(ReturnCode.InvalidEnv)
|
|||||||
|
|
||||||
const app = express()
|
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) => {
|
const onError: ErrorRequestHandler = (err, req, res, next) => {
|
||||||
console.error(err.stack)
|
console.error(err.stack)
|
||||||
res.status(500).send('Something broke!')
|
res.status(500).send('Something broke!')
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ interface ExecuteFileParams {
|
|||||||
returnJson?: boolean
|
returnJson?: boolean
|
||||||
session?: Session
|
session?: Session
|
||||||
runTime: RunTimeType
|
runTime: RunTimeType
|
||||||
|
forceStringResult?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ExecuteProgramParams extends Omit<ExecuteFileParams, 'programPath'> {
|
interface ExecuteProgramParams extends Omit<ExecuteFileParams, 'programPath'> {
|
||||||
@@ -42,7 +43,8 @@ export class ExecutionController {
|
|||||||
otherArgs,
|
otherArgs,
|
||||||
returnJson,
|
returnJson,
|
||||||
session,
|
session,
|
||||||
runTime
|
runTime,
|
||||||
|
forceStringResult
|
||||||
}: ExecuteFileParams) {
|
}: ExecuteFileParams) {
|
||||||
const program = await readFile(programPath)
|
const program = await readFile(programPath)
|
||||||
|
|
||||||
@@ -53,7 +55,8 @@ export class ExecutionController {
|
|||||||
otherArgs,
|
otherArgs,
|
||||||
returnJson,
|
returnJson,
|
||||||
session,
|
session,
|
||||||
runTime
|
runTime,
|
||||||
|
forceStringResult
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +66,8 @@ export class ExecutionController {
|
|||||||
vars,
|
vars,
|
||||||
otherArgs,
|
otherArgs,
|
||||||
session: sessionByFileUpload,
|
session: sessionByFileUpload,
|
||||||
runTime
|
runTime,
|
||||||
|
forceStringResult
|
||||||
}: ExecuteProgramParams): Promise<ExecuteReturnRaw> {
|
}: ExecuteProgramParams): Promise<ExecuteReturnRaw> {
|
||||||
const sessionController = getSessionController(runTime)
|
const sessionController = getSessionController(runTime)
|
||||||
|
|
||||||
@@ -104,7 +108,7 @@ export class ExecutionController {
|
|||||||
const fileResponse: boolean = httpHeaders.hasOwnProperty('content-type')
|
const fileResponse: boolean = httpHeaders.hasOwnProperty('content-type')
|
||||||
|
|
||||||
const webout = (await fileExists(weboutPath))
|
const webout = (await fileExists(weboutPath))
|
||||||
? fileResponse
|
? fileResponse && !forceStringResult
|
||||||
? await readFileBinary(weboutPath)
|
? await readFileBinary(weboutPath)
|
||||||
: await readFile(weboutPath)
|
: await readFile(weboutPath)
|
||||||
: ''
|
: ''
|
||||||
|
|||||||
@@ -110,17 +110,13 @@ export const processProgram = async (
|
|||||||
|
|
||||||
// create a stream that will write to console outputs to log file
|
// create a stream that will write to console outputs to log file
|
||||||
const writeStream = fs.createWriteStream(logPath)
|
const writeStream = fs.createWriteStream(logPath)
|
||||||
|
|
||||||
// waiting for the open event so that we can have underlying file descriptor
|
// waiting for the open event so that we can have underlying file descriptor
|
||||||
await once(writeStream, 'open')
|
await once(writeStream, 'open')
|
||||||
|
|
||||||
execFileSync(executablePath, [codePath], {
|
execFileSync(executablePath, [codePath], {
|
||||||
stdio: ['ignore', writeStream, writeStream]
|
stdio: ['ignore', writeStream, writeStream]
|
||||||
})
|
})
|
||||||
|
|
||||||
// copy the code file to log and end write stream
|
// copy the code file to log and end write stream
|
||||||
writeStream.end(program)
|
writeStream.end(program)
|
||||||
|
|
||||||
session.completed = true
|
session.completed = true
|
||||||
console.log('session completed', session)
|
console.log('session completed', session)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
|
|||||||
@@ -2,6 +2,16 @@ import { readFile } from '@sasjs/utils'
|
|||||||
import express from 'express'
|
import express from 'express'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { Request, Post, Get } from 'tsoa'
|
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 {
|
export interface Sas9Response {
|
||||||
content: string
|
content: string
|
||||||
@@ -16,9 +26,17 @@ export interface MockFileRead {
|
|||||||
|
|
||||||
export class MockSas9Controller {
|
export class MockSas9Controller {
|
||||||
private loggedIn: string | undefined
|
private loggedIn: string | undefined
|
||||||
|
private mocksPath = process.env.STATIC_MOCK_LOCATION || 'mocks'
|
||||||
|
|
||||||
@Get('/SASStoredProcess')
|
@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) {
|
if (!this.loggedIn) {
|
||||||
return {
|
return {
|
||||||
content: '',
|
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([
|
return await getMockResponseFromFile([
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
'mocks',
|
'mocks',
|
||||||
'generic',
|
|
||||||
'sas9',
|
'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/')
|
@Post('/SASStoredProcess/do/')
|
||||||
public async sasStoredProcessDo(
|
public async sasStoredProcessDoPost(
|
||||||
@Request() req: express.Request
|
@Request() req: express.Request
|
||||||
): Promise<Sas9Response> {
|
): Promise<Sas9Response> {
|
||||||
if (!this.loggedIn) {
|
if (!this.loggedIn) {
|
||||||
@@ -53,23 +141,38 @@ export class MockSas9Controller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let program = req.query._program?.toString() || ''
|
const program = req.query._program ?? req.body?._program
|
||||||
program = program.replace('/', '')
|
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([
|
return {
|
||||||
process.cwd(),
|
content: result.result as string
|
||||||
'mocks',
|
}
|
||||||
...program.split('/')
|
} catch (err) {
|
||||||
])
|
console.log('err', err)
|
||||||
|
|
||||||
if (content.error) {
|
|
||||||
return content
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsedContent = parseJsonIfValid(content.content)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content: parsedContent
|
content: 'No webout returned.'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,8 +188,8 @@ export class MockSas9Controller {
|
|||||||
return await getMockResponseFromFile([
|
return await getMockResponseFromFile([
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
'mocks',
|
'mocks',
|
||||||
'generic',
|
|
||||||
'sas9',
|
'sas9',
|
||||||
|
'generic',
|
||||||
'logged-in'
|
'logged-in'
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
@@ -95,21 +198,27 @@ export class MockSas9Controller {
|
|||||||
return await getMockResponseFromFile([
|
return await getMockResponseFromFile([
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
'mocks',
|
'mocks',
|
||||||
'generic',
|
|
||||||
'sas9',
|
'sas9',
|
||||||
|
'generic',
|
||||||
'login'
|
'login'
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/SASLogon/login')
|
@Post('/SASLogon/login')
|
||||||
public async loginPost(req: express.Request): Promise<Sas9Response> {
|
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
|
this.loggedIn = req.body.username
|
||||||
|
|
||||||
return await getMockResponseFromFile([
|
return await getMockResponseFromFile([
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
'mocks',
|
'mocks',
|
||||||
'generic',
|
|
||||||
'sas9',
|
'sas9',
|
||||||
|
'generic',
|
||||||
'logged-in'
|
'logged-in'
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
@@ -122,8 +231,8 @@ export class MockSas9Controller {
|
|||||||
return await getMockResponseFromFile([
|
return await getMockResponseFromFile([
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
'mocks',
|
'mocks',
|
||||||
'generic',
|
|
||||||
'sas9',
|
'sas9',
|
||||||
|
'generic',
|
||||||
'public-access-denied'
|
'public-access-denied'
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
@@ -131,8 +240,8 @@ export class MockSas9Controller {
|
|||||||
return await getMockResponseFromFile([
|
return await getMockResponseFromFile([
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
'mocks',
|
'mocks',
|
||||||
'generic',
|
|
||||||
'sas9',
|
'sas9',
|
||||||
|
'generic',
|
||||||
'logged-out'
|
'logged-out'
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
@@ -152,23 +261,6 @@ export class MockSas9Controller {
|
|||||||
private isPublicAccount = () => this.loggedIn?.toLowerCase() === 'public'
|
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 (
|
const getMockResponseFromFile = async (
|
||||||
filePath: string[]
|
filePath: string[]
|
||||||
): Promise<MockFileRead> => {
|
): Promise<MockFileRead> => {
|
||||||
|
|||||||
@@ -15,5 +15,5 @@ export const setupRoutes = (app: Express) => {
|
|||||||
appStreamRouter(req, res, next)
|
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 sasViyaWebRouter from './sasviya-web'
|
||||||
import webRouter from './web'
|
import webRouter from './web'
|
||||||
import { MOCK_SERVERTYPEType } from '../../utils'
|
import { MOCK_SERVERTYPEType } from '../../utils'
|
||||||
|
import { csrfProtection } from '../../middlewares'
|
||||||
|
|
||||||
const router = express.Router()
|
const router = express.Router()
|
||||||
|
|
||||||
@@ -18,7 +19,7 @@ switch (MOCK_SERVERTYPE) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
router.use('/', webRouter)
|
router.use('/', csrfProtection, webRouter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,25 @@ import express from 'express'
|
|||||||
import { generateCSRFToken } from '../../middlewares'
|
import { generateCSRFToken } from '../../middlewares'
|
||||||
import { WebController } from '../../controllers'
|
import { WebController } from '../../controllers'
|
||||||
import { MockSas9Controller } from '../../controllers/mock-sas9'
|
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 sas9WebRouter = express.Router()
|
||||||
const webController = new WebController()
|
const webController = new WebController()
|
||||||
// Mock controller must be singleton because it keeps the states
|
// Mock controller must be singleton because it keeps the states
|
||||||
// for example `isLoggedIn` and potentially more in future mocks
|
// for example `isLoggedIn` and potentially more in future mocks
|
||||||
const controller = new MockSas9Controller()
|
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) => {
|
sas9WebRouter.get('/', async (req, res) => {
|
||||||
let response
|
let response
|
||||||
@@ -27,7 +40,7 @@ sas9WebRouter.get('/', async (req, res) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
sas9WebRouter.get('/SASStoredProcess', async (req, res) => {
|
sas9WebRouter.get('/SASStoredProcess', async (req, res) => {
|
||||||
const response = await controller.sasStoredProcess()
|
const response = await controller.sasStoredProcess(req)
|
||||||
|
|
||||||
if (response.redirect) {
|
if (response.redirect) {
|
||||||
res.redirect(response.redirect)
|
res.redirect(response.redirect)
|
||||||
@@ -41,8 +54,8 @@ sas9WebRouter.get('/SASStoredProcess', async (req, res) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
sas9WebRouter.post('/SASStoredProcess/do/', async (req, res) => {
|
sas9WebRouter.get('/SASStoredProcess/do/', async (req, res) => {
|
||||||
const response = await controller.sasStoredProcessDo(req)
|
const response = await controller.sasStoredProcessDoGet(req)
|
||||||
|
|
||||||
if (response.redirect) {
|
if (response.redirect) {
|
||||||
res.redirect(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) => {
|
sas9WebRouter.get('/SASLogon/login', async (req, res) => {
|
||||||
const response = await controller.loginGet()
|
const response = await controller.loginGet()
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,10 @@ webRouter.get('/', async (req, res) => {
|
|||||||
} catch (_) {
|
} catch (_) {
|
||||||
response = '<html><head></head><body>Web Build is not present</body></html>'
|
response = '<html><head></head><body>Web Build is not present</body></html>'
|
||||||
} finally {
|
} 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(
|
const injectedContent = response?.replace(
|
||||||
'</head>',
|
'</head>',
|
||||||
`${codeToInject}</head>`
|
`${codeToInject}</head>`
|
||||||
|
|||||||
@@ -18,10 +18,12 @@ export const getPreProgramVariables = (req: Request): PreProgramVars => {
|
|||||||
|
|
||||||
if (cookies.length) httpHeaders.push(`cookie: ${cookies.join('; ')}`)
|
if (cookies.length) httpHeaders.push(`cookie: ${cookies.join('; ')}`)
|
||||||
|
|
||||||
|
//In desktop mode when mocking mode is enabled, user was undefined.
|
||||||
|
//So this is workaround.
|
||||||
return {
|
return {
|
||||||
username: user!.username,
|
username: user ? user.username : 'demo',
|
||||||
userId: user!.userId,
|
userId: user ? user.userId : 0,
|
||||||
displayName: user!.displayName,
|
displayName: user ? user.displayName : 'demo',
|
||||||
serverUrl: protocol + host,
|
serverUrl: protocol + host,
|
||||||
httpHeaders
|
httpHeaders
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ export const setProcessVariables = async () => {
|
|||||||
process.rLoc = process.env.R_PATH
|
process.rLoc = process.env.R_PATH
|
||||||
} else {
|
} else {
|
||||||
const { sasLoc, nodeLoc, pythonLoc, rLoc } = await getDesktopFields()
|
const { sasLoc, nodeLoc, pythonLoc, rLoc } = await getDesktopFields()
|
||||||
|
|
||||||
process.sasLoc = sasLoc
|
process.sasLoc = sasLoc
|
||||||
process.nodeLoc = nodeLoc
|
process.nodeLoc = nodeLoc
|
||||||
process.pythonLoc = pythonLoc
|
process.pythonLoc = pythonLoc
|
||||||
|
|||||||
@@ -267,7 +267,7 @@ const verifyRUN_TIMES = (): string[] => {
|
|||||||
return errors
|
return errors
|
||||||
}
|
}
|
||||||
|
|
||||||
const verifyExecutablePaths = () => {
|
const verifyExecutablePaths = (): string[] => {
|
||||||
const errors: string[] = []
|
const errors: string[] = []
|
||||||
const { RUN_TIMES, SAS_PATH, NODE_PATH, PYTHON_PATH, R_PATH, MODE } =
|
const { RUN_TIMES, SAS_PATH, NODE_PATH, PYTHON_PATH, R_PATH, MODE } =
|
||||||
process.env
|
process.env
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ const SASjsEditor = ({
|
|||||||
setTab
|
setTab
|
||||||
}: SASjsEditorProps) => {
|
}: SASjsEditorProps) => {
|
||||||
const {
|
const {
|
||||||
ctrlPressed,
|
|
||||||
fileContent,
|
fileContent,
|
||||||
isLoading,
|
isLoading,
|
||||||
log,
|
log,
|
||||||
@@ -64,8 +63,6 @@ const SASjsEditor = ({
|
|||||||
handleDiffEditorDidMount,
|
handleDiffEditorDidMount,
|
||||||
handleEditorDidMount,
|
handleEditorDidMount,
|
||||||
handleFilePathInput,
|
handleFilePathInput,
|
||||||
handleKeyDown,
|
|
||||||
handleKeyUp,
|
|
||||||
handleRunBtnClick,
|
handleRunBtnClick,
|
||||||
handleTabChange,
|
handleTabChange,
|
||||||
saveFile,
|
saveFile,
|
||||||
@@ -99,7 +96,6 @@ const SASjsEditor = ({
|
|||||||
original={prevFileContent}
|
original={prevFileContent}
|
||||||
value={fileContent}
|
value={fileContent}
|
||||||
editorDidMount={handleDiffEditorDidMount}
|
editorDidMount={handleDiffEditorDidMount}
|
||||||
options={{ readOnly: ctrlPressed }}
|
|
||||||
onChange={(val) => setFileContent(val)}
|
onChange={(val) => setFileContent(val)}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
@@ -108,7 +104,6 @@ const SASjsEditor = ({
|
|||||||
language={getLanguageFromExtension(selectedFileExtension)}
|
language={getLanguageFromExtension(selectedFileExtension)}
|
||||||
value={fileContent}
|
value={fileContent}
|
||||||
editorDidMount={handleEditorDidMount}
|
editorDidMount={handleEditorDidMount}
|
||||||
options={{ readOnly: ctrlPressed }}
|
|
||||||
onChange={(val) => setFileContent(val)}
|
onChange={(val) => setFileContent(val)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@@ -176,8 +171,6 @@ const SASjsEditor = ({
|
|||||||
{fileMenu}
|
{fileMenu}
|
||||||
</Box>
|
</Box>
|
||||||
<Paper
|
<Paper
|
||||||
onKeyUp={handleKeyUp}
|
|
||||||
onKeyDown={handleKeyDown}
|
|
||||||
sx={{
|
sx={{
|
||||||
height: 'calc(100vh - 170px)',
|
height: 'calc(100vh - 170px)',
|
||||||
padding: '10px',
|
padding: '10px',
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ const useEditor = ({
|
|||||||
const [prevFileContent, setPrevFileContent] = useStateWithCallback('')
|
const [prevFileContent, setPrevFileContent] = useStateWithCallback('')
|
||||||
const [fileContent, setFileContent] = useState('')
|
const [fileContent, setFileContent] = useState('')
|
||||||
const [log, setLog] = useState('')
|
const [log, setLog] = useState('')
|
||||||
const [ctrlPressed, setCtrlPressed] = useState(false)
|
|
||||||
const [webout, setWebout] = useState('')
|
const [webout, setWebout] = useState('')
|
||||||
const [runTimes, setRunTimes] = useState<string[]>([])
|
const [runTimes, setRunTimes] = useState<string[]>([])
|
||||||
const [selectedRunTime, setSelectedRunTime] = useState('')
|
const [selectedRunTime, setSelectedRunTime] = useState('')
|
||||||
@@ -148,53 +147,47 @@ const useEditor = ({
|
|||||||
const handleRunBtnClick = () =>
|
const handleRunBtnClick = () =>
|
||||||
runCode(getSelection(editorRef.current as any) || fileContent)
|
runCode(getSelection(editorRef.current as any) || fileContent)
|
||||||
|
|
||||||
const runCode = (code: string) => {
|
const runCode = useCallback(
|
||||||
setIsLoading(true)
|
(code: string) => {
|
||||||
axios
|
setIsLoading(true)
|
||||||
.post(`/SASjsApi/code/execute`, {
|
axios
|
||||||
code: programPathInjection(
|
.post(`/SASjsApi/code/execute`, {
|
||||||
code,
|
code: programPathInjection(
|
||||||
selectedFilePath,
|
code,
|
||||||
selectedRunTime as RunTimeType
|
selectedFilePath,
|
||||||
),
|
selectedRunTime as RunTimeType
|
||||||
runTime: selectedRunTime
|
),
|
||||||
})
|
runTime: selectedRunTime
|
||||||
.then((res: any) => {
|
})
|
||||||
setWebout(res.data.split(SASJS_LOGS_SEPARATOR)[0] ?? '')
|
.then((res: any) => {
|
||||||
setLog(res.data.split(SASJS_LOGS_SEPARATOR)[1] ?? '')
|
setWebout(res.data.split(SASJS_LOGS_SEPARATOR)[0] ?? '')
|
||||||
setTab('log')
|
setLog(res.data.split(SASJS_LOGS_SEPARATOR)[1] ?? '')
|
||||||
|
setTab('log')
|
||||||
|
|
||||||
// Scroll to bottom of log
|
// Scroll to bottom of log
|
||||||
const logElement = document.getElementById('log')
|
const logElement = document.getElementById('log')
|
||||||
if (logElement) logElement.scrollTop = logElement.scrollHeight
|
if (logElement) logElement.scrollTop = logElement.scrollHeight
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
setModalTitle('Abort')
|
setModalTitle('Abort')
|
||||||
setModalPayload(
|
setModalPayload(
|
||||||
typeof err.response.data === 'object'
|
typeof err.response.data === 'object'
|
||||||
? JSON.stringify(err.response.data)
|
? JSON.stringify(err.response.data)
|
||||||
: err.response.data
|
: err.response.data
|
||||||
)
|
)
|
||||||
setOpenModal(true)
|
setOpenModal(true)
|
||||||
})
|
})
|
||||||
.finally(() => setIsLoading(false))
|
.finally(() => setIsLoading(false))
|
||||||
}
|
},
|
||||||
|
[
|
||||||
const handleKeyDown = (event: any) => {
|
selectedFilePath,
|
||||||
if (event.ctrlKey) {
|
selectedRunTime,
|
||||||
if (event.key === 'v') {
|
setModalPayload,
|
||||||
setCtrlPressed(false)
|
setModalTitle,
|
||||||
}
|
setOpenModal,
|
||||||
|
setTab
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleChangeRunTime = (event: SelectChangeEvent) => {
|
const handleChangeRunTime = (event: SelectChangeEvent) => {
|
||||||
setSelectedRunTime(event.target.value as RunTimeType)
|
setSelectedRunTime(event.target.value as RunTimeType)
|
||||||
@@ -223,7 +216,28 @@ const useEditor = ({
|
|||||||
if (prevFileContent !== fileContent) return saveFile()
|
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(() => {
|
useEffect(() => {
|
||||||
setRunTimes(Object.values(appContext.runTimes))
|
setRunTimes(Object.values(appContext.runTimes))
|
||||||
@@ -277,7 +291,6 @@ const useEditor = ({
|
|||||||
}, [selectedFileExtension, runTimes])
|
}, [selectedFileExtension, runTimes])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ctrlPressed,
|
|
||||||
fileContent,
|
fileContent,
|
||||||
isLoading,
|
isLoading,
|
||||||
log,
|
log,
|
||||||
@@ -293,8 +306,6 @@ const useEditor = ({
|
|||||||
handleDiffEditorDidMount,
|
handleDiffEditorDidMount,
|
||||||
handleEditorDidMount,
|
handleEditorDidMount,
|
||||||
handleFilePathInput,
|
handleFilePathInput,
|
||||||
handleKeyDown,
|
|
||||||
handleKeyUp,
|
|
||||||
handleRunBtnClick,
|
handleRunBtnClick,
|
||||||
handleTabChange,
|
handleTabChange,
|
||||||
saveFile,
|
saveFile,
|
||||||
|
|||||||
Reference in New Issue
Block a user