mirror of
https://github.com/sasjs/server.git
synced 2025-12-10 11:24:35 +00:00
chore: swagger docs generated
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,5 +6,6 @@ node_modules/
|
||||
sas/
|
||||
tmp/
|
||||
build/
|
||||
public/
|
||||
certificates/
|
||||
.env
|
||||
|
||||
1572
package-lock.json
generated
1572
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,9 +4,11 @@
|
||||
"description": "SASjs server",
|
||||
"main": "./src/server.ts",
|
||||
"scripts": {
|
||||
"prestart": "npm run swagger",
|
||||
"start": "nodemon ./src/server.ts",
|
||||
"start:prod": "nodemon ./src/prod-server.ts",
|
||||
"build": "rimraf build && tsc",
|
||||
"swagger": "tsoa spec",
|
||||
"semantic-release": "semantic-release -d",
|
||||
"prepare": "[ -d .git ] && git config core.hooksPath ./.git-hooks || true",
|
||||
"test": "mkdir -p tmp && jest --coverage",
|
||||
@@ -27,16 +29,21 @@
|
||||
"joi": "^17.4.2",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"mongoose": "^6.0.12",
|
||||
"multer": "^1.4.3"
|
||||
"morgan": "^1.10.0",
|
||||
"multer": "^1.4.3",
|
||||
"swagger-ui-express": "^4.1.6",
|
||||
"tsoa": "^3.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bcryptjs": "^2.4.2",
|
||||
"@types/express": "^4.17.12",
|
||||
"@types/jest": "^26.0.24",
|
||||
"@types/jsonwebtoken": "^8.5.5",
|
||||
"@types/morgan": "^1.9.3",
|
||||
"@types/multer": "^1.4.7",
|
||||
"@types/node": "^15.12.2",
|
||||
"@types/supertest": "^2.0.11",
|
||||
"@types/swagger-ui-express": "^4.1.3",
|
||||
"dotenv": "^10.0.0",
|
||||
"jest": "^27.0.6",
|
||||
"mongodb-memory-server": "^8.0.0",
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import express from 'express'
|
||||
import morgan from 'morgan'
|
||||
import webRouter from './routes/web'
|
||||
import apiRouter from './routes/api'
|
||||
|
||||
const app = express()
|
||||
|
||||
app.use(express.json({ limit: '50mb' }))
|
||||
app.use(morgan('tiny'))
|
||||
app.use(express.static('public'))
|
||||
|
||||
app.use('/', webRouter)
|
||||
app.use('/SASjsApi', apiRouter)
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
import bcrypt from 'bcryptjs'
|
||||
import User from '../model/User'
|
||||
|
||||
export const createUser = async (data: any) => {
|
||||
const { displayName, username, password, isAdmin, isActive } = data
|
||||
|
||||
// Checking if user is already in the database
|
||||
const usernameExist = await User.findOne({ username })
|
||||
if (usernameExist) throw new Error('Username already exists.')
|
||||
|
||||
// Hash passwords
|
||||
const salt = await bcrypt.genSalt(10)
|
||||
const hashPassword = await bcrypt.hash(password, salt)
|
||||
|
||||
// Create a new user
|
||||
const user = new User({
|
||||
displayName,
|
||||
username,
|
||||
password: hashPassword,
|
||||
isAdmin,
|
||||
isActive
|
||||
})
|
||||
|
||||
const savedUser = await user.save()
|
||||
|
||||
return {
|
||||
displayName: savedUser.displayName,
|
||||
username: savedUser.username,
|
||||
isAdmin: savedUser.isAdmin,
|
||||
isActive: savedUser.isActive
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import bcrypt from 'bcryptjs'
|
||||
import User from '../model/User'
|
||||
|
||||
export const deleteUser = async (
|
||||
username: string,
|
||||
isAdmin: boolean,
|
||||
data: any
|
||||
) => {
|
||||
const { password } = data
|
||||
|
||||
const user = await User.findOne({ username })
|
||||
if (!user) throw new Error('Username is not found.')
|
||||
|
||||
if (!isAdmin) {
|
||||
const validPass = await bcrypt.compare(password, user.password)
|
||||
if (!validPass) throw new Error('Invalid password.')
|
||||
}
|
||||
|
||||
await User.deleteOne({ username })
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import bcrypt from 'bcryptjs'
|
||||
import User from '../model/User'
|
||||
|
||||
export const updateUser = async (currentUsername: string, data: any) => {
|
||||
const { displayName, username, password, isAdmin, isActive } = data
|
||||
|
||||
const params: any = { displayName, isAdmin, isActive }
|
||||
|
||||
if (username && currentUsername !== username) {
|
||||
// Checking if username is already in the database
|
||||
const usernameExist = await User.findOne({ username })
|
||||
if (usernameExist) throw new Error('Username already exists.')
|
||||
|
||||
params.username = username
|
||||
}
|
||||
|
||||
if (password) {
|
||||
// Hash passwords
|
||||
const salt = await bcrypt.genSalt(10)
|
||||
params.password = await bcrypt.hash(password, salt)
|
||||
}
|
||||
|
||||
const updatedUser = await User.findOneAndUpdate(
|
||||
{ username: currentUsername },
|
||||
params,
|
||||
{ new: true }
|
||||
)
|
||||
|
||||
return {
|
||||
displayName: updatedUser.displayName,
|
||||
username: updatedUser.username,
|
||||
isAdmin: updatedUser.isAdmin,
|
||||
isActive: updatedUser.isActive
|
||||
}
|
||||
}
|
||||
180
src/controllers/user.ts
Normal file
180
src/controllers/user.ts
Normal file
@@ -0,0 +1,180 @@
|
||||
import {
|
||||
Route,
|
||||
Path,
|
||||
Query,
|
||||
Example,
|
||||
Get,
|
||||
Post,
|
||||
Patch,
|
||||
Delete,
|
||||
Body,
|
||||
Hidden
|
||||
} from 'tsoa'
|
||||
import bcrypt from 'bcryptjs'
|
||||
|
||||
import User, { UserPayload } from '../model/User'
|
||||
|
||||
interface userResponse {
|
||||
username: string
|
||||
displayName: string
|
||||
}
|
||||
|
||||
interface userDetailsResponse {
|
||||
displayName: string
|
||||
username: string
|
||||
isActive: boolean
|
||||
isAdmin: boolean
|
||||
}
|
||||
|
||||
@Route('user')
|
||||
export default class UserController {
|
||||
/**
|
||||
* Get list of all users (username, displayname). All users can request this.
|
||||
*
|
||||
*/
|
||||
@Example<userResponse[]>([
|
||||
{
|
||||
username: 'johnusername',
|
||||
displayName: 'John'
|
||||
},
|
||||
{
|
||||
username: 'starkusername',
|
||||
displayName: 'Stark'
|
||||
}
|
||||
])
|
||||
@Get('/')
|
||||
public async getAllUsers(): Promise<userResponse[]> {
|
||||
return getAllUsers()
|
||||
}
|
||||
|
||||
/**
|
||||
* Create user with the following attributes: UserId, UserName, Password, isAdmin, isActive. Admin only task.
|
||||
*
|
||||
*/
|
||||
@Example<userDetailsResponse>({
|
||||
displayName: 'John Snow',
|
||||
username: 'johnSnow01',
|
||||
isAdmin: false,
|
||||
isActive: true
|
||||
})
|
||||
@Post('/')
|
||||
public async createUser(
|
||||
@Body() body: UserPayload
|
||||
): Promise<userDetailsResponse> {
|
||||
return createUser(body)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user properties - such as displayName. Can be performed either by admins, or the user in question.
|
||||
* @param username The user's identifier
|
||||
* @example username "johnSnow01"
|
||||
*/
|
||||
@Example<userDetailsResponse>({
|
||||
displayName: 'John Snow',
|
||||
username: 'johnSnow01',
|
||||
isAdmin: false,
|
||||
isActive: true
|
||||
})
|
||||
@Patch('{username}')
|
||||
public async updateUser(
|
||||
@Path() username: string,
|
||||
@Body() body: UserPayload
|
||||
): Promise<userDetailsResponse> {
|
||||
return updateUser(username, body)
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a user. Can be performed either by admins, or the user in question.
|
||||
* @param username The user's identifier
|
||||
* @example username "johnSnow01"
|
||||
*/
|
||||
@Delete('{username}')
|
||||
public async deleteUser(
|
||||
@Path() username: string,
|
||||
@Body() body: { password?: string },
|
||||
@Query() @Hidden() isAdmin: boolean = false
|
||||
) {
|
||||
return deleteUser(username, isAdmin, body)
|
||||
}
|
||||
}
|
||||
|
||||
const getAllUsers = async () =>
|
||||
await User.find({}).select({ _id: 0, username: 1, displayName: 1 }).exec()
|
||||
|
||||
const createUser = async (data: any): Promise<userDetailsResponse> => {
|
||||
const { displayName, username, password, isAdmin, isActive } = data
|
||||
|
||||
// Checking if user is already in the database
|
||||
const usernameExist = await User.findOne({ username })
|
||||
if (usernameExist) throw new Error('Username already exists.')
|
||||
|
||||
// Hash passwords
|
||||
const salt = await bcrypt.genSalt(10)
|
||||
const hashPassword = await bcrypt.hash(password, salt)
|
||||
|
||||
// Create a new user
|
||||
const user = new User({
|
||||
displayName,
|
||||
username,
|
||||
password: hashPassword,
|
||||
isAdmin,
|
||||
isActive
|
||||
})
|
||||
|
||||
const savedUser = await user.save()
|
||||
|
||||
return {
|
||||
displayName: savedUser.displayName,
|
||||
username: savedUser.username,
|
||||
isAdmin: savedUser.isAdmin,
|
||||
isActive: savedUser.isActive
|
||||
}
|
||||
}
|
||||
|
||||
const updateUser = async (currentUsername: string, data: any) => {
|
||||
const { displayName, username, password, isAdmin, isActive } = data
|
||||
|
||||
const params: any = { displayName, isAdmin, isActive }
|
||||
|
||||
if (username && currentUsername !== username) {
|
||||
// Checking if username is already in the database
|
||||
const usernameExist = await User.findOne({ username })
|
||||
if (usernameExist) throw new Error('Username already exists.')
|
||||
|
||||
params.username = username
|
||||
}
|
||||
|
||||
if (password) {
|
||||
// Hash passwords
|
||||
const salt = await bcrypt.genSalt(10)
|
||||
params.password = await bcrypt.hash(password, salt)
|
||||
}
|
||||
|
||||
const updatedUser = await User.findOneAndUpdate(
|
||||
{ username: currentUsername },
|
||||
params,
|
||||
{ new: true }
|
||||
)
|
||||
if (!updatedUser) throw new Error('Unable to update user')
|
||||
|
||||
return {
|
||||
displayName: updatedUser.displayName,
|
||||
username: updatedUser.username,
|
||||
isAdmin: updatedUser.isAdmin,
|
||||
isActive: updatedUser.isActive
|
||||
}
|
||||
}
|
||||
|
||||
const deleteUser = async (username: string, isAdmin: boolean, data: any) => {
|
||||
const { password } = data
|
||||
|
||||
const user = await User.findOne({ username })
|
||||
if (!user) throw new Error('Username is not found.')
|
||||
|
||||
if (!isAdmin) {
|
||||
const validPass = await bcrypt.compare(password, user.password)
|
||||
if (!validPass) throw new Error('Invalid password.')
|
||||
}
|
||||
|
||||
await User.deleteOne({ username })
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './authenticateToken'
|
||||
export * from './verifyAdmin'
|
||||
export * from './verifyAdminIfNeeded'
|
||||
|
||||
9
src/middlewares/verifyAdminIfNeeded.ts
Normal file
9
src/middlewares/verifyAdminIfNeeded.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export const verifyAdminIfNeeded = (req: any, res: any, next: any) => {
|
||||
const { user } = req
|
||||
const { username } = req.params
|
||||
|
||||
if (!user.isAdmin && user.username !== username) {
|
||||
return res.status(401).send('Admin account required')
|
||||
}
|
||||
next()
|
||||
}
|
||||
@@ -1,42 +1,76 @@
|
||||
import mongoose from 'mongoose'
|
||||
import { Schema, model } from 'mongoose'
|
||||
|
||||
const userSchema = new mongoose.Schema({
|
||||
displayName: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
username: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
password: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
isAdmin: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
tokens: [
|
||||
{
|
||||
clientId: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
accessToken: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
refreshToken: {
|
||||
type: String,
|
||||
required: true
|
||||
export interface UserPayload {
|
||||
/**
|
||||
* Display name for user
|
||||
* @example "John Snow"
|
||||
*/
|
||||
displayName: string
|
||||
/**
|
||||
* Username for user
|
||||
* @example "johnSnow01"
|
||||
*/
|
||||
username: string
|
||||
/**
|
||||
* Password for user
|
||||
*/
|
||||
password: string
|
||||
/**
|
||||
* Account should be admin or not, defaults to false
|
||||
* @example "false"
|
||||
*/
|
||||
isAdmin?: boolean
|
||||
/**
|
||||
* Account should be active or not, defaults to true
|
||||
* @example "true"
|
||||
*/
|
||||
isActive?: boolean
|
||||
}
|
||||
|
||||
interface UserSchema extends UserPayload {
|
||||
isAdmin: boolean
|
||||
isActive: boolean
|
||||
tokens: [{ [key: string]: string }]
|
||||
}
|
||||
|
||||
export default model(
|
||||
'User',
|
||||
new Schema<UserSchema>({
|
||||
displayName: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
username: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
password: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
isAdmin: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
tokens: [
|
||||
{
|
||||
clientId: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
accessToken: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
refreshToken: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
export default mongoose.model('User', userSchema)
|
||||
]
|
||||
})
|
||||
)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import express from 'express'
|
||||
import dotenv from 'dotenv'
|
||||
import swaggerUi from 'swagger-ui-express'
|
||||
|
||||
import { authenticateAccessToken, verifyAdmin } from '../../middlewares'
|
||||
|
||||
@@ -19,5 +20,14 @@ router.use('/stp', authenticateAccessToken, stpRouter)
|
||||
router.use('/user', userRouter)
|
||||
router.use('/client', authenticateAccessToken, verifyAdmin, clientRouter)
|
||||
router.use('/auth', authRouter)
|
||||
router.use(
|
||||
'/',
|
||||
swaggerUi.serve,
|
||||
swaggerUi.setup(undefined, {
|
||||
swaggerOptions: {
|
||||
url: '/swagger.json'
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
export default router
|
||||
|
||||
@@ -2,7 +2,7 @@ import mongoose, { Mongoose } from 'mongoose'
|
||||
import { MongoMemoryServer } from 'mongodb-memory-server'
|
||||
import request from 'supertest'
|
||||
import app from '../../../app'
|
||||
import { createUser } from '../../../controllers/createUser'
|
||||
import UserController from '../../../controllers/user'
|
||||
import { createClient } from '../../../controllers/createClient'
|
||||
import {
|
||||
generateAccessToken,
|
||||
@@ -27,6 +27,7 @@ const user = {
|
||||
describe('auth', () => {
|
||||
let con: Mongoose
|
||||
let mongoServer: MongoMemoryServer
|
||||
const userController = new UserController()
|
||||
|
||||
beforeAll(async () => {
|
||||
mongoServer = await MongoMemoryServer.create()
|
||||
@@ -49,7 +50,7 @@ describe('auth', () => {
|
||||
})
|
||||
|
||||
it('should respond with authorization code', async () => {
|
||||
await createUser(user)
|
||||
await userController.createUser(user)
|
||||
|
||||
const res = await request(app)
|
||||
.post('/SASjsApi/auth/authorize')
|
||||
@@ -117,7 +118,7 @@ describe('auth', () => {
|
||||
})
|
||||
|
||||
it('should respond with Forbidden if password is incorrect', async () => {
|
||||
await createUser(user)
|
||||
await userController.createUser(user)
|
||||
|
||||
const res = await request(app)
|
||||
.post('/SASjsApi/auth/authorize')
|
||||
@@ -133,7 +134,7 @@ describe('auth', () => {
|
||||
})
|
||||
|
||||
it('should respond with Forbidden if clientId is incorrect', async () => {
|
||||
await createUser(user)
|
||||
await userController.createUser(user)
|
||||
|
||||
const res = await request(app)
|
||||
.post('/SASjsApi/auth/authorize')
|
||||
@@ -155,7 +156,7 @@ describe('auth', () => {
|
||||
username: user.username
|
||||
}
|
||||
beforeAll(async () => {
|
||||
await createUser(user)
|
||||
await userController.createUser(user)
|
||||
})
|
||||
afterAll(async () => {
|
||||
const collections = mongoose.connection.collections
|
||||
@@ -250,7 +251,7 @@ describe('auth', () => {
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
await createUser(user)
|
||||
await userController.createUser(user)
|
||||
await saveTokensInDB(user.username, clientId, 'accessToken', refreshToken)
|
||||
})
|
||||
|
||||
@@ -294,7 +295,7 @@ describe('auth', () => {
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
await createUser(user)
|
||||
await userController.createUser(user)
|
||||
await saveTokensInDB(user.username, clientId, accessToken, 'refreshToken')
|
||||
})
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ import { MongoMemoryServer } from 'mongodb-memory-server'
|
||||
import request from 'supertest'
|
||||
import app from '../../../app'
|
||||
import { createClient } from '../../../controllers/createClient'
|
||||
import UserController from '../../../controllers/user'
|
||||
import { generateAccessToken } from '../auth'
|
||||
import { createUser } from '../../../controllers/createUser'
|
||||
import { saveTokensInDB } from '../../../utils'
|
||||
|
||||
const client = {
|
||||
@@ -23,9 +23,10 @@ const newClient = {
|
||||
clientSecret: 'newClientSecret'
|
||||
}
|
||||
|
||||
describe('user', () => {
|
||||
describe('client', () => {
|
||||
let con: Mongoose
|
||||
let mongoServer: MongoMemoryServer
|
||||
const userController = new UserController()
|
||||
|
||||
beforeAll(async () => {
|
||||
mongoServer = await MongoMemoryServer.create()
|
||||
@@ -45,7 +46,7 @@ describe('user', () => {
|
||||
})
|
||||
|
||||
beforeAll(async () => {
|
||||
await createUser(adminUser)
|
||||
await userController.createUser(adminUser)
|
||||
await saveTokensInDB(
|
||||
adminUser.username,
|
||||
client.clientId,
|
||||
@@ -93,7 +94,7 @@ describe('user', () => {
|
||||
clientId: client.clientId,
|
||||
username: user.username
|
||||
})
|
||||
await createUser(user)
|
||||
await userController.createUser(user)
|
||||
await saveTokensInDB(
|
||||
user.username,
|
||||
client.clientId,
|
||||
@@ -105,7 +106,7 @@ describe('user', () => {
|
||||
.post('/SASjsApi/client')
|
||||
.auth(accessToken, { type: 'bearer' })
|
||||
.send(newClient)
|
||||
.expect(403)
|
||||
.expect(401)
|
||||
|
||||
expect(res.text).toEqual('Admin account required')
|
||||
expect(res.body).toEqual({})
|
||||
|
||||
@@ -3,11 +3,11 @@ import { MongoMemoryServer } from 'mongodb-memory-server'
|
||||
import request from 'supertest'
|
||||
import app from '../../../app'
|
||||
import { getTreeExample } from '../../../controllers/deploy'
|
||||
import UserController from '../../../controllers/user'
|
||||
import { getTmpFilesFolderPath } from '../../../utils/file'
|
||||
import { folderExists, fileExists, readFile, deleteFolder } from '@sasjs/utils'
|
||||
import path from 'path'
|
||||
import { generateAccessToken } from '../auth'
|
||||
import { createUser } from '../../../controllers/createUser'
|
||||
import { saveTokensInDB } from '../../../utils'
|
||||
|
||||
const clientId = 'someclientID'
|
||||
@@ -22,6 +22,7 @@ const user = {
|
||||
describe('files', () => {
|
||||
let con: Mongoose
|
||||
let mongoServer: MongoMemoryServer
|
||||
const controller = new UserController()
|
||||
|
||||
beforeAll(async () => {
|
||||
mongoServer = await MongoMemoryServer.create()
|
||||
@@ -40,7 +41,7 @@ describe('files', () => {
|
||||
})
|
||||
|
||||
beforeAll(async () => {
|
||||
await createUser(user)
|
||||
await controller.createUser(user)
|
||||
await saveTokensInDB(user.username, clientId, accessToken, 'refreshToken')
|
||||
})
|
||||
const shouldFailAssertion = async (payload: any) => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import mongoose, { Mongoose } from 'mongoose'
|
||||
import { MongoMemoryServer } from 'mongodb-memory-server'
|
||||
import request from 'supertest'
|
||||
import app from '../../../app'
|
||||
import { createUser } from '../../../controllers/createUser'
|
||||
import UserController from '../../../controllers/user'
|
||||
import { generateAccessToken } from '../auth'
|
||||
import { saveTokensInDB } from '../../../utils'
|
||||
|
||||
@@ -25,6 +25,7 @@ const user = {
|
||||
describe('user', () => {
|
||||
let con: Mongoose
|
||||
let mongoServer: MongoMemoryServer
|
||||
const controller = new UserController()
|
||||
|
||||
beforeAll(async () => {
|
||||
mongoServer = await MongoMemoryServer.create()
|
||||
@@ -44,7 +45,7 @@ describe('user', () => {
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
await createUser(adminUser)
|
||||
await controller.createUser(adminUser)
|
||||
await saveTokensInDB(
|
||||
adminUser.username,
|
||||
clientId,
|
||||
@@ -87,21 +88,21 @@ describe('user', () => {
|
||||
clientId,
|
||||
username: user.username
|
||||
})
|
||||
await createUser(user)
|
||||
await controller.createUser(user)
|
||||
await saveTokensInDB(user.username, clientId, accessToken, 'refreshToken')
|
||||
|
||||
const res = await request(app)
|
||||
.post('/SASjsApi/user')
|
||||
.auth(accessToken, { type: 'bearer' })
|
||||
.send(user)
|
||||
.expect(403)
|
||||
.expect(401)
|
||||
|
||||
expect(res.text).toEqual('Admin account required')
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Forbidden if username is already present', async () => {
|
||||
await createUser(user)
|
||||
await controller.createUser(user)
|
||||
|
||||
const res = await request(app)
|
||||
.post('/SASjsApi/user')
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import express from 'express'
|
||||
import { createUser } from '../../controllers/createUser'
|
||||
import { updateUser } from '../../controllers/updateUser'
|
||||
import { deleteUser } from '../../controllers/deleteUser'
|
||||
import { authenticateAccessToken, verifyAdmin } from '../../middlewares'
|
||||
import UserController from '../../controllers/user'
|
||||
import {
|
||||
authenticateAccessToken,
|
||||
verifyAdmin,
|
||||
verifyAdminIfNeeded
|
||||
} from '../../middlewares'
|
||||
import User from '../../model/User'
|
||||
import {
|
||||
deleteUserValidation,
|
||||
@@ -12,29 +14,31 @@ import {
|
||||
|
||||
const userRouter = express.Router()
|
||||
|
||||
// create user
|
||||
userRouter.post('/', authenticateAccessToken, verifyAdmin, async (req, res) => {
|
||||
const { error, value: data } = registerUserValidation(req.body)
|
||||
const { error, value: body } = registerUserValidation(req.body)
|
||||
if (error) return res.status(400).send(error.details[0].message)
|
||||
|
||||
const controller = new UserController()
|
||||
try {
|
||||
const savedUser = await createUser(data)
|
||||
res.send(savedUser)
|
||||
const response = await controller.createUser(body)
|
||||
res.send(response)
|
||||
} catch (err: any) {
|
||||
res.status(403).send(err.toString())
|
||||
}
|
||||
})
|
||||
|
||||
userRouter.get('/', authenticateAccessToken, async (req, res) => {
|
||||
const controller = new UserController()
|
||||
try {
|
||||
const users = await User.find({})
|
||||
.select({ _id: 0, username: 1, displayName: 1, isAdmin: 1, isActive: 1 })
|
||||
.exec()
|
||||
res.send(users)
|
||||
const response = await controller.getAllUsers()
|
||||
res.send(response)
|
||||
} catch (err: any) {
|
||||
res.status(403).send(err.toString())
|
||||
}
|
||||
})
|
||||
|
||||
// get one user
|
||||
userRouter.get('/:username', authenticateAccessToken, async (req: any, res) => {
|
||||
const { username } = req.params
|
||||
try {
|
||||
@@ -47,48 +51,45 @@ userRouter.get('/:username', authenticateAccessToken, async (req: any, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
// update user
|
||||
userRouter.patch(
|
||||
'/:username',
|
||||
authenticateAccessToken,
|
||||
verifyAdminIfNeeded,
|
||||
async (req: any, res) => {
|
||||
const { user } = req
|
||||
const { username } = req.params
|
||||
|
||||
// only an admin can update other users
|
||||
if (!user.isAdmin && user.username !== username) {
|
||||
return res.status(401).send('Admin account required')
|
||||
}
|
||||
|
||||
// only an admin can update `isActive` and `isAdmin` fields
|
||||
const { error, value: data } = 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 user = await updateUser(username, data)
|
||||
res.send(user)
|
||||
const response = await controller.updateUser(username, body)
|
||||
res.send(response)
|
||||
} catch (err: any) {
|
||||
res.status(403).send(err.toString())
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// delete user
|
||||
userRouter.delete(
|
||||
'/:username',
|
||||
authenticateAccessToken,
|
||||
verifyAdminIfNeeded,
|
||||
async (req: any, res) => {
|
||||
const { user } = req
|
||||
const { username } = req.params
|
||||
|
||||
// only an admin can delete other users
|
||||
if (!user.isAdmin && user.username !== username) {
|
||||
return res.status(401).send('Admin account required')
|
||||
}
|
||||
|
||||
// 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 deleteUser(username, user.isAdmin, data)
|
||||
await controller.deleteUser(username, data, user.isAdmin)
|
||||
res.status(200).send('Account Deleted!')
|
||||
} catch (err: any) {
|
||||
res.status(403).send(err.toString())
|
||||
|
||||
@@ -2,6 +2,7 @@ import User from '../model/User'
|
||||
|
||||
export const removeTokensInDB = async (username: string, clientId: string) => {
|
||||
const user = await User.findOne({ username })
|
||||
if (!user) return
|
||||
|
||||
const tokenObjIndex = user.tokens.findIndex(
|
||||
(tokenObj: any) => tokenObj.clientId === clientId
|
||||
|
||||
@@ -7,6 +7,7 @@ export const saveTokensInDB = async (
|
||||
refreshToken: string
|
||||
) => {
|
||||
const user = await User.findOne({ username })
|
||||
if (!user) return
|
||||
|
||||
const currentTokenObj = user.tokens.find(
|
||||
(tokenObj: any) => tokenObj.clientId === clientId
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
"outDir": "./build",
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"resolveJsonModule": true
|
||||
"resolveJsonModule": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true
|
||||
},
|
||||
"ts-node": {
|
||||
"files": true
|
||||
|
||||
Reference in New Issue
Block a user