import express from 'express' import { Security, Route, Tags, Path, Query, Example, Get, Post, Patch, Delete, Body, Hidden, Request } from 'tsoa' import { desktopUser } from '../middlewares' import User, { UserPayload } from '../model/User' import { getUserAutoExec, updateUserAutoExec, ModeType } from '../utils' import { GroupResponse } from './group' export interface UserResponse { id: number username: string displayName: string } interface UserDetailsResponse { id: number displayName: string username: string isActive: boolean isAdmin: boolean autoExec?: string groups?: GroupResponse[] } @Security('bearerAuth') @Route('SASjsApi/user') @Tags('User') export class UserController { /** * @summary Get list of all users (username, displayname). All users can request this. * */ @Example([ { id: 123, username: 'johnusername', displayName: 'John' }, { id: 456, username: 'starkusername', displayName: 'Stark' } ]) @Get('/') public async getAllUsers(): Promise { return getAllUsers() } /** * @summary Create user with the following attributes: UserId, UserName, Password, isAdmin, isActive. Admin only task. * */ @Example({ id: 1234, displayName: 'John Snow', username: 'johnSnow01', isAdmin: false, isActive: true }) @Post('/') public async createUser( @Body() body: UserPayload ): Promise { return createUser(body) } /** * Only Admin or user itself will get user autoExec code. * @summary Get user properties - such as group memberships, userName, displayName. * @param username The User's username * @example username "johnSnow01" */ @Get('by/username/{username}') public async getUserByUsername( @Request() req: express.Request, @Path() username: string ): Promise { const { MODE } = process.env if (MODE === ModeType.Desktop) return getDesktopAutoExec() const { user } = req const getAutoExec = user!.isAdmin || user!.username == username return getUser({ username }, getAutoExec) } /** * Only Admin or user itself will get user autoExec code. * @summary Get user properties - such as group memberships, userName, displayName. * @param userId The user's identifier * @example userId 1234 */ @Get('{userId}') public async getUser( @Request() req: express.Request, @Path() userId: number ): Promise { const { MODE } = process.env if (MODE === ModeType.Desktop) return getDesktopAutoExec() const { user } = req const getAutoExec = user!.isAdmin || user!.userId == userId return getUser({ id: userId }, getAutoExec) } /** * @summary Update user properties - such as displayName. Can be performed either by admins, or the user in question. * @param username The User's username * @example username "johnSnow01" */ @Example({ id: 1234, displayName: 'John Snow', username: 'johnSnow01', isAdmin: false, isActive: true }) @Patch('by/username/{username}') public async updateUserByUsername( @Path() username: string, @Body() body: UserPayload ): Promise { const { MODE } = process.env if (MODE === ModeType.Desktop) return updateDesktopAutoExec(body.autoExec ?? '') return updateUser({ username }, body) } /** * @summary Update user properties - such as displayName. Can be performed either by admins, or the user in question. * @param userId The user's identifier * @example userId "1234" */ @Example({ id: 1234, displayName: 'John Snow', username: 'johnSnow01', isAdmin: false, isActive: true }) @Patch('{userId}') public async updateUser( @Path() userId: number, @Body() body: UserPayload ): Promise { const { MODE } = process.env if (MODE === ModeType.Desktop) return updateDesktopAutoExec(body.autoExec ?? '') return updateUser({ id: userId }, body) } /** * @summary Delete a user. Can be performed either by admins, or the user in question. * @param username The User's username * @example username "johnSnow01" */ @Delete('by/username/{username}') public async deleteUserByUsername( @Path() username: string, @Body() body: { password?: string }, @Query() @Hidden() isAdmin: boolean = false ) { return deleteUser({ username }, isAdmin, body) } /** * @summary Delete a user. Can be performed either by admins, or the user in question. * @param userId The user's identifier * @example userId 1234 */ @Delete('{userId}') public async deleteUser( @Path() userId: number, @Body() body: { password?: string }, @Query() @Hidden() isAdmin: boolean = false ) { return deleteUser({ id: userId }, isAdmin, body) } } const getAllUsers = async (): Promise => await User.find({}) .select({ _id: 0, id: 1, username: 1, displayName: 1 }) .exec() const createUser = async (data: UserPayload): Promise => { const { displayName, username, password, isAdmin, isActive, autoExec } = 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 hashPassword = User.hashPassword(password) // Create a new user const user = new User({ displayName, username, password: hashPassword, isAdmin, isActive, autoExec }) const savedUser = await user.save() return { id: savedUser.id, displayName: savedUser.displayName, username: savedUser.username, isActive: savedUser.isActive, isAdmin: savedUser.isAdmin, autoExec: savedUser.autoExec } } interface GetUserBy { id?: number username?: string } const getUser = async ( findBy: GetUserBy, getAutoExec: boolean ): Promise => { const user = (await User.findOne( findBy, `id displayName username isActive isAdmin autoExec -_id` ).populate( 'groups', 'groupId name description -_id' )) as unknown as UserDetailsResponse if (!user) throw new Error('User is not found.') return { id: user.id, displayName: user.displayName, username: user.username, isActive: user.isActive, isAdmin: user.isAdmin, autoExec: getAutoExec ? user.autoExec ?? '' : undefined, groups: user.groups } } const getDesktopAutoExec = async () => { return { ...desktopUser, id: desktopUser.userId, autoExec: await getUserAutoExec() } } const updateUser = async ( findBy: GetUserBy, data: Partial ): Promise => { const { displayName, username, password, isAdmin, isActive, autoExec } = data const params: any = { displayName, isAdmin, isActive, autoExec } if (username) { // Checking if user is already in the database const usernameExist = await User.findOne({ username }) if (usernameExist) { if ( (findBy.id && usernameExist.id != findBy.id) || (findBy.username && usernameExist.username != findBy.username) ) throw new Error('Username already exists.') } params.username = username } if (password) { // Hash passwords params.password = User.hashPassword(password) } const updatedUser = await User.findOneAndUpdate(findBy, params, { new: true }) if (!updatedUser) throw new Error(`Unable to find user with ${findBy.id || findBy.username}`) return { id: updatedUser.id, username: updatedUser.username, displayName: updatedUser.displayName, isAdmin: updatedUser.isAdmin, isActive: updatedUser.isActive, autoExec: updatedUser.autoExec } } const updateDesktopAutoExec = async (autoExec: string) => { await updateUserAutoExec(autoExec) return { ...desktopUser, id: desktopUser.userId, autoExec } } const deleteUser = async ( findBy: GetUserBy, isAdmin: boolean, { password }: { password?: string } ) => { const user = await User.findOne(findBy) if (!user) throw new Error('User is not found.') if (!isAdmin) { const validPass = user.comparePassword(password!) if (!validPass) throw new Error('Invalid password.') } await User.deleteOne(findBy) }