mirror of
https://github.com/sasjs/server.git
synced 2025-12-10 11:24:35 +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 { verifyTokenInDB } from './verifyTokenInDB'
|
||||
import { verifyTokenInDB } from '../utils'
|
||||
|
||||
export const authenticateAccessToken = (req: any, res: any, next: any) => {
|
||||
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 User from '../../model/User'
|
||||
|
||||
import {
|
||||
authenticateAccessToken,
|
||||
authenticateRefreshToken,
|
||||
authenticateRefreshToken
|
||||
} from '../../middlewares'
|
||||
|
||||
import {
|
||||
authorizeValidation,
|
||||
removeTokensInDB,
|
||||
saveTokensInDB,
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import express from 'express'
|
||||
import dotenv from 'dotenv'
|
||||
|
||||
import { authenticateAccessToken, verifyAdmin } from '../../middlewares'
|
||||
|
||||
import driveRouter from './drive'
|
||||
import stpRouter from './stp'
|
||||
import userRouter from './user'
|
||||
import clientRouter from './client'
|
||||
import authRouter, { connectDB } from './auth'
|
||||
import { authenticateAccessToken } from '../../utils'
|
||||
|
||||
dotenv.config()
|
||||
connectDB()
|
||||
@@ -15,14 +16,8 @@ const router = express.Router()
|
||||
|
||||
router.use('/drive', authenticateAccessToken, driveRouter)
|
||||
router.use('/stp', authenticateAccessToken, stpRouter)
|
||||
router.use('/user', authenticateAccessToken, verifyAdmin, userRouter)
|
||||
router.use('/user', userRouter)
|
||||
router.use('/client', authenticateAccessToken, verifyAdmin, clientRouter)
|
||||
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
|
||||
|
||||
@@ -1,25 +1,99 @@
|
||||
import express from 'express'
|
||||
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()
|
||||
|
||||
userRouter.post('/', async (req, res) => {
|
||||
userRouter.post('/', authenticateAccessToken, verifyAdmin, async (req, res) => {
|
||||
const { error, value: data } = registerUserValidation(req.body)
|
||||
if (error) return res.status(400).send(error.details[0].message)
|
||||
|
||||
try {
|
||||
const savedUser = await createUser(data)
|
||||
res.send({
|
||||
displayName: savedUser.displayName,
|
||||
username: savedUser.username,
|
||||
isAdmin: savedUser.isAdmin,
|
||||
isActive: savedUser.isActive,
|
||||
tokens: []
|
||||
})
|
||||
res.send(savedUser)
|
||||
} catch (err: any) {
|
||||
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
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export * from './authenticateToken'
|
||||
export * from './file'
|
||||
export * from './removeTokensInDB'
|
||||
export * from './saveTokensInDB'
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import Joi from 'joi'
|
||||
|
||||
const usernameSchema = Joi.string().alphanum().min(6).max(20).required()
|
||||
const passwordSchema = Joi.string().min(6).max(1024).required()
|
||||
const usernameSchema = Joi.string().alphanum().min(6).max(20)
|
||||
const passwordSchema = Joi.string().min(6).max(1024)
|
||||
|
||||
export const authorizeValidation = (data: any): Joi.ValidationResult =>
|
||||
Joi.object({
|
||||
username: usernameSchema,
|
||||
password: passwordSchema,
|
||||
username: usernameSchema.required(),
|
||||
password: passwordSchema.required(),
|
||||
clientId: Joi.string().required()
|
||||
}).validate(data)
|
||||
|
||||
@@ -19,12 +19,40 @@ export const tokenValidation = (data: any): Joi.ValidationResult =>
|
||||
export const registerUserValidation = (data: any): Joi.ValidationResult =>
|
||||
Joi.object({
|
||||
displayName: Joi.string().min(6).required(),
|
||||
username: usernameSchema,
|
||||
password: passwordSchema,
|
||||
username: usernameSchema.required(),
|
||||
password: passwordSchema.required(),
|
||||
isAdmin: Joi.boolean(),
|
||||
isActive: Joi.boolean()
|
||||
}).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 =>
|
||||
Joi.object({
|
||||
clientId: Joi.string().required(),
|
||||
|
||||
@@ -8,6 +8,8 @@ export const verifyTokenInDB = async (
|
||||
) => {
|
||||
const dbUser = await User.findOne({ username })
|
||||
|
||||
if (!dbUser) return undefined
|
||||
|
||||
const currentTokenObj = dbUser.tokens.find(
|
||||
(tokenObj: any) => tokenObj.clientId === clientId
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user