mirror of
https://github.com/sasjs/server.git
synced 2026-01-11 00:10:06 +00:00
Merge branch 'main' into issue-139
This commit is contained in:
@@ -13,19 +13,6 @@ import { InfoJWT } from '../../types'
|
||||
const authRouter = express.Router()
|
||||
const controller = new AuthController()
|
||||
|
||||
authRouter.post('/authorize', async (req, res) => {
|
||||
const { error, value: body } = authorizeValidation(req.body)
|
||||
if (error) return res.status(400).send(error.details[0].message)
|
||||
|
||||
try {
|
||||
const response = await controller.authorize(body)
|
||||
|
||||
res.send(response)
|
||||
} catch (err: any) {
|
||||
res.status(403).send(err.toString())
|
||||
}
|
||||
})
|
||||
|
||||
authRouter.post('/token', async (req, res) => {
|
||||
const { error, value: body } = tokenValidation(req.body)
|
||||
if (error) return res.status(400).send(error.details[0].message)
|
||||
@@ -39,8 +26,11 @@ authRouter.post('/token', async (req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
authRouter.post('/refresh', authenticateRefreshToken, async (req: any, res) => {
|
||||
const userInfo: InfoJWT = req.user
|
||||
authRouter.post('/refresh', authenticateRefreshToken, async (req, res) => {
|
||||
const userInfo: InfoJWT = {
|
||||
userId: req.user!.userId!,
|
||||
clientId: req.user!.clientId!
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await controller.refresh(userInfo)
|
||||
@@ -51,8 +41,11 @@ authRouter.post('/refresh', authenticateRefreshToken, async (req: any, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
authRouter.delete('/logout', authenticateAccessToken, async (req: any, res) => {
|
||||
const userInfo: InfoJWT = req.user
|
||||
authRouter.delete('/logout', authenticateAccessToken, async (req, res) => {
|
||||
const userInfo: InfoJWT = {
|
||||
userId: req.user!.userId!,
|
||||
clientId: req.user!.clientId!
|
||||
}
|
||||
|
||||
try {
|
||||
await controller.logout(userInfo)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import express from 'express'
|
||||
import { runSASValidation } from '../../utils'
|
||||
import { runCodeValidation } from '../../utils'
|
||||
import { CodeController } from '../../controllers/'
|
||||
|
||||
const runRouter = express.Router()
|
||||
@@ -7,11 +7,11 @@ const runRouter = express.Router()
|
||||
const controller = new CodeController()
|
||||
|
||||
runRouter.post('/execute', async (req, res) => {
|
||||
const { error, value: body } = runSASValidation(req.body)
|
||||
const { error, value: body } = runCodeValidation(req.body)
|
||||
if (error) return res.status(400).send(error.details[0].message)
|
||||
|
||||
try {
|
||||
const response = await controller.executeSASCode(req, body)
|
||||
const response = await controller.executeCode(req, body)
|
||||
|
||||
if (response instanceof Buffer) {
|
||||
res.writeHead(200, (req as any).sasHeaders)
|
||||
|
||||
@@ -7,9 +7,12 @@ import { multerSingle } from '../../middlewares/multer'
|
||||
import { DriveController } from '../../controllers/'
|
||||
import {
|
||||
deployValidation,
|
||||
extractJSONFromZip,
|
||||
extractName,
|
||||
fileBodyValidation,
|
||||
fileParamValidation,
|
||||
folderParamValidation
|
||||
folderParamValidation,
|
||||
isZipFile
|
||||
} from '../../utils'
|
||||
|
||||
const controller = new DriveController()
|
||||
@@ -49,7 +52,24 @@ driveRouter.post(
|
||||
async (req, res) => {
|
||||
if (!req.file) return res.status(400).send('"file" is not present.')
|
||||
|
||||
const fileContent = await readFile(req.file.path)
|
||||
let fileContent: string = ''
|
||||
|
||||
const { value: zipFile } = isZipFile(req.file)
|
||||
if (zipFile) {
|
||||
fileContent = await extractJSONFromZip(zipFile)
|
||||
const fileInZip = extractName(zipFile.originalname)
|
||||
|
||||
if (!fileContent) {
|
||||
deleteFile(req.file.path)
|
||||
return res
|
||||
.status(400)
|
||||
.send(
|
||||
`No content present in ${fileInZip} of compressed file ${zipFile.originalname}`
|
||||
)
|
||||
}
|
||||
} else {
|
||||
fileContent = await readFile(req.file.path)
|
||||
}
|
||||
|
||||
let jsonContent
|
||||
try {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import express from 'express'
|
||||
import { GroupController } from '../../controllers/'
|
||||
import { authenticateAccessToken, verifyAdmin } from '../../middlewares'
|
||||
import { registerGroupValidation } from '../../utils'
|
||||
import { getGroupValidation, registerGroupValidation } from '../../utils'
|
||||
|
||||
const groupRouter = express.Router()
|
||||
|
||||
@@ -18,7 +18,11 @@ groupRouter.post(
|
||||
const response = await controller.createGroup(body)
|
||||
res.send(response)
|
||||
} catch (err: any) {
|
||||
res.status(403).send(err.toString())
|
||||
const statusCode = err.code
|
||||
|
||||
delete err.code
|
||||
|
||||
res.status(statusCode).send(err.message)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -29,35 +33,73 @@ groupRouter.get('/', authenticateAccessToken, async (req, res) => {
|
||||
const response = await controller.getAllGroups()
|
||||
res.send(response)
|
||||
} catch (err: any) {
|
||||
res.status(403).send(err.toString())
|
||||
const statusCode = err.code
|
||||
|
||||
delete err.code
|
||||
|
||||
res.status(statusCode).send(err.message)
|
||||
}
|
||||
})
|
||||
|
||||
groupRouter.get('/:groupId', authenticateAccessToken, async (req: any, res) => {
|
||||
groupRouter.get('/:groupId', authenticateAccessToken, async (req, res) => {
|
||||
const { groupId } = req.params
|
||||
|
||||
const controller = new GroupController()
|
||||
try {
|
||||
const response = await controller.getGroup(groupId)
|
||||
const response = await controller.getGroup(parseInt(groupId))
|
||||
res.send(response)
|
||||
} catch (err: any) {
|
||||
res.status(403).send(err.toString())
|
||||
const statusCode = err.code
|
||||
|
||||
delete err.code
|
||||
|
||||
res.status(statusCode).send(err.message)
|
||||
}
|
||||
})
|
||||
|
||||
groupRouter.get(
|
||||
'/by/groupname/:name',
|
||||
authenticateAccessToken,
|
||||
async (req, res) => {
|
||||
const { error, value: params } = getGroupValidation(req.params)
|
||||
if (error) return res.status(400).send(error.details[0].message)
|
||||
|
||||
const { name } = params
|
||||
|
||||
const controller = new GroupController()
|
||||
try {
|
||||
const response = await controller.getGroupByGroupName(name)
|
||||
res.send(response)
|
||||
} catch (err: any) {
|
||||
const statusCode = err.code
|
||||
|
||||
delete err.code
|
||||
|
||||
res.status(statusCode).send(err.message)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
groupRouter.post(
|
||||
'/:groupId/:userId',
|
||||
authenticateAccessToken,
|
||||
verifyAdmin,
|
||||
async (req: any, res) => {
|
||||
async (req, res) => {
|
||||
const { groupId, userId } = req.params
|
||||
|
||||
const controller = new GroupController()
|
||||
try {
|
||||
const response = await controller.addUserToGroup(groupId, userId)
|
||||
const response = await controller.addUserToGroup(
|
||||
parseInt(groupId),
|
||||
parseInt(userId)
|
||||
)
|
||||
res.send(response)
|
||||
} catch (err: any) {
|
||||
res.status(403).send(err.toString())
|
||||
const statusCode = err.code
|
||||
|
||||
delete err.code
|
||||
|
||||
res.status(statusCode).send(err.message)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -66,15 +108,22 @@ groupRouter.delete(
|
||||
'/:groupId/:userId',
|
||||
authenticateAccessToken,
|
||||
verifyAdmin,
|
||||
async (req: any, res) => {
|
||||
async (req, res) => {
|
||||
const { groupId, userId } = req.params
|
||||
|
||||
const controller = new GroupController()
|
||||
try {
|
||||
const response = await controller.removeUserFromGroup(groupId, userId)
|
||||
const response = await controller.removeUserFromGroup(
|
||||
parseInt(groupId),
|
||||
parseInt(userId)
|
||||
)
|
||||
res.send(response)
|
||||
} catch (err: any) {
|
||||
res.status(403).send(err.toString())
|
||||
const statusCode = err.code
|
||||
|
||||
delete err.code
|
||||
|
||||
res.status(statusCode).send(err.message)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -83,15 +132,19 @@ groupRouter.delete(
|
||||
'/:groupId',
|
||||
authenticateAccessToken,
|
||||
verifyAdmin,
|
||||
async (req: any, res) => {
|
||||
async (req, res) => {
|
||||
const { groupId } = req.params
|
||||
|
||||
const controller = new GroupController()
|
||||
try {
|
||||
await controller.deleteGroup(groupId)
|
||||
await controller.deleteGroup(parseInt(groupId))
|
||||
res.status(200).send('Group Deleted!')
|
||||
} catch (err: any) {
|
||||
res.status(403).send(err.toString())
|
||||
const statusCode = err.code
|
||||
|
||||
delete err.code
|
||||
|
||||
res.status(statusCode).send(err.message)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -5,7 +5,6 @@ import swaggerUi from 'swagger-ui-express'
|
||||
import {
|
||||
authenticateAccessToken,
|
||||
desktopRestrict,
|
||||
desktopUsername,
|
||||
verifyAdmin
|
||||
} from '../../middlewares'
|
||||
|
||||
@@ -23,7 +22,7 @@ import permissionRouter from './permission'
|
||||
const router = express.Router()
|
||||
|
||||
router.use('/info', infoRouter)
|
||||
router.use('/session', desktopUsername, authenticateAccessToken, sessionRouter)
|
||||
router.use('/session', authenticateAccessToken, sessionRouter)
|
||||
router.use('/auth', desktopRestrict, authRouter)
|
||||
router.use(
|
||||
'/client',
|
||||
@@ -38,12 +37,22 @@ router.use('/stp', authenticateAccessToken, stpRouter)
|
||||
router.use('/code', authenticateAccessToken, codeRouter)
|
||||
router.use('/user', desktopRestrict, userRouter)
|
||||
router.use('/permission', desktopRestrict, permissionRouter)
|
||||
|
||||
router.use(
|
||||
'/',
|
||||
swaggerUi.serve,
|
||||
swaggerUi.setup(undefined, {
|
||||
swaggerOptions: {
|
||||
url: '/swagger.yaml'
|
||||
url: '/swagger.yaml',
|
||||
requestInterceptor: (request: any) => {
|
||||
request.credentials = 'include'
|
||||
|
||||
const cookie = document.cookie
|
||||
const startIndex = cookie.indexOf('XSRF-TOKEN')
|
||||
const csrf = cookie.slice(startIndex + 11).split('; ')[0]
|
||||
request.headers['X-XSRF-TOKEN'] = csrf
|
||||
return request
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
@@ -49,114 +49,6 @@ describe('auth', () => {
|
||||
await mongoServer.stop()
|
||||
})
|
||||
|
||||
describe('authorize', () => {
|
||||
afterEach(async () => {
|
||||
const collections = mongoose.connection.collections
|
||||
const collection = collections['users']
|
||||
await collection.deleteMany({})
|
||||
})
|
||||
|
||||
it('should respond with authorization code', async () => {
|
||||
await userController.createUser(user)
|
||||
|
||||
const res = await request(app)
|
||||
.post('/SASjsApi/auth/authorize')
|
||||
.send({
|
||||
username: user.username,
|
||||
password: user.password,
|
||||
clientId
|
||||
})
|
||||
.expect(200)
|
||||
|
||||
expect(res.body).toHaveProperty('code')
|
||||
})
|
||||
|
||||
it('should respond with Bad Request if username is missing', async () => {
|
||||
const res = await request(app)
|
||||
.post('/SASjsApi/auth/authorize')
|
||||
.send({
|
||||
password: user.password,
|
||||
clientId
|
||||
})
|
||||
.expect(400)
|
||||
|
||||
expect(res.text).toEqual(`"username" is required`)
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Bad Request if password is missing', async () => {
|
||||
const res = await request(app)
|
||||
.post('/SASjsApi/auth/authorize')
|
||||
.send({
|
||||
username: user.username,
|
||||
clientId
|
||||
})
|
||||
.expect(400)
|
||||
|
||||
expect(res.text).toEqual(`"password" is required`)
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Bad Request if clientId is missing', async () => {
|
||||
const res = await request(app)
|
||||
.post('/SASjsApi/auth/authorize')
|
||||
.send({
|
||||
username: user.username,
|
||||
password: user.password
|
||||
})
|
||||
.expect(400)
|
||||
|
||||
expect(res.text).toEqual(`"clientId" is required`)
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Forbidden if username is incorrect', async () => {
|
||||
const res = await request(app)
|
||||
.post('/SASjsApi/auth/authorize')
|
||||
.send({
|
||||
username: user.username,
|
||||
password: user.password,
|
||||
clientId
|
||||
})
|
||||
.expect(403)
|
||||
|
||||
expect(res.text).toEqual('Error: Username is not found.')
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Forbidden if password is incorrect', async () => {
|
||||
await userController.createUser(user)
|
||||
|
||||
const res = await request(app)
|
||||
.post('/SASjsApi/auth/authorize')
|
||||
.send({
|
||||
username: user.username,
|
||||
password: 'WrongPassword',
|
||||
clientId
|
||||
})
|
||||
.expect(403)
|
||||
|
||||
expect(res.text).toEqual('Error: Invalid password.')
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Forbidden if clientId is incorrect', async () => {
|
||||
await userController.createUser(user)
|
||||
|
||||
const res = await request(app)
|
||||
.post('/SASjsApi/auth/authorize')
|
||||
.send({
|
||||
username: user.username,
|
||||
password: user.password,
|
||||
clientId: 'WrongClientID'
|
||||
})
|
||||
.expect(403)
|
||||
|
||||
expect(res.text).toEqual('Error: Invalid clientId.')
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
})
|
||||
|
||||
describe('token', () => {
|
||||
const userInfo: InfoJWT = {
|
||||
clientId,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Express } from 'express'
|
||||
import mongoose, { Mongoose } from 'mongoose'
|
||||
import { MongoMemoryServer } from 'mongodb-memory-server'
|
||||
import request from 'supertest'
|
||||
import AdmZip from 'adm-zip'
|
||||
|
||||
import {
|
||||
folderExists,
|
||||
@@ -21,17 +22,17 @@ import * as fileUtilModules from '../../../utils/file'
|
||||
const timestamp = generateTimestamp()
|
||||
const tmpFolder = path.join(process.cwd(), `tmp-${timestamp}`)
|
||||
jest
|
||||
.spyOn(fileUtilModules, 'getTmpFolderPath')
|
||||
.spyOn(fileUtilModules, 'getSasjsRootFolder')
|
||||
.mockImplementation(() => tmpFolder)
|
||||
jest
|
||||
.spyOn(fileUtilModules, 'getTmpUploadsPath')
|
||||
.spyOn(fileUtilModules, 'getUploadsFolder')
|
||||
.mockImplementation(() => path.join(tmpFolder, 'uploads'))
|
||||
|
||||
import appPromise from '../../../app'
|
||||
import { UserController } from '../../../controllers/'
|
||||
import { getTreeExample } from '../../../controllers/internal'
|
||||
import { generateAccessToken, saveTokensInDB } from '../../../utils/'
|
||||
const { getTmpFilesFolderPath } = fileUtilModules
|
||||
const { getFilesFolder } = fileUtilModules
|
||||
|
||||
const clientId = 'someclientID'
|
||||
const user = {
|
||||
@@ -72,11 +73,52 @@ describe('drive', () => {
|
||||
})
|
||||
|
||||
describe('deploy', () => {
|
||||
const shouldFailAssertion = async (payload: any) => {
|
||||
const res = await request(app)
|
||||
.post('/SASjsApi/drive/deploy')
|
||||
.auth(accessToken, { type: 'bearer' })
|
||||
.send({ appLoc: '/Public', fileTree: payload })
|
||||
const makeRequest = async (payload: any, type: string = 'payload') => {
|
||||
const requestUrl =
|
||||
type === 'payload'
|
||||
? '/SASjsApi/drive/deploy'
|
||||
: '/SASjsApi/drive/deploy/upload'
|
||||
|
||||
if (type === 'payload') {
|
||||
return await request(app)
|
||||
.post(requestUrl)
|
||||
.auth(accessToken, { type: 'bearer' })
|
||||
.send({ appLoc: '/Public', fileTree: payload })
|
||||
}
|
||||
if (type === 'file') {
|
||||
const deployContents = JSON.stringify({
|
||||
appLoc: '/Public',
|
||||
fileTree: payload
|
||||
})
|
||||
return await request(app)
|
||||
.post(requestUrl)
|
||||
.auth(accessToken, { type: 'bearer' })
|
||||
.attach('file', Buffer.from(deployContents), 'deploy.json')
|
||||
} else {
|
||||
const deployContents = JSON.stringify({
|
||||
appLoc: '/Public',
|
||||
fileTree: payload
|
||||
})
|
||||
const zip = new AdmZip()
|
||||
// add file directly
|
||||
zip.addFile(
|
||||
'deploy.json',
|
||||
Buffer.from(deployContents, 'utf8'),
|
||||
'entry comment goes here'
|
||||
)
|
||||
|
||||
return await request(app)
|
||||
.post(requestUrl)
|
||||
.auth(accessToken, { type: 'bearer' })
|
||||
.attach('file', zip.toBuffer(), 'deploy.json.zip')
|
||||
}
|
||||
}
|
||||
|
||||
const shouldFailAssertion = async (
|
||||
payload: any,
|
||||
type: string = 'payload'
|
||||
) => {
|
||||
const res = await makeRequest(payload, type)
|
||||
|
||||
expect(res.statusCode).toEqual(400)
|
||||
|
||||
@@ -157,10 +199,10 @@ describe('drive', () => {
|
||||
expect(res.text).toEqual(
|
||||
'{"status":"success","message":"Files deployed successfully to @sasjs/server."}'
|
||||
)
|
||||
await expect(folderExists(getTmpFilesFolderPath())).resolves.toEqual(true)
|
||||
await expect(folderExists(getFilesFolder())).resolves.toEqual(true)
|
||||
|
||||
const testJobFolder = path.join(
|
||||
getTmpFilesFolderPath(),
|
||||
getFilesFolder(),
|
||||
'public',
|
||||
'jobs',
|
||||
'extract'
|
||||
@@ -174,7 +216,241 @@ describe('drive', () => {
|
||||
|
||||
await expect(readFile(testJobFile)).resolves.toEqual(exampleService.code)
|
||||
|
||||
await deleteFolder(path.join(getTmpFilesFolderPath(), 'public'))
|
||||
await deleteFolder(path.join(getFilesFolder(), 'public'))
|
||||
})
|
||||
|
||||
describe('upload', () => {
|
||||
it('should respond with payload example if valid JSON file was not provided', async () => {
|
||||
await shouldFailAssertion(null, 'file')
|
||||
await shouldFailAssertion(undefined, 'file')
|
||||
await shouldFailAssertion('data', 'file')
|
||||
await shouldFailAssertion({}, 'file')
|
||||
await shouldFailAssertion(
|
||||
{
|
||||
userId: 1,
|
||||
title: 'test is cool'
|
||||
},
|
||||
'file'
|
||||
)
|
||||
await shouldFailAssertion(
|
||||
{
|
||||
membersWRONG: []
|
||||
},
|
||||
'file'
|
||||
)
|
||||
await shouldFailAssertion(
|
||||
{
|
||||
members: {}
|
||||
},
|
||||
'file'
|
||||
)
|
||||
await shouldFailAssertion(
|
||||
{
|
||||
members: [
|
||||
{
|
||||
nameWRONG: 'jobs',
|
||||
type: 'folder',
|
||||
members: []
|
||||
}
|
||||
]
|
||||
},
|
||||
'file'
|
||||
)
|
||||
await shouldFailAssertion(
|
||||
{
|
||||
members: [
|
||||
{
|
||||
name: 'jobs',
|
||||
type: 'WRONG',
|
||||
members: []
|
||||
}
|
||||
]
|
||||
},
|
||||
'file'
|
||||
)
|
||||
await shouldFailAssertion(
|
||||
{
|
||||
members: [
|
||||
{
|
||||
name: 'jobs',
|
||||
type: 'folder',
|
||||
members: [
|
||||
{
|
||||
name: 'extract',
|
||||
type: 'folder',
|
||||
members: [
|
||||
{
|
||||
name: 'makedata1',
|
||||
type: 'service',
|
||||
codeWRONG: '%put Hello World!;'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
'file'
|
||||
)
|
||||
})
|
||||
|
||||
it('should successfully deploy if valid JSON file was provided', async () => {
|
||||
const deployContents = JSON.stringify({
|
||||
appLoc: '/public',
|
||||
fileTree: getTreeExample()
|
||||
})
|
||||
const res = await request(app)
|
||||
.post('/SASjsApi/drive/deploy/upload')
|
||||
.auth(accessToken, { type: 'bearer' })
|
||||
.attach('file', Buffer.from(deployContents), 'deploy.json')
|
||||
|
||||
expect(res.statusCode).toEqual(200)
|
||||
expect(res.text).toEqual(
|
||||
'{"status":"success","message":"Files deployed successfully to @sasjs/server."}'
|
||||
)
|
||||
await expect(folderExists(getFilesFolder())).resolves.toEqual(true)
|
||||
|
||||
const testJobFolder = path.join(
|
||||
getFilesFolder(),
|
||||
'public',
|
||||
'jobs',
|
||||
'extract'
|
||||
)
|
||||
await expect(folderExists(testJobFolder)).resolves.toEqual(true)
|
||||
|
||||
const exampleService = getExampleService()
|
||||
const testJobFile =
|
||||
path.join(testJobFolder, exampleService.name) + '.sas'
|
||||
|
||||
await expect(fileExists(testJobFile)).resolves.toEqual(true)
|
||||
|
||||
await expect(readFile(testJobFile)).resolves.toEqual(
|
||||
exampleService.code
|
||||
)
|
||||
|
||||
await deleteFolder(path.join(getFilesFolder(), 'public'))
|
||||
})
|
||||
})
|
||||
|
||||
describe('upload - zipped', () => {
|
||||
it('should respond with payload example if valid Zipped file was not provided', async () => {
|
||||
await shouldFailAssertion(null, 'zip')
|
||||
await shouldFailAssertion(undefined, 'zip')
|
||||
await shouldFailAssertion('data', 'zip')
|
||||
await shouldFailAssertion({}, 'zip')
|
||||
await shouldFailAssertion(
|
||||
{
|
||||
userId: 1,
|
||||
title: 'test is cool'
|
||||
},
|
||||
'zip'
|
||||
)
|
||||
await shouldFailAssertion(
|
||||
{
|
||||
membersWRONG: []
|
||||
},
|
||||
'zip'
|
||||
)
|
||||
await shouldFailAssertion(
|
||||
{
|
||||
members: {}
|
||||
},
|
||||
'zip'
|
||||
)
|
||||
await shouldFailAssertion(
|
||||
{
|
||||
members: [
|
||||
{
|
||||
nameWRONG: 'jobs',
|
||||
type: 'folder',
|
||||
members: []
|
||||
}
|
||||
]
|
||||
},
|
||||
'zip'
|
||||
)
|
||||
await shouldFailAssertion(
|
||||
{
|
||||
members: [
|
||||
{
|
||||
name: 'jobs',
|
||||
type: 'WRONG',
|
||||
members: []
|
||||
}
|
||||
]
|
||||
},
|
||||
'zip'
|
||||
)
|
||||
await shouldFailAssertion(
|
||||
{
|
||||
members: [
|
||||
{
|
||||
name: 'jobs',
|
||||
type: 'folder',
|
||||
members: [
|
||||
{
|
||||
name: 'extract',
|
||||
type: 'folder',
|
||||
members: [
|
||||
{
|
||||
name: 'makedata1',
|
||||
type: 'service',
|
||||
codeWRONG: '%put Hello World!;'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
'zip'
|
||||
)
|
||||
})
|
||||
|
||||
it('should successfully deploy if valid Zipped file was provided', async () => {
|
||||
const deployContents = JSON.stringify({
|
||||
appLoc: '/public',
|
||||
fileTree: getTreeExample()
|
||||
})
|
||||
|
||||
const zip = new AdmZip()
|
||||
// add file directly
|
||||
zip.addFile(
|
||||
'deploy.json',
|
||||
Buffer.from(deployContents, 'utf8'),
|
||||
'entry comment goes here'
|
||||
)
|
||||
const res = await request(app)
|
||||
.post('/SASjsApi/drive/deploy/upload')
|
||||
.auth(accessToken, { type: 'bearer' })
|
||||
.attach('file', zip.toBuffer(), 'deploy.json.zip')
|
||||
|
||||
expect(res.statusCode).toEqual(200)
|
||||
expect(res.text).toEqual(
|
||||
'{"status":"success","message":"Files deployed successfully to @sasjs/server."}'
|
||||
)
|
||||
await expect(folderExists(getFilesFolder())).resolves.toEqual(true)
|
||||
|
||||
const testJobFolder = path.join(
|
||||
getFilesFolder(),
|
||||
'public',
|
||||
'jobs',
|
||||
'extract'
|
||||
)
|
||||
await expect(folderExists(testJobFolder)).resolves.toEqual(true)
|
||||
|
||||
const exampleService = getExampleService()
|
||||
const testJobFile =
|
||||
path.join(testJobFolder, exampleService.name) + '.sas'
|
||||
|
||||
await expect(fileExists(testJobFile)).resolves.toEqual(true)
|
||||
|
||||
await expect(readFile(testJobFile)).resolves.toEqual(
|
||||
exampleService.code
|
||||
)
|
||||
|
||||
await deleteFolder(path.join(getFilesFolder(), 'public'))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -192,7 +468,7 @@ describe('drive', () => {
|
||||
})
|
||||
|
||||
it('should get a SAS folder on drive having _folderPath as query param', async () => {
|
||||
const pathToDrive = fileUtilModules.getTmpFilesFolderPath()
|
||||
const pathToDrive = fileUtilModules.getFilesFolder()
|
||||
|
||||
const dirLevel1 = 'level1'
|
||||
const dirLevel2 = 'level2'
|
||||
@@ -267,10 +543,7 @@ describe('drive', () => {
|
||||
const fileToCopyPath = path.join(__dirname, 'files', 'sample.sas')
|
||||
const filePath = '/my/path/code.sas'
|
||||
|
||||
const pathToCopy = path.join(
|
||||
fileUtilModules.getTmpFilesFolderPath(),
|
||||
filePath
|
||||
)
|
||||
const pathToCopy = path.join(fileUtilModules.getFilesFolder(), filePath)
|
||||
await copy(fileToCopyPath, pathToCopy)
|
||||
|
||||
const res = await request(app)
|
||||
@@ -333,7 +606,7 @@ describe('drive', () => {
|
||||
const pathToUpload = `/my/path/code-${generateTimestamp()}.sas`
|
||||
|
||||
const pathToCopy = path.join(
|
||||
fileUtilModules.getTmpFilesFolderPath(),
|
||||
fileUtilModules.getFilesFolder(),
|
||||
pathToUpload
|
||||
)
|
||||
await copy(fileToAttachPath, pathToCopy)
|
||||
@@ -445,7 +718,7 @@ describe('drive', () => {
|
||||
const pathToUpload = '/my/path/code.sas'
|
||||
|
||||
const pathToCopy = path.join(
|
||||
fileUtilModules.getTmpFilesFolderPath(),
|
||||
fileUtilModules.getFilesFolder(),
|
||||
pathToUpload
|
||||
)
|
||||
await copy(fileToAttachPath, pathToCopy)
|
||||
@@ -467,7 +740,7 @@ describe('drive', () => {
|
||||
const pathToUpload = '/my/path/code.sas'
|
||||
|
||||
const pathToCopy = path.join(
|
||||
fileUtilModules.getTmpFilesFolderPath(),
|
||||
fileUtilModules.getFilesFolder(),
|
||||
pathToUpload
|
||||
)
|
||||
await copy(fileToAttachPath, pathToCopy)
|
||||
@@ -603,10 +876,7 @@ describe('drive', () => {
|
||||
const fileToCopyContent = await readFile(fileToCopyPath)
|
||||
const filePath = '/my/path/code.sas'
|
||||
|
||||
const pathToCopy = path.join(
|
||||
fileUtilModules.getTmpFilesFolderPath(),
|
||||
filePath
|
||||
)
|
||||
const pathToCopy = path.join(fileUtilModules.getFilesFolder(), filePath)
|
||||
await copy(fileToCopyPath, pathToCopy)
|
||||
|
||||
const res = await request(app)
|
||||
|
||||
@@ -23,7 +23,7 @@ const user = {
|
||||
}
|
||||
|
||||
const group = {
|
||||
name: 'DCGroup1',
|
||||
name: 'dcgroup1',
|
||||
description: 'DC group for testing purposes.'
|
||||
}
|
||||
|
||||
@@ -70,6 +70,32 @@ describe('group', () => {
|
||||
expect(res.body.users).toEqual([])
|
||||
})
|
||||
|
||||
it('should respond with Conflict when group already exists with same name', async () => {
|
||||
await groupController.createGroup(group)
|
||||
|
||||
const res = await request(app)
|
||||
.post('/SASjsApi/group')
|
||||
.auth(adminAccessToken, { type: 'bearer' })
|
||||
.send(group)
|
||||
.expect(409)
|
||||
|
||||
expect(res.text).toEqual('Group name already exists.')
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Bad Request when group name does not match the group name schema', async () => {
|
||||
const res = await request(app)
|
||||
.post('/SASjsApi/group')
|
||||
.auth(adminAccessToken, { type: 'bearer' })
|
||||
.send({ ...group, name: 'Wrong Group Name' })
|
||||
.expect(400)
|
||||
|
||||
expect(res.text).toEqual(
|
||||
'"name" must only contain alpha-numeric characters'
|
||||
)
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Unauthorized if access token is not present', async () => {
|
||||
const res = await request(app).post('/SASjsApi/group').send().expect(401)
|
||||
|
||||
@@ -125,14 +151,51 @@ describe('group', () => {
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Forbidden if groupId is incorrect', async () => {
|
||||
it(`should delete group's reference from users' groups array`, async () => {
|
||||
const dbGroup = await groupController.createGroup(group)
|
||||
const dbUser1 = await userController.createUser({
|
||||
...user,
|
||||
username: 'deletegroup1'
|
||||
})
|
||||
const dbUser2 = await userController.createUser({
|
||||
...user,
|
||||
username: 'deletegroup2'
|
||||
})
|
||||
|
||||
await groupController.addUserToGroup(dbGroup.groupId, dbUser1.id)
|
||||
await groupController.addUserToGroup(dbGroup.groupId, dbUser2.id)
|
||||
|
||||
await request(app)
|
||||
.delete(`/SASjsApi/group/${dbGroup.groupId}`)
|
||||
.auth(adminAccessToken, { type: 'bearer' })
|
||||
.send()
|
||||
.expect(200)
|
||||
|
||||
const res1 = await request(app)
|
||||
.get(`/SASjsApi/user/${dbUser1.id}`)
|
||||
.auth(adminAccessToken, { type: 'bearer' })
|
||||
.send()
|
||||
.expect(200)
|
||||
|
||||
expect(res1.body.groups).toEqual([])
|
||||
|
||||
const res2 = await request(app)
|
||||
.get(`/SASjsApi/user/${dbUser2.id}`)
|
||||
.auth(adminAccessToken, { type: 'bearer' })
|
||||
.send()
|
||||
.expect(200)
|
||||
|
||||
expect(res2.body.groups).toEqual([])
|
||||
})
|
||||
|
||||
it('should respond with Not Found if groupId is incorrect', async () => {
|
||||
const res = await request(app)
|
||||
.delete(`/SASjsApi/group/1234`)
|
||||
.auth(adminAccessToken, { type: 'bearer' })
|
||||
.send()
|
||||
.expect(403)
|
||||
.expect(404)
|
||||
|
||||
expect(res.text).toEqual('Error: No Group deleted!')
|
||||
expect(res.text).toEqual('Group not found.')
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
@@ -216,16 +279,76 @@ describe('group', () => {
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Forbidden if groupId is incorrect', async () => {
|
||||
it('should respond with Not Found if groupId is incorrect', async () => {
|
||||
const res = await request(app)
|
||||
.get('/SASjsApi/group/1234')
|
||||
.auth(adminAccessToken, { type: 'bearer' })
|
||||
.send()
|
||||
.expect(403)
|
||||
.expect(404)
|
||||
|
||||
expect(res.text).toEqual('Error: Group not found.')
|
||||
expect(res.text).toEqual('Group not found.')
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
describe('by group name', () => {
|
||||
it('should respond with group', async () => {
|
||||
const { name } = await groupController.createGroup(group)
|
||||
|
||||
const res = await request(app)
|
||||
.get(`/SASjsApi/group/by/groupname/${name}`)
|
||||
.auth(adminAccessToken, { type: 'bearer' })
|
||||
.send()
|
||||
.expect(200)
|
||||
|
||||
expect(res.body.groupId).toBeTruthy()
|
||||
expect(res.body.name).toEqual(group.name)
|
||||
expect(res.body.description).toEqual(group.description)
|
||||
expect(res.body.isActive).toEqual(true)
|
||||
expect(res.body.users).toEqual([])
|
||||
})
|
||||
|
||||
it('should respond with group when access token is not of an admin account', async () => {
|
||||
const accessToken = await generateSaveTokenAndCreateUser({
|
||||
...user,
|
||||
username: 'getbyname' + user.username
|
||||
})
|
||||
|
||||
const { name } = await groupController.createGroup(group)
|
||||
|
||||
const res = await request(app)
|
||||
.get(`/SASjsApi/group/by/groupname/${name}`)
|
||||
.auth(accessToken, { type: 'bearer' })
|
||||
.send()
|
||||
.expect(200)
|
||||
|
||||
expect(res.body.groupId).toBeTruthy()
|
||||
expect(res.body.name).toEqual(group.name)
|
||||
expect(res.body.description).toEqual(group.description)
|
||||
expect(res.body.isActive).toEqual(true)
|
||||
expect(res.body.users).toEqual([])
|
||||
})
|
||||
|
||||
it('should respond with Unauthorized if access token is not present', async () => {
|
||||
const res = await request(app)
|
||||
.get('/SASjsApi/group/by/groupname/dcgroup')
|
||||
.send()
|
||||
.expect(401)
|
||||
|
||||
expect(res.text).toEqual('Unauthorized')
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Not Found if groupname is incorrect', async () => {
|
||||
const res = await request(app)
|
||||
.get('/SASjsApi/group/by/groupname/randomCharacters')
|
||||
.auth(adminAccessToken, { type: 'bearer' })
|
||||
.send()
|
||||
.expect(404)
|
||||
|
||||
expect(res.text).toEqual('Group not found.')
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('getAll', () => {
|
||||
@@ -245,8 +368,8 @@ describe('group', () => {
|
||||
expect(res.body).toEqual([
|
||||
{
|
||||
groupId: expect.anything(),
|
||||
name: 'DCGroup1',
|
||||
description: 'DC group for testing purposes.'
|
||||
name: group.name,
|
||||
description: group.description
|
||||
}
|
||||
])
|
||||
})
|
||||
@@ -267,8 +390,8 @@ describe('group', () => {
|
||||
expect(res.body).toEqual([
|
||||
{
|
||||
groupId: expect.anything(),
|
||||
name: 'DCGroup1',
|
||||
description: 'DC group for testing purposes.'
|
||||
name: group.name,
|
||||
description: group.description
|
||||
}
|
||||
])
|
||||
})
|
||||
@@ -309,6 +432,34 @@ describe('group', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it(`should add group to user's groups array`, async () => {
|
||||
const dbGroup = await groupController.createGroup(group)
|
||||
const dbUser = await userController.createUser({
|
||||
...user,
|
||||
username: 'addUserToGroup'
|
||||
})
|
||||
|
||||
await request(app)
|
||||
.post(`/SASjsApi/group/${dbGroup.groupId}/${dbUser.id}`)
|
||||
.auth(adminAccessToken, { type: 'bearer' })
|
||||
.send()
|
||||
.expect(200)
|
||||
|
||||
const res = await request(app)
|
||||
.get(`/SASjsApi/user/${dbUser.id}`)
|
||||
.auth(adminAccessToken, { type: 'bearer' })
|
||||
.send()
|
||||
.expect(200)
|
||||
|
||||
expect(res.body.groups).toEqual([
|
||||
{
|
||||
groupId: expect.anything(),
|
||||
name: group.name,
|
||||
description: group.description
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('should respond with group without duplicating user', async () => {
|
||||
const dbGroup = await groupController.createGroup(group)
|
||||
const dbUser = await userController.createUser({
|
||||
@@ -362,26 +513,26 @@ describe('group', () => {
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Forbidden if groupId is incorrect', async () => {
|
||||
it('should respond with Not Found if groupId is incorrect', async () => {
|
||||
const res = await request(app)
|
||||
.post('/SASjsApi/group/123/123')
|
||||
.auth(adminAccessToken, { type: 'bearer' })
|
||||
.send()
|
||||
.expect(403)
|
||||
.expect(404)
|
||||
|
||||
expect(res.text).toEqual('Error: Group not found.')
|
||||
expect(res.text).toEqual('Group not found.')
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Forbidden if userId is incorrect', async () => {
|
||||
it('should respond with Not Found if userId is incorrect', async () => {
|
||||
const dbGroup = await groupController.createGroup(group)
|
||||
const res = await request(app)
|
||||
.post(`/SASjsApi/group/${dbGroup.groupId}/123`)
|
||||
.auth(adminAccessToken, { type: 'bearer' })
|
||||
.send()
|
||||
.expect(403)
|
||||
.expect(404)
|
||||
|
||||
expect(res.text).toEqual('Error: User not found.')
|
||||
expect(res.text).toEqual('User not found.')
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
})
|
||||
@@ -412,6 +563,29 @@ describe('group', () => {
|
||||
expect(res.body.users).toEqual([])
|
||||
})
|
||||
|
||||
it(`should remove group from user's groups array`, async () => {
|
||||
const dbGroup = await groupController.createGroup(group)
|
||||
const dbUser = await userController.createUser({
|
||||
...user,
|
||||
username: 'removeGroupFromUser'
|
||||
})
|
||||
await groupController.addUserToGroup(dbGroup.groupId, dbUser.id)
|
||||
|
||||
await request(app)
|
||||
.delete(`/SASjsApi/group/${dbGroup.groupId}/${dbUser.id}`)
|
||||
.auth(adminAccessToken, { type: 'bearer' })
|
||||
.send()
|
||||
.expect(200)
|
||||
|
||||
const res = await request(app)
|
||||
.get(`/SASjsApi/user/${dbUser.id}`)
|
||||
.auth(adminAccessToken, { type: 'bearer' })
|
||||
.send()
|
||||
.expect(200)
|
||||
|
||||
expect(res.body.groups).toEqual([])
|
||||
})
|
||||
|
||||
it('should respond with Unauthorized if access token is not present', async () => {
|
||||
const res = await request(app)
|
||||
.delete('/SASjsApi/group/123/123')
|
||||
@@ -438,26 +612,26 @@ describe('group', () => {
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Forbidden if groupId is incorrect', async () => {
|
||||
it('should respond with Not Found if groupId is incorrect', async () => {
|
||||
const res = await request(app)
|
||||
.delete('/SASjsApi/group/123/123')
|
||||
.auth(adminAccessToken, { type: 'bearer' })
|
||||
.send()
|
||||
.expect(403)
|
||||
.expect(404)
|
||||
|
||||
expect(res.text).toEqual('Error: Group not found.')
|
||||
expect(res.text).toEqual('Group not found.')
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Forbidden if userId is incorrect', async () => {
|
||||
it('should respond with Not Found if userId is incorrect', async () => {
|
||||
const dbGroup = await groupController.createGroup(group)
|
||||
const res = await request(app)
|
||||
.delete(`/SASjsApi/group/${dbGroup.groupId}/123`)
|
||||
.auth(adminAccessToken, { type: 'bearer' })
|
||||
.send()
|
||||
.expect(403)
|
||||
.expect(404)
|
||||
|
||||
expect(res.text).toEqual('Error: User not found.')
|
||||
expect(res.text).toEqual('User not found.')
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
})
|
||||
|
||||
383
api/src/routes/api/spec/stp.spec.ts
Normal file
383
api/src/routes/api/spec/stp.spec.ts
Normal file
@@ -0,0 +1,383 @@
|
||||
import path from 'path'
|
||||
import { Express } from 'express'
|
||||
import mongoose, { Mongoose } from 'mongoose'
|
||||
import { MongoMemoryServer } from 'mongodb-memory-server'
|
||||
import request from 'supertest'
|
||||
import appPromise from '../../../app'
|
||||
import { UserController } from '../../../controllers/'
|
||||
import {
|
||||
generateAccessToken,
|
||||
saveTokensInDB,
|
||||
getFilesFolder,
|
||||
RunTimeType,
|
||||
generateUniqueFileName,
|
||||
getSessionsFolder
|
||||
} from '../../../utils'
|
||||
import { createFile, generateTimestamp, deleteFolder } from '@sasjs/utils'
|
||||
import {
|
||||
SASSessionController,
|
||||
JSSessionController
|
||||
} from '../../../controllers/internal'
|
||||
import * as ProcessProgramModule from '../../../controllers/internal/processProgram'
|
||||
import { Session } from '../../../types'
|
||||
|
||||
const clientId = 'someclientID'
|
||||
|
||||
const user = {
|
||||
displayName: 'Test User',
|
||||
username: 'testUsername',
|
||||
password: '87654321',
|
||||
isAdmin: false,
|
||||
isActive: true
|
||||
}
|
||||
|
||||
const sampleSasProgram = '%put hello world!;'
|
||||
const sampleJsProgram = `console.log('hello world!/')`
|
||||
|
||||
const filesFolder = getFilesFolder()
|
||||
|
||||
describe('stp', () => {
|
||||
let app: Express
|
||||
let con: Mongoose
|
||||
let mongoServer: MongoMemoryServer
|
||||
let accessToken: string
|
||||
|
||||
beforeAll(async () => {
|
||||
app = await appPromise
|
||||
mongoServer = await MongoMemoryServer.create()
|
||||
con = await mongoose.connect(mongoServer.getUri())
|
||||
accessToken = await generateSaveTokenAndCreateUser(user)
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await con.connection.dropDatabase()
|
||||
await con.connection.close()
|
||||
await mongoServer.stop()
|
||||
})
|
||||
|
||||
describe('execute', () => {
|
||||
const testFilesFolder = `test-stp-${generateTimestamp()}`
|
||||
|
||||
describe('get', () => {
|
||||
describe('with runtime js', () => {
|
||||
const testFilesFolder = `test-stp-${generateTimestamp()}`
|
||||
|
||||
beforeAll(() => {
|
||||
process.runTimes = [RunTimeType.JS]
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules() // it clears the cache
|
||||
setupMocks()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
jest.resetAllMocks()
|
||||
await deleteFolder(path.join(filesFolder, testFilesFolder))
|
||||
})
|
||||
|
||||
it('should execute js program when both js and sas program are present', async () => {
|
||||
const programPath = path.join(testFilesFolder, 'program')
|
||||
const sasProgramPath = path.join(filesFolder, `${programPath}.sas`)
|
||||
const jsProgramPath = path.join(filesFolder, `${programPath}.js`)
|
||||
await createFile(sasProgramPath, sampleSasProgram)
|
||||
await createFile(jsProgramPath, sampleJsProgram)
|
||||
|
||||
await request(app)
|
||||
.get(`/SASjsApi/stp/execute?_program=${programPath}`)
|
||||
.auth(accessToken, { type: 'bearer' })
|
||||
.send()
|
||||
.expect(200)
|
||||
|
||||
expect(ProcessProgramModule.processProgram).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
RunTimeType.JS,
|
||||
expect.anything(),
|
||||
undefined
|
||||
)
|
||||
})
|
||||
|
||||
it('should throw error when js program is not present but sas program exists', async () => {
|
||||
const programPath = path.join(testFilesFolder, 'program')
|
||||
const sasProgramPath = path.join(filesFolder, `${programPath}.sas`)
|
||||
await createFile(sasProgramPath, sampleSasProgram)
|
||||
|
||||
await request(app)
|
||||
.get(`/SASjsApi/stp/execute?_program=${programPath}`)
|
||||
.auth(accessToken, { type: 'bearer' })
|
||||
.send()
|
||||
.expect(400)
|
||||
})
|
||||
})
|
||||
|
||||
describe('with runtime sas', () => {
|
||||
beforeAll(() => {
|
||||
process.runTimes = [RunTimeType.SAS]
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules() // it clears the cache
|
||||
setupMocks()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
jest.resetAllMocks()
|
||||
await deleteFolder(path.join(filesFolder, testFilesFolder))
|
||||
})
|
||||
|
||||
it('should execute sas program when both sas and js programs are present', async () => {
|
||||
const programPath = path.join(testFilesFolder, 'program')
|
||||
const sasProgramPath = path.join(filesFolder, `${programPath}.sas`)
|
||||
const jsProgramPath = path.join(filesFolder, `${programPath}.js`)
|
||||
await createFile(sasProgramPath, sampleSasProgram)
|
||||
await createFile(jsProgramPath, sampleJsProgram)
|
||||
|
||||
await request(app)
|
||||
.get(`/SASjsApi/stp/execute?_program=${programPath}`)
|
||||
.auth(accessToken, { type: 'bearer' })
|
||||
.send()
|
||||
.expect(200)
|
||||
|
||||
expect(ProcessProgramModule.processProgram).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
RunTimeType.SAS,
|
||||
expect.anything(),
|
||||
undefined
|
||||
)
|
||||
})
|
||||
|
||||
it('should throw error when sas program do not exit but js exists', async () => {
|
||||
const programPath = path.join(testFilesFolder, 'program')
|
||||
const jsProgramPath = path.join(filesFolder, `${programPath}.js`)
|
||||
await createFile(jsProgramPath, sampleJsProgram)
|
||||
|
||||
await request(app)
|
||||
.get(`/SASjsApi/stp/execute?_program=${programPath}`)
|
||||
.auth(accessToken, { type: 'bearer' })
|
||||
.send()
|
||||
.expect(400)
|
||||
})
|
||||
})
|
||||
|
||||
describe('with runtime js and sas', () => {
|
||||
beforeAll(() => {
|
||||
process.runTimes = [RunTimeType.JS, RunTimeType.SAS]
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules() // it clears the cache
|
||||
setupMocks()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
jest.resetAllMocks()
|
||||
await deleteFolder(path.join(filesFolder, testFilesFolder))
|
||||
})
|
||||
|
||||
it('should execute js program when both js and sas program are present', async () => {
|
||||
const programPath = path.join(testFilesFolder, 'program')
|
||||
const sasProgramPath = path.join(filesFolder, `${programPath}.sas`)
|
||||
const jsProgramPath = path.join(filesFolder, `${programPath}.js`)
|
||||
await createFile(sasProgramPath, sampleSasProgram)
|
||||
await createFile(jsProgramPath, sampleJsProgram)
|
||||
|
||||
await request(app)
|
||||
.get(`/SASjsApi/stp/execute?_program=${programPath}`)
|
||||
.auth(accessToken, { type: 'bearer' })
|
||||
.send()
|
||||
.expect(200)
|
||||
|
||||
expect(ProcessProgramModule.processProgram).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
RunTimeType.JS,
|
||||
expect.anything(),
|
||||
undefined
|
||||
)
|
||||
})
|
||||
|
||||
it('should execute sas program when js program is not present but sas program exists', async () => {
|
||||
const programPath = path.join(testFilesFolder, 'program')
|
||||
const sasProgramPath = path.join(filesFolder, `${programPath}.sas`)
|
||||
await createFile(sasProgramPath, sampleSasProgram)
|
||||
|
||||
await request(app)
|
||||
.get(`/SASjsApi/stp/execute?_program=${programPath}`)
|
||||
.auth(accessToken, { type: 'bearer' })
|
||||
.send()
|
||||
.expect(200)
|
||||
|
||||
expect(ProcessProgramModule.processProgram).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
RunTimeType.SAS,
|
||||
expect.anything(),
|
||||
undefined
|
||||
)
|
||||
})
|
||||
|
||||
it('should throw error when both sas and js programs do not exist', async () => {
|
||||
const programPath = path.join(testFilesFolder, 'program')
|
||||
|
||||
await request(app)
|
||||
.get(`/SASjsApi/stp/execute?_program=${programPath}`)
|
||||
.auth(accessToken, { type: 'bearer' })
|
||||
.send()
|
||||
.expect(400)
|
||||
})
|
||||
})
|
||||
|
||||
describe('with runtime sas and js', () => {
|
||||
beforeAll(() => {
|
||||
process.runTimes = [RunTimeType.SAS, RunTimeType.JS]
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules() // it clears the cache
|
||||
setupMocks()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
jest.resetAllMocks()
|
||||
await deleteFolder(path.join(filesFolder, testFilesFolder))
|
||||
})
|
||||
|
||||
it('should execute sas program when both sas and js programs exist', async () => {
|
||||
const programPath = path.join(testFilesFolder, 'program')
|
||||
const sasProgramPath = path.join(filesFolder, `${programPath}.sas`)
|
||||
const jsProgramPath = path.join(filesFolder, `${programPath}.js`)
|
||||
await createFile(sasProgramPath, sampleSasProgram)
|
||||
await createFile(jsProgramPath, sampleJsProgram)
|
||||
|
||||
await request(app)
|
||||
.get(`/SASjsApi/stp/execute?_program=${programPath}`)
|
||||
.auth(accessToken, { type: 'bearer' })
|
||||
.send()
|
||||
.expect(200)
|
||||
|
||||
expect(ProcessProgramModule.processProgram).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
RunTimeType.SAS,
|
||||
expect.anything(),
|
||||
undefined
|
||||
)
|
||||
})
|
||||
|
||||
it('should execute js program when sas program is not present but js program exists', async () => {
|
||||
const programPath = path.join(testFilesFolder, 'program')
|
||||
const jsProgramPath = path.join(filesFolder, `${programPath}.js`)
|
||||
await createFile(jsProgramPath, sampleJsProgram)
|
||||
|
||||
await request(app)
|
||||
.get(`/SASjsApi/stp/execute?_program=${programPath}`)
|
||||
.auth(accessToken, { type: 'bearer' })
|
||||
.send()
|
||||
.expect(200)
|
||||
|
||||
expect(ProcessProgramModule.processProgram).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
RunTimeType.JS,
|
||||
expect.anything(),
|
||||
undefined
|
||||
)
|
||||
})
|
||||
|
||||
it('should throw error when both sas and js programs do not exist', async () => {
|
||||
const programPath = path.join(testFilesFolder, 'program')
|
||||
|
||||
await request(app)
|
||||
.get(`/SASjsApi/stp/execute?_program=${programPath}`)
|
||||
.auth(accessToken, { type: 'bearer' })
|
||||
.send()
|
||||
.expect(400)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const generateSaveTokenAndCreateUser = async (
|
||||
someUser: any
|
||||
): Promise<string> => {
|
||||
const userController = new UserController()
|
||||
const dbUser = await userController.createUser(someUser)
|
||||
|
||||
return generateAndSaveToken(dbUser.id)
|
||||
}
|
||||
|
||||
const generateAndSaveToken = async (userId: number) => {
|
||||
const accessToken = generateAccessToken({
|
||||
clientId,
|
||||
userId
|
||||
})
|
||||
await saveTokensInDB(userId, clientId, accessToken, 'refreshToken')
|
||||
return accessToken
|
||||
}
|
||||
|
||||
const setupMocks = async () => {
|
||||
jest
|
||||
.spyOn(SASSessionController.prototype, 'getSession')
|
||||
.mockImplementation(mockedGetSession)
|
||||
|
||||
jest
|
||||
.spyOn(JSSessionController.prototype, 'getSession')
|
||||
.mockImplementation(mockedGetSession)
|
||||
|
||||
jest
|
||||
.spyOn(ProcessProgramModule, 'processProgram')
|
||||
.mockImplementation(() => Promise.resolve())
|
||||
}
|
||||
|
||||
const mockedGetSession = async () => {
|
||||
const sessionId = generateUniqueFileName(generateTimestamp())
|
||||
const sessionFolder = path.join(getSessionsFolder(), sessionId)
|
||||
|
||||
const creationTimeStamp = sessionId.split('-').pop() as string
|
||||
// death time of session is 15 mins from creation
|
||||
const deathTimeStamp = (
|
||||
parseInt(creationTimeStamp) +
|
||||
15 * 60 * 1000 -
|
||||
1000
|
||||
).toString()
|
||||
|
||||
const session: Session = {
|
||||
id: sessionId,
|
||||
ready: true,
|
||||
inUse: true,
|
||||
consumed: false,
|
||||
completed: false,
|
||||
creationTimeStamp,
|
||||
deathTimeStamp,
|
||||
path: sessionFolder
|
||||
}
|
||||
|
||||
return session
|
||||
}
|
||||
@@ -3,23 +3,24 @@ import mongoose, { Mongoose } from 'mongoose'
|
||||
import { MongoMemoryServer } from 'mongodb-memory-server'
|
||||
import request from 'supertest'
|
||||
import appPromise from '../../../app'
|
||||
import { UserController } from '../../../controllers/'
|
||||
import { UserController, GroupController } from '../../../controllers/'
|
||||
import { generateAccessToken, saveTokensInDB } from '../../../utils'
|
||||
|
||||
const clientId = 'someclientID'
|
||||
const adminUser = {
|
||||
displayName: 'Test Admin',
|
||||
username: 'testAdminUsername',
|
||||
username: 'testadminusername',
|
||||
password: '12345678',
|
||||
isAdmin: true,
|
||||
isActive: true
|
||||
}
|
||||
const user = {
|
||||
displayName: 'Test User',
|
||||
username: 'testUsername',
|
||||
username: 'testusername',
|
||||
password: '87654321',
|
||||
isAdmin: false,
|
||||
isActive: true
|
||||
isActive: true,
|
||||
autoExec: 'some sas code for auto exec;'
|
||||
}
|
||||
|
||||
const controller = new UserController()
|
||||
@@ -64,6 +65,21 @@ describe('user', () => {
|
||||
expect(res.body.displayName).toEqual(user.displayName)
|
||||
expect(res.body.isAdmin).toEqual(user.isAdmin)
|
||||
expect(res.body.isActive).toEqual(user.isActive)
|
||||
expect(res.body.autoExec).toEqual(user.autoExec)
|
||||
})
|
||||
|
||||
it('should respond with new user having username as lowercase', async () => {
|
||||
const res = await request(app)
|
||||
.post('/SASjsApi/user')
|
||||
.auth(adminAccessToken, { type: 'bearer' })
|
||||
.send({ ...user, username: user.username.toUpperCase() })
|
||||
.expect(200)
|
||||
|
||||
expect(res.body.username).toEqual(user.username)
|
||||
expect(res.body.displayName).toEqual(user.displayName)
|
||||
expect(res.body.isAdmin).toEqual(user.isAdmin)
|
||||
expect(res.body.isActive).toEqual(user.isActive)
|
||||
expect(res.body.autoExec).toEqual(user.autoExec)
|
||||
})
|
||||
|
||||
it('should respond with Unauthorized if access token is not present', async () => {
|
||||
@@ -242,7 +258,7 @@ describe('user', () => {
|
||||
const dbUser1 = await controller.createUser(user)
|
||||
const dbUser2 = await controller.createUser({
|
||||
...user,
|
||||
username: 'randomUser'
|
||||
username: 'randomuser'
|
||||
})
|
||||
|
||||
const res = await request(app)
|
||||
@@ -254,6 +270,102 @@ describe('user', () => {
|
||||
expect(res.text).toEqual('Error: Username already exists.')
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
describe('by username', () => {
|
||||
it('should respond with updated user when admin user requests', async () => {
|
||||
const dbUser = await controller.createUser(user)
|
||||
const newDisplayName = 'My new display Name'
|
||||
|
||||
const res = await request(app)
|
||||
.patch(`/SASjsApi/user/by/username/${user.username}`)
|
||||
.auth(adminAccessToken, { type: 'bearer' })
|
||||
.send({ ...user, displayName: newDisplayName })
|
||||
.expect(200)
|
||||
|
||||
expect(res.body.username).toEqual(user.username)
|
||||
expect(res.body.displayName).toEqual(newDisplayName)
|
||||
expect(res.body.isAdmin).toEqual(user.isAdmin)
|
||||
expect(res.body.isActive).toEqual(user.isActive)
|
||||
})
|
||||
|
||||
it('should respond with updated user when user himself requests', async () => {
|
||||
const dbUser = await controller.createUser(user)
|
||||
const accessToken = await generateAndSaveToken(dbUser.id)
|
||||
const newDisplayName = 'My new display Name'
|
||||
|
||||
const res = await request(app)
|
||||
.patch(`/SASjsApi/user/by/username/${user.username}`)
|
||||
.auth(accessToken, { type: 'bearer' })
|
||||
.send({
|
||||
displayName: newDisplayName,
|
||||
username: user.username,
|
||||
password: user.password
|
||||
})
|
||||
.expect(200)
|
||||
|
||||
expect(res.body.username).toEqual(user.username)
|
||||
expect(res.body.displayName).toEqual(newDisplayName)
|
||||
expect(res.body.isAdmin).toEqual(user.isAdmin)
|
||||
expect(res.body.isActive).toEqual(user.isActive)
|
||||
})
|
||||
|
||||
it('should respond with Bad Request, only admin can update isAdmin/isActive', async () => {
|
||||
const dbUser = await controller.createUser(user)
|
||||
const accessToken = await generateAndSaveToken(dbUser.id)
|
||||
const newDisplayName = 'My new display Name'
|
||||
|
||||
await request(app)
|
||||
.patch(`/SASjsApi/user/by/username/${user.username}`)
|
||||
.auth(accessToken, { type: 'bearer' })
|
||||
.send({ ...user, displayName: newDisplayName })
|
||||
.expect(400)
|
||||
})
|
||||
|
||||
it('should respond with Unauthorized if access token is not present', async () => {
|
||||
const res = await request(app)
|
||||
.patch('/SASjsApi/user/by/username/1234')
|
||||
.send(user)
|
||||
.expect(401)
|
||||
|
||||
expect(res.text).toEqual('Unauthorized')
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Unauthorized when access token is not of an admin account or himself', async () => {
|
||||
const dbUser1 = await controller.createUser(user)
|
||||
const dbUser2 = await controller.createUser({
|
||||
...user,
|
||||
username: 'randomUser'
|
||||
})
|
||||
const accessToken = await generateAndSaveToken(dbUser2.id)
|
||||
|
||||
const res = await request(app)
|
||||
.patch(`/SASjsApi/user/${dbUser1.id}`)
|
||||
.auth(accessToken, { type: 'bearer' })
|
||||
.send(user)
|
||||
.expect(401)
|
||||
|
||||
expect(res.text).toEqual('Admin account required')
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Forbidden if username is already present', async () => {
|
||||
const dbUser1 = await controller.createUser(user)
|
||||
const dbUser2 = await controller.createUser({
|
||||
...user,
|
||||
username: 'randomuser'
|
||||
})
|
||||
|
||||
const res = await request(app)
|
||||
.patch(`/SASjsApi/user/by/username/${dbUser1.username}`)
|
||||
.auth(adminAccessToken, { type: 'bearer' })
|
||||
.send({ username: dbUser2.username })
|
||||
.expect(403)
|
||||
|
||||
expect(res.text).toEqual('Error: Username already exists.')
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('delete', () => {
|
||||
@@ -347,6 +459,89 @@ describe('user', () => {
|
||||
expect(res.text).toEqual('Error: Invalid password.')
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
describe('by username', () => {
|
||||
it('should respond with OK when admin user requests', async () => {
|
||||
const dbUser = await controller.createUser(user)
|
||||
|
||||
const res = await request(app)
|
||||
.delete(`/SASjsApi/user/by/username/${dbUser.username}`)
|
||||
.auth(adminAccessToken, { type: 'bearer' })
|
||||
.send()
|
||||
.expect(200)
|
||||
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with OK when user himself requests', async () => {
|
||||
const dbUser = await controller.createUser(user)
|
||||
const accessToken = await generateAndSaveToken(dbUser.id)
|
||||
|
||||
const res = await request(app)
|
||||
.delete(`/SASjsApi/user/by/username/${dbUser.username}`)
|
||||
.auth(accessToken, { type: 'bearer' })
|
||||
.send({ password: user.password })
|
||||
.expect(200)
|
||||
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Bad Request when user himself requests and password is missing', async () => {
|
||||
const dbUser = await controller.createUser(user)
|
||||
const accessToken = await generateAndSaveToken(dbUser.id)
|
||||
|
||||
const res = await request(app)
|
||||
.delete(`/SASjsApi/user/by/username/${dbUser.username}`)
|
||||
.auth(accessToken, { type: 'bearer' })
|
||||
.send()
|
||||
.expect(400)
|
||||
|
||||
expect(res.text).toEqual(`"password" is required`)
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Unauthorized when access token is not present', async () => {
|
||||
const res = await request(app)
|
||||
.delete('/SASjsApi/user/by/username/RandomUsername')
|
||||
.send(user)
|
||||
.expect(401)
|
||||
|
||||
expect(res.text).toEqual('Unauthorized')
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Unauthorized when access token is not of an admin account or himself', async () => {
|
||||
const dbUser1 = await controller.createUser(user)
|
||||
const dbUser2 = await controller.createUser({
|
||||
...user,
|
||||
username: 'randomUser'
|
||||
})
|
||||
const accessToken = await generateAndSaveToken(dbUser2.id)
|
||||
|
||||
const res = await request(app)
|
||||
.delete(`/SASjsApi/user/by/username/${dbUser1.username}`)
|
||||
.auth(accessToken, { type: 'bearer' })
|
||||
.send(user)
|
||||
.expect(401)
|
||||
|
||||
expect(res.text).toEqual('Admin account required')
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Forbidden when user himself requests and password is incorrect', async () => {
|
||||
const dbUser = await controller.createUser(user)
|
||||
const accessToken = await generateAndSaveToken(dbUser.id)
|
||||
|
||||
const res = await request(app)
|
||||
.delete(`/SASjsApi/user/by/username/${dbUser.username}`)
|
||||
.auth(accessToken, { type: 'bearer' })
|
||||
.send({ password: 'incorrectpassword' })
|
||||
.expect(403)
|
||||
|
||||
expect(res.text).toEqual('Error: Invalid password.')
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('get', () => {
|
||||
@@ -360,7 +555,26 @@ describe('user', () => {
|
||||
await deleteAllUsers()
|
||||
})
|
||||
|
||||
it('should respond with user', async () => {
|
||||
it('should respond with user autoExec when same user requests', async () => {
|
||||
const dbUser = await controller.createUser(user)
|
||||
const userId = dbUser.id
|
||||
const accessToken = await generateAndSaveToken(userId)
|
||||
|
||||
const res = await request(app)
|
||||
.get(`/SASjsApi/user/${userId}`)
|
||||
.auth(accessToken, { type: 'bearer' })
|
||||
.send()
|
||||
.expect(200)
|
||||
|
||||
expect(res.body.username).toEqual(user.username)
|
||||
expect(res.body.displayName).toEqual(user.displayName)
|
||||
expect(res.body.isAdmin).toEqual(user.isAdmin)
|
||||
expect(res.body.isActive).toEqual(user.isActive)
|
||||
expect(res.body.autoExec).toEqual(user.autoExec)
|
||||
expect(res.body.groups).toEqual([])
|
||||
})
|
||||
|
||||
it('should respond with user autoExec when admin user requests', async () => {
|
||||
const dbUser = await controller.createUser(user)
|
||||
const userId = dbUser.id
|
||||
|
||||
@@ -374,6 +588,8 @@ describe('user', () => {
|
||||
expect(res.body.displayName).toEqual(user.displayName)
|
||||
expect(res.body.isAdmin).toEqual(user.isAdmin)
|
||||
expect(res.body.isActive).toEqual(user.isActive)
|
||||
expect(res.body.autoExec).toEqual(user.autoExec)
|
||||
expect(res.body.groups).toEqual([])
|
||||
})
|
||||
|
||||
it('should respond with user when access token is not of an admin account', async () => {
|
||||
@@ -395,6 +611,35 @@ describe('user', () => {
|
||||
expect(res.body.displayName).toEqual(user.displayName)
|
||||
expect(res.body.isAdmin).toEqual(user.isAdmin)
|
||||
expect(res.body.isActive).toEqual(user.isActive)
|
||||
expect(res.body.autoExec).toBeUndefined()
|
||||
expect(res.body.groups).toEqual([])
|
||||
})
|
||||
|
||||
it('should respond with user along with associated groups', async () => {
|
||||
const dbUser = await controller.createUser(user)
|
||||
const userId = dbUser.id
|
||||
const accessToken = await generateAndSaveToken(userId)
|
||||
|
||||
const group = {
|
||||
name: 'DCGroup1',
|
||||
description: 'DC group for testing purposes.'
|
||||
}
|
||||
const groupController = new GroupController()
|
||||
const dbGroup = await groupController.createGroup(group)
|
||||
await groupController.addUserToGroup(dbGroup.groupId, dbUser.id)
|
||||
|
||||
const res = await request(app)
|
||||
.get(`/SASjsApi/user/${userId}`)
|
||||
.auth(accessToken, { type: 'bearer' })
|
||||
.send()
|
||||
.expect(200)
|
||||
|
||||
expect(res.body.username).toEqual(user.username)
|
||||
expect(res.body.displayName).toEqual(user.displayName)
|
||||
expect(res.body.isAdmin).toEqual(user.isAdmin)
|
||||
expect(res.body.isActive).toEqual(user.isActive)
|
||||
expect(res.body.autoExec).toEqual(user.autoExec)
|
||||
expect(res.body.groups.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('should respond with Unauthorized if access token is not present', async () => {
|
||||
@@ -419,6 +664,86 @@ describe('user', () => {
|
||||
expect(res.text).toEqual('Error: User is not found.')
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
describe('by username', () => {
|
||||
it('should respond with user autoExec when same user requests', async () => {
|
||||
const dbUser = await controller.createUser(user)
|
||||
const userId = dbUser.id
|
||||
const accessToken = await generateAndSaveToken(userId)
|
||||
|
||||
const res = await request(app)
|
||||
.get(`/SASjsApi/user/by/username/${dbUser.username}`)
|
||||
.auth(accessToken, { type: 'bearer' })
|
||||
.send()
|
||||
.expect(200)
|
||||
|
||||
expect(res.body.username).toEqual(user.username)
|
||||
expect(res.body.displayName).toEqual(user.displayName)
|
||||
expect(res.body.isAdmin).toEqual(user.isAdmin)
|
||||
expect(res.body.isActive).toEqual(user.isActive)
|
||||
expect(res.body.autoExec).toEqual(user.autoExec)
|
||||
})
|
||||
|
||||
it('should respond with user autoExec when admin user requests', async () => {
|
||||
const dbUser = await controller.createUser(user)
|
||||
|
||||
const res = await request(app)
|
||||
.get(`/SASjsApi/user/by/username/${dbUser.username}`)
|
||||
.auth(adminAccessToken, { type: 'bearer' })
|
||||
.send()
|
||||
.expect(200)
|
||||
|
||||
expect(res.body.username).toEqual(user.username)
|
||||
expect(res.body.displayName).toEqual(user.displayName)
|
||||
expect(res.body.isAdmin).toEqual(user.isAdmin)
|
||||
expect(res.body.isActive).toEqual(user.isActive)
|
||||
expect(res.body.autoExec).toEqual(user.autoExec)
|
||||
})
|
||||
|
||||
it('should respond with user when access token is not of an admin account', async () => {
|
||||
const accessToken = await generateSaveTokenAndCreateUser({
|
||||
...user,
|
||||
username: 'randomUser'
|
||||
})
|
||||
|
||||
const dbUser = await controller.createUser(user)
|
||||
|
||||
const res = await request(app)
|
||||
.get(`/SASjsApi/user/by/username/${dbUser.username}`)
|
||||
.auth(accessToken, { type: 'bearer' })
|
||||
.send()
|
||||
.expect(200)
|
||||
|
||||
expect(res.body.username).toEqual(user.username)
|
||||
expect(res.body.displayName).toEqual(user.displayName)
|
||||
expect(res.body.isAdmin).toEqual(user.isAdmin)
|
||||
expect(res.body.isActive).toEqual(user.isActive)
|
||||
expect(res.body.autoExec).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should respond with Unauthorized if access token is not present', async () => {
|
||||
const res = await request(app)
|
||||
.get('/SASjsApi/user/by/username/randomUsername')
|
||||
.send()
|
||||
.expect(401)
|
||||
|
||||
expect(res.text).toEqual('Unauthorized')
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Forbidden if username is incorrect', async () => {
|
||||
await controller.createUser(user)
|
||||
|
||||
const res = await request(app)
|
||||
.get('/SASjsApi/user/by/username/randomUsername')
|
||||
.auth(adminAccessToken, { type: 'bearer' })
|
||||
.send()
|
||||
.expect(403)
|
||||
|
||||
expect(res.text).toEqual('Error: User is not found.')
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('getAll', () => {
|
||||
|
||||
182
api/src/routes/api/spec/web.spec.ts
Normal file
182
api/src/routes/api/spec/web.spec.ts
Normal file
@@ -0,0 +1,182 @@
|
||||
import { Express } from 'express'
|
||||
import mongoose, { Mongoose } from 'mongoose'
|
||||
import { MongoMemoryServer } from 'mongodb-memory-server'
|
||||
import request from 'supertest'
|
||||
import appPromise from '../../../app'
|
||||
import { UserController, ClientController } from '../../../controllers/'
|
||||
|
||||
const clientId = 'someclientID'
|
||||
const clientSecret = 'someclientSecret'
|
||||
const user = {
|
||||
id: 1234,
|
||||
displayName: 'Test User',
|
||||
username: 'testusername',
|
||||
password: '87654321',
|
||||
isAdmin: false,
|
||||
isActive: true
|
||||
}
|
||||
|
||||
describe('web', () => {
|
||||
let app: Express
|
||||
let con: Mongoose
|
||||
let mongoServer: MongoMemoryServer
|
||||
const userController = new UserController()
|
||||
const clientController = new ClientController()
|
||||
|
||||
beforeAll(async () => {
|
||||
app = await appPromise
|
||||
|
||||
mongoServer = await MongoMemoryServer.create()
|
||||
con = await mongoose.connect(mongoServer.getUri())
|
||||
await clientController.createClient({ clientId, clientSecret })
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await con.connection.dropDatabase()
|
||||
await con.connection.close()
|
||||
await mongoServer.stop()
|
||||
})
|
||||
|
||||
describe('home', () => {
|
||||
it('should respond with CSRF Token', async () => {
|
||||
await request(app)
|
||||
.get('/')
|
||||
.expect(
|
||||
'set-cookie',
|
||||
/_csrf=.*; Max-Age=86400000; Path=\/; HttpOnly,XSRF-TOKEN=.*; Path=\//
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('SASLogon/login', () => {
|
||||
let csrfToken: string
|
||||
let cookies: string
|
||||
|
||||
beforeAll(async () => {
|
||||
;({ csrfToken, cookies } = await getCSRF(app))
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
const collections = mongoose.connection.collections
|
||||
const collection = collections['users']
|
||||
await collection.deleteMany({})
|
||||
})
|
||||
|
||||
it('should respond with successful login', async () => {
|
||||
await userController.createUser(user)
|
||||
|
||||
const res = await request(app)
|
||||
.post('/SASLogon/login')
|
||||
.set('Cookie', cookies)
|
||||
.set('x-xsrf-token', csrfToken)
|
||||
.send({
|
||||
username: user.username,
|
||||
password: user.password
|
||||
})
|
||||
.expect(200)
|
||||
|
||||
expect(res.body.loggedIn).toBeTruthy()
|
||||
expect(res.body.user).toEqual({
|
||||
id: expect.any(Number),
|
||||
username: user.username,
|
||||
displayName: user.displayName
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('SASLogon/authorize', () => {
|
||||
let csrfToken: string
|
||||
let cookies: string
|
||||
let authCookies: string
|
||||
|
||||
beforeAll(async () => {
|
||||
;({ csrfToken, cookies } = await getCSRF(app))
|
||||
|
||||
await userController.createUser(user)
|
||||
|
||||
const credentials = {
|
||||
username: user.username,
|
||||
password: user.password
|
||||
}
|
||||
|
||||
;({ cookies: authCookies } = await performLogin(
|
||||
app,
|
||||
credentials,
|
||||
cookies,
|
||||
csrfToken
|
||||
))
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
const collections = mongoose.connection.collections
|
||||
const collection = collections['users']
|
||||
await collection.deleteMany({})
|
||||
})
|
||||
|
||||
it('should respond with authorization code', async () => {
|
||||
const res = await request(app)
|
||||
.post('/SASLogon/authorize')
|
||||
.set('Cookie', [authCookies, cookies].join('; '))
|
||||
.set('x-xsrf-token', csrfToken)
|
||||
.send({ clientId })
|
||||
|
||||
expect(res.body).toHaveProperty('code')
|
||||
})
|
||||
|
||||
it('should respond with Bad Request if clientId is missing', async () => {
|
||||
const res = await request(app)
|
||||
.post('/SASLogon/authorize')
|
||||
.set('Cookie', [authCookies, cookies].join('; '))
|
||||
.set('x-xsrf-token', csrfToken)
|
||||
.send({})
|
||||
.expect(400)
|
||||
|
||||
expect(res.text).toEqual(`"clientId" is required`)
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Forbidden if clientId is incorrect', async () => {
|
||||
const res = await request(app)
|
||||
.post('/SASLogon/authorize')
|
||||
.set('Cookie', [authCookies, cookies].join('; '))
|
||||
.set('x-xsrf-token', csrfToken)
|
||||
.send({
|
||||
clientId: 'WrongClientID'
|
||||
})
|
||||
.expect(403)
|
||||
|
||||
expect(res.text).toEqual('Error: Invalid clientId.')
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const getCSRF = async (app: Express) => {
|
||||
// make request to get CSRF
|
||||
const { header } = await request(app).get('/')
|
||||
const cookies = header['set-cookie'].join()
|
||||
|
||||
const csrfToken = extractCSRF(cookies)
|
||||
return { csrfToken, cookies }
|
||||
}
|
||||
|
||||
const performLogin = async (
|
||||
app: Express,
|
||||
credentials: { username: string; password: string },
|
||||
cookies: string,
|
||||
csrfToken: string
|
||||
) => {
|
||||
const { header } = await request(app)
|
||||
.post('/SASLogon/login')
|
||||
.set('Cookie', cookies)
|
||||
.set('x-xsrf-token', csrfToken)
|
||||
.send(credentials)
|
||||
|
||||
const newCookies: string = header['set-cookie'].join()
|
||||
return { cookies: newCookies }
|
||||
}
|
||||
|
||||
const extractCSRF = (cookies: string) =>
|
||||
/_csrf=(.*); Max-Age=86400000; Path=\/; HttpOnly,XSRF-TOKEN=(.*); Path=\//.exec(
|
||||
cookies
|
||||
)![2]
|
||||
@@ -34,23 +34,25 @@ stpRouter.post(
|
||||
'/execute',
|
||||
fileUploadController.preUploadMiddleware,
|
||||
fileUploadController.getMulterUploadObject().any(),
|
||||
async (req: any, res: any) => {
|
||||
const { error: errQ, value: query } = executeProgramRawValidation(req.query)
|
||||
const { error: errB, value: body } = executeProgramRawValidation(req.body)
|
||||
async (req, res: any) => {
|
||||
// below validations are moved to preUploadMiddleware
|
||||
// const { error: errQ, value: query } = executeProgramRawValidation(req.query)
|
||||
// const { error: errB, value: body } = executeProgramRawValidation(req.body)
|
||||
|
||||
if (errQ && errB) return res.status(400).send(errB.details[0].message)
|
||||
// if (errQ && errB) return res.status(400).send(errB.details[0].message)
|
||||
|
||||
try {
|
||||
const response = await controller.executeReturnJson(
|
||||
req,
|
||||
body,
|
||||
query?._program
|
||||
req.body,
|
||||
req.query?._program as string
|
||||
)
|
||||
|
||||
if (response instanceof Buffer) {
|
||||
res.writeHead(200, (req as any).sasHeaders)
|
||||
return res.end(response)
|
||||
}
|
||||
// TODO: investigate if this code is required
|
||||
// if (response instanceof Buffer) {
|
||||
// res.writeHead(200, (req as any).sasHeaders)
|
||||
// return res.end(response)
|
||||
// }
|
||||
|
||||
res.send(response)
|
||||
} catch (err: any) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from '../../middlewares'
|
||||
import {
|
||||
deleteUserValidation,
|
||||
getUserValidation,
|
||||
registerUserValidation,
|
||||
updateUserValidation
|
||||
} from '../../utils'
|
||||
@@ -36,12 +37,31 @@ userRouter.get('/', authenticateAccessToken, async (req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
userRouter.get('/:userId', authenticateAccessToken, async (req: any, res) => {
|
||||
userRouter.get(
|
||||
'/by/username/:username',
|
||||
authenticateAccessToken,
|
||||
async (req, res) => {
|
||||
const { error, value: params } = getUserValidation(req.params)
|
||||
if (error) return res.status(400).send(error.details[0].message)
|
||||
|
||||
const { username } = params
|
||||
|
||||
const controller = new UserController()
|
||||
try {
|
||||
const response = await controller.getUserByUsername(req, username)
|
||||
res.send(response)
|
||||
} catch (err: any) {
|
||||
res.status(403).send(err.toString())
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
userRouter.get('/:userId', authenticateAccessToken, async (req, res) => {
|
||||
const { userId } = req.params
|
||||
|
||||
const controller = new UserController()
|
||||
try {
|
||||
const response = await controller.getUser(userId)
|
||||
const response = await controller.getUser(req, parseInt(userId))
|
||||
res.send(response)
|
||||
} catch (err: any) {
|
||||
res.status(403).send(err.toString())
|
||||
@@ -49,20 +69,26 @@ userRouter.get('/:userId', authenticateAccessToken, async (req: any, res) => {
|
||||
})
|
||||
|
||||
userRouter.patch(
|
||||
'/:userId',
|
||||
'/by/username/:username',
|
||||
authenticateAccessToken,
|
||||
verifyAdminIfNeeded,
|
||||
async (req: any, res) => {
|
||||
async (req, res) => {
|
||||
const { user } = req
|
||||
const { userId } = req.params
|
||||
const { error: errorUsername, value: params } = getUserValidation(
|
||||
req.params
|
||||
)
|
||||
if (errorUsername)
|
||||
return res.status(400).send(errorUsername.details[0].message)
|
||||
|
||||
const { username } = params
|
||||
|
||||
// only an admin can update `isActive` and `isAdmin` fields
|
||||
const { error, value: body } = updateUserValidation(req.body, user.isAdmin)
|
||||
const { error, value: body } = updateUserValidation(req.body, user!.isAdmin)
|
||||
if (error) return res.status(400).send(error.details[0].message)
|
||||
|
||||
const controller = new UserController()
|
||||
try {
|
||||
const response = await controller.updateUser(userId, body)
|
||||
const response = await controller.updateUserByUsername(username, body)
|
||||
res.send(response)
|
||||
} catch (err: any) {
|
||||
res.status(403).send(err.toString())
|
||||
@@ -70,21 +96,71 @@ userRouter.patch(
|
||||
}
|
||||
)
|
||||
|
||||
userRouter.patch(
|
||||
'/:userId',
|
||||
authenticateAccessToken,
|
||||
verifyAdminIfNeeded,
|
||||
async (req, res) => {
|
||||
const { user } = req
|
||||
const { userId } = req.params
|
||||
|
||||
// only an admin can update `isActive` and `isAdmin` fields
|
||||
const { error, value: body } = updateUserValidation(req.body, user!.isAdmin)
|
||||
if (error) return res.status(400).send(error.details[0].message)
|
||||
|
||||
const controller = new UserController()
|
||||
try {
|
||||
const response = await controller.updateUser(parseInt(userId), body)
|
||||
res.send(response)
|
||||
} catch (err: any) {
|
||||
res.status(403).send(err.toString())
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
userRouter.delete(
|
||||
'/by/username/:username',
|
||||
authenticateAccessToken,
|
||||
verifyAdminIfNeeded,
|
||||
async (req, res) => {
|
||||
const { user } = req
|
||||
const { error: errorUsername, value: params } = getUserValidation(
|
||||
req.params
|
||||
)
|
||||
if (errorUsername)
|
||||
return res.status(400).send(errorUsername.details[0].message)
|
||||
|
||||
const { username } = params
|
||||
|
||||
// only an admin can delete user without providing password
|
||||
const { error, value: data } = deleteUserValidation(req.body, user!.isAdmin)
|
||||
if (error) return res.status(400).send(error.details[0].message)
|
||||
|
||||
const controller = new UserController()
|
||||
try {
|
||||
await controller.deleteUserByUsername(username, data, user!.isAdmin)
|
||||
res.status(200).send('Account Deleted!')
|
||||
} catch (err: any) {
|
||||
res.status(403).send(err.toString())
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
userRouter.delete(
|
||||
'/:userId',
|
||||
authenticateAccessToken,
|
||||
verifyAdminIfNeeded,
|
||||
async (req: any, res) => {
|
||||
async (req, res) => {
|
||||
const { user } = req
|
||||
const { userId } = req.params
|
||||
|
||||
// only an admin can delete user without providing password
|
||||
const { error, value: data } = deleteUserValidation(req.body, user.isAdmin)
|
||||
const { error, value: data } = deleteUserValidation(req.body, user!.isAdmin)
|
||||
if (error) return res.status(400).send(error.details[0].message)
|
||||
|
||||
const controller = new UserController()
|
||||
try {
|
||||
await controller.deleteUser(userId, data, user.isAdmin)
|
||||
await controller.deleteUser(parseInt(userId), data, user!.isAdmin)
|
||||
res.status(200).send('Account Deleted!')
|
||||
} catch (err: any) {
|
||||
res.status(403).send(err.toString())
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { AppStreamConfig } from '../../types'
|
||||
import { script } from './script'
|
||||
import { style } from './style'
|
||||
|
||||
const defaultAppLogo = '/sasjs-logo.svg'
|
||||
@@ -24,13 +23,21 @@ export const appStreamHtml = (appStreamConfig: AppStreamConfig) => `
|
||||
${style}
|
||||
</head>
|
||||
<body>
|
||||
<h1>App Stream</h1>
|
||||
<header>
|
||||
<a href="/"><img src="/logo.png" alt="logo" class="logo"></a>
|
||||
<h1>App Stream</h1>
|
||||
</header>
|
||||
<div class="app-container">
|
||||
${Object.entries(appStreamConfig)
|
||||
.map(([streamServiceName, entry]) =>
|
||||
singleAppStreamHtml(streamServiceName, entry.appLoc, entry.streamLogo)
|
||||
)
|
||||
.join('')}
|
||||
${Object.entries(appStreamConfig)
|
||||
.map(([streamServiceName, entry]) =>
|
||||
singleAppStreamHtml(
|
||||
streamServiceName,
|
||||
entry.appLoc,
|
||||
entry.streamLogo
|
||||
)
|
||||
)
|
||||
.join('')}
|
||||
|
||||
<a class="app" title="Upload build.json">
|
||||
<input id="fileId" type="file" hidden />
|
||||
<button id="uploadButton" style="margin-bottom: 5px; cursor: pointer">
|
||||
@@ -39,6 +46,7 @@ export const appStreamHtml = (appStreamConfig: AppStreamConfig) => `
|
||||
<span id="uploadMessage">Upload New App</span>
|
||||
</a>
|
||||
</div>
|
||||
${script}
|
||||
<script src="/axios.min.js"></script>
|
||||
<script src="/app-streams-script.js"></script>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import path from 'path'
|
||||
import express from 'express'
|
||||
import express, { Request } from 'express'
|
||||
import { folderExists } from '@sasjs/utils'
|
||||
|
||||
import { addEntryToAppStreamConfig, getTmpFilesFolderPath } from '../../utils'
|
||||
import { addEntryToAppStreamConfig, getFilesFolder } from '../../utils'
|
||||
import { appStreamHtml } from './appStreamHtml'
|
||||
|
||||
const appStreams: { [key: string]: string } = {}
|
||||
|
||||
const router = express.Router()
|
||||
|
||||
router.get('/', async (_, res) => {
|
||||
router.get('/', async (req, res) => {
|
||||
const content = appStreamHtml(process.appStreamConfig)
|
||||
|
||||
res.cookie('XSRF-TOKEN', req.csrfToken())
|
||||
|
||||
return res.send(content)
|
||||
})
|
||||
|
||||
@@ -20,7 +24,7 @@ export const publishAppStream = async (
|
||||
streamLogo?: string,
|
||||
addEntryToFile: boolean = true
|
||||
) => {
|
||||
const driveFilesPath = getTmpFilesFolderPath()
|
||||
const driveFilesPath = getFilesFolder()
|
||||
|
||||
const appLocParts = appLoc.replace(/^\//, '')?.split('/')
|
||||
const appLocPath = path.join(driveFilesPath, ...appLocParts)
|
||||
@@ -42,7 +46,7 @@ export const publishAppStream = async (
|
||||
streamServiceName = `AppStreamName${appCount + 1}`
|
||||
}
|
||||
|
||||
router.use(`/${streamServiceName}`, express.static(pathToDeployment))
|
||||
appStreams[streamServiceName] = pathToDeployment
|
||||
|
||||
addEntryToAppStreamConfig(
|
||||
streamServiceName,
|
||||
@@ -62,4 +66,26 @@ export const publishAppStream = async (
|
||||
return {}
|
||||
}
|
||||
|
||||
router.get(`/*`, function (req: Request, res, next) {
|
||||
const reqPath = req.path.replace(/^\//, '')
|
||||
|
||||
// Redirecting to url with trailing slash for appStream base URL only
|
||||
if (reqPath.split('/').length === 1 && !reqPath.endsWith('/'))
|
||||
// navigating to same url with slash at start
|
||||
return res.redirect(301, `${reqPath}/`)
|
||||
|
||||
const appStream = reqPath.split('/')[0]
|
||||
const appStreamFilesPath = appStreams[appStream]
|
||||
if (appStreamFilesPath) {
|
||||
// resourcePath is without appStream base path
|
||||
const resourcePath = reqPath.split('/').slice(1).join('/') || 'index.html'
|
||||
|
||||
req.url = resourcePath
|
||||
|
||||
return express.static(appStreamFilesPath)(req, res, next)
|
||||
}
|
||||
|
||||
return res.send("There's no App Stream available here.")
|
||||
})
|
||||
|
||||
export default router
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
export const script = `<script>
|
||||
const inputElement = document.getElementById('fileId')
|
||||
|
||||
document
|
||||
.getElementById('uploadButton')
|
||||
.addEventListener('click', function () {
|
||||
inputElement.click()
|
||||
})
|
||||
|
||||
inputElement.addEventListener(
|
||||
'change',
|
||||
function () {
|
||||
const fileList = this.files /* now you can work with the file list */
|
||||
|
||||
updateFileUploadMessage('Requesting ...')
|
||||
|
||||
const file = fileList[0]
|
||||
const formData = new FormData()
|
||||
|
||||
formData.append('file', file)
|
||||
fetch('/SASjsApi/drive/deploy/upload', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(async (res) => {
|
||||
const { status, ok } = res
|
||||
if (status === 200 && ok) {
|
||||
const data = await res.json()
|
||||
return (
|
||||
data.message +
|
||||
'\\nstreamServiceName: ' +
|
||||
data.streamServiceName +
|
||||
'\\nrefreshing page once alert box closes.'
|
||||
)
|
||||
}
|
||||
throw await res.text()
|
||||
})
|
||||
.then((message) => {
|
||||
alert(message)
|
||||
location.reload()
|
||||
})
|
||||
.catch((error) => {
|
||||
alert(error)
|
||||
resetFileUpload()
|
||||
updateFileUploadMessage('Upload New App')
|
||||
})
|
||||
},
|
||||
false
|
||||
)
|
||||
|
||||
function updateFileUploadMessage(message) {
|
||||
document.getElementById('uploadMessage').innerHTML = message
|
||||
}
|
||||
|
||||
function resetFileUpload() {
|
||||
inputElement.value = null
|
||||
}
|
||||
</script>`
|
||||
@@ -5,18 +5,71 @@ export const style = `<style>
|
||||
.app-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-top: 50px;
|
||||
}
|
||||
.app-container .app {
|
||||
width: 150px;
|
||||
height: 180px;
|
||||
margin: 10px;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
|
||||
background: #efefef;
|
||||
padding: 10px;
|
||||
border-radius: 7px;
|
||||
border: 1px solid #d7d7d7;
|
||||
}
|
||||
.app-container .app img{
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
#uploadButton {
|
||||
border: 0
|
||||
}
|
||||
|
||||
#uploadButton:focus {
|
||||
outline: 0
|
||||
}
|
||||
|
||||
#uploadMessage {
|
||||
position: relative;
|
||||
bottom: -5px;
|
||||
}
|
||||
|
||||
header {
|
||||
transition: box-shadow 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
|
||||
box-shadow: rgb(0 0 0 / 20%) 0px 2px 4px -1px, rgb(0 0 0 / 14%) 0px 4px 5px 0px, rgb(0 0 0 / 12%) 0px 1px 10px 0px;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
flex-shrink: 0;
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
left: auto;
|
||||
right: 0px;
|
||||
background-color: rgb(0, 0, 0);
|
||||
color: rgb(255, 255, 255);
|
||||
z-index: 1201;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
margin: 13px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
header a {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
header .logo {
|
||||
width: 35px;
|
||||
margin-left: 10px;
|
||||
align-self: center;
|
||||
}
|
||||
</style>`
|
||||
|
||||
@@ -4,14 +4,16 @@ import webRouter from './web'
|
||||
import apiRouter from './api'
|
||||
import appStreamRouter from './appStream'
|
||||
|
||||
import { csrfProtection } from '../app'
|
||||
|
||||
export const setupRoutes = (app: Express) => {
|
||||
app.use('/SASjsApi', apiRouter)
|
||||
|
||||
app.use('/AppStream', function (req, res, next) {
|
||||
app.use('/AppStream', csrfProtection, function (req, res, next) {
|
||||
// this needs to be a function to hook on
|
||||
// whatever the current router is
|
||||
appStreamRouter(req, res, next)
|
||||
})
|
||||
|
||||
app.use('/', webRouter)
|
||||
app.use('/', csrfProtection, webRouter)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import express from 'express'
|
||||
import { csrfProtection } from '../../app'
|
||||
import webRouter from './web'
|
||||
|
||||
const router = express.Router()
|
||||
|
||||
router.use('/', csrfProtection, webRouter)
|
||||
router.use('/', webRouter)
|
||||
|
||||
export default router
|
||||
|
||||
@@ -1,44 +1,59 @@
|
||||
import path from 'path'
|
||||
import express from 'express'
|
||||
import { fileExists } from '@sasjs/utils'
|
||||
import { WebController } from '../../controllers/web'
|
||||
import { getWebBuildFolderPath, loginWebValidation } from '../../utils'
|
||||
import { authenticateAccessToken, desktopRestrict } from '../../middlewares'
|
||||
import { authorizeValidation, loginWebValidation } from '../../utils'
|
||||
|
||||
const webRouter = express.Router()
|
||||
const controller = new WebController()
|
||||
|
||||
webRouter.get('/', async (_, res) => {
|
||||
const indexHtmlPath = path.join(getWebBuildFolderPath(), 'index.html')
|
||||
webRouter.get('/', async (req, res) => {
|
||||
let response
|
||||
try {
|
||||
response = await controller.home()
|
||||
} catch (_) {
|
||||
response = 'Web Build is not present'
|
||||
} finally {
|
||||
res.cookie('XSRF-TOKEN', req.csrfToken())
|
||||
|
||||
if (await fileExists(indexHtmlPath)) return res.sendFile(indexHtmlPath)
|
||||
|
||||
return res.send('Web Build is not present')
|
||||
return res.send(response)
|
||||
}
|
||||
})
|
||||
|
||||
webRouter.get('/form', function (req, res) {
|
||||
// pass the csrfToken to the view
|
||||
res.send({ csrfToken: req.csrfToken() })
|
||||
})
|
||||
|
||||
webRouter.post('/login', async (req, res) => {
|
||||
webRouter.post('/SASLogon/login', desktopRestrict, async (req, res) => {
|
||||
const { error, value: body } = loginWebValidation(req.body)
|
||||
if (error) return res.status(400).send(error.details[0].message)
|
||||
|
||||
const controller = new WebController()
|
||||
try {
|
||||
const response = await controller.login(req, body)
|
||||
res.send(response)
|
||||
} catch (err: any) {
|
||||
res.status(400).send(err.toString())
|
||||
res.status(403).send(err.toString())
|
||||
}
|
||||
})
|
||||
|
||||
webRouter.get('/logout', async (req, res) => {
|
||||
const controller = new WebController()
|
||||
webRouter.post(
|
||||
'/SASLogon/authorize',
|
||||
desktopRestrict,
|
||||
authenticateAccessToken,
|
||||
async (req, res) => {
|
||||
const { error, value: body } = authorizeValidation(req.body)
|
||||
if (error) return res.status(400).send(error.details[0].message)
|
||||
|
||||
try {
|
||||
const response = await controller.authorize(req, body)
|
||||
res.send(response)
|
||||
} catch (err: any) {
|
||||
res.status(403).send(err.toString())
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
webRouter.get('/SASLogon/logout', desktopRestrict, async (req, res) => {
|
||||
try {
|
||||
await controller.logout(req)
|
||||
res.status(200).send()
|
||||
res.status(200).send('OK!')
|
||||
} catch (err: any) {
|
||||
res.status(400).send(err.toString())
|
||||
res.status(403).send(err.toString())
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user