mirror of
https://github.com/sasjs/server.git
synced 2026-01-02 12:50:05 +00:00
feat: user operation apis added
This commit is contained in:
20
src/controllers/deleteUser.ts
Normal file
20
src/controllers/deleteUser.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
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 })
|
||||||
|
}
|
||||||
35
src/controllers/updateUser.ts
Normal file
35
src/controllers/updateUser.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import jwt from 'jsonwebtoken'
|
import jwt from 'jsonwebtoken'
|
||||||
import { verifyTokenInDB } from './verifyTokenInDB'
|
import { verifyTokenInDB } from '../utils'
|
||||||
|
|
||||||
export const authenticateAccessToken = (req: any, res: any, next: any) => {
|
export const authenticateAccessToken = (req: any, res: any, next: any) => {
|
||||||
authenticateToken(
|
authenticateToken(
|
||||||
2
src/middlewares/index.ts
Normal file
2
src/middlewares/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './authenticateToken'
|
||||||
|
export * from './verifyAdmin'
|
||||||
5
src/middlewares/verifyAdmin.ts
Normal file
5
src/middlewares/verifyAdmin.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export const verifyAdmin = (req: any, res: any, next: any) => {
|
||||||
|
const { user } = req
|
||||||
|
if (!user?.isAdmin) return res.status(401).send('Admin account required')
|
||||||
|
next()
|
||||||
|
}
|
||||||
@@ -5,9 +5,13 @@ import jwt from 'jsonwebtoken'
|
|||||||
|
|
||||||
import Client from '../../model/Client'
|
import Client from '../../model/Client'
|
||||||
import User from '../../model/User'
|
import User from '../../model/User'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
authenticateAccessToken,
|
authenticateAccessToken,
|
||||||
authenticateRefreshToken,
|
authenticateRefreshToken
|
||||||
|
} from '../../middlewares'
|
||||||
|
|
||||||
|
import {
|
||||||
authorizeValidation,
|
authorizeValidation,
|
||||||
removeTokensInDB,
|
removeTokensInDB,
|
||||||
saveTokensInDB,
|
saveTokensInDB,
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import express from 'express'
|
import express from 'express'
|
||||||
import dotenv from 'dotenv'
|
import dotenv from 'dotenv'
|
||||||
|
|
||||||
|
import { authenticateAccessToken, verifyAdmin } from '../../middlewares'
|
||||||
|
|
||||||
import driveRouter from './drive'
|
import driveRouter from './drive'
|
||||||
import stpRouter from './stp'
|
import stpRouter from './stp'
|
||||||
import userRouter from './user'
|
import userRouter from './user'
|
||||||
import clientRouter from './client'
|
import clientRouter from './client'
|
||||||
import authRouter, { connectDB } from './auth'
|
import authRouter, { connectDB } from './auth'
|
||||||
import { authenticateAccessToken } from '../../utils'
|
|
||||||
|
|
||||||
dotenv.config()
|
dotenv.config()
|
||||||
connectDB()
|
connectDB()
|
||||||
@@ -15,14 +16,8 @@ const router = express.Router()
|
|||||||
|
|
||||||
router.use('/drive', authenticateAccessToken, driveRouter)
|
router.use('/drive', authenticateAccessToken, driveRouter)
|
||||||
router.use('/stp', authenticateAccessToken, stpRouter)
|
router.use('/stp', authenticateAccessToken, stpRouter)
|
||||||
router.use('/user', authenticateAccessToken, verifyAdmin, 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)
|
||||||
|
|
||||||
function verifyAdmin(req: any, res: any, next: any) {
|
|
||||||
const { user } = req
|
|
||||||
if (!user?.isAdmin) return res.status(403).send('Admin account required')
|
|
||||||
next()
|
|
||||||
}
|
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|||||||
@@ -1,25 +1,99 @@
|
|||||||
import express from 'express'
|
import express from 'express'
|
||||||
import { createUser } from '../../controllers/createUser'
|
import { createUser } from '../../controllers/createUser'
|
||||||
import { registerUserValidation } from '../../utils'
|
import { updateUser } from '../../controllers/updateUser'
|
||||||
|
import { deleteUser } from '../../controllers/deleteUser'
|
||||||
|
import { authenticateAccessToken, verifyAdmin } from '../../middlewares'
|
||||||
|
import User from '../../model/User'
|
||||||
|
import {
|
||||||
|
deleteUserValidation,
|
||||||
|
registerUserValidation,
|
||||||
|
updateUserValidation
|
||||||
|
} from '../../utils'
|
||||||
|
|
||||||
const userRouter = express.Router()
|
const userRouter = express.Router()
|
||||||
|
|
||||||
userRouter.post('/', async (req, res) => {
|
userRouter.post('/', authenticateAccessToken, verifyAdmin, async (req, res) => {
|
||||||
const { error, value: data } = registerUserValidation(req.body)
|
const { error, value: data } = 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)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const savedUser = await createUser(data)
|
const savedUser = await createUser(data)
|
||||||
res.send({
|
res.send(savedUser)
|
||||||
displayName: savedUser.displayName,
|
|
||||||
username: savedUser.username,
|
|
||||||
isAdmin: savedUser.isAdmin,
|
|
||||||
isActive: savedUser.isActive,
|
|
||||||
tokens: []
|
|
||||||
})
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
res.status(403).send(err.toString())
|
res.status(403).send(err.toString())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
userRouter.get('/', authenticateAccessToken, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const users = await User.find({})
|
||||||
|
.select({ _id: 0, username: 1, displayName: 1, isAdmin: 1, isActive: 1 })
|
||||||
|
.exec()
|
||||||
|
res.send(users)
|
||||||
|
} catch (err: any) {
|
||||||
|
res.status(403).send(err.toString())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
userRouter.get('/:username', authenticateAccessToken, async (req: any, res) => {
|
||||||
|
const { username } = req.params
|
||||||
|
try {
|
||||||
|
const user = await User.findOne({ username })
|
||||||
|
.select({ _id: 0, username: 1, displayName: 1, isAdmin: 1, isActive: 1 })
|
||||||
|
.exec()
|
||||||
|
res.send(user)
|
||||||
|
} catch (err: any) {
|
||||||
|
res.status(403).send(err.toString())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
userRouter.patch(
|
||||||
|
'/:username',
|
||||||
|
authenticateAccessToken,
|
||||||
|
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)
|
||||||
|
if (error) return res.status(400).send(error.details[0].message)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const user = await updateUser(username, data)
|
||||||
|
res.send(user)
|
||||||
|
} catch (err: any) {
|
||||||
|
res.status(403).send(err.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
userRouter.delete(
|
||||||
|
'/:username',
|
||||||
|
authenticateAccessToken,
|
||||||
|
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')
|
||||||
|
}
|
||||||
|
|
||||||
|
const { error, value: data } = deleteUserValidation(req.body, user.isAdmin)
|
||||||
|
if (error) return res.status(400).send(error.details[0].message)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await deleteUser(username, user.isAdmin, data)
|
||||||
|
res.status(200).send('Account Deleted!')
|
||||||
|
} catch (err: any) {
|
||||||
|
res.status(403).send(err.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
export default userRouter
|
export default userRouter
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
export * from './authenticateToken'
|
|
||||||
export * from './file'
|
export * from './file'
|
||||||
export * from './removeTokensInDB'
|
export * from './removeTokensInDB'
|
||||||
export * from './saveTokensInDB'
|
export * from './saveTokensInDB'
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import Joi from 'joi'
|
import Joi from 'joi'
|
||||||
|
|
||||||
const usernameSchema = Joi.string().alphanum().min(6).max(20).required()
|
const usernameSchema = Joi.string().alphanum().min(6).max(20)
|
||||||
const passwordSchema = Joi.string().min(6).max(1024).required()
|
const passwordSchema = Joi.string().min(6).max(1024)
|
||||||
|
|
||||||
export const authorizeValidation = (data: any): Joi.ValidationResult =>
|
export const authorizeValidation = (data: any): Joi.ValidationResult =>
|
||||||
Joi.object({
|
Joi.object({
|
||||||
username: usernameSchema,
|
username: usernameSchema.required(),
|
||||||
password: passwordSchema,
|
password: passwordSchema.required(),
|
||||||
clientId: Joi.string().required()
|
clientId: Joi.string().required()
|
||||||
}).validate(data)
|
}).validate(data)
|
||||||
|
|
||||||
@@ -19,12 +19,40 @@ export const tokenValidation = (data: any): Joi.ValidationResult =>
|
|||||||
export const registerUserValidation = (data: any): Joi.ValidationResult =>
|
export const registerUserValidation = (data: any): Joi.ValidationResult =>
|
||||||
Joi.object({
|
Joi.object({
|
||||||
displayName: Joi.string().min(6).required(),
|
displayName: Joi.string().min(6).required(),
|
||||||
username: usernameSchema,
|
username: usernameSchema.required(),
|
||||||
password: passwordSchema,
|
password: passwordSchema.required(),
|
||||||
isAdmin: Joi.boolean(),
|
isAdmin: Joi.boolean(),
|
||||||
isActive: Joi.boolean()
|
isActive: Joi.boolean()
|
||||||
}).validate(data)
|
}).validate(data)
|
||||||
|
|
||||||
|
export const deleteUserValidation = (
|
||||||
|
data: any,
|
||||||
|
isAdmin: boolean = false
|
||||||
|
): Joi.ValidationResult =>
|
||||||
|
Joi.object(
|
||||||
|
isAdmin
|
||||||
|
? {}
|
||||||
|
: {
|
||||||
|
password: passwordSchema.required()
|
||||||
|
}
|
||||||
|
).validate(data)
|
||||||
|
|
||||||
|
export const updateUserValidation = (
|
||||||
|
data: any,
|
||||||
|
isAdmin: boolean = false
|
||||||
|
): Joi.ValidationResult => {
|
||||||
|
const validationChecks: any = {
|
||||||
|
displayName: Joi.string().min(6),
|
||||||
|
username: usernameSchema,
|
||||||
|
password: passwordSchema
|
||||||
|
}
|
||||||
|
if (isAdmin) {
|
||||||
|
validationChecks.isAdmin = Joi.boolean()
|
||||||
|
validationChecks.isActive = Joi.boolean()
|
||||||
|
}
|
||||||
|
return Joi.object(validationChecks).validate(data)
|
||||||
|
}
|
||||||
|
|
||||||
export const registerClientValidation = (data: any): Joi.ValidationResult =>
|
export const registerClientValidation = (data: any): Joi.ValidationResult =>
|
||||||
Joi.object({
|
Joi.object({
|
||||||
clientId: Joi.string().required(),
|
clientId: Joi.string().required(),
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ export const verifyTokenInDB = async (
|
|||||||
) => {
|
) => {
|
||||||
const dbUser = await User.findOne({ username })
|
const dbUser = await User.findOne({ username })
|
||||||
|
|
||||||
|
if (!dbUser) return undefined
|
||||||
|
|
||||||
const currentTokenObj = dbUser.tokens.find(
|
const currentTokenObj = dbUser.tokens.find(
|
||||||
(tokenObj: any) => tokenObj.clientId === clientId
|
(tokenObj: any) => tokenObj.clientId === clientId
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user