mirror of
https://github.com/sasjs/server.git
synced 2026-01-07 22:50:05 +00:00
chore: swagger docs generated
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,5 +6,6 @@ node_modules/
|
|||||||
sas/
|
sas/
|
||||||
tmp/
|
tmp/
|
||||||
build/
|
build/
|
||||||
|
public/
|
||||||
certificates/
|
certificates/
|
||||||
.env
|
.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",
|
"description": "SASjs server",
|
||||||
"main": "./src/server.ts",
|
"main": "./src/server.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"prestart": "npm run swagger",
|
||||||
"start": "nodemon ./src/server.ts",
|
"start": "nodemon ./src/server.ts",
|
||||||
"start:prod": "nodemon ./src/prod-server.ts",
|
"start:prod": "nodemon ./src/prod-server.ts",
|
||||||
"build": "rimraf build && tsc",
|
"build": "rimraf build && tsc",
|
||||||
|
"swagger": "tsoa spec",
|
||||||
"semantic-release": "semantic-release -d",
|
"semantic-release": "semantic-release -d",
|
||||||
"prepare": "[ -d .git ] && git config core.hooksPath ./.git-hooks || true",
|
"prepare": "[ -d .git ] && git config core.hooksPath ./.git-hooks || true",
|
||||||
"test": "mkdir -p tmp && jest --coverage",
|
"test": "mkdir -p tmp && jest --coverage",
|
||||||
@@ -27,16 +29,21 @@
|
|||||||
"joi": "^17.4.2",
|
"joi": "^17.4.2",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"mongoose": "^6.0.12",
|
"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": {
|
"devDependencies": {
|
||||||
"@types/bcryptjs": "^2.4.2",
|
"@types/bcryptjs": "^2.4.2",
|
||||||
"@types/express": "^4.17.12",
|
"@types/express": "^4.17.12",
|
||||||
"@types/jest": "^26.0.24",
|
"@types/jest": "^26.0.24",
|
||||||
"@types/jsonwebtoken": "^8.5.5",
|
"@types/jsonwebtoken": "^8.5.5",
|
||||||
|
"@types/morgan": "^1.9.3",
|
||||||
"@types/multer": "^1.4.7",
|
"@types/multer": "^1.4.7",
|
||||||
"@types/node": "^15.12.2",
|
"@types/node": "^15.12.2",
|
||||||
"@types/supertest": "^2.0.11",
|
"@types/supertest": "^2.0.11",
|
||||||
|
"@types/swagger-ui-express": "^4.1.3",
|
||||||
"dotenv": "^10.0.0",
|
"dotenv": "^10.0.0",
|
||||||
"jest": "^27.0.6",
|
"jest": "^27.0.6",
|
||||||
"mongodb-memory-server": "^8.0.0",
|
"mongodb-memory-server": "^8.0.0",
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import express from 'express'
|
import express from 'express'
|
||||||
|
import morgan from 'morgan'
|
||||||
import webRouter from './routes/web'
|
import webRouter from './routes/web'
|
||||||
import apiRouter from './routes/api'
|
import apiRouter from './routes/api'
|
||||||
|
|
||||||
const app = express()
|
const app = express()
|
||||||
|
|
||||||
app.use(express.json({ limit: '50mb' }))
|
app.use(express.json({ limit: '50mb' }))
|
||||||
|
app.use(morgan('tiny'))
|
||||||
|
app.use(express.static('public'))
|
||||||
|
|
||||||
app.use('/', webRouter)
|
app.use('/', webRouter)
|
||||||
app.use('/SASjsApi', apiRouter)
|
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 './authenticateToken'
|
||||||
export * from './verifyAdmin'
|
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({
|
export interface UserPayload {
|
||||||
displayName: {
|
/**
|
||||||
type: String,
|
* Display name for user
|
||||||
required: true
|
* @example "John Snow"
|
||||||
},
|
*/
|
||||||
username: {
|
displayName: string
|
||||||
type: String,
|
/**
|
||||||
required: true
|
* Username for user
|
||||||
},
|
* @example "johnSnow01"
|
||||||
password: {
|
*/
|
||||||
type: String,
|
username: string
|
||||||
required: true
|
/**
|
||||||
},
|
* Password for user
|
||||||
isAdmin: {
|
*/
|
||||||
type: Boolean,
|
password: string
|
||||||
default: false
|
/**
|
||||||
},
|
* Account should be admin or not, defaults to false
|
||||||
isActive: {
|
* @example "false"
|
||||||
type: Boolean,
|
*/
|
||||||
default: true
|
isAdmin?: boolean
|
||||||
},
|
/**
|
||||||
tokens: [
|
* Account should be active or not, defaults to true
|
||||||
{
|
* @example "true"
|
||||||
clientId: {
|
*/
|
||||||
type: String,
|
isActive?: boolean
|
||||||
required: true
|
}
|
||||||
},
|
|
||||||
accessToken: {
|
interface UserSchema extends UserPayload {
|
||||||
type: String,
|
isAdmin: boolean
|
||||||
required: true
|
isActive: boolean
|
||||||
},
|
tokens: [{ [key: string]: string }]
|
||||||
refreshToken: {
|
}
|
||||||
type: String,
|
|
||||||
required: true
|
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 express from 'express'
|
||||||
import dotenv from 'dotenv'
|
import dotenv from 'dotenv'
|
||||||
|
import swaggerUi from 'swagger-ui-express'
|
||||||
|
|
||||||
import { authenticateAccessToken, verifyAdmin } from '../../middlewares'
|
import { authenticateAccessToken, verifyAdmin } from '../../middlewares'
|
||||||
|
|
||||||
@@ -19,5 +20,14 @@ router.use('/stp', authenticateAccessToken, stpRouter)
|
|||||||
router.use('/user', userRouter)
|
router.use('/user', userRouter)
|
||||||
router.use('/client', authenticateAccessToken, verifyAdmin, clientRouter)
|
router.use('/client', authenticateAccessToken, verifyAdmin, clientRouter)
|
||||||
router.use('/auth', authRouter)
|
router.use('/auth', authRouter)
|
||||||
|
router.use(
|
||||||
|
'/',
|
||||||
|
swaggerUi.serve,
|
||||||
|
swaggerUi.setup(undefined, {
|
||||||
|
swaggerOptions: {
|
||||||
|
url: '/swagger.json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import mongoose, { Mongoose } from 'mongoose'
|
|||||||
import { MongoMemoryServer } from 'mongodb-memory-server'
|
import { MongoMemoryServer } from 'mongodb-memory-server'
|
||||||
import request from 'supertest'
|
import request from 'supertest'
|
||||||
import app from '../../../app'
|
import app from '../../../app'
|
||||||
import { createUser } from '../../../controllers/createUser'
|
import UserController from '../../../controllers/user'
|
||||||
import { createClient } from '../../../controllers/createClient'
|
import { createClient } from '../../../controllers/createClient'
|
||||||
import {
|
import {
|
||||||
generateAccessToken,
|
generateAccessToken,
|
||||||
@@ -27,6 +27,7 @@ const user = {
|
|||||||
describe('auth', () => {
|
describe('auth', () => {
|
||||||
let con: Mongoose
|
let con: Mongoose
|
||||||
let mongoServer: MongoMemoryServer
|
let mongoServer: MongoMemoryServer
|
||||||
|
const userController = new UserController()
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
mongoServer = await MongoMemoryServer.create()
|
mongoServer = await MongoMemoryServer.create()
|
||||||
@@ -49,7 +50,7 @@ describe('auth', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should respond with authorization code', async () => {
|
it('should respond with authorization code', async () => {
|
||||||
await createUser(user)
|
await userController.createUser(user)
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.post('/SASjsApi/auth/authorize')
|
.post('/SASjsApi/auth/authorize')
|
||||||
@@ -117,7 +118,7 @@ describe('auth', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should respond with Forbidden if password is incorrect', async () => {
|
it('should respond with Forbidden if password is incorrect', async () => {
|
||||||
await createUser(user)
|
await userController.createUser(user)
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.post('/SASjsApi/auth/authorize')
|
.post('/SASjsApi/auth/authorize')
|
||||||
@@ -133,7 +134,7 @@ describe('auth', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should respond with Forbidden if clientId is incorrect', async () => {
|
it('should respond with Forbidden if clientId is incorrect', async () => {
|
||||||
await createUser(user)
|
await userController.createUser(user)
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.post('/SASjsApi/auth/authorize')
|
.post('/SASjsApi/auth/authorize')
|
||||||
@@ -155,7 +156,7 @@ describe('auth', () => {
|
|||||||
username: user.username
|
username: user.username
|
||||||
}
|
}
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await createUser(user)
|
await userController.createUser(user)
|
||||||
})
|
})
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
const collections = mongoose.connection.collections
|
const collections = mongoose.connection.collections
|
||||||
@@ -250,7 +251,7 @@ describe('auth', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await createUser(user)
|
await userController.createUser(user)
|
||||||
await saveTokensInDB(user.username, clientId, 'accessToken', refreshToken)
|
await saveTokensInDB(user.username, clientId, 'accessToken', refreshToken)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -294,7 +295,7 @@ describe('auth', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await createUser(user)
|
await userController.createUser(user)
|
||||||
await saveTokensInDB(user.username, clientId, accessToken, 'refreshToken')
|
await saveTokensInDB(user.username, clientId, accessToken, 'refreshToken')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import { MongoMemoryServer } from 'mongodb-memory-server'
|
|||||||
import request from 'supertest'
|
import request from 'supertest'
|
||||||
import app from '../../../app'
|
import app from '../../../app'
|
||||||
import { createClient } from '../../../controllers/createClient'
|
import { createClient } from '../../../controllers/createClient'
|
||||||
|
import UserController from '../../../controllers/user'
|
||||||
import { generateAccessToken } from '../auth'
|
import { generateAccessToken } from '../auth'
|
||||||
import { createUser } from '../../../controllers/createUser'
|
|
||||||
import { saveTokensInDB } from '../../../utils'
|
import { saveTokensInDB } from '../../../utils'
|
||||||
|
|
||||||
const client = {
|
const client = {
|
||||||
@@ -23,9 +23,10 @@ const newClient = {
|
|||||||
clientSecret: 'newClientSecret'
|
clientSecret: 'newClientSecret'
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('user', () => {
|
describe('client', () => {
|
||||||
let con: Mongoose
|
let con: Mongoose
|
||||||
let mongoServer: MongoMemoryServer
|
let mongoServer: MongoMemoryServer
|
||||||
|
const userController = new UserController()
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
mongoServer = await MongoMemoryServer.create()
|
mongoServer = await MongoMemoryServer.create()
|
||||||
@@ -45,7 +46,7 @@ describe('user', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await createUser(adminUser)
|
await userController.createUser(adminUser)
|
||||||
await saveTokensInDB(
|
await saveTokensInDB(
|
||||||
adminUser.username,
|
adminUser.username,
|
||||||
client.clientId,
|
client.clientId,
|
||||||
@@ -93,7 +94,7 @@ describe('user', () => {
|
|||||||
clientId: client.clientId,
|
clientId: client.clientId,
|
||||||
username: user.username
|
username: user.username
|
||||||
})
|
})
|
||||||
await createUser(user)
|
await userController.createUser(user)
|
||||||
await saveTokensInDB(
|
await saveTokensInDB(
|
||||||
user.username,
|
user.username,
|
||||||
client.clientId,
|
client.clientId,
|
||||||
@@ -105,7 +106,7 @@ describe('user', () => {
|
|||||||
.post('/SASjsApi/client')
|
.post('/SASjsApi/client')
|
||||||
.auth(accessToken, { type: 'bearer' })
|
.auth(accessToken, { type: 'bearer' })
|
||||||
.send(newClient)
|
.send(newClient)
|
||||||
.expect(403)
|
.expect(401)
|
||||||
|
|
||||||
expect(res.text).toEqual('Admin account required')
|
expect(res.text).toEqual('Admin account required')
|
||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import { MongoMemoryServer } from 'mongodb-memory-server'
|
|||||||
import request from 'supertest'
|
import request from 'supertest'
|
||||||
import app from '../../../app'
|
import app from '../../../app'
|
||||||
import { getTreeExample } from '../../../controllers/deploy'
|
import { getTreeExample } from '../../../controllers/deploy'
|
||||||
|
import UserController from '../../../controllers/user'
|
||||||
import { getTmpFilesFolderPath } from '../../../utils/file'
|
import { getTmpFilesFolderPath } from '../../../utils/file'
|
||||||
import { folderExists, fileExists, readFile, deleteFolder } from '@sasjs/utils'
|
import { folderExists, fileExists, readFile, deleteFolder } from '@sasjs/utils'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { generateAccessToken } from '../auth'
|
import { generateAccessToken } from '../auth'
|
||||||
import { createUser } from '../../../controllers/createUser'
|
|
||||||
import { saveTokensInDB } from '../../../utils'
|
import { saveTokensInDB } from '../../../utils'
|
||||||
|
|
||||||
const clientId = 'someclientID'
|
const clientId = 'someclientID'
|
||||||
@@ -22,6 +22,7 @@ const user = {
|
|||||||
describe('files', () => {
|
describe('files', () => {
|
||||||
let con: Mongoose
|
let con: Mongoose
|
||||||
let mongoServer: MongoMemoryServer
|
let mongoServer: MongoMemoryServer
|
||||||
|
const controller = new UserController()
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
mongoServer = await MongoMemoryServer.create()
|
mongoServer = await MongoMemoryServer.create()
|
||||||
@@ -40,7 +41,7 @@ describe('files', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await createUser(user)
|
await controller.createUser(user)
|
||||||
await saveTokensInDB(user.username, clientId, accessToken, 'refreshToken')
|
await saveTokensInDB(user.username, clientId, accessToken, 'refreshToken')
|
||||||
})
|
})
|
||||||
const shouldFailAssertion = async (payload: any) => {
|
const shouldFailAssertion = async (payload: any) => {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import mongoose, { Mongoose } from 'mongoose'
|
|||||||
import { MongoMemoryServer } from 'mongodb-memory-server'
|
import { MongoMemoryServer } from 'mongodb-memory-server'
|
||||||
import request from 'supertest'
|
import request from 'supertest'
|
||||||
import app from '../../../app'
|
import app from '../../../app'
|
||||||
import { createUser } from '../../../controllers/createUser'
|
import UserController from '../../../controllers/user'
|
||||||
import { generateAccessToken } from '../auth'
|
import { generateAccessToken } from '../auth'
|
||||||
import { saveTokensInDB } from '../../../utils'
|
import { saveTokensInDB } from '../../../utils'
|
||||||
|
|
||||||
@@ -25,6 +25,7 @@ const user = {
|
|||||||
describe('user', () => {
|
describe('user', () => {
|
||||||
let con: Mongoose
|
let con: Mongoose
|
||||||
let mongoServer: MongoMemoryServer
|
let mongoServer: MongoMemoryServer
|
||||||
|
const controller = new UserController()
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
mongoServer = await MongoMemoryServer.create()
|
mongoServer = await MongoMemoryServer.create()
|
||||||
@@ -44,7 +45,7 @@ describe('user', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await createUser(adminUser)
|
await controller.createUser(adminUser)
|
||||||
await saveTokensInDB(
|
await saveTokensInDB(
|
||||||
adminUser.username,
|
adminUser.username,
|
||||||
clientId,
|
clientId,
|
||||||
@@ -87,21 +88,21 @@ describe('user', () => {
|
|||||||
clientId,
|
clientId,
|
||||||
username: user.username
|
username: user.username
|
||||||
})
|
})
|
||||||
await createUser(user)
|
await controller.createUser(user)
|
||||||
await saveTokensInDB(user.username, clientId, accessToken, 'refreshToken')
|
await saveTokensInDB(user.username, clientId, accessToken, 'refreshToken')
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.post('/SASjsApi/user')
|
.post('/SASjsApi/user')
|
||||||
.auth(accessToken, { type: 'bearer' })
|
.auth(accessToken, { type: 'bearer' })
|
||||||
.send(user)
|
.send(user)
|
||||||
.expect(403)
|
.expect(401)
|
||||||
|
|
||||||
expect(res.text).toEqual('Admin account required')
|
expect(res.text).toEqual('Admin account required')
|
||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should respond with Forbidden if username is already present', async () => {
|
it('should respond with Forbidden if username is already present', async () => {
|
||||||
await createUser(user)
|
await controller.createUser(user)
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.post('/SASjsApi/user')
|
.post('/SASjsApi/user')
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import express from 'express'
|
import express from 'express'
|
||||||
import { createUser } from '../../controllers/createUser'
|
import UserController from '../../controllers/user'
|
||||||
import { updateUser } from '../../controllers/updateUser'
|
import {
|
||||||
import { deleteUser } from '../../controllers/deleteUser'
|
authenticateAccessToken,
|
||||||
import { authenticateAccessToken, verifyAdmin } from '../../middlewares'
|
verifyAdmin,
|
||||||
|
verifyAdminIfNeeded
|
||||||
|
} from '../../middlewares'
|
||||||
import User from '../../model/User'
|
import User from '../../model/User'
|
||||||
import {
|
import {
|
||||||
deleteUserValidation,
|
deleteUserValidation,
|
||||||
@@ -12,29 +14,31 @@ import {
|
|||||||
|
|
||||||
const userRouter = express.Router()
|
const userRouter = express.Router()
|
||||||
|
|
||||||
|
// create user
|
||||||
userRouter.post('/', authenticateAccessToken, verifyAdmin, async (req, res) => {
|
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)
|
if (error) return res.status(400).send(error.details[0].message)
|
||||||
|
|
||||||
|
const controller = new UserController()
|
||||||
try {
|
try {
|
||||||
const savedUser = await createUser(data)
|
const response = await controller.createUser(body)
|
||||||
res.send(savedUser)
|
res.send(response)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
res.status(403).send(err.toString())
|
res.status(403).send(err.toString())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
userRouter.get('/', authenticateAccessToken, async (req, res) => {
|
userRouter.get('/', authenticateAccessToken, async (req, res) => {
|
||||||
|
const controller = new UserController()
|
||||||
try {
|
try {
|
||||||
const users = await User.find({})
|
const response = await controller.getAllUsers()
|
||||||
.select({ _id: 0, username: 1, displayName: 1, isAdmin: 1, isActive: 1 })
|
res.send(response)
|
||||||
.exec()
|
|
||||||
res.send(users)
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
res.status(403).send(err.toString())
|
res.status(403).send(err.toString())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// get one user
|
||||||
userRouter.get('/:username', authenticateAccessToken, async (req: any, res) => {
|
userRouter.get('/:username', authenticateAccessToken, async (req: any, res) => {
|
||||||
const { username } = req.params
|
const { username } = req.params
|
||||||
try {
|
try {
|
||||||
@@ -47,48 +51,45 @@ userRouter.get('/:username', authenticateAccessToken, async (req: any, res) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// update user
|
||||||
userRouter.patch(
|
userRouter.patch(
|
||||||
'/:username',
|
'/:username',
|
||||||
authenticateAccessToken,
|
authenticateAccessToken,
|
||||||
|
verifyAdminIfNeeded,
|
||||||
async (req: any, res) => {
|
async (req: any, res) => {
|
||||||
const { user } = req
|
const { user } = req
|
||||||
const { username } = req.params
|
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
|
// 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)
|
if (error) return res.status(400).send(error.details[0].message)
|
||||||
|
|
||||||
|
const controller = new UserController()
|
||||||
try {
|
try {
|
||||||
const user = await updateUser(username, data)
|
const response = await controller.updateUser(username, body)
|
||||||
res.send(user)
|
res.send(response)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
res.status(403).send(err.toString())
|
res.status(403).send(err.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// delete user
|
||||||
userRouter.delete(
|
userRouter.delete(
|
||||||
'/:username',
|
'/:username',
|
||||||
authenticateAccessToken,
|
authenticateAccessToken,
|
||||||
|
verifyAdminIfNeeded,
|
||||||
async (req: any, res) => {
|
async (req: any, res) => {
|
||||||
const { user } = req
|
const { user } = req
|
||||||
const { username } = req.params
|
const { username } = req.params
|
||||||
|
|
||||||
// only an admin can delete other users
|
// only an admin can delete user without providing password
|
||||||
if (!user.isAdmin && user.username !== username) {
|
|
||||||
return res.status(401).send('Admin account required')
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
||||||
try {
|
try {
|
||||||
await deleteUser(username, user.isAdmin, data)
|
await controller.deleteUser(username, 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())
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import User from '../model/User'
|
|||||||
|
|
||||||
export const removeTokensInDB = async (username: string, clientId: string) => {
|
export const removeTokensInDB = async (username: string, clientId: string) => {
|
||||||
const user = await User.findOne({ username })
|
const user = await User.findOne({ username })
|
||||||
|
if (!user) return
|
||||||
|
|
||||||
const tokenObjIndex = user.tokens.findIndex(
|
const tokenObjIndex = user.tokens.findIndex(
|
||||||
(tokenObj: any) => tokenObj.clientId === clientId
|
(tokenObj: any) => tokenObj.clientId === clientId
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export const saveTokensInDB = async (
|
|||||||
refreshToken: string
|
refreshToken: string
|
||||||
) => {
|
) => {
|
||||||
const user = await User.findOne({ username })
|
const user = await User.findOne({ username })
|
||||||
|
if (!user) return
|
||||||
|
|
||||||
const currentTokenObj = user.tokens.find(
|
const currentTokenObj = user.tokens.find(
|
||||||
(tokenObj: any) => tokenObj.clientId === clientId
|
(tokenObj: any) => tokenObj.clientId === clientId
|
||||||
|
|||||||
@@ -6,7 +6,9 @@
|
|||||||
"outDir": "./build",
|
"outDir": "./build",
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"resolveJsonModule": true
|
"resolveJsonModule": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"emitDecoratorMetadata": true
|
||||||
},
|
},
|
||||||
"ts-node": {
|
"ts-node": {
|
||||||
"files": true
|
"files": true
|
||||||
|
|||||||
Reference in New Issue
Block a user