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

feat(api): added autoexec + major type setting changes

This commit is contained in:
Saad Jutt
2022-05-24 21:12:32 +05:00
parent 1fed5ea6ac
commit 2a7223ad7d
26 changed files with 227 additions and 99 deletions

1
.gitignore vendored
View File

@@ -4,6 +4,7 @@ node_modules/
.DS_Store .DS_Store
.env* .env*
sas/ sas/
sasjs_root/
tmp/ tmp/
build/ build/
sasjsbuild/ sasjsbuild/

View File

@@ -323,6 +323,8 @@ components:
type: boolean type: boolean
isAdmin: isAdmin:
type: boolean type: boolean
autoExec:
type: string
required: required:
- id - id
- displayName - displayName
@@ -352,6 +354,10 @@ components:
type: boolean type: boolean
description: 'Account should be active or not, defaults to true' description: 'Account should be active or not, defaults to true'
example: 'true' example: 'true'
autoExec:
type: string
description: 'User-specific auto-exec code'
example: '<SAS code>'
required: required:
- displayName - displayName
- username - username
@@ -989,6 +995,7 @@ paths:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/UserDetailsResponse' $ref: '#/components/schemas/UserDetailsResponse'
description: 'Only Admin or user itself will get user autoExec code.'
summary: 'Get user properties - such as group memberships, userName, displayName.' summary: 'Get user properties - such as group memberships, userName, displayName.'
tags: tags:
- User - User

View File

@@ -1,7 +1,6 @@
import express from 'express' import express from 'express'
import { Request, Security, Route, Tags, Post, Body } from 'tsoa' import { Request, Security, Route, Tags, Post, Body } from 'tsoa'
import { ExecuteReturnJson, ExecutionController } from './internal' import { ExecuteReturnJson, ExecutionController } from './internal'
import { PreProgramVars } from '../types'
import { ExecuteReturnJsonResponse } from '.' import { ExecuteReturnJsonResponse } from '.'
import { getPreProgramVariables, parseLogToArray } from '../utils' import { getPreProgramVariables, parseLogToArray } from '../utils'
@@ -30,14 +29,18 @@ export class CodeController {
} }
} }
const executeSASCode = async (req: any, { code }: ExecuteSASCodePayload) => { const executeSASCode = async (
req: express.Request,
{ code }: ExecuteSASCodePayload
) => {
const { user } = req
try { try {
const { webout, log, httpHeaders } = const { webout, log, httpHeaders } =
(await new ExecutionController().executeProgram( (await new ExecutionController().executeProgram(
code, code,
getPreProgramVariables(req), getPreProgramVariables(req),
{ ...req.query, _debug: 131 }, { ...req.query, _debug: 131 },
undefined, { userAutoExec: user?.autoExec },
true true
)) as ExecuteReturnJson )) as ExecuteReturnJson

View File

@@ -119,6 +119,10 @@ filename _webout "${weboutPath}" mod;
/* dynamic user-provided vars */ /* dynamic user-provided vars */
${preProgramVarStatments} ${preProgramVarStatments}
/* user auto exec starts */
${otherArgs?.userAutoExec}
/* user auto exec ends */
/* actual job code */ /* actual job code */
${program}` ${program}`

View File

@@ -1,14 +1,15 @@
import { Request, RequestHandler } from 'express'
import multer from 'multer' import multer from 'multer'
import { uuidv4 } from '@sasjs/utils' import { uuidv4 } from '@sasjs/utils'
import { getSessionController } from '.' import { getSessionController } from '.'
export class FileUploadController { export class FileUploadController {
private storage = multer.diskStorage({ private storage = multer.diskStorage({
destination: function (req: any, file: any, cb: any) { destination: function (req: Request, file: any, cb: any) {
//Sending the intercepted files to the sessions subfolder //Sending the intercepted files to the sessions subfolder
cb(null, req.sasSession.path) cb(null, req.sasSession?.path)
}, },
filename: function (req: any, file: any, cb: any) { filename: function (req: Request, file: any, cb: any) {
//req_file prefix + unique hash added to sas request files //req_file prefix + unique hash added to sas request files
cb(null, `req_file_${uuidv4().replace(/-/gm, '')}`) cb(null, `req_file_${uuidv4().replace(/-/gm, '')}`)
} }
@@ -18,7 +19,7 @@ export class FileUploadController {
//It will intercept request and generate unique uuid to be used as a subfolder name //It will intercept request and generate unique uuid to be used as a subfolder name
//that will store the files uploaded //that will store the files uploaded
public preUploadMiddleware = async (req: any, res: any, next: any) => { public preUploadMiddleware: RequestHandler = async (req, res, next) => {
let session let session
const sessionController = getSessionController() const sessionController = getSessionController()

View File

@@ -23,8 +23,8 @@ export class SessionController {
} }
} }
const session = (req: any) => ({ const session = (req: express.Request) => ({
id: req.user.userId, id: req.user!.userId,
username: req.user.username, username: req.user!.username,
displayName: req.user.displayName displayName: req.user!.displayName
}) })

View File

@@ -26,6 +26,7 @@ import {
makeFilesNamesMap, makeFilesNamesMap,
parseLogToArray parseLogToArray
} from '../utils' } from '../utils'
import { MulterFile } from '../types/Upload'
interface ExecuteReturnJsonPayload { interface ExecuteReturnJsonPayload {
/** /**
@@ -167,7 +168,7 @@ const executeReturnRaw = async (
} }
const executeReturnJson = async ( const executeReturnJson = async (
req: any, req: express.Request,
_program: string _program: string
): Promise<ExecuteReturnJsonResponse> => { ): Promise<ExecuteReturnJsonResponse> => {
const sasCodePath = const sasCodePath =
@@ -175,7 +176,9 @@ const executeReturnJson = async (
.join(getFilesFolder(), _program) .join(getFilesFolder(), _program)
.replace(new RegExp('/', 'g'), path.sep) + '.sas' .replace(new RegExp('/', 'g'), path.sep) + '.sas'
const filesNamesMap = req.files?.length ? makeFilesNamesMap(req.files) : null const filesNamesMap = req.files?.length
? makeFilesNamesMap(req.files as MulterFile[])
: null
try { try {
const { webout, log, httpHeaders } = const { webout, log, httpHeaders } =

View File

@@ -1,3 +1,4 @@
import express from 'express'
import { import {
Security, Security,
Route, Route,
@@ -10,7 +11,8 @@ import {
Patch, Patch,
Delete, Delete,
Body, Body,
Hidden Hidden,
Request
} from 'tsoa' } from 'tsoa'
import User, { UserPayload } from '../model/User' import User, { UserPayload } from '../model/User'
@@ -27,6 +29,7 @@ interface UserDetailsResponse {
username: string username: string
isActive: boolean isActive: boolean
isAdmin: boolean isAdmin: boolean
autoExec?: string
} }
@Security('bearerAuth') @Security('bearerAuth')
@@ -73,13 +76,19 @@ export class UserController {
} }
/** /**
* Only Admin or user itself will get user autoExec code.
* @summary Get user properties - such as group memberships, userName, displayName. * @summary Get user properties - such as group memberships, userName, displayName.
* @param userId The user's identifier * @param userId The user's identifier
* @example userId 1234 * @example userId 1234
*/ */
@Get('{userId}') @Get('{userId}')
public async getUser(@Path() userId: number): Promise<UserDetailsResponse> { public async getUser(
return getUser(userId) @Request() req: express.Request,
@Path() userId: number
): Promise<UserDetailsResponse> {
const { user } = req
const getAutoExec = user!.isAdmin || user!.userId == userId
return getUser(userId, getAutoExec)
} }
/** /**
@@ -123,7 +132,7 @@ const getAllUsers = async (): Promise<UserResponse[]> =>
.exec() .exec()
const createUser = async (data: UserPayload): Promise<UserDetailsResponse> => { const createUser = async (data: UserPayload): Promise<UserDetailsResponse> => {
const { displayName, username, password, isAdmin, isActive } = data const { displayName, username, password, isAdmin, isActive, autoExec } = data
// Checking if user is already in the database // Checking if user is already in the database
const usernameExist = await User.findOne({ username }) const usernameExist = await User.findOne({ username })
@@ -138,7 +147,8 @@ const createUser = async (data: UserPayload): Promise<UserDetailsResponse> => {
username, username,
password: hashPassword, password: hashPassword,
isAdmin, isAdmin,
isActive isActive,
autoExec
}) })
const savedUser = await user.save() const savedUser = await user.save()
@@ -148,38 +158,42 @@ const createUser = async (data: UserPayload): Promise<UserDetailsResponse> => {
displayName: savedUser.displayName, displayName: savedUser.displayName,
username: savedUser.username, username: savedUser.username,
isActive: savedUser.isActive, isActive: savedUser.isActive,
isAdmin: savedUser.isAdmin isAdmin: savedUser.isAdmin,
autoExec: savedUser.autoExec
} }
} }
const getUser = async (id: number): Promise<UserDetailsResponse> => { const getUser = async (
id: number,
getAutoExec: boolean
): Promise<UserDetailsResponse> => {
const user = await User.findOne({ id }) const user = await User.findOne({ id })
.select({
_id: 0,
id: 1,
username: 1,
displayName: 1,
isAdmin: 1,
isActive: 1
})
.exec()
if (!user) throw new Error('User is not found.') if (!user) throw new Error('User is not found.')
return user return {
id: user.id,
displayName: user.displayName,
username: user.username,
isActive: user.isActive,
isAdmin: user.isAdmin,
autoExec: getAutoExec ? user.autoExec : undefined
}
} }
const updateUser = async ( const updateUser = async (
id: number, id: number,
data: UserPayload data: Partial<UserPayload>
): Promise<UserDetailsResponse> => { ): Promise<UserDetailsResponse> => {
const { displayName, username, password, isAdmin, isActive } = data const { displayName, username, password, isAdmin, isActive, autoExec } = data
const params: any = { displayName, isAdmin, isActive } const params: any = { displayName, isAdmin, isActive, autoExec }
if (username) { if (username) {
// Checking if user is already in the database // Checking if user is already in the database
const usernameExist = await User.findOne({ username }) const usernameExist = await User.findOne({ username })
if (usernameExist?.id != id) throw new Error('Username already exists.') if (usernameExist && usernameExist.id != id)
throw new Error('Username already exists.')
params.username = username params.username = username
} }
@@ -189,18 +203,17 @@ const updateUser = async (
} }
const updatedUser = await User.findOneAndUpdate({ id }, params, { new: true }) const updatedUser = await User.findOneAndUpdate({ id }, params, { new: true })
.select({
_id: 0,
id: 1,
username: 1,
displayName: 1,
isAdmin: 1,
isActive: 1
})
.exec()
if (!updatedUser) throw new Error('Unable to update user')
return updatedUser if (!updatedUser) throw new Error(`Unable to find user with id: ${id}`)
return {
id: updatedUser.id,
username: updatedUser.username,
displayName: updatedUser.displayName,
isAdmin: updatedUser.isAdmin,
isActive: updatedUser.isActive,
autoExec: updatedUser.autoExec
}
} }
const deleteUser = async ( const deleteUser = async (

View File

@@ -90,7 +90,8 @@ const login = async (
username: user.username, username: user.username,
displayName: user.displayName, displayName: user.displayName,
isAdmin: user.isAdmin, isAdmin: user.isAdmin,
isActive: user.isActive isActive: user.isActive,
autoExec: user.autoExec
} }
return { return {

View File

@@ -1,14 +1,27 @@
import { RequestHandler, Request, Response, NextFunction } from 'express'
import jwt from 'jsonwebtoken' import jwt from 'jsonwebtoken'
import { csrfProtection } from '../app' import { csrfProtection } from '../app'
import { verifyTokenInDB } from '../utils' import { fetchLatestAutoExec, verifyTokenInDB } from '../utils'
export const authenticateAccessToken = (req: any, res: any, next: any) => { export const authenticateAccessToken: RequestHandler = async (
req,
res,
next
) => {
// if request is coming from web and has valid session // if request is coming from web and has valid session
// we can validate the request and check for CSRF Token // we can validate the request and check for CSRF Token
if (req.session?.loggedIn) { if (req.session?.loggedIn) {
req.user = req.session.user if (req.session.user) {
const user = await fetchLatestAutoExec(req.session.user)
if (user) {
if (user.isActive) {
req.user = user
return csrfProtection(req, res, next) return csrfProtection(req, res, next)
} else return res.sendStatus(401)
}
}
return res.sendStatus(401)
} }
authenticateToken( authenticateToken(
@@ -20,7 +33,7 @@ export const authenticateAccessToken = (req: any, res: any, next: any) => {
) )
} }
export const authenticateRefreshToken = (req: any, res: any, next: any) => { export const authenticateRefreshToken: RequestHandler = (req, res, next) => {
authenticateToken( authenticateToken(
req, req,
res, res,
@@ -31,16 +44,16 @@ export const authenticateRefreshToken = (req: any, res: any, next: any) => {
} }
const authenticateToken = ( const authenticateToken = (
req: any, req: Request,
res: any, res: Response,
next: any, next: NextFunction,
key: string, key: string,
tokenType: 'accessToken' | 'refreshToken' tokenType: 'accessToken' | 'refreshToken'
) => { ) => {
const { MODE } = process.env const { MODE } = process.env
if (MODE?.trim() !== 'server') { if (MODE?.trim() !== 'server') {
req.user = { req.user = {
userId: '1234', userId: 1234,
clientId: 'desktopModeClientId', clientId: 'desktopModeClientId',
username: 'desktopModeUsername', username: 'desktopModeUsername',
displayName: 'desktopModeDisplayName', displayName: 'desktopModeDisplayName',

View File

@@ -1,11 +1,13 @@
export const desktopRestrict = (req: any, res: any, next: any) => { import { RequestHandler } from 'express'
export const desktopRestrict: RequestHandler = (req, res, next) => {
const { MODE } = process.env const { MODE } = process.env
if (MODE?.trim() !== 'server') if (MODE?.trim() !== 'server')
return res.status(403).send('Not Allowed while in Desktop Mode.') return res.status(403).send('Not Allowed while in Desktop Mode.')
next() next()
} }
export const desktopUsername = (req: any, res: any, next: any) => { export const desktopUsername: RequestHandler = (req, res, next) => {
const { MODE } = process.env const { MODE } = process.env
if (MODE?.trim() !== 'server') if (MODE?.trim() !== 'server')
return res.status(200).send({ return res.status(200).send({

View File

@@ -1,4 +1,6 @@
export const verifyAdmin = (req: any, res: any, next: any) => { import { RequestHandler } from 'express'
export const verifyAdmin: RequestHandler = (req, res, next) => {
const { MODE } = process.env const { MODE } = process.env
if (MODE?.trim() !== 'server') return next() if (MODE?.trim() !== 'server') return next()

View File

@@ -1,8 +1,10 @@
export const verifyAdminIfNeeded = (req: any, res: any, next: any) => { import { RequestHandler } from 'express'
export const verifyAdminIfNeeded: RequestHandler = (req, res, next) => {
const { user } = req const { user } = req
const userId = parseInt(req.params.userId) const userId = parseInt(req.params.userId)
if (!user.isAdmin && user.userId !== userId) { if (!user?.isAdmin && user?.userId !== userId) {
return res.status(401).send('Admin account required') return res.status(401).send('Admin account required')
} }
next() next()

View File

@@ -27,12 +27,18 @@ export interface UserPayload {
* @example "true" * @example "true"
*/ */
isActive?: boolean isActive?: boolean
/**
* User-specific auto-exec code
* @example "<SAS code>"
*/
autoExec?: string
} }
interface IUserDocument extends UserPayload, Document { interface IUserDocument extends UserPayload, Document {
id: number id: number
isAdmin: boolean isAdmin: boolean
isActive: boolean isActive: boolean
autoExec: string
groups: Schema.Types.ObjectId[] groups: Schema.Types.ObjectId[]
tokens: [{ [key: string]: string }] tokens: [{ [key: string]: string }]
} }
@@ -66,6 +72,9 @@ const userSchema = new Schema<IUserDocument>({
type: Boolean, type: Boolean,
default: true default: true
}, },
autoExec: {
type: String
},
groups: [{ type: Schema.Types.ObjectId, ref: 'Group' }], groups: [{ type: Schema.Types.ObjectId, ref: 'Group' }],
tokens: [ tokens: [
{ {

View File

@@ -26,8 +26,11 @@ authRouter.post('/token', async (req, res) => {
} }
}) })
authRouter.post('/refresh', authenticateRefreshToken, async (req: any, res) => { authRouter.post('/refresh', authenticateRefreshToken, async (req, res) => {
const userInfo: InfoJWT = req.user const userInfo: InfoJWT = {
userId: req.user!.userId!,
clientId: req.user!.clientId!
}
try { try {
const response = await controller.refresh(userInfo) const response = await controller.refresh(userInfo)
@@ -38,8 +41,11 @@ authRouter.post('/refresh', authenticateRefreshToken, async (req: any, res) => {
} }
}) })
authRouter.delete('/logout', authenticateAccessToken, async (req: any, res) => { authRouter.delete('/logout', authenticateAccessToken, async (req, res) => {
const userInfo: InfoJWT = req.user const userInfo: InfoJWT = {
userId: req.user!.userId!,
clientId: req.user!.clientId!
}
try { try {
await controller.logout(userInfo) await controller.logout(userInfo)

View File

@@ -33,12 +33,12 @@ groupRouter.get('/', authenticateAccessToken, async (req, res) => {
} }
}) })
groupRouter.get('/:groupId', authenticateAccessToken, async (req: any, res) => { groupRouter.get('/:groupId', authenticateAccessToken, async (req, res) => {
const { groupId } = req.params const { groupId } = req.params
const controller = new GroupController() const controller = new GroupController()
try { try {
const response = await controller.getGroup(groupId) const response = await controller.getGroup(parseInt(groupId))
res.send(response) res.send(response)
} catch (err: any) { } catch (err: any) {
res.status(403).send(err.toString()) res.status(403).send(err.toString())
@@ -49,12 +49,15 @@ groupRouter.post(
'/:groupId/:userId', '/:groupId/:userId',
authenticateAccessToken, authenticateAccessToken,
verifyAdmin, verifyAdmin,
async (req: any, res) => { async (req, res) => {
const { groupId, userId } = req.params const { groupId, userId } = req.params
const controller = new GroupController() const controller = new GroupController()
try { try {
const response = await controller.addUserToGroup(groupId, userId) const response = await controller.addUserToGroup(
parseInt(groupId),
parseInt(userId)
)
res.send(response) res.send(response)
} catch (err: any) { } catch (err: any) {
res.status(403).send(err.toString()) res.status(403).send(err.toString())
@@ -66,12 +69,15 @@ groupRouter.delete(
'/:groupId/:userId', '/:groupId/:userId',
authenticateAccessToken, authenticateAccessToken,
verifyAdmin, verifyAdmin,
async (req: any, res) => { async (req, res) => {
const { groupId, userId } = req.params const { groupId, userId } = req.params
const controller = new GroupController() const controller = new GroupController()
try { try {
const response = await controller.removeUserFromGroup(groupId, userId) const response = await controller.removeUserFromGroup(
parseInt(groupId),
parseInt(userId)
)
res.send(response) res.send(response)
} catch (err: any) { } catch (err: any) {
res.status(403).send(err.toString()) res.status(403).send(err.toString())
@@ -83,12 +89,12 @@ groupRouter.delete(
'/:groupId', '/:groupId',
authenticateAccessToken, authenticateAccessToken,
verifyAdmin, verifyAdmin,
async (req: any, res) => { async (req, res) => {
const { groupId } = req.params const { groupId } = req.params
const controller = new GroupController() const controller = new GroupController()
try { try {
await controller.deleteGroup(groupId) await controller.deleteGroup(parseInt(groupId))
res.status(200).send('Group Deleted!') res.status(200).send('Group Deleted!')
} catch (err: any) { } catch (err: any) {
res.status(403).send(err.toString()) res.status(403).send(err.toString())

View File

@@ -19,7 +19,8 @@ const user = {
username: 'testUsername', username: 'testUsername',
password: '87654321', password: '87654321',
isAdmin: false, isAdmin: false,
isActive: true isActive: true,
autoExec: 'some sas code for auto exec;'
} }
const controller = new UserController() const controller = new UserController()
@@ -64,6 +65,7 @@ describe('user', () => {
expect(res.body.displayName).toEqual(user.displayName) expect(res.body.displayName).toEqual(user.displayName)
expect(res.body.isAdmin).toEqual(user.isAdmin) expect(res.body.isAdmin).toEqual(user.isAdmin)
expect(res.body.isActive).toEqual(user.isActive) 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 () => { it('should respond with Unauthorized if access token is not present', async () => {
@@ -360,7 +362,25 @@ describe('user', () => {
await deleteAllUsers() 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)
})
it('should respond with user autoExec when admin user requests', async () => {
const dbUser = await controller.createUser(user) const dbUser = await controller.createUser(user)
const userId = dbUser.id const userId = dbUser.id
@@ -374,6 +394,7 @@ describe('user', () => {
expect(res.body.displayName).toEqual(user.displayName) expect(res.body.displayName).toEqual(user.displayName)
expect(res.body.isAdmin).toEqual(user.isAdmin) expect(res.body.isAdmin).toEqual(user.isAdmin)
expect(res.body.isActive).toEqual(user.isActive) 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 () => { it('should respond with user when access token is not of an admin account', async () => {
@@ -395,6 +416,7 @@ describe('user', () => {
expect(res.body.displayName).toEqual(user.displayName) expect(res.body.displayName).toEqual(user.displayName)
expect(res.body.isAdmin).toEqual(user.isAdmin) expect(res.body.isAdmin).toEqual(user.isAdmin)
expect(res.body.isActive).toEqual(user.isActive) expect(res.body.isActive).toEqual(user.isActive)
expect(res.body.autoExec).toBeUndefined()
}) })
it('should respond with Unauthorized if access token is not present', async () => { it('should respond with Unauthorized if access token is not present', async () => {

View File

@@ -34,7 +34,7 @@ stpRouter.post(
'/execute', '/execute',
fileUploadController.preUploadMiddleware, fileUploadController.preUploadMiddleware,
fileUploadController.getMulterUploadObject().any(), fileUploadController.getMulterUploadObject().any(),
async (req: any, res: any) => { async (req, res: any) => {
const { error: errQ, value: query } = executeProgramRawValidation(req.query) const { error: errQ, value: query } = executeProgramRawValidation(req.query)
const { error: errB, value: body } = executeProgramRawValidation(req.body) const { error: errB, value: body } = executeProgramRawValidation(req.body)

View File

@@ -36,12 +36,12 @@ userRouter.get('/', authenticateAccessToken, async (req, res) => {
} }
}) })
userRouter.get('/:userId', authenticateAccessToken, async (req: any, res) => { userRouter.get('/:userId', authenticateAccessToken, async (req, res) => {
const { userId } = req.params const { userId } = req.params
const controller = new UserController() const controller = new UserController()
try { try {
const response = await controller.getUser(userId) const response = await controller.getUser(req, parseInt(userId))
res.send(response) res.send(response)
} catch (err: any) { } catch (err: any) {
res.status(403).send(err.toString()) res.status(403).send(err.toString())
@@ -52,17 +52,17 @@ userRouter.patch(
'/:userId', '/:userId',
authenticateAccessToken, authenticateAccessToken,
verifyAdminIfNeeded, verifyAdminIfNeeded,
async (req: any, res) => { async (req, res) => {
const { user } = req const { user } = req
const { userId } = req.params const { userId } = req.params
// only an admin can update `isActive` and `isAdmin` fields // 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) if (error) return res.status(400).send(error.details[0].message)
const controller = new UserController() const controller = new UserController()
try { try {
const response = await controller.updateUser(userId, body) const response = await controller.updateUser(parseInt(userId), body)
res.send(response) res.send(response)
} catch (err: any) { } catch (err: any) {
res.status(403).send(err.toString()) res.status(403).send(err.toString())
@@ -74,17 +74,17 @@ userRouter.delete(
'/:userId', '/:userId',
authenticateAccessToken, authenticateAccessToken,
verifyAdminIfNeeded, verifyAdminIfNeeded,
async (req: any, res) => { async (req, res) => {
const { user } = req const { user } = req
const { userId } = req.params const { userId } = req.params
// only an admin can delete user without providing password // 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) if (error) return res.status(400).send(error.details[0].message)
const controller = new UserController() const controller = new UserController()
try { try {
await controller.deleteUser(userId, data, user.isAdmin) await controller.deleteUser(parseInt(userId), data, user!.isAdmin)
res.status(200).send('Account Deleted!') res.status(200).send('Account Deleted!')
} catch (err: any) { } catch (err: any) {
res.status(403).send(err.toString()) res.status(403).send(err.toString())

View File

@@ -0,0 +1,9 @@
export interface RequestUser {
userId: number
clientId: string
username: string
displayName: string
isAdmin: boolean
isActive: boolean
autoExec?: string
}

View File

@@ -5,3 +5,4 @@ export * from './InfoJWT'
export * from './PreProgramVars' export * from './PreProgramVars'
export * from './Session' export * from './Session'
export * from './TreeNode' export * from './TreeNode'
export * from './RequestUser'

View File

@@ -2,13 +2,6 @@ import express from 'express'
declare module 'express-session' { declare module 'express-session' {
interface SessionData { interface SessionData {
loggedIn: boolean loggedIn: boolean
user: { user: import('../').RequestUser
userId: number
clientId: string
username: string
displayName: string
isAdmin: boolean
isActive: boolean
}
} }
} }

7
api/src/types/system/express.d.ts vendored Normal file
View File

@@ -0,0 +1,7 @@
declare namespace Express {
export interface Request {
accessToken?: string
user?: import('../').RequestUser
sasSession?: import('../').Session
}
}

View File

@@ -1,6 +1,7 @@
import { Request } from 'express'
import { PreProgramVars } from '../types' import { PreProgramVars } from '../types'
export const getPreProgramVariables = (req: any): PreProgramVars => { export const getPreProgramVariables = (req: Request): PreProgramVars => {
const host = req.get('host') const host = req.get('host')
const protocol = req.protocol + '://' const protocol = req.protocol + '://'
const { user, accessToken } = req const { user, accessToken } = req
@@ -20,9 +21,9 @@ export const getPreProgramVariables = (req: any): PreProgramVars => {
if (cookies.length) httpHeaders.push(`cookie: ${cookies.join('; ')}`) if (cookies.length) httpHeaders.push(`cookie: ${cookies.join('; ')}`)
return { return {
username: user.username, username: user!.username,
userId: user.userId, userId: user!.userId,
displayName: user.displayName, displayName: user!.displayName,
serverUrl: protocol + host, serverUrl: protocol + host,
httpHeaders httpHeaders
} }

View File

@@ -35,7 +35,8 @@ export const registerUserValidation = (data: any): Joi.ValidationResult =>
username: usernameSchema.required(), username: usernameSchema.required(),
password: passwordSchema.required(), password: passwordSchema.required(),
isAdmin: Joi.boolean(), isAdmin: Joi.boolean(),
isActive: Joi.boolean() isActive: Joi.boolean(),
autoExec: Joi.string()
}).validate(data) }).validate(data)
export const deleteUserValidation = ( export const deleteUserValidation = (
@@ -57,7 +58,8 @@ export const updateUserValidation = (
const validationChecks: any = { const validationChecks: any = {
displayName: Joi.string().min(6), displayName: Joi.string().min(6),
username: usernameSchema, username: usernameSchema,
password: passwordSchema password: passwordSchema,
autoExec: Joi.string()
} }
if (isAdmin) { if (isAdmin) {
validationChecks.isAdmin = Joi.boolean() validationChecks.isAdmin = Joi.boolean()

View File

@@ -1,11 +1,30 @@
import User from '../model/User' import User from '../model/User'
import { RequestUser } from '../types'
export const fetchLatestAutoExec = async (
reqUser: RequestUser
): Promise<RequestUser | undefined> => {
const dbUser = await User.findOne({ id: reqUser.userId })
if (!dbUser) return undefined
return {
userId: reqUser.userId,
clientId: reqUser.clientId,
username: dbUser.username,
displayName: dbUser.displayName,
isAdmin: dbUser.isAdmin,
isActive: dbUser.isActive,
autoExec: dbUser.autoExec
}
}
export const verifyTokenInDB = async ( export const verifyTokenInDB = async (
userId: number, userId: number,
clientId: string, clientId: string,
token: string, token: string,
tokenType: 'accessToken' | 'refreshToken' tokenType: 'accessToken' | 'refreshToken'
) => { ): Promise<RequestUser | undefined> => {
const dbUser = await User.findOne({ id: userId }) const dbUser = await User.findOne({ id: userId })
if (!dbUser) return undefined if (!dbUser) return undefined
@@ -21,7 +40,8 @@ export const verifyTokenInDB = async (
username: dbUser.username, username: dbUser.username,
displayName: dbUser.displayName, displayName: dbUser.displayName,
isAdmin: dbUser.isAdmin, isAdmin: dbUser.isAdmin,
isActive: dbUser.isActive isActive: dbUser.isActive,
autoExec: dbUser.autoExec
} }
: undefined : undefined
} }