1
0
mirror of https://github.com/sasjs/server.git synced 2025-12-10 19:34:34 +00:00

Compare commits

...

19 Commits

Author SHA1 Message Date
semantic-release-bot
7e684b54a6 chore(release): 0.24.0 [skip ci]
# [0.24.0](https://github.com/sasjs/server/compare/v0.23.4...v0.24.0) (2022-10-28)

### Features

* cli mock testing ([6434123](6434123401))
* mocking sas9 responses with JS STP ([36be3a7](36be3a7d5e))
2022-10-28 10:05:48 +00:00
Sabir Hassan
aafda2922b Merge pull request #306 from sasjs/sas9-tests-mock-dynamic
feat: cli mock testing
2022-10-28 15:01:00 +05:00
418bf41e38 style: lint 2022-10-28 11:53:42 +02:00
81f0b03b09 chore: comments address 2022-10-28 11:53:25 +02:00
fe5ae44aab chore: typo 2022-10-17 18:32:58 +02:00
36be3a7d5e feat: mocking sas9 responses with JS STP 2022-10-17 18:31:08 +02:00
6434123401 feat: cli mock testing 2022-10-11 18:37:20 +02:00
semantic-release-bot
0a6b972c65 chore(release): 0.23.4 [skip ci]
## [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](2412622367))
2022-10-11 15:26:38 +00:00
Allan Bowe
be11707042 Merge pull request #303 from sasjs/issue-301
fix: add action to editor ref for running code
2022-10-11 16:08:57 +01:00
2412622367 fix: add action to editor ref for running code 2022-10-10 16:51:46 +05:00
semantic-release-bot
de3a190a8d chore(release): 0.23.3 [skip ci]
## [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](94072c3d24))
2022-10-09 20:32:07 +00:00
Allan Bowe
d5daafc6ed Merge pull request #302 from sasjs/cookies-with-domain
fix: added domain for session cookies
2022-10-09 21:26:40 +01:00
Saad Jutt
b1a2677b8c chore: specified domain for cookie for csrf as well 2022-10-10 00:48:13 +05:00
Saad Jutt
94072c3d24 fix: added domain for session cookies 2022-10-09 22:08:01 +05:00
semantic-release-bot
b64c0c12da chore(release): 0.23.2 [skip ci]
## [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](14731e8824))
* bumping sasjs/score ([258cc35](258cc35f14))
* reverting commit ([fda0e0b](fda0e0b57d))
2022-10-06 12:41:15 +00:00
Allan Bowe
79bc7b0e28 Merge pull request #300 from sasjs/corebump
fix: bumping sasjs/score
2022-10-06 13:36:20 +01:00
Allan Bowe
fda0e0b57d fix: reverting commit 2022-10-06 12:35:59 +00:00
Allan Bowe
14731e8824 fix: bump in correct place 2022-10-06 12:34:48 +00:00
Allan Bowe
258cc35f14 fix: bumping sasjs/score 2022-10-06 12:32:13 +00:00
26 changed files with 308 additions and 141 deletions

2
.gitignore vendored
View File

@@ -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/

View File

@@ -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)

View File

@@ -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
# #

View File

@@ -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

View File

View File

@@ -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
View File

@@ -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",

View File

@@ -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",

View File

@@ -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,

View File

@@ -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!')

View File

@@ -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)
: '' : ''

View File

@@ -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) {

View File

@@ -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> => {

View File

@@ -15,5 +15,5 @@ export const setupRoutes = (app: Express) => {
appStreamRouter(req, res, next) appStreamRouter(req, res, next)
}) })
app.use('/', csrfProtection, webRouter) app.use('/', webRouter)
} }

View File

@@ -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)
} }
} }

View File

@@ -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()

View File

@@ -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>`

View File

@@ -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
} }

View File

@@ -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

View File

@@ -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

View File

@@ -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',

View File

@@ -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,