diff --git a/api/src/middlewares/authenticateToken.ts b/api/src/middlewares/authenticateToken.ts index 23d5d0a..90c7027 100644 --- a/api/src/middlewares/authenticateToken.ts +++ b/api/src/middlewares/authenticateToken.ts @@ -3,7 +3,6 @@ import jwt from 'jsonwebtoken' import { csrfProtection } from '../app' import { fetchLatestAutoExec, ModeType, verifyTokenInDB } from '../utils' import { desktopUser } from './desktop' -import { authorize } from './authorize' export const authenticateAccessToken: RequestHandler = async ( req, @@ -25,7 +24,7 @@ export const authenticateAccessToken: RequestHandler = async ( if (user) { if (user.isActive) { req.user = user - return csrfProtection(req, res, () => authorize(req, res, next)) + return csrfProtection(req, res, next) } else return res.sendStatus(401) } } @@ -35,7 +34,7 @@ export const authenticateAccessToken: RequestHandler = async ( authenticateToken( req, res, - () => authorize(req, res, next), + next, process.env.ACCESS_TOKEN_SECRET as string, 'accessToken' ) diff --git a/api/src/middlewares/authorize.ts b/api/src/middlewares/authorize.ts index 004b7f7..dc336d4 100644 --- a/api/src/middlewares/authorize.ts +++ b/api/src/middlewares/authorize.ts @@ -13,13 +13,15 @@ export const authorize: RequestHandler = async (req, res, next) => { const dbUser = await User.findOne({ id: user.userId }) if (!dbUser) return res.sendStatus(401) - const uri = req.baseUrl + req.path + const uri = req.baseUrl + req.route.path // find permission w.r.t user permission = await Permission.findOne({ uri, user: dbUser._id }) - if (permission && permission.setting === PermissionSetting.grant) - return next() + if (permission) { + if (permission.setting === PermissionSetting.grant) return next() + else res.sendStatus(401) + } // find permission w.r.t user's groups for (const group of dbUser.groups) { diff --git a/api/src/routes/api/code.ts b/api/src/routes/api/code.ts index 09171c0..4c7fb57 100644 --- a/api/src/routes/api/code.ts +++ b/api/src/routes/api/code.ts @@ -1,12 +1,13 @@ import express from 'express' import { runCodeValidation } from '../../utils' import { CodeController } from '../../controllers/' +import { authorize } from '../../middlewares' const runRouter = express.Router() const controller = new CodeController() -runRouter.post('/execute', async (req, res) => { +runRouter.post('/execute', authorize, async (req, res) => { const { error, value: body } = runCodeValidation(req.body) if (error) return res.status(400).send(error.details[0].message) diff --git a/api/src/routes/api/drive.ts b/api/src/routes/api/drive.ts index 6126946..6b9115e 100644 --- a/api/src/routes/api/drive.ts +++ b/api/src/routes/api/drive.ts @@ -3,6 +3,7 @@ import { deleteFile, readFile } from '@sasjs/utils' import { publishAppStream } from '../appStream' +import { authorize } from '../../middlewares' import { multerSingle } from '../../middlewares/multer' import { DriveController } from '../../controllers/' import { @@ -19,7 +20,7 @@ const controller = new DriveController() const driveRouter = express.Router() -driveRouter.post('/deploy', async (req, res) => { +driveRouter.post('/deploy', authorize, async (req, res) => { const { error, value: body } = deployValidation(req.body) if (error) return res.status(400).send(error.details[0].message) @@ -48,6 +49,7 @@ driveRouter.post('/deploy', async (req, res) => { driveRouter.post( '/deploy/upload', + authorize, (...arg) => multerSingle('file', arg), async (req, res) => { if (!req.file) return res.status(400).send('"file" is not present.') @@ -111,7 +113,7 @@ driveRouter.post( } ) -driveRouter.get('/file', async (req, res) => { +driveRouter.get('/file', authorize, async (req, res) => { const { error: errQ, value: query } = fileParamValidation(req.query) if (errQ) return res.status(400).send(errQ.details[0].message) @@ -123,7 +125,7 @@ driveRouter.get('/file', async (req, res) => { } }) -driveRouter.get('/folder', async (req, res) => { +driveRouter.get('/folder', authorize, async (req, res) => { const { error: errQ, value: query } = folderParamValidation(req.query) if (errQ) return res.status(400).send(errQ.details[0].message) @@ -136,7 +138,7 @@ driveRouter.get('/folder', async (req, res) => { } }) -driveRouter.delete('/file', async (req, res) => { +driveRouter.delete('/file', authorize, async (req, res) => { const { error: errQ, value: query } = fileParamValidation(req.query) if (errQ) return res.status(400).send(errQ.details[0].message) @@ -151,6 +153,7 @@ driveRouter.delete('/file', async (req, res) => { driveRouter.post( '/file', + authorize, (...arg) => multerSingle('file', arg), async (req, res) => { const { error: errQ, value: query } = fileParamValidation(req.query) @@ -179,6 +182,7 @@ driveRouter.post( driveRouter.patch( '/file', + authorize, (...arg) => multerSingle('file', arg), async (req, res) => { const { error: errQ, value: query } = fileParamValidation(req.query) @@ -205,7 +209,7 @@ driveRouter.patch( } ) -driveRouter.get('/fileTree', async (req, res) => { +driveRouter.get('/fileTree', authorize, async (req, res) => { try { const response = await controller.getFileTree() res.send(response) diff --git a/api/src/routes/api/permission.ts b/api/src/routes/api/permission.ts index 0dd98b0..d354744 100644 --- a/api/src/routes/api/permission.ts +++ b/api/src/routes/api/permission.ts @@ -1,6 +1,10 @@ import express from 'express' import { PermissionController } from '../../controllers/' -import { authenticateAccessToken, verifyAdmin } from '../../middlewares' +import { + authenticateAccessToken, + verifyAdmin, + authorize +} from '../../middlewares' import { registerPermissionValidation, updatePermissionValidation @@ -9,16 +13,21 @@ import { const permissionRouter = express.Router() const controller = new PermissionController() -permissionRouter.get('/', authenticateAccessToken, async (req, res) => { - try { - const response = await controller.getAllPermissions() - res.send(response) - } catch (err: any) { - const statusCode = err.code - delete err.code - res.status(statusCode).send(err.message) +permissionRouter.get( + '/', + authenticateAccessToken, + authorize, + async (req, res) => { + try { + const response = await controller.getAllPermissions() + res.send(response) + } catch (err: any) { + const statusCode = err.code + delete err.code + res.status(statusCode).send(err.message) + } } -}) +) permissionRouter.post( '/', diff --git a/api/src/routes/api/spec/drive.spec.ts b/api/src/routes/api/spec/drive.spec.ts index e9e6e8a..d9475c4 100644 --- a/api/src/routes/api/spec/drive.spec.ts +++ b/api/src/routes/api/spec/drive.spec.ts @@ -29,7 +29,12 @@ jest .mockImplementation(() => path.join(tmpFolder, 'uploads')) import appPromise from '../../../app' -import { UserController } from '../../../controllers/' +import { + UserController, + PermissionController, + PermissionSetting, + PrincipalType +} from '../../../controllers/' import { getTreeExample } from '../../../controllers/internal' import { generateAccessToken, saveTokensInDB } from '../../../utils/' const { getFilesFolder } = fileUtilModules @@ -48,6 +53,7 @@ describe('drive', () => { let con: Mongoose let mongoServer: MongoMemoryServer const controller = new UserController() + const permissionController = new PermissionController() let accessToken: string @@ -58,11 +64,31 @@ describe('drive', () => { con = await mongoose.connect(mongoServer.getUri()) const dbUser = await controller.createUser(user) - accessToken = generateAccessToken({ - clientId, - userId: dbUser.id + accessToken = await generateAndSaveToken(dbUser.id) + permissionController.createPermission({ + uri: '/SASjsApi/drive/deploy', + principalType: PrincipalType.user, + principalId: dbUser.id, + setting: PermissionSetting.grant + }) + permissionController.createPermission({ + uri: '/SASjsApi/drive/deploy/upload', + principalType: PrincipalType.user, + principalId: dbUser.id, + setting: PermissionSetting.grant + }) + permissionController.createPermission({ + uri: '/SASjsApi/drive/file', + principalType: PrincipalType.user, + principalId: dbUser.id, + setting: PermissionSetting.grant + }) + permissionController.createPermission({ + uri: '/SASjsApi/drive/folder', + principalType: PrincipalType.user, + principalId: dbUser.id, + setting: PermissionSetting.grant }) - await saveTokensInDB(dbUser.id, clientId, accessToken, 'refreshToken') }) afterAll(async () => { @@ -945,3 +971,12 @@ describe('drive', () => { const getExampleService = (): ServiceMember => ((getTreeExample().members[0] as FolderMember).members[0] as FolderMember) .members[0] as ServiceMember + +const generateAndSaveToken = async (userId: number) => { + const adminAccessToken = generateAccessToken({ + clientId, + userId + }) + await saveTokensInDB(userId, clientId, adminAccessToken, 'refreshToken') + return adminAccessToken +} diff --git a/api/src/routes/api/spec/permission.spec.ts b/api/src/routes/api/spec/permission.spec.ts index 23c1fbc..ecd3587 100644 --- a/api/src/routes/api/spec/permission.spec.ts +++ b/api/src/routes/api/spec/permission.spec.ts @@ -440,17 +440,25 @@ describe('permission', () => { }) it('should give a list of all permissions when user is not admin', async () => { - const accessToken = await generateSaveTokenAndCreateUser({ + const dbUser = await userController.createUser({ ...user, username: 'get' + user.username }) + const accessToken = await generateAndSaveToken(dbUser.id) + await permissionController.createPermission({ + uri: '/SASjsApi/permission/', + principalType: PrincipalType.user, + principalId: dbUser.id, + setting: PermissionSetting.grant + }) + const res = await request(app) .get('/SASjsApi/permission/') .auth(accessToken, { type: 'bearer' }) .send() .expect(200) - expect(res.body).toHaveLength(2) + expect(res.body).toHaveLength(3) }) }) }) diff --git a/api/src/routes/api/spec/stp.spec.ts b/api/src/routes/api/spec/stp.spec.ts index f80ab8b..eb34570 100644 --- a/api/src/routes/api/spec/stp.spec.ts +++ b/api/src/routes/api/spec/stp.spec.ts @@ -4,7 +4,12 @@ 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, + PermissionController, + PermissionSetting, + PrincipalType +} from '../../../controllers/' import { generateAccessToken, saveTokensInDB, @@ -41,12 +46,21 @@ describe('stp', () => { let con: Mongoose let mongoServer: MongoMemoryServer let accessToken: string + const userController = new UserController() + const permissionController = new PermissionController() beforeAll(async () => { app = await appPromise mongoServer = await MongoMemoryServer.create() con = await mongoose.connect(mongoServer.getUri()) - accessToken = await generateSaveTokenAndCreateUser(user) + const dbUser = await userController.createUser(user) + accessToken = await generateAndSaveToken(dbUser.id) + await permissionController.createPermission({ + uri: '/SASjsApi/stp/execute', + principalType: PrincipalType.user, + principalId: dbUser.id, + setting: PermissionSetting.grant + }) }) afterAll(async () => { diff --git a/api/src/routes/api/stp.ts b/api/src/routes/api/stp.ts index 858feb5..14a3dea 100644 --- a/api/src/routes/api/stp.ts +++ b/api/src/routes/api/stp.ts @@ -2,13 +2,14 @@ import express from 'express' import { executeProgramRawValidation } from '../../utils' import { STPController } from '../../controllers/' import { FileUploadController } from '../../controllers/internal' +import { authorize } from '../../middlewares' const stpRouter = express.Router() const fileUploadController = new FileUploadController() const controller = new STPController() -stpRouter.get('/execute', async (req, res) => { +stpRouter.get('/execute', authorize, async (req, res) => { const { error, value: query } = executeProgramRawValidation(req.query) if (error) return res.status(400).send(error.details[0].message) @@ -32,6 +33,7 @@ stpRouter.get('/execute', async (req, res) => { stpRouter.post( '/execute', + authorize, fileUploadController.preUploadMiddleware, fileUploadController.getMulterUploadObject().any(), async (req, res: any) => {