mirror of
https://github.com/sasjs/server.git
synced 2025-12-11 03:34:35 +00:00
chore: added incremental field 'id' in user collection
This commit is contained in:
82
package-lock.json
generated
82
package-lock.json
generated
@@ -14,6 +14,7 @@
|
||||
"joi": "^17.4.2",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"mongoose": "^6.0.12",
|
||||
"mongoose-sequence": "^5.3.1",
|
||||
"morgan": "^1.10.0",
|
||||
"multer": "^1.4.3",
|
||||
"swagger-ui-express": "^4.1.6",
|
||||
@@ -24,6 +25,7 @@
|
||||
"@types/express": "^4.17.12",
|
||||
"@types/jest": "^26.0.24",
|
||||
"@types/jsonwebtoken": "^8.5.5",
|
||||
"@types/mongoose-sequence": "^3.0.6",
|
||||
"@types/morgan": "^1.9.3",
|
||||
"@types/multer": "^1.4.7",
|
||||
"@types/node": "^15.12.2",
|
||||
@@ -2399,6 +2401,25 @@
|
||||
"integrity": "sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/mongoose": {
|
||||
"version": "5.11.97",
|
||||
"resolved": "https://registry.npmjs.org/@types/mongoose/-/mongoose-5.11.97.tgz",
|
||||
"integrity": "sha512-cqwOVYT3qXyLiGw7ueU2kX9noE8DPGRY6z8eUxudhXY8NZ7DMKYAxyZkLSevGfhCX3dO/AoX5/SO9lAzfjon0Q==",
|
||||
"deprecated": "Mongoose publishes its own types, so you do not need to install this package.",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"mongoose": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/mongoose-sequence": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/mongoose-sequence/-/mongoose-sequence-3.0.6.tgz",
|
||||
"integrity": "sha512-S6DD4rSlSnUI9BQvR/ACtekpylSIm0pEKayG9NqOlkUo3Q/AZLBmdi0IozSGPQ8JcB2ZSm81nLdZPhTqyOqrQg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/mongoose": "^5.10.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/morgan": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.3.tgz",
|
||||
@@ -2824,6 +2845,14 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/async": {
|
||||
"version": "2.6.3",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
|
||||
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.14"
|
||||
}
|
||||
},
|
||||
"node_modules/async-mutex": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.3.2.tgz",
|
||||
@@ -7684,8 +7713,7 @@
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"node_modules/lodash.capitalize": {
|
||||
"version": "4.2.1",
|
||||
@@ -8258,6 +8286,18 @@
|
||||
"url": "https://opencollective.com/mongoose"
|
||||
}
|
||||
},
|
||||
"node_modules/mongoose-sequence": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/mongoose-sequence/-/mongoose-sequence-5.3.1.tgz",
|
||||
"integrity": "sha512-kQB1ctCdAQT8YdQzoHV0CpBRsO4RNVy03SOkzM6TQKBbGBs1ZgVS4UlKsuvBPaiPt9q5tKgQZvorGJ1awbHDqA==",
|
||||
"dependencies": {
|
||||
"async": "^2.5.0",
|
||||
"lodash": "^4.17.20"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"mongoose": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/mongoose/node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
@@ -16077,6 +16117,24 @@
|
||||
"integrity": "sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/mongoose": {
|
||||
"version": "5.11.97",
|
||||
"resolved": "https://registry.npmjs.org/@types/mongoose/-/mongoose-5.11.97.tgz",
|
||||
"integrity": "sha512-cqwOVYT3qXyLiGw7ueU2kX9noE8DPGRY6z8eUxudhXY8NZ7DMKYAxyZkLSevGfhCX3dO/AoX5/SO9lAzfjon0Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"mongoose": "*"
|
||||
}
|
||||
},
|
||||
"@types/mongoose-sequence": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/mongoose-sequence/-/mongoose-sequence-3.0.6.tgz",
|
||||
"integrity": "sha512-S6DD4rSlSnUI9BQvR/ACtekpylSIm0pEKayG9NqOlkUo3Q/AZLBmdi0IozSGPQ8JcB2ZSm81nLdZPhTqyOqrQg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/mongoose": "^5.10.5"
|
||||
}
|
||||
},
|
||||
"@types/morgan": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.3.tgz",
|
||||
@@ -16439,6 +16497,14 @@
|
||||
"integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=",
|
||||
"dev": true
|
||||
},
|
||||
"async": {
|
||||
"version": "2.6.3",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
|
||||
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
|
||||
"requires": {
|
||||
"lodash": "^4.17.14"
|
||||
}
|
||||
},
|
||||
"async-mutex": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.3.2.tgz",
|
||||
@@ -20154,8 +20220,7 @@
|
||||
"lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"lodash.capitalize": {
|
||||
"version": "4.2.1",
|
||||
@@ -20595,6 +20660,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"mongoose-sequence": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/mongoose-sequence/-/mongoose-sequence-5.3.1.tgz",
|
||||
"integrity": "sha512-kQB1ctCdAQT8YdQzoHV0CpBRsO4RNVy03SOkzM6TQKBbGBs1ZgVS4UlKsuvBPaiPt9q5tKgQZvorGJ1awbHDqA==",
|
||||
"requires": {
|
||||
"async": "^2.5.0",
|
||||
"lodash": "^4.17.20"
|
||||
}
|
||||
},
|
||||
"morgan": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz",
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
"joi": "^17.4.2",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"mongoose": "^6.0.12",
|
||||
"mongoose-sequence": "^5.3.1",
|
||||
"morgan": "^1.10.0",
|
||||
"multer": "^1.4.3",
|
||||
"swagger-ui-express": "^4.1.6",
|
||||
@@ -39,6 +40,7 @@
|
||||
"@types/express": "^4.17.12",
|
||||
"@types/jest": "^26.0.24",
|
||||
"@types/jsonwebtoken": "^8.5.5",
|
||||
"@types/mongoose-sequence": "^3.0.6",
|
||||
"@types/morgan": "^1.9.3",
|
||||
"@types/multer": "^1.4.7",
|
||||
"@types/node": "^15.12.2",
|
||||
|
||||
@@ -15,11 +15,13 @@ import bcrypt from 'bcryptjs'
|
||||
import User, { UserPayload } from '../model/User'
|
||||
|
||||
interface userResponse {
|
||||
id: number
|
||||
username: string
|
||||
displayName: string
|
||||
}
|
||||
|
||||
interface userDetailsResponse {
|
||||
id: number
|
||||
displayName: string
|
||||
username: string
|
||||
isActive: boolean
|
||||
@@ -34,10 +36,12 @@ export default class UserController {
|
||||
*/
|
||||
@Example<userResponse[]>([
|
||||
{
|
||||
id: 123,
|
||||
username: 'johnusername',
|
||||
displayName: 'John'
|
||||
},
|
||||
{
|
||||
id: 456,
|
||||
username: 'starkusername',
|
||||
displayName: 'Stark'
|
||||
}
|
||||
@@ -52,6 +56,7 @@ export default class UserController {
|
||||
*
|
||||
*/
|
||||
@Example<userDetailsResponse>({
|
||||
id: 1234,
|
||||
displayName: 'John Snow',
|
||||
username: 'johnSnow01',
|
||||
isAdmin: false,
|
||||
@@ -64,42 +69,55 @@ export default class UserController {
|
||||
return createUser(body)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user properties - such as group memberships, userName, displayName.
|
||||
* @param userId The user's identifier
|
||||
* @example userId 1234
|
||||
*/
|
||||
@Get('{userId}')
|
||||
public async getUser(@Path() userId: number): Promise<userDetailsResponse> {
|
||||
return getUser(userId)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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"
|
||||
* @param userId The user's identifier
|
||||
* @example userId "1234"
|
||||
*/
|
||||
@Example<userDetailsResponse>({
|
||||
id: 1234,
|
||||
displayName: 'John Snow',
|
||||
username: 'johnSnow01',
|
||||
isAdmin: false,
|
||||
isActive: true
|
||||
})
|
||||
@Patch('{username}')
|
||||
@Patch('{userId}')
|
||||
public async updateUser(
|
||||
@Path() username: string,
|
||||
@Path() userId: number,
|
||||
@Body() body: UserPayload
|
||||
): Promise<userDetailsResponse> {
|
||||
return updateUser(username, body)
|
||||
return updateUser(userId, body)
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a user. Can be performed either by admins, or the user in question.
|
||||
* @param username The user's identifier
|
||||
* @example username "johnSnow01"
|
||||
* @param userId The user's identifier
|
||||
* @example userId 1234
|
||||
*/
|
||||
@Delete('{username}')
|
||||
@Delete('{userId}')
|
||||
public async deleteUser(
|
||||
@Path() username: string,
|
||||
@Path() userId: number,
|
||||
@Body() body: { password?: string },
|
||||
@Query() @Hidden() isAdmin: boolean = false
|
||||
) {
|
||||
return deleteUser(username, isAdmin, body)
|
||||
return deleteUser(userId, isAdmin, body)
|
||||
}
|
||||
}
|
||||
|
||||
const getAllUsers = async () =>
|
||||
await User.find({}).select({ _id: 0, username: 1, displayName: 1 }).exec()
|
||||
const getAllUsers = async (): Promise<userResponse[]> =>
|
||||
await User.find({})
|
||||
.select({ _id: 0, id: 1, username: 1, displayName: 1 })
|
||||
.exec()
|
||||
|
||||
const createUser = async (data: any): Promise<userDetailsResponse> => {
|
||||
const { displayName, username, password, isAdmin, isActive } = data
|
||||
@@ -123,26 +141,29 @@ const createUser = async (data: any): Promise<userDetailsResponse> => {
|
||||
|
||||
const savedUser = await user.save()
|
||||
|
||||
return {
|
||||
displayName: savedUser.displayName,
|
||||
username: savedUser.username,
|
||||
isAdmin: savedUser.isAdmin,
|
||||
isActive: savedUser.isActive
|
||||
}
|
||||
return savedUser
|
||||
}
|
||||
|
||||
const updateUser = async (currentUsername: string, data: any) => {
|
||||
const getUser = async (id: number) => {
|
||||
const user = await User.findOne({ id })
|
||||
.select({
|
||||
_id: 0,
|
||||
id: 1,
|
||||
username: 1,
|
||||
displayName: 1,
|
||||
isAdmin: 1,
|
||||
isActive: 1
|
||||
})
|
||||
.exec()
|
||||
if (!user) throw new Error('User is not found.')
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
const updateUser = async (id: number, 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
|
||||
}
|
||||
const params: any = { displayName, username, isAdmin, isActive }
|
||||
|
||||
if (password) {
|
||||
// Hash passwords
|
||||
@@ -150,31 +171,31 @@ const updateUser = async (currentUsername: string, data: any) => {
|
||||
params.password = await bcrypt.hash(password, salt)
|
||||
}
|
||||
|
||||
const updatedUser = await User.findOneAndUpdate(
|
||||
{ username: currentUsername },
|
||||
params,
|
||||
{ new: true }
|
||||
)
|
||||
const updatedUser = await User.findOneAndUpdate({ id }, params, { new: true })
|
||||
.select({
|
||||
_id: 0,
|
||||
id: 1,
|
||||
username: 1,
|
||||
displayName: 1,
|
||||
isAdmin: 1,
|
||||
isActive: 1
|
||||
})
|
||||
.exec()
|
||||
if (!updatedUser) throw new Error('Unable to update user')
|
||||
|
||||
return {
|
||||
displayName: updatedUser.displayName,
|
||||
username: updatedUser.username,
|
||||
isAdmin: updatedUser.isAdmin,
|
||||
isActive: updatedUser.isActive
|
||||
}
|
||||
return updatedUser
|
||||
}
|
||||
|
||||
const deleteUser = async (username: string, isAdmin: boolean, data: any) => {
|
||||
const deleteUser = async (id: number, isAdmin: boolean, data: any) => {
|
||||
const { password } = data
|
||||
|
||||
const user = await User.findOne({ username })
|
||||
if (!user) throw new Error('Username is not found.')
|
||||
const user = await User.findOne({ id })
|
||||
if (!user) throw new Error('User is not found.')
|
||||
|
||||
if (!isAdmin) {
|
||||
const validPass = await bcrypt.compare(password, user.password)
|
||||
if (!validPass) throw new Error('Invalid password.')
|
||||
}
|
||||
|
||||
await User.deleteOne({ username })
|
||||
await User.deleteOne({ id })
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ const authenticateToken = (
|
||||
|
||||
// verify this valid token's entry in DB
|
||||
const user = await verifyTokenInDB(
|
||||
data?.username,
|
||||
data?.userId,
|
||||
data?.clientId,
|
||||
token,
|
||||
tokenType
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
export const verifyAdminIfNeeded = (req: any, res: any, next: any) => {
|
||||
const { user } = req
|
||||
const { username } = req.params
|
||||
const { userId } = req.params
|
||||
|
||||
if (!user.isAdmin && user.username !== username) {
|
||||
if (!user.isAdmin && user.id !== userId) {
|
||||
return res.status(401).send('Admin account required')
|
||||
}
|
||||
next()
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { Schema, model } from 'mongoose'
|
||||
import { number } from 'joi'
|
||||
import mongoose, { Schema, model } from 'mongoose'
|
||||
const AutoIncrement = require('mongoose-sequence')(mongoose)
|
||||
|
||||
export interface UserPayload {
|
||||
/**
|
||||
@@ -27,50 +29,52 @@ export interface UserPayload {
|
||||
isActive?: boolean
|
||||
}
|
||||
|
||||
interface UserSchema extends UserPayload {
|
||||
interface User extends UserPayload {
|
||||
id: number
|
||||
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
|
||||
}
|
||||
const UserSchema = new Schema<User>({
|
||||
displayName: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
username: {
|
||||
type: String,
|
||||
required: true,
|
||||
unique: 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
|
||||
}
|
||||
]
|
||||
})
|
||||
)
|
||||
}
|
||||
]
|
||||
})
|
||||
UserSchema.plugin(AutoIncrement, { inc_field: 'id' })
|
||||
|
||||
export default model('User', UserSchema)
|
||||
|
||||
@@ -46,14 +46,14 @@ export const connectDB = () => {
|
||||
}
|
||||
}
|
||||
|
||||
export const saveCode = (username: string, clientId: string, code: string) => {
|
||||
if (authCodes[username]) return (authCodes[username][clientId] = code)
|
||||
export const saveCode = (userId: number, clientId: string, code: string) => {
|
||||
if (authCodes[userId]) return (authCodes[userId][clientId] = code)
|
||||
|
||||
authCodes[username] = { [clientId]: code }
|
||||
return authCodes[username][clientId]
|
||||
authCodes[userId] = { [clientId]: code }
|
||||
return authCodes[userId][clientId]
|
||||
}
|
||||
export const deleteCode = (username: string, clientId: string) =>
|
||||
delete authCodes[username][clientId]
|
||||
export const deleteCode = (userId: number, clientId: string) =>
|
||||
delete authCodes[userId][clientId]
|
||||
|
||||
authRouter.post('/authorize', async (req, res) => {
|
||||
const { error, value } = authorizeValidation(req.body)
|
||||
@@ -76,10 +76,10 @@ authRouter.post('/authorize', async (req, res) => {
|
||||
// generate authorization code against clientId
|
||||
const userInfo: InfoJWT = {
|
||||
clientId,
|
||||
username
|
||||
userId: user.id
|
||||
}
|
||||
|
||||
const code = saveCode(username, clientId, generateAuthCode(userInfo))
|
||||
const code = saveCode(user.id, clientId, generateAuthCode(userInfo))
|
||||
|
||||
res.json({ code })
|
||||
})
|
||||
@@ -93,10 +93,9 @@ authRouter.post('/token', async (req, res) => {
|
||||
const userInfo = await verifyAuthCode(clientId, code)
|
||||
if (!userInfo) return res.sendStatus(403)
|
||||
|
||||
if (authCodes[userInfo.username][clientId] !== code)
|
||||
return res.sendStatus(403)
|
||||
if (authCodes[userInfo.userId][clientId] !== code) return res.sendStatus(403)
|
||||
|
||||
deleteCode(userInfo.username, clientId)
|
||||
deleteCode(userInfo.userId, clientId)
|
||||
|
||||
const accessToken = generateAccessToken(userInfo)
|
||||
const refreshToken = jwt.sign(
|
||||
@@ -104,15 +103,15 @@ authRouter.post('/token', async (req, res) => {
|
||||
process.env.REFRESH_TOKEN_SECRET as string
|
||||
)
|
||||
|
||||
await saveTokensInDB(userInfo.username, clientId, accessToken, refreshToken)
|
||||
await saveTokensInDB(userInfo.userId, clientId, accessToken, refreshToken)
|
||||
|
||||
res.json({ accessToken: accessToken, refreshToken: refreshToken })
|
||||
})
|
||||
|
||||
authRouter.post('/refresh', authenticateRefreshToken, async (req: any, res) => {
|
||||
const { username, clientId } = req.user
|
||||
const { userId, clientId } = req.user
|
||||
const userInfo = {
|
||||
username,
|
||||
userId,
|
||||
clientId
|
||||
}
|
||||
|
||||
@@ -122,7 +121,7 @@ authRouter.post('/refresh', authenticateRefreshToken, async (req: any, res) => {
|
||||
process.env.REFRESH_TOKEN_SECRET as string
|
||||
)
|
||||
|
||||
await saveTokensInDB(userInfo.username, clientId, accessToken, refreshToken)
|
||||
await saveTokensInDB(userInfo.userId, clientId, accessToken, refreshToken)
|
||||
|
||||
res.json({ accessToken: accessToken, refreshToken: refreshToken })
|
||||
})
|
||||
@@ -160,7 +159,7 @@ const verifyAuthCode = async (
|
||||
|
||||
const clientInfo: InfoJWT = {
|
||||
clientId: data?.clientId,
|
||||
username: data?.username
|
||||
userId: data?.userId
|
||||
}
|
||||
if (clientInfo.clientId === clientId) {
|
||||
return resolve(clientInfo)
|
||||
|
||||
@@ -17,6 +17,7 @@ import { saveTokensInDB, verifyTokenInDB } from '../../../utils'
|
||||
const clientId = 'someclientID'
|
||||
const clientSecret = 'someclientSecret'
|
||||
const user = {
|
||||
id: 1234,
|
||||
displayName: 'Test User',
|
||||
username: 'testUsername',
|
||||
password: '87654321',
|
||||
@@ -153,7 +154,7 @@ describe('auth', () => {
|
||||
describe('token', () => {
|
||||
const userInfo: InfoJWT = {
|
||||
clientId,
|
||||
username: user.username
|
||||
userId: user.id
|
||||
}
|
||||
beforeAll(async () => {
|
||||
await userController.createUser(user)
|
||||
@@ -166,7 +167,7 @@ describe('auth', () => {
|
||||
|
||||
it('should respond with access and refresh tokens', async () => {
|
||||
const code = saveCode(
|
||||
userInfo.username,
|
||||
userInfo.userId,
|
||||
userInfo.clientId,
|
||||
generateAuthCode(userInfo)
|
||||
)
|
||||
@@ -197,7 +198,7 @@ describe('auth', () => {
|
||||
|
||||
it('should respond with Bad Request if clientId is missing', async () => {
|
||||
const code = saveCode(
|
||||
userInfo.username,
|
||||
userInfo.userId,
|
||||
userInfo.clientId,
|
||||
generateAuthCode(userInfo)
|
||||
)
|
||||
@@ -227,7 +228,7 @@ describe('auth', () => {
|
||||
|
||||
it('should respond with Forbidden if clientId is invalid', async () => {
|
||||
const code = saveCode(
|
||||
userInfo.username,
|
||||
userInfo.userId,
|
||||
userInfo.clientId,
|
||||
generateAuthCode(userInfo)
|
||||
)
|
||||
@@ -245,14 +246,21 @@ describe('auth', () => {
|
||||
})
|
||||
|
||||
describe('refresh', () => {
|
||||
const refreshToken = generateRefreshToken({
|
||||
clientId,
|
||||
username: user.username
|
||||
})
|
||||
let refreshToken: string
|
||||
let currentUser: any
|
||||
|
||||
beforeEach(async () => {
|
||||
await userController.createUser(user)
|
||||
await saveTokensInDB(user.username, clientId, 'accessToken', refreshToken)
|
||||
currentUser = await userController.createUser(user)
|
||||
refreshToken = generateRefreshToken({
|
||||
clientId,
|
||||
userId: currentUser.id
|
||||
})
|
||||
await saveTokensInDB(
|
||||
currentUser.id,
|
||||
clientId,
|
||||
'accessToken',
|
||||
refreshToken
|
||||
)
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
@@ -289,14 +297,22 @@ describe('auth', () => {
|
||||
})
|
||||
|
||||
describe('logout', () => {
|
||||
const accessToken = generateAccessToken({
|
||||
clientId,
|
||||
username: user.username
|
||||
})
|
||||
let accessToken: string
|
||||
let currentUser: any
|
||||
|
||||
beforeEach(async () => {
|
||||
await userController.createUser(user)
|
||||
await saveTokensInDB(user.username, clientId, accessToken, 'refreshToken')
|
||||
currentUser = await userController.createUser(user)
|
||||
accessToken = generateAccessToken({
|
||||
clientId,
|
||||
userId: currentUser.id
|
||||
})
|
||||
|
||||
await saveTokensInDB(
|
||||
currentUser.id,
|
||||
clientId,
|
||||
accessToken,
|
||||
'refreshToken'
|
||||
)
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
@@ -322,7 +338,7 @@ describe('auth', () => {
|
||||
|
||||
expect(
|
||||
await verifyTokenInDB(
|
||||
user.username,
|
||||
currentUser.id,
|
||||
clientId,
|
||||
accessToken,
|
||||
'accessToken'
|
||||
|
||||
@@ -40,15 +40,17 @@ describe('client', () => {
|
||||
})
|
||||
|
||||
describe('create', () => {
|
||||
const adminAccessToken = generateAccessToken({
|
||||
clientId: client.clientId,
|
||||
username: adminUser.username
|
||||
})
|
||||
let adminAccessToken: string
|
||||
let dbUser: any
|
||||
|
||||
beforeAll(async () => {
|
||||
await userController.createUser(adminUser)
|
||||
dbUser = await userController.createUser(adminUser)
|
||||
adminAccessToken = generateAccessToken({
|
||||
clientId: client.clientId,
|
||||
userId: dbUser.id
|
||||
})
|
||||
await saveTokensInDB(
|
||||
adminUser.username,
|
||||
dbUser.id,
|
||||
client.clientId,
|
||||
adminAccessToken,
|
||||
'refreshToken'
|
||||
@@ -90,13 +92,13 @@ describe('client', () => {
|
||||
isAdmin: false,
|
||||
isActive: true
|
||||
}
|
||||
const dbUser = await userController.createUser(user)
|
||||
const accessToken = generateAccessToken({
|
||||
clientId: client.clientId,
|
||||
username: user.username
|
||||
userId: dbUser.id
|
||||
})
|
||||
await userController.createUser(user)
|
||||
await saveTokensInDB(
|
||||
user.username,
|
||||
dbUser.id,
|
||||
client.clientId,
|
||||
accessToken,
|
||||
'refreshToken'
|
||||
|
||||
@@ -35,14 +35,16 @@ describe('files', () => {
|
||||
await mongoServer.stop()
|
||||
})
|
||||
describe('deploy', () => {
|
||||
const accessToken = generateAccessToken({
|
||||
clientId,
|
||||
username: user.username
|
||||
})
|
||||
let accessToken: string
|
||||
let dbUser: any
|
||||
|
||||
beforeAll(async () => {
|
||||
await controller.createUser(user)
|
||||
await saveTokensInDB(user.username, clientId, accessToken, 'refreshToken')
|
||||
dbUser = await controller.createUser(user)
|
||||
accessToken = generateAccessToken({
|
||||
clientId,
|
||||
userId: dbUser.id
|
||||
})
|
||||
await saveTokensInDB(dbUser.id, clientId, accessToken, 'refreshToken')
|
||||
})
|
||||
const shouldFailAssertion = async (payload: any) => {
|
||||
const res = await request(app)
|
||||
|
||||
@@ -39,15 +39,16 @@ describe('user', () => {
|
||||
})
|
||||
|
||||
describe('create', () => {
|
||||
const adminAccessToken = generateAccessToken({
|
||||
clientId,
|
||||
username: adminUser.username
|
||||
})
|
||||
let adminAccessToken: string
|
||||
|
||||
beforeEach(async () => {
|
||||
await controller.createUser(adminUser)
|
||||
const dbUser = await controller.createUser(adminUser)
|
||||
adminAccessToken = generateAccessToken({
|
||||
clientId,
|
||||
userId: dbUser.id
|
||||
})
|
||||
await saveTokensInDB(
|
||||
adminUser.username,
|
||||
dbUser.id,
|
||||
clientId,
|
||||
adminAccessToken,
|
||||
'refreshToken'
|
||||
@@ -84,12 +85,12 @@ describe('user', () => {
|
||||
})
|
||||
|
||||
it('should respond with Forbideen if access token is not of an admin account', async () => {
|
||||
const dbUser = await controller.createUser(user)
|
||||
const accessToken = generateAccessToken({
|
||||
clientId,
|
||||
username: user.username
|
||||
userId: dbUser.id
|
||||
})
|
||||
await controller.createUser(user)
|
||||
await saveTokensInDB(user.username, clientId, accessToken, 'refreshToken')
|
||||
await saveTokensInDB(dbUser.id, clientId, accessToken, 'refreshToken')
|
||||
|
||||
const res = await request(app)
|
||||
.post('/SASjsApi/user')
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
verifyAdmin,
|
||||
verifyAdminIfNeeded
|
||||
} from '../../middlewares'
|
||||
import User from '../../model/User'
|
||||
import {
|
||||
deleteUserValidation,
|
||||
registerUserValidation,
|
||||
@@ -39,13 +38,13 @@ userRouter.get('/', authenticateAccessToken, async (req, res) => {
|
||||
})
|
||||
|
||||
// get one user
|
||||
userRouter.get('/:username', authenticateAccessToken, async (req: any, res) => {
|
||||
const { username } = req.params
|
||||
userRouter.get('/:userId', authenticateAccessToken, async (req: any, res) => {
|
||||
const { userId } = req.params
|
||||
|
||||
const controller = new UserController()
|
||||
try {
|
||||
const user = await User.findOne({ username })
|
||||
.select({ _id: 0, username: 1, displayName: 1, isAdmin: 1, isActive: 1 })
|
||||
.exec()
|
||||
res.send(user)
|
||||
const response = await controller.getUser(userId)
|
||||
res.send(response)
|
||||
} catch (err: any) {
|
||||
res.status(403).send(err.toString())
|
||||
}
|
||||
@@ -53,12 +52,12 @@ userRouter.get('/:username', authenticateAccessToken, async (req: any, res) => {
|
||||
|
||||
// update user
|
||||
userRouter.patch(
|
||||
'/:username',
|
||||
'/:userId',
|
||||
authenticateAccessToken,
|
||||
verifyAdminIfNeeded,
|
||||
async (req: any, res) => {
|
||||
const { user } = req
|
||||
const { username } = req.params
|
||||
const { userId } = req.params
|
||||
|
||||
// only an admin can update `isActive` and `isAdmin` fields
|
||||
const { error, value: body } = updateUserValidation(req.body, user.isAdmin)
|
||||
@@ -66,7 +65,7 @@ userRouter.patch(
|
||||
|
||||
const controller = new UserController()
|
||||
try {
|
||||
const response = await controller.updateUser(username, body)
|
||||
const response = await controller.updateUser(userId, body)
|
||||
res.send(response)
|
||||
} catch (err: any) {
|
||||
res.status(403).send(err.toString())
|
||||
@@ -76,12 +75,12 @@ userRouter.patch(
|
||||
|
||||
// delete user
|
||||
userRouter.delete(
|
||||
'/:username',
|
||||
'/:userId',
|
||||
authenticateAccessToken,
|
||||
verifyAdminIfNeeded,
|
||||
async (req: any, res) => {
|
||||
const { user } = req
|
||||
const { username } = req.params
|
||||
const { userId } = req.params
|
||||
|
||||
// only an admin can delete user without providing password
|
||||
const { error, value: data } = deleteUserValidation(req.body, user.isAdmin)
|
||||
@@ -89,7 +88,7 @@ userRouter.delete(
|
||||
|
||||
const controller = new UserController()
|
||||
try {
|
||||
await controller.deleteUser(username, data, user.isAdmin)
|
||||
await controller.deleteUser(userId, data, user.isAdmin)
|
||||
res.status(200).send('Account Deleted!')
|
||||
} catch (err: any) {
|
||||
res.status(403).send(err.toString())
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export interface InfoJWT {
|
||||
clientId: string
|
||||
username: string
|
||||
userId: number
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import User from '../model/User'
|
||||
|
||||
export const saveTokensInDB = async (
|
||||
username: string,
|
||||
userId: number,
|
||||
clientId: string,
|
||||
accessToken: string,
|
||||
refreshToken: string
|
||||
) => {
|
||||
const user = await User.findOne({ username })
|
||||
const user = await User.findOne({ id: userId })
|
||||
if (!user) return
|
||||
|
||||
const currentTokenObj = user.tokens.find(
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import User from '../model/User'
|
||||
|
||||
export const verifyTokenInDB = async (
|
||||
username: string,
|
||||
userId: number,
|
||||
clientId: string,
|
||||
token: string,
|
||||
tokenType: 'accessToken' | 'refreshToken'
|
||||
) => {
|
||||
const dbUser = await User.findOne({ username })
|
||||
const dbUser = await User.findOne({ id: userId })
|
||||
|
||||
if (!dbUser) return undefined
|
||||
|
||||
@@ -16,8 +16,9 @@ export const verifyTokenInDB = async (
|
||||
|
||||
return currentTokenObj?.[tokenType] === token
|
||||
? {
|
||||
userId: dbUser.id,
|
||||
clientId,
|
||||
username,
|
||||
username: dbUser.username,
|
||||
isAdmin: dbUser.isAdmin,
|
||||
isActive: dbUser.isActive
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user