mirror of
https://github.com/sasjs/server.git
synced 2025-12-10 19:34:34 +00:00
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,6 +5,8 @@ node_modules/
|
||||
.env*
|
||||
sas/
|
||||
sasjs_root/
|
||||
api/mocks/custom/*
|
||||
!api/mocks/custom/.keep
|
||||
tmp/
|
||||
build/
|
||||
sasjsbuild/
|
||||
|
||||
@@ -96,6 +96,9 @@ PROTOCOL=
|
||||
# default: 5000
|
||||
PORT=
|
||||
|
||||
# options: [sas9|sasviya]
|
||||
# If not present, mocking function is disabled
|
||||
MOCK_SERVERTYPE=
|
||||
|
||||
#
|
||||
## Additional SAS Options
|
||||
|
||||
0
api/mocks/custom/.keep
Normal file
0
api/mocks/custom/.keep
Normal file
1
api/mocks/generic/sas9/logged-in
Normal file
1
api/mocks/generic/sas9/logged-in
Normal file
@@ -0,0 +1 @@
|
||||
You have signed in.
|
||||
1
api/mocks/generic/sas9/logged-out
Normal file
1
api/mocks/generic/sas9/logged-out
Normal file
@@ -0,0 +1 @@
|
||||
You have signed out.
|
||||
30
api/mocks/generic/sas9/login
Normal file
30
api/mocks/generic/sas9/login
Normal file
@@ -0,0 +1,30 @@
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" dir="ltr" class="bg">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="initial-scale=1" />
|
||||
</head>
|
||||
|
||||
|
||||
<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="execution" value="e2s1" aria-hidden="true" />
|
||||
<input type="hidden" name="_eventId" value="submit" aria-hidden="true" />
|
||||
|
||||
<span class="userid">
|
||||
|
||||
<input id="username" name="username" tabindex="3" aria-labelledby="username1 message1 message2 message3" name="username" placeholder="User ID" type="text" autofocus="true" value="" maxlength="500" autocomplete="off" />
|
||||
</span>
|
||||
<span class="password">
|
||||
|
||||
<input id="password" name="password" tabindex="4" name="password" placeholder="Password" type="password" value="" maxlength="500" autocomplete="off" />
|
||||
</span>
|
||||
|
||||
<button type="submit" class="btn-submit" title="Sign In" tabindex="5" onClick="this.disabled=true;setSubmitUrl(this.form);this.form.submit();return false;">Sign In</button>
|
||||
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</html>
|
||||
1
api/mocks/generic/sas9/sas-stored-process
Normal file
1
api/mocks/generic/sas9/sas-stored-process
Normal file
@@ -0,0 +1 @@
|
||||
"title": "Log Off SAS Demo User"
|
||||
File diff suppressed because it is too large
Load Diff
143
api/src/controllers/mock-sas9.ts
Normal file
143
api/src/controllers/mock-sas9.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
import { readFile } from '@sasjs/utils'
|
||||
import express from 'express'
|
||||
import path from 'path'
|
||||
import { Request, Post, Get } from 'tsoa'
|
||||
|
||||
export interface Sas9Response {
|
||||
content: string
|
||||
redirect?: string
|
||||
error?: boolean
|
||||
}
|
||||
|
||||
export interface MockFileRead {
|
||||
content: string
|
||||
error?: boolean
|
||||
}
|
||||
|
||||
export class MockSas9Controller {
|
||||
private loggedIn: boolean = false
|
||||
|
||||
@Get('/SASStoredProcess')
|
||||
public async sasStoredProcess(): Promise<Sas9Response> {
|
||||
if (!this.loggedIn) {
|
||||
return {
|
||||
content: '',
|
||||
redirect: '/SASLogon/login'
|
||||
}
|
||||
}
|
||||
|
||||
return await getMockResponseFromFile([
|
||||
process.cwd(),
|
||||
'mocks',
|
||||
'generic',
|
||||
'sas9',
|
||||
'sas-stored-process'
|
||||
])
|
||||
}
|
||||
|
||||
@Post('/SASStoredProcess/do/')
|
||||
public async sasStoredProcessDo(
|
||||
@Request() req: express.Request
|
||||
): Promise<Sas9Response> {
|
||||
if (!this.loggedIn) {
|
||||
return {
|
||||
content: '',
|
||||
redirect: '/SASLogon/login'
|
||||
}
|
||||
}
|
||||
|
||||
let program = req.query._program?.toString() || ''
|
||||
program = program.replace('/', '')
|
||||
|
||||
const content = await getMockResponseFromFile([
|
||||
process.cwd(),
|
||||
'mocks',
|
||||
...program.split('/')
|
||||
])
|
||||
|
||||
if (content.error) {
|
||||
return content
|
||||
}
|
||||
|
||||
const parsedContent = parseJsonIfValid(content.content)
|
||||
|
||||
return {
|
||||
content: parsedContent
|
||||
}
|
||||
}
|
||||
|
||||
@Get('/SASLogon/login')
|
||||
public async loginGet(): Promise<Sas9Response> {
|
||||
return await getMockResponseFromFile([
|
||||
process.cwd(),
|
||||
'mocks',
|
||||
'generic',
|
||||
'sas9',
|
||||
'login'
|
||||
])
|
||||
}
|
||||
|
||||
@Post('/SASLogon/login')
|
||||
public async loginPost(): Promise<Sas9Response> {
|
||||
this.loggedIn = true
|
||||
|
||||
return await getMockResponseFromFile([
|
||||
process.cwd(),
|
||||
'mocks',
|
||||
'generic',
|
||||
'sas9',
|
||||
'logged-in'
|
||||
])
|
||||
}
|
||||
|
||||
@Get('/SASLogon/logout')
|
||||
public async logout(): Promise<Sas9Response> {
|
||||
this.loggedIn = false
|
||||
|
||||
return await getMockResponseFromFile([
|
||||
process.cwd(),
|
||||
'mocks',
|
||||
'generic',
|
||||
'sas9',
|
||||
'logged-out'
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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> => {
|
||||
const filePathParsed = path.join(...filePath)
|
||||
let error: boolean = false
|
||||
|
||||
let file = await readFile(filePathParsed).catch((err: any) => {
|
||||
const errMsg = `Error reading mocked file on path: ${filePathParsed}\nError: ${err}`
|
||||
console.error(errMsg)
|
||||
|
||||
error = true
|
||||
|
||||
return errMsg
|
||||
})
|
||||
|
||||
return {
|
||||
content: file,
|
||||
error: error
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,25 @@
|
||||
import express from 'express'
|
||||
import sas9WebRouter from './sas9-web'
|
||||
import sasViyaWebRouter from './sasviya-web'
|
||||
import webRouter from './web'
|
||||
import { MOCK_SERVERTYPEType } from '../../utils'
|
||||
|
||||
const router = express.Router()
|
||||
|
||||
router.use('/', webRouter)
|
||||
const { MOCK_SERVERTYPE } = process.env
|
||||
|
||||
switch (MOCK_SERVERTYPE) {
|
||||
case MOCK_SERVERTYPEType.SAS9: {
|
||||
router.use('/', sas9WebRouter)
|
||||
break
|
||||
}
|
||||
case MOCK_SERVERTYPEType.SASVIYA: {
|
||||
router.use('/', sasViyaWebRouter)
|
||||
break
|
||||
}
|
||||
default: {
|
||||
router.use('/', webRouter)
|
||||
}
|
||||
}
|
||||
|
||||
export default router
|
||||
|
||||
88
api/src/routes/web/sas9-web.ts
Normal file
88
api/src/routes/web/sas9-web.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import express from 'express'
|
||||
import { WebController } from '../../controllers'
|
||||
import { MockSas9Controller } from '../../controllers/mock-sas9'
|
||||
|
||||
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()
|
||||
|
||||
sas9WebRouter.get('/', async (req, res) => {
|
||||
let response
|
||||
try {
|
||||
response = await webController.home()
|
||||
} catch (_) {
|
||||
response = '<html><head></head><body>Web Build is not present</body></html>'
|
||||
} finally {
|
||||
const codeToInject = `<script>document.cookie = 'XSRF-TOKEN=${req.csrfToken()}; Max-Age=86400; SameSite=Strict; Path=/;'</script>`
|
||||
const injectedContent = response?.replace(
|
||||
'</head>',
|
||||
`${codeToInject}</head>`
|
||||
)
|
||||
|
||||
return res.send(injectedContent)
|
||||
}
|
||||
})
|
||||
|
||||
sas9WebRouter.get('/SASStoredProcess', async (req, res) => {
|
||||
const response = await controller.sasStoredProcess()
|
||||
|
||||
if (response.redirect) {
|
||||
res.redirect(response.redirect)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
res.send(response.content)
|
||||
} catch (err: any) {
|
||||
res.status(403).send(err.toString())
|
||||
}
|
||||
})
|
||||
|
||||
sas9WebRouter.post('/SASStoredProcess/do/', async (req, res) => {
|
||||
const response = await controller.sasStoredProcessDo(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()
|
||||
|
||||
try {
|
||||
res.send(response.content)
|
||||
} catch (err: any) {
|
||||
res.status(403).send(err.toString())
|
||||
}
|
||||
})
|
||||
|
||||
sas9WebRouter.post('/SASLogon/login', async (req, res) => {
|
||||
const response = await controller.loginPost()
|
||||
|
||||
try {
|
||||
res.send(response.content)
|
||||
} catch (err: any) {
|
||||
res.status(403).send(err.toString())
|
||||
}
|
||||
})
|
||||
|
||||
sas9WebRouter.get('/SASLogon/logout', async (req, res) => {
|
||||
const response = await controller.logout()
|
||||
|
||||
try {
|
||||
res.send(response.content)
|
||||
} catch (err: any) {
|
||||
res.status(403).send(err.toString())
|
||||
}
|
||||
})
|
||||
|
||||
export default sas9WebRouter
|
||||
32
api/src/routes/web/sasviya-web.ts
Normal file
32
api/src/routes/web/sasviya-web.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import express from 'express'
|
||||
import { WebController } from '../../controllers/web'
|
||||
|
||||
const sasViyaWebRouter = express.Router()
|
||||
const controller = new WebController()
|
||||
|
||||
sasViyaWebRouter.get('/', async (req, res) => {
|
||||
let response
|
||||
try {
|
||||
response = await controller.home()
|
||||
} catch (_) {
|
||||
response = '<html><head></head><body>Web Build is not present</body></html>'
|
||||
} finally {
|
||||
const codeToInject = `<script>document.cookie = 'XSRF-TOKEN=${req.csrfToken()}; Max-Age=86400; SameSite=Strict; Path=/;'</script>`
|
||||
const injectedContent = response?.replace(
|
||||
'</head>',
|
||||
`${codeToInject}</head>`
|
||||
)
|
||||
|
||||
return res.send(injectedContent)
|
||||
}
|
||||
})
|
||||
|
||||
sasViyaWebRouter.post('/SASJobExecution/', async (req, res) => {
|
||||
try {
|
||||
res.send({ test: 'test' })
|
||||
} catch (err: any) {
|
||||
res.status(403).send(err.toString())
|
||||
}
|
||||
})
|
||||
|
||||
export default sasViyaWebRouter
|
||||
@@ -1,3 +1,8 @@
|
||||
export enum MOCK_SERVERTYPEType {
|
||||
SAS9 = 'sas9',
|
||||
SASVIYA = 'sasviya'
|
||||
}
|
||||
|
||||
export enum ModeType {
|
||||
Server = 'server',
|
||||
Desktop = 'desktop'
|
||||
@@ -40,6 +45,8 @@ export enum ReturnCode {
|
||||
export const verifyEnvVariables = (): ReturnCode => {
|
||||
const errors: string[] = []
|
||||
|
||||
errors.push(...verifyMOCK_SERVERTYPE())
|
||||
|
||||
errors.push(...verifyMODE())
|
||||
|
||||
errors.push(...verifyPROTOCOL())
|
||||
@@ -66,6 +73,23 @@ export const verifyEnvVariables = (): ReturnCode => {
|
||||
return ReturnCode.Success
|
||||
}
|
||||
|
||||
const verifyMOCK_SERVERTYPE = (): string[] => {
|
||||
const errors: string[] = []
|
||||
const { MOCK_SERVERTYPE } = process.env
|
||||
|
||||
if (MOCK_SERVERTYPE) {
|
||||
const modeTypes = Object.values(MOCK_SERVERTYPEType)
|
||||
if (!modeTypes.includes(MOCK_SERVERTYPE as MOCK_SERVERTYPEType))
|
||||
errors.push(
|
||||
`- MOCK_SERVERTYPE '${MOCK_SERVERTYPE}'\n - valid options ${modeTypes}`
|
||||
)
|
||||
} else {
|
||||
process.env.MOCK_SERVERTYPE = undefined
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
const verifyMODE = (): string[] => {
|
||||
const errors: string[] = []
|
||||
const { MODE } = process.env
|
||||
|
||||
Reference in New Issue
Block a user