From 7c7e23696dac8524ee72279ef7436a7dc2c9fa5a Mon Sep 17 00:00:00 2001 From: rubikscraft Date: Fri, 1 Apr 2022 13:18:05 +0200 Subject: [PATCH] addres users by their id --- .../src/collections/roledb/roledb.service.ts | 5 +- .../src/collections/userdb/userdb.service.ts | 65 +++++++++---------- .../auth/guards/localauth.strategy.ts | 1 - .../src/managers/auth/guards/main.guard.ts | 2 +- backend/src/managers/auth/guest.service.ts | 2 +- .../src/routes/api/user/user.controller.ts | 4 +- .../routes/api/user/usermanage.controller.ts | 12 ++-- .../src/app/models/forms-dto/fulluser.dto.ts | 5 -- .../app/models/forms/updateuser.control.ts | 16 ++++- .../roles/settings-roles.component.ts | 4 +- .../settings-users-edit.component.ts | 24 ++++--- .../users/settings-users.component.ts | 4 +- .../users/settings-users.routing.module.ts | 2 +- .../user/register/register.component.ts | 9 ++- frontend/src/app/services/api/info.service.ts | 2 - .../app/services/api/usermanage.service.ts | 13 ++-- shared/src/dto/api/roles.dto.ts | 2 +- shared/src/dto/api/usermanage.dto.ts | 17 +++-- 18 files changed, 99 insertions(+), 90 deletions(-) delete mode 100644 frontend/src/app/models/forms-dto/fulluser.dto.ts diff --git a/backend/src/collections/roledb/roledb.service.ts b/backend/src/collections/roledb/roledb.service.ts index a261c84..4eebf99 100644 --- a/backend/src/collections/roledb/roledb.service.ts +++ b/backend/src/collections/roledb/roledb.service.ts @@ -52,7 +52,10 @@ export class RolesService { } try { - return await this.rolesRepository.remove(roleToModify); + // Makes sure we can return the id + const cloned = plainToClass(ERoleBackend, roleToModify); + await this.rolesRepository.remove(roleToModify); + return cloned; } catch (e: any) { return Fail(e?.message); } diff --git a/backend/src/collections/userdb/userdb.service.ts b/backend/src/collections/userdb/userdb.service.ts index 53b2961..3c50045 100644 --- a/backend/src/collections/userdb/userdb.service.ts +++ b/backend/src/collections/userdb/userdb.service.ts @@ -10,7 +10,6 @@ import { HasSuccess } from 'picsur-shared/dist/types'; import { makeUnique } from 'picsur-shared/dist/util/unique'; -import { strictValidate } from 'picsur-shared/dist/util/validate'; import { Repository } from 'typeorm'; import { Permissions } from '../../models/dto/permissions.dto'; import { @@ -74,10 +73,8 @@ export class UsersService { return plainToClass(EUserBackend, user); } - public async delete( - user: string | EUserBackend, - ): AsyncFailable { - const userToModify = await this.resolve(user); + public async delete(uuid: string): AsyncFailable { + const userToModify = await this.findOne(uuid); if (HasFailed(userToModify)) return userToModify; if (UndeletableUsersList.includes(userToModify.username)) { @@ -85,7 +82,10 @@ export class UsersService { } try { - return await this.usersRepository.remove(userToModify); + // Makes sure we can return the id + const cloned = plainToClass(EUserBackend, userToModify); + await this.usersRepository.remove(userToModify); + return cloned; } catch (e: any) { return Fail(e?.message); } @@ -94,10 +94,10 @@ export class UsersService { // Updating public async setRoles( - user: string | EUserBackend, + uuid: string, roles: string[], ): AsyncFailable { - const userToModify = await this.resolve(user); + const userToModify = await this.findOne(uuid); if (HasFailed(userToModify)) return userToModify; if (ImmutableUsersList.includes(userToModify.username)) { @@ -138,20 +138,18 @@ export class UsersService { return true; } - public async getPermissions( - user: string | EUserBackend, - ): AsyncFailable { - const userToModify = await this.resolve(user); + public async getPermissions(uuid: string): AsyncFailable { + const userToModify = await this.findOne(uuid); if (HasFailed(userToModify)) return userToModify; return await this.rolesService.getPermissions(userToModify.roles); } public async updatePassword( - user: string | EUserBackend, + uuid: string, password: string, ): AsyncFailable { - let userToModify = await this.resolve(user); + let userToModify = await this.findOne(uuid); if (HasFailed(userToModify)) return userToModify; const strength = await this.getBCryptStrength(); @@ -174,7 +172,7 @@ export class UsersService { username: string, password: string, ): AsyncFailable { - const user = await this.findOne(username, true); + const user = await this.findByUsername(username, true); if (HasFailed(user)) return user; if (LockedLoginUsersList.includes(user.username)) { @@ -185,12 +183,12 @@ export class UsersService { if (!(await bcrypt.compare(password, user.password))) return Fail('Wrong password'); - return await this.findOne(username); + return await this.findOne(user.id); } // Listing - public async findOne( + public async findByUsername( username: string, // Also fetch fields that aren't normally sent to the client // (e.g. hashed password) @@ -213,6 +211,19 @@ export class UsersService { } } + public async findOne(uuid: string): AsyncFailable { + try { + const found = await this.usersRepository.findOne({ + where: { id: uuid }, + }); + + if (!found) return Fail('User not found'); + return found as EUserBackend; + } catch (e: any) { + return Fail(e?.message); + } + } + public async findMany( count: number, page: number, @@ -231,26 +242,10 @@ export class UsersService { } public async exists(username: string): Promise { - return HasSuccess(await this.findOne(username)); + return HasSuccess(await this.findByUsername(username)); } - // Internal resolver - - private async resolve( - user: string | EUserBackend, - ): AsyncFailable { - if (typeof user === 'string') { - return await this.findOne(user); - } else { - user = plainToClass(EUserBackend, user); - const errors = await strictValidate(user); - if (errors.length > 0) { - this.logger.warn(errors); - return Fail('Invalid user'); - } - return user; - } - } + // Internal private filterAddedRoles(roles: string[]): string[] { const filteredRoles = roles.filter( diff --git a/backend/src/managers/auth/guards/localauth.strategy.ts b/backend/src/managers/auth/guards/localauth.strategy.ts index 96b8fa8..a7f0c5f 100644 --- a/backend/src/managers/auth/guards/localauth.strategy.ts +++ b/backend/src/managers/auth/guards/localauth.strategy.ts @@ -15,7 +15,6 @@ export class LocalAuthStrategy extends PassportStrategy(Strategy, 'local') { username: string, password: string, ): AsyncFailable { - // All this does is call the usersservice authenticate for authentication const user = await this.usersService.authenticate(username, password); if (HasFailed(user)) { diff --git a/backend/src/managers/auth/guards/main.guard.ts b/backend/src/managers/auth/guards/main.guard.ts index 8c952c0..c746408 100644 --- a/backend/src/managers/auth/guards/main.guard.ts +++ b/backend/src/managers/auth/guards/main.guard.ts @@ -50,7 +50,7 @@ export class MainAuthGuard extends AuthGuard(['jwt', 'guest']) { } // These are the permissions the user has - const userPermissions = await this.usersService.getPermissions(user); + const userPermissions = await this.usersService.getPermissions(user.id); if (HasFailed(userPermissions)) { this.logger.warn('User Permissions: ' + userPermissions.getReason()); throw new InternalServerErrorException(); diff --git a/backend/src/managers/auth/guest.service.ts b/backend/src/managers/auth/guest.service.ts index a2d8181..aaa3345 100644 --- a/backend/src/managers/auth/guest.service.ts +++ b/backend/src/managers/auth/guest.service.ts @@ -14,7 +14,7 @@ export class GuestService { } public async getGuestUser(): Promise { - const user = await this.usersService.findOne('guest'); + const user = await this.usersService.findByUsername('guest'); if (HasFailed(user)) { return this.fallBackUser; } diff --git a/backend/src/routes/api/user/user.controller.ts b/backend/src/routes/api/user/user.controller.ts index 23ae76e..f3de767 100644 --- a/backend/src/routes/api/user/user.controller.ts +++ b/backend/src/routes/api/user/user.controller.ts @@ -66,7 +66,7 @@ export class UserController { @Get('me') @RequiredPermissions(Permission.UserKeepLogin) async me(@Request() req: AuthFasityRequest): Promise { - const user = await this.usersService.findOne(req.user.username); + const user = await this.usersService.findOne(req.user.id); if (HasFailed(user)) { this.logger.warn(user.getReason()); @@ -88,7 +88,7 @@ export class UserController { async refresh( @Request() req: AuthFasityRequest, ): Promise { - const permissions = await this.usersService.getPermissions(req.user); + const permissions = await this.usersService.getPermissions(req.user.id); if (HasFailed(permissions)) { this.logger.warn(permissions.getReason()); throw new InternalServerErrorException('Could not get permissions'); diff --git a/backend/src/routes/api/user/usermanage.controller.ts b/backend/src/routes/api/user/usermanage.controller.ts index 579487c..fc117a5 100644 --- a/backend/src/routes/api/user/usermanage.controller.ts +++ b/backend/src/routes/api/user/usermanage.controller.ts @@ -78,9 +78,9 @@ export class UserManageController { @Post('delete') async delete( - @Body() deleteData: UserDeleteRequest, + @Body() body: UserDeleteRequest, ): Promise { - const user = await this.usersService.delete(deleteData.username); + const user = await this.usersService.delete(body.id); if (HasFailed(user)) { this.logger.warn(user.getReason()); throw new InternalServerErrorException('Could not delete user'); @@ -91,7 +91,7 @@ export class UserManageController { @Post('info') async getUser(@Body() body: UserInfoRequest): Promise { - const user = await this.usersService.findOne(body.username); + const user = await this.usersService.findOne(body.id); if (HasFailed(user)) { this.logger.warn(user.getReason()); throw new InternalServerErrorException('Could not find user'); @@ -104,14 +104,14 @@ export class UserManageController { async setPermissions( @Body() body: UserUpdateRequest, ): Promise { - let user = await this.usersService.findOne(body.username); + let user = await this.usersService.findOne(body.id); if (HasFailed(user)) { this.logger.warn(user.getReason()); throw new InternalServerErrorException('Could not find user'); } if (body.roles) { - user = await this.usersService.setRoles(user, body.roles); + user = await this.usersService.setRoles(user.id, body.roles); if (HasFailed(user)) { this.logger.warn(user.getReason()); throw new InternalServerErrorException('Could not update user'); @@ -119,7 +119,7 @@ export class UserManageController { } if (body.password) { - user = await this.usersService.updatePassword(user, body.password); + user = await this.usersService.updatePassword(user.id, body.password); if (HasFailed(user)) { this.logger.warn(user.getReason()); throw new InternalServerErrorException('Could not update user'); diff --git a/frontend/src/app/models/forms-dto/fulluser.dto.ts b/frontend/src/app/models/forms-dto/fulluser.dto.ts deleted file mode 100644 index 273f5c1..0000000 --- a/frontend/src/app/models/forms-dto/fulluser.dto.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface FullUserModel { - username: string; - password: string; - roles: string[]; -} diff --git a/frontend/src/app/models/forms/updateuser.control.ts b/frontend/src/app/models/forms/updateuser.control.ts index f5b797c..0fcac86 100644 --- a/frontend/src/app/models/forms/updateuser.control.ts +++ b/frontend/src/app/models/forms/updateuser.control.ts @@ -1,5 +1,5 @@ import { FormControl } from '@angular/forms'; -import { FullUserModel } from '../forms-dto/fulluser.dto'; +import { UserCreateRequest, UserUpdateRequest } from 'picsur-shared/dist/dto/api/usermanage.dto'; import { CreatePasswordError, CreateUsernameError, @@ -8,6 +8,7 @@ import { } from '../validators/user.validator'; export class UpdateUserControl { + private id: string = ''; public username = new FormControl('', UsernameValidators); public password = new FormControl('', PasswordValidators); public roles = new FormControl([]); @@ -30,6 +31,10 @@ export class UpdateUserControl { // Data interaction + public putId(id: string) { + this.id = id; + } + public putUsername(username: string) { this.username.setValue(username); } @@ -38,11 +43,18 @@ export class UpdateUserControl { this.roles.setValue(roles); } - public getData(): FullUserModel { + public getDataCreate(): UserCreateRequest { return { username: this.username.value, password: this.password.value, roles: this.selectedRoles, }; } + + public getDataUpdate(): UserUpdateRequest { + return { + ...this.getDataCreate(), + id: this.id, + }; + } } diff --git a/frontend/src/app/routes/settings/roles/settings-roles.component.ts b/frontend/src/app/routes/settings/roles/settings-roles.component.ts index 896a8d7..d2c0b64 100644 --- a/frontend/src/app/routes/settings/roles/settings-roles.component.ts +++ b/frontend/src/app/routes/settings/roles/settings-roles.component.ts @@ -80,11 +80,11 @@ export class SettingsRolesComponent implements OnInit, AfterViewInit { const result = await this.rolesService.deleteRole(role.name); if (HasFailed(result)) { this.utilService.showSnackBar( - 'Failed to delete user', + 'Failed to delete role', SnackBarType.Error ); } else { - this.utilService.showSnackBar('User deleted', SnackBarType.Success); + this.utilService.showSnackBar('Role deleted', SnackBarType.Success); } } diff --git a/frontend/src/app/routes/settings/users/settings-users-edit/settings-users-edit.component.ts b/frontend/src/app/routes/settings/users/settings-users-edit/settings-users-edit.component.ts index c810a02..1604e76 100644 --- a/frontend/src/app/routes/settings/users/settings-users-edit/settings-users-edit.component.ts +++ b/frontend/src/app/routes/settings/users/settings-users-edit/settings-users-edit.component.ts @@ -60,14 +60,14 @@ export class SettingsUsersEditComponent implements OnInit { } private async initUser() { - const username = this.route.snapshot.paramMap.get('username'); + const uuid = this.route.snapshot.paramMap.get('uuid'); // Get special roles const SpecialRoles = await this.staticInfo.getSpecialRoles(); this.soulBoundRoles = SpecialRoles.SoulBoundRoles; // Check if edit or add - if (!username) { + if (!uuid) { this.mode = EditMode.add; this.model.putRoles(SpecialRoles.DefaultRoles); @@ -76,10 +76,10 @@ export class SettingsUsersEditComponent implements OnInit { // Set known data this.mode = EditMode.edit; - this.model.putUsername(username); + this.model.putId(uuid); // Fetch more data - const user = await this.userManageService.getUser(username); + const user = await this.userManageService.getUser(uuid); if (HasFailed(user)) { this.utilService.showSnackBar('Failed to get user', SnackBarType.Error); return; @@ -128,9 +128,8 @@ export class SettingsUsersEditComponent implements OnInit { } async updateUser() { - const data = this.model.getData(); - if (this.adding) { + const data = this.model.getDataCreate(); const resultUser = await this.userManageService.createUser(data); if (HasFailed(resultUser)) { this.utilService.showSnackBar( @@ -142,13 +141,10 @@ export class SettingsUsersEditComponent implements OnInit { this.utilService.showSnackBar('User created', SnackBarType.Success); } else { - const updateData = data.password - ? data - : { username: data.username, roles: data.roles }; + const data = this.model.getDataUpdate(); + if (!data.password) delete data.password; - const resultUser = await this.userManageService.updateUser( - updateData as any - ); + const resultUser = await this.userManageService.updateUser(data); if (HasFailed(resultUser)) { this.utilService.showSnackBar( 'Failed to update user', @@ -167,7 +163,9 @@ export class SettingsUsersEditComponent implements OnInit { if (this.adding) { return false; } else { - return this.ImmutableUsersList.includes(this.model.getData().username); + return this.ImmutableUsersList.includes( + this.model.getDataCreate().username + ); } } } diff --git a/frontend/src/app/routes/settings/users/settings-users.component.ts b/frontend/src/app/routes/settings/users/settings-users.component.ts index 573870a..9420c93 100644 --- a/frontend/src/app/routes/settings/users/settings-users.component.ts +++ b/frontend/src/app/routes/settings/users/settings-users.component.ts @@ -51,7 +51,7 @@ export class SettingsUsersComponent implements OnInit { } public editUser(user: EUser) { - this.router.navigate(['/settings/users/edit', user.username]); + this.router.navigate(['/settings/users/edit', user.id]); } public async deleteUser(user: EUser) { @@ -73,7 +73,7 @@ export class SettingsUsersComponent implements OnInit { }); if (pressedButton === 'delete') { - const result = await this.userManageService.deleteUser(user.username); + const result = await this.userManageService.deleteUser(user.id); if (HasFailed(result)) { this.utilService.showSnackBar( 'Failed to delete user', diff --git a/frontend/src/app/routes/settings/users/settings-users.routing.module.ts b/frontend/src/app/routes/settings/users/settings-users.routing.module.ts index ac18785..755a46f 100644 --- a/frontend/src/app/routes/settings/users/settings-users.routing.module.ts +++ b/frontend/src/app/routes/settings/users/settings-users.routing.module.ts @@ -10,7 +10,7 @@ const routes: PRoutes = [ component: SettingsUsersComponent, }, { - path: 'edit/:username', + path: 'edit/:uuid', component: SettingsUsersEditComponent, }, { diff --git a/frontend/src/app/routes/user/register/register.component.ts b/frontend/src/app/routes/user/register/register.component.ts index 65595a9..c74676d 100644 --- a/frontend/src/app/routes/user/register/register.component.ts +++ b/frontend/src/app/routes/user/register/register.component.ts @@ -63,7 +63,7 @@ export class RegisterComponent implements OnInit { } if (!this.userService.isLoggedIn) { - const loginResult = this.userService.login(data.username, data.password); + const loginResult = await this.userService.login(data.username, data.password); if (HasFailed(loginResult)) { this.logger.warn(loginResult.getReason()); this.utilService.showSnackBar( @@ -71,11 +71,16 @@ export class RegisterComponent implements OnInit { SnackBarType.Error ); } - } else { + this.utilService.showSnackBar( 'Register successful', SnackBarType.Success ); + } else { + this.utilService.showSnackBar( + 'Register successful, did not log in', + SnackBarType.Success + ); } this.router.navigate(['/']); diff --git a/frontend/src/app/services/api/info.service.ts b/frontend/src/app/services/api/info.service.ts index e5c0e34..5e63637 100644 --- a/frontend/src/app/services/api/info.service.ts +++ b/frontend/src/app/services/api/info.service.ts @@ -53,8 +53,6 @@ export class InfoService { const serverVersion = info.version; const clientVersion = this.getFrontendVersion(); - console.log(serverVersion, clientVersion); - if (!isSemVer(serverVersion) || !isSemVer(clientVersion)) { return Fail(`Not a valid semver: ${serverVersion} or ${clientVersion}`); } diff --git a/frontend/src/app/services/api/usermanage.service.ts b/frontend/src/app/services/api/usermanage.service.ts index e402f99..3443f7c 100644 --- a/frontend/src/app/services/api/usermanage.service.ts +++ b/frontend/src/app/services/api/usermanage.service.ts @@ -13,7 +13,6 @@ import { } from 'picsur-shared/dist/dto/api/usermanage.dto'; import { EUser } from 'picsur-shared/dist/entities/user.entity'; import { AsyncFailable, Open } from 'picsur-shared/dist/types'; -import { FullUserModel } from 'src/app/models/forms-dto/fulluser.dto'; import { ApiService } from './api.service'; @Injectable({ @@ -22,12 +21,12 @@ import { ApiService } from './api.service'; export class UserManageService { constructor(private api: ApiService) {} - public async getUser(username: string): AsyncFailable { + public async getUser(id: string): AsyncFailable { return await this.api.post( UserInfoRequest, UserInfoResponse, 'api/user/info', - { username } + { id } ); } @@ -45,7 +44,7 @@ export class UserManageService { return Open(result, 'users'); } - public async createUser(user: FullUserModel): AsyncFailable { + public async createUser(user: UserCreateRequest): AsyncFailable { return await this.api.post( UserCreateRequest, UserCreateResponse, @@ -54,7 +53,7 @@ export class UserManageService { ); } - public async updateUser(user: FullUserModel): AsyncFailable { + public async updateUser(user: UserUpdateRequest): AsyncFailable { return await this.api.post( UserUpdateRequest, UserUpdateResponse, @@ -63,12 +62,12 @@ export class UserManageService { ); } - public async deleteUser(username: string): AsyncFailable { + public async deleteUser(id: string): AsyncFailable { return await this.api.post( UserDeleteRequest, UserDeleteResponse, '/api/user/delete', - { username } + { id } ); } } diff --git a/shared/src/dto/api/roles.dto.ts b/shared/src/dto/api/roles.dto.ts index 97ed9bd..76c19bf 100644 --- a/shared/src/dto/api/roles.dto.ts +++ b/shared/src/dto/api/roles.dto.ts @@ -29,7 +29,7 @@ export class RoleUpdateRequest extends RoleNamePermsObject {} export class RoleUpdateResponse extends ERole {} // RoleCreate -export class RoleCreateRequest extends ERole {} +export class RoleCreateRequest extends RoleNamePermsObject {} export class RoleCreateResponse extends ERole {} // RoleDelete diff --git a/shared/src/dto/api/usermanage.dto.ts b/shared/src/dto/api/usermanage.dto.ts index 29e7b31..c23d01e 100644 --- a/shared/src/dto/api/usermanage.dto.ts +++ b/shared/src/dto/api/usermanage.dto.ts @@ -5,10 +5,11 @@ import { IsOptional, ValidateNested } from 'class-validator'; -import { EUser, NamePassUser, UsernameUser } from '../../entities/user.entity'; +import { EUser, NamePassUser } from '../../entities/user.entity'; import { IsPosInt } from '../../validators/positive-int.validator'; import { IsStringList } from '../../validators/string-list.validator'; -import { IsPlainTextPwd } from '../../validators/user.validators'; +import { IsPlainTextPwd, IsUsername } from '../../validators/user.validators'; +import { EntityIDObject } from '../idobject.dto'; // UserList export class UserListRequest { @@ -42,15 +43,19 @@ export class UserCreateRequest extends NamePassUser { export class UserCreateResponse extends EUser {} // UserDelete -export class UserDeleteRequest extends UsernameUser {} +export class UserDeleteRequest extends EntityIDObject {} export class UserDeleteResponse extends EUser {} // UserInfo -export class UserInfoRequest extends UsernameUser {} +export class UserInfoRequest extends EntityIDObject {} export class UserInfoResponse extends EUser {} -// UserUpdateRoles -export class UserUpdateRequest extends UsernameUser { +// UserUpdate +export class UserUpdateRequest extends EntityIDObject { + @IsOptional() + @IsUsername() + username?: string; + @IsOptional() @IsStringList() roles?: string[];