diff --git a/backend/src/collections/roledb/roledb.service.ts b/backend/src/collections/roledb/roledb.service.ts index dfd56c8..05030c6 100644 --- a/backend/src/collections/roledb/roledb.service.ts +++ b/backend/src/collections/roledb/roledb.service.ts @@ -1,7 +1,6 @@ import { Injectable, Logger } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { plainToClass } from 'class-transformer'; -import { validate } from 'class-validator'; import { Permissions } from 'picsur-shared/dist/dto/permissions'; import { ImmuteableRolesList, @@ -14,6 +13,7 @@ import { HasFailed, HasSuccess } from 'picsur-shared/dist/types'; +import { strictValidate } from 'picsur-shared/dist/util/validate'; import { In, Repository } from 'typeorm'; import { ERoleBackend } from '../../models/entities/role.entity'; @@ -172,7 +172,7 @@ export class RolesService { return await this.findOne(user); } else { user = plainToClass(ERoleBackend, user); - const errors = await validate(user, { forbidUnknownValues: true }); + const errors = await strictValidate(user); if (errors.length > 0) { this.logger.warn(errors); return Fail('Invalid role'); diff --git a/backend/src/collections/syspreferencesdb/syspreferencedb.service.ts b/backend/src/collections/syspreferencesdb/syspreferencedb.service.ts index 4c071f1..9a1d0ec 100644 --- a/backend/src/collections/syspreferencesdb/syspreferencedb.service.ts +++ b/backend/src/collections/syspreferencesdb/syspreferencedb.service.ts @@ -1,7 +1,6 @@ import { Injectable, Logger } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { plainToClass } from 'class-transformer'; -import { validate } from 'class-validator'; import { InternalSysprefRepresentation, SysPreferences, @@ -14,6 +13,7 @@ import { Failable, HasFailed } from 'picsur-shared/dist/types'; +import { strictValidate } from 'picsur-shared/dist/util/validate'; import { Repository } from 'typeorm'; import { ESysPreferenceBackend } from '../../models/entities/syspreference.entity'; import { SysPreferenceDefaultsService } from './syspreferencedefaults.service'; @@ -81,9 +81,7 @@ export class SysPreferenceService { ESysPreferenceBackend, foundSysPreference, ); - const errors = await validate(foundSysPreference, { - forbidUnknownValues: true, - }); + const errors = await strictValidate(foundSysPreference); if (errors.length > 0) { this.logger.warn(errors); return Fail('Invalid preference'); @@ -183,9 +181,7 @@ export class SysPreferenceService { verifySysPreference.value = validatedValue; // Just to be sure - const errors = await validate(verifySysPreference, { - forbidUnknownValues: true, - }); + const errors = await strictValidate(verifySysPreference); if (errors.length > 0) { this.logger.warn(errors); return Fail('Invalid preference'); diff --git a/backend/src/collections/userdb/userdb.service.ts b/backend/src/collections/userdb/userdb.service.ts index e12f64b..9dd2769 100644 --- a/backend/src/collections/userdb/userdb.service.ts +++ b/backend/src/collections/userdb/userdb.service.ts @@ -2,7 +2,6 @@ import { Injectable, Logger } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import * as bcrypt from 'bcrypt'; import { plainToClass } from 'class-transformer'; -import { validate } from 'class-validator'; import { Permissions } from 'picsur-shared/dist/dto/permissions'; import { PermanentRolesList, Roles } from 'picsur-shared/dist/dto/roles.dto'; import { @@ -11,6 +10,7 @@ import { HasFailed, HasSuccess } from 'picsur-shared/dist/types'; +import { strictValidate } from 'picsur-shared/dist/util/validate'; import { Repository } from 'typeorm'; import { EUserBackend } from '../../models/entities/user.entity'; import { GetCols } from '../collectionutils'; @@ -170,7 +170,7 @@ export class UsersService { return await this.findOne(user); } else { user = plainToClass(EUserBackend, user); - const errors = await validate(user, { forbidUnknownValues: true }); + const errors = await strictValidate(user); if (errors.length > 0) { this.logger.warn(errors); return Fail('Invalid user'); diff --git a/backend/src/decorators/multipart.pipe.ts b/backend/src/decorators/multipart.pipe.ts index 0607dcc..c9b42a2 100644 --- a/backend/src/decorators/multipart.pipe.ts +++ b/backend/src/decorators/multipart.pipe.ts @@ -5,10 +5,10 @@ import { PipeTransform, Scope } from '@nestjs/common'; -import { validate } from 'class-validator'; import { FastifyRequest } from 'fastify'; import { MultipartFields, MultipartFile } from 'fastify-multipart'; import { Newable } from 'picsur-shared/dist/types'; +import { strictValidate } from 'picsur-shared/dist/util/validate'; import { MultipartConfigService } from '../config/multipart.config.service'; import { MultiPartFieldDto, @@ -61,7 +61,7 @@ export class MultiPartPipe implements PipeTransform { } } - const errors = await validate(dtoClass, { forbidUnknownValues: true }); + const errors = await strictValidate(dtoClass); if (errors.length > 0) { this.logger.warn(errors); throw new BadRequestException('Invalid file'); diff --git a/backend/src/main.ts b/backend/src/main.ts index 22612b8..70c24c4 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -5,6 +5,7 @@ import { NestFastifyApplication } from '@nestjs/platform-fastify'; import * as multipart from 'fastify-multipart'; +import { ValidateOptions } from 'picsur-shared/dist/util/validate'; import { AppModule } from './app.module'; import { UsersService } from './collections/userdb/userdb.service'; import { HostConfigService } from './config/host.config.service'; @@ -28,12 +29,7 @@ async function bootstrap() { ); app.useGlobalFilters(new MainExceptionFilter()); app.useGlobalInterceptors(new SuccessInterceptor()); - app.useGlobalPipes( - new ValidationPipe({ - disableErrorMessages: true, - forbidUnknownValues: true, - }), - ); + app.useGlobalPipes(new ValidationPipe(ValidateOptions)); app.useGlobalGuards( new MainAuthGuard(app.get(Reflector), app.get(UsersService)), ); diff --git a/backend/src/managers/auth/auth.service.ts b/backend/src/managers/auth/auth.service.ts index 28189c0..0174d45 100644 --- a/backend/src/managers/auth/auth.service.ts +++ b/backend/src/managers/auth/auth.service.ts @@ -1,8 +1,8 @@ import { Injectable, Logger } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { instanceToPlain, plainToClass } from 'class-transformer'; -import { validate } from 'class-validator'; import { JwtDataDto } from 'picsur-shared/dist/dto/jwt.dto'; +import { strictValidate } from 'picsur-shared/dist/util/validate'; import { EUserBackend } from '../../models/entities/user.entity'; @Injectable() @@ -16,7 +16,7 @@ export class AuthManagerService { user, }); - const errors = await validate(jwtData, { forbidUnknownValues: true }); + const errors = await strictValidate(jwtData); if (errors.length > 0) { this.logger.warn(errors); throw new Error('Invalid jwt token generated'); diff --git a/backend/src/managers/auth/guards/admin.guard.ts b/backend/src/managers/auth/guards/admin.guard.ts index 70ab145..10e2750 100644 --- a/backend/src/managers/auth/guards/admin.guard.ts +++ b/backend/src/managers/auth/guards/admin.guard.ts @@ -5,7 +5,7 @@ import { Logger } from '@nestjs/common'; import { plainToClass } from 'class-transformer'; -import { validate } from 'class-validator'; +import { strictValidate } from 'picsur-shared/dist/util/validate'; import { EUserBackend } from '../../../models/entities/user.entity'; @Injectable() @@ -20,7 +20,7 @@ export class AdminGuard implements CanActivate { } const user = plainToClass(EUserBackend, request.user); - const errors = await validate(user, { forbidUnknownValues: true }); + const errors = await strictValidate(user); if (errors.length > 0) { this.logger.warn(errors); return false; diff --git a/backend/src/managers/auth/guards/jwt.strategy.ts b/backend/src/managers/auth/guards/jwt.strategy.ts index 01de761..a482fae 100644 --- a/backend/src/managers/auth/guards/jwt.strategy.ts +++ b/backend/src/managers/auth/guards/jwt.strategy.ts @@ -6,9 +6,9 @@ import { } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { plainToClass } from 'class-transformer'; -import { validate } from 'class-validator'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { JwtDataDto } from 'picsur-shared/dist/dto/jwt.dto'; +import { strictValidate } from 'picsur-shared/dist/util/validate'; import { EUserBackend } from '../../../models/entities/user.entity'; @Injectable() @@ -26,9 +26,7 @@ export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') { async validate(payload: any): Promise { const jwt = plainToClass(JwtDataDto, payload); - const errors = await validate(jwt, { - forbidUnknownValues: true, - }); + const errors = await strictValidate(jwt); if (errors.length > 0) { this.logger.warn(errors); diff --git a/backend/src/managers/auth/guards/main.guard.ts b/backend/src/managers/auth/guards/main.guard.ts index 2986fec..ec219f4 100644 --- a/backend/src/managers/auth/guards/main.guard.ts +++ b/backend/src/managers/auth/guards/main.guard.ts @@ -8,12 +8,12 @@ import { import { Reflector } from '@nestjs/core'; import { AuthGuard } from '@nestjs/passport'; import { plainToClass } from 'class-transformer'; -import { validate } from 'class-validator'; import { Permissions } from 'picsur-shared/dist/dto/permissions'; import { Fail, Failable, HasFailed } from 'picsur-shared/dist/types'; import { isPermissionsArray } from 'picsur-shared/dist/util/permissions'; +import { strictValidate } from 'picsur-shared/dist/util/validate'; import { UsersService } from '../../../collections/userdb/userdb.service'; import { EUserBackend } from '../../../models/entities/user.entity'; @@ -77,9 +77,7 @@ export class MainAuthGuard extends AuthGuard(['jwt', 'guest']) { private async validateUser(user: EUserBackend): Promise { const userClass = plainToClass(EUserBackend, user); - const errors = await validate(userClass, { - forbidUnknownValues: true, - }); + const errors = await strictValidate(userClass); if (errors.length > 0) { this.logger.error( diff --git a/backend/src/routes/api/api.module.ts b/backend/src/routes/api/api.module.ts index fc1f5e0..770330a 100644 --- a/backend/src/routes/api/api.module.ts +++ b/backend/src/routes/api/api.module.ts @@ -1,9 +1,9 @@ import { Module } from '@nestjs/common'; -import { UserApiModule } from './auth/user.module'; import { ExperimentModule } from './experiment/experiment.module'; import { InfoModule } from './info/info.module'; import { PrefModule } from './pref/pref.module'; import { RolesApiModule } from './roles/roles.module'; +import { UserApiModule } from './user/user.module'; @Module({ imports: [ diff --git a/backend/src/routes/api/auth/user.controller.ts b/backend/src/routes/api/user/user.controller.ts similarity index 54% rename from backend/src/routes/api/auth/user.controller.ts rename to backend/src/routes/api/user/user.controller.ts index 4693ce0..4b9f709 100644 --- a/backend/src/routes/api/auth/user.controller.ts +++ b/backend/src/routes/api/user/user.controller.ts @@ -8,18 +8,11 @@ import { Request } from '@nestjs/common'; import { - UserDeleteRequest, - UserDeleteResponse, - UserInfoRequest, - UserInfoResponse, - UserListResponse, UserLoginResponse, UserMePermissionsResponse, UserMeResponse, UserRegisterRequest, - UserRegisterResponse, - UserUpdateRolesRequest, - UserUpdateRolesResponse + UserRegisterResponse } from 'picsur-shared/dist/dto/api/user.dto'; import { Permission } from 'picsur-shared/dist/dto/permissions'; import { HasFailed } from 'picsur-shared/dist/types'; @@ -34,7 +27,7 @@ import AuthFasityRequest from '../../../models/dto/authrequest.dto'; @Controller('api/user') export class UserController { - private readonly logger = new Logger('AuthController'); + private readonly logger = new Logger('UserController'); constructor( private usersService: UsersService, @@ -66,65 +59,6 @@ export class UserController { return user; } - @Post('delete') - @RequiredPermissions(Permission.UserManage) - async delete( - @Body() deleteData: UserDeleteRequest, - ): Promise { - const user = await this.usersService.delete(deleteData.username); - if (HasFailed(user)) { - this.logger.warn(user.getReason()); - throw new InternalServerErrorException('Could not delete user'); - } - - return user; - } - - @Post('roles') - @RequiredPermissions(Permission.UserManage) - async setPermissions( - @Body() body: UserUpdateRolesRequest, - ): Promise { - const updatedUser = await this.usersService.setRoles( - body.username, - body.roles, - ); - - if (HasFailed(updatedUser)) { - this.logger.warn(updatedUser.getReason()); - throw new InternalServerErrorException('Could not update user'); - } - - return updatedUser; - } - - @Post('info') - @RequiredPermissions(Permission.UserManage) - async getUser(@Body() body: UserInfoRequest): Promise { - const user = await this.usersService.findOne(body.username); - if (HasFailed(user)) { - this.logger.warn(user.getReason()); - throw new InternalServerErrorException('Could not find user'); - } - - return user; - } - - @Get('list') - @RequiredPermissions(Permission.UserManage) - async listUsers(): Promise { - const users = await this.usersService.findAll(); - if (HasFailed(users)) { - this.logger.warn(users.getReason()); - throw new InternalServerErrorException('Could not list users'); - } - - return { - users, - total: users.length, - }; - } - @Get('me') @RequiredPermissions(Permission.UserMe) async me(@Request() req: AuthFasityRequest): Promise { diff --git a/backend/src/routes/api/auth/user.module.ts b/backend/src/routes/api/user/user.module.ts similarity index 66% rename from backend/src/routes/api/auth/user.module.ts rename to backend/src/routes/api/user/user.module.ts index 6e41c69..b4ded0d 100644 --- a/backend/src/routes/api/auth/user.module.ts +++ b/backend/src/routes/api/user/user.module.ts @@ -1,9 +1,10 @@ import { Module } from '@nestjs/common'; import { AuthManagerModule } from '../../../managers/auth/auth.module'; import { UserController } from './user.controller'; +import { UserManageController } from './usermanage.controller'; @Module({ imports: [AuthManagerModule], - controllers: [UserController], + controllers: [UserController, UserManageController], }) export class UserApiModule {} diff --git a/backend/src/routes/api/user/usermanage.controller.ts b/backend/src/routes/api/user/usermanage.controller.ts new file mode 100644 index 0000000..a184c1d --- /dev/null +++ b/backend/src/routes/api/user/usermanage.controller.ts @@ -0,0 +1,85 @@ +import { + Body, + Controller, + Get, + InternalServerErrorException, + Logger, + Post +} from '@nestjs/common'; +import { + UserDeleteRequest, + UserDeleteResponse, + UserInfoRequest, + UserInfoResponse, + UserListResponse, + UserUpdateRolesRequest, + UserUpdateRolesResponse +} from 'picsur-shared/dist/dto/api/usermanage.dto'; +import { Permission } from 'picsur-shared/dist/dto/permissions'; +import { HasFailed } from 'picsur-shared/dist/types'; +import { UsersService } from '../../../collections/userdb/userdb.service'; +import { RequiredPermissions } from '../../../decorators/permissions.decorator'; + +@Controller('api/user') +@RequiredPermissions(Permission.UserManage) +export class UserManageController { + private readonly logger = new Logger('UserManageController'); + + constructor(private usersService: UsersService) {} + + @Get('list') + async listUsers(): Promise { + const users = await this.usersService.findAll(); + if (HasFailed(users)) { + this.logger.warn(users.getReason()); + throw new InternalServerErrorException('Could not list users'); + } + + return { + users, + total: users.length, + }; + } + + @Post('delete') + async delete( + @Body() deleteData: UserDeleteRequest, + ): Promise { + const user = await this.usersService.delete(deleteData.username); + if (HasFailed(user)) { + this.logger.warn(user.getReason()); + throw new InternalServerErrorException('Could not delete user'); + } + + return user; + } + + @Post('roles') + async setPermissions( + @Body() body: UserUpdateRolesRequest, + ): Promise { + const updatedUser = await this.usersService.setRoles( + body.username, + body.roles, + ); + + if (HasFailed(updatedUser)) { + this.logger.warn(updatedUser.getReason()); + throw new InternalServerErrorException('Could not update user'); + } + + return updatedUser; + } + + @Post('info') + async getUser(@Body() body: UserInfoRequest): Promise { + console.log(body); + const user = await this.usersService.findOne(body.username); + if (HasFailed(user)) { + this.logger.warn(user.getReason()); + throw new InternalServerErrorException('Could not find user'); + } + + return user; + } +} diff --git a/frontend/src/app/routes/settings/settings-users/settings-users.component.html b/frontend/src/app/routes/settings/settings-users/settings-users.component.html new file mode 100644 index 0000000..2029f43 --- /dev/null +++ b/frontend/src/app/routes/settings/settings-users/settings-users.component.html @@ -0,0 +1,2 @@ +

Users

+ diff --git a/frontend/src/app/routes/settings/settings-users/settings-users.component.ts b/frontend/src/app/routes/settings/settings-users/settings-users.component.ts new file mode 100644 index 0000000..fac6d1c --- /dev/null +++ b/frontend/src/app/routes/settings/settings-users/settings-users.component.ts @@ -0,0 +1,12 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + templateUrl: './settings-users.component.html', +}) +export class SettingsUsersComponent implements OnInit { + constructor() {} + + ngOnInit(): void { + } + +} diff --git a/frontend/src/app/routes/settings/settings-users/settings-users.module.ts b/frontend/src/app/routes/settings/settings-users/settings-users.module.ts new file mode 100644 index 0000000..22b23c7 --- /dev/null +++ b/frontend/src/app/routes/settings/settings-users/settings-users.module.ts @@ -0,0 +1,13 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { SettingsUsersComponent } from './settings-users.component'; +import { SettingsUsersRoutingModule } from './settings-users.routing.module'; + +@NgModule({ + declarations: [SettingsUsersComponent], + imports: [ + CommonModule, + SettingsUsersRoutingModule, + ], +}) +export class SettingsUsersRouteModule {} diff --git a/frontend/src/app/routes/settings/settings-users/settings-users.routing.module.ts b/frontend/src/app/routes/settings/settings-users/settings-users.routing.module.ts new file mode 100644 index 0000000..badd0e1 --- /dev/null +++ b/frontend/src/app/routes/settings/settings-users/settings-users.routing.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { PRoutes } from 'src/app/models/picsur-routes'; +import { SettingsUsersComponent } from './settings-users.component'; + +const routes: PRoutes = [ + { + path: '', + component: SettingsUsersComponent, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class SettingsUsersRoutingModule {} diff --git a/frontend/src/app/routes/settings/settings.routing.module.ts b/frontend/src/app/routes/settings/settings.routing.module.ts index 14954dc..21bc358 100644 --- a/frontend/src/app/routes/settings/settings.routing.module.ts +++ b/frontend/src/app/routes/settings/settings.routing.module.ts @@ -7,6 +7,7 @@ import { SidebarResolverService } from 'src/app/services/sidebar-resolver/sideba import { SettingsGeneralRouteModule } from './settings-general/settings-general.module'; import { SettingsSidebarComponent } from './settings-sidebar/settings-sidebar.component'; import { SettingsSysprefRouteModule } from './settings-syspref/settings-syspref.module'; +import { SettingsUsersRouteModule } from './settings-users/settings-users.module'; const SettingsRoutes: PRoutes = [ { @@ -40,6 +41,18 @@ const SettingsRoutes: PRoutes = [ }, }, }, + { + path: 'users', + loadChildren: () => SettingsUsersRouteModule, + data: { + permissions: [Permission.UserManage], + page: { + title: 'Users', + icon: 'people', + category: 'system', + }, + }, + }, ], canActivate: [PermissionGuard], canActivateChild: [PermissionGuard], diff --git a/frontend/src/app/services/api/api.service.ts b/frontend/src/app/services/api/api.service.ts index f50d80c..84983b7 100644 --- a/frontend/src/app/services/api/api.service.ts +++ b/frontend/src/app/services/api/api.service.ts @@ -1,11 +1,8 @@ import { Injectable } from '@angular/core'; import { ClassConstructor, plainToClass } from 'class-transformer'; -import { validate } from 'class-validator'; -import { - ApiResponse, - ApiSuccessResponse -} from 'picsur-shared/dist/dto/api'; +import { ApiResponse, ApiSuccessResponse } from 'picsur-shared/dist/dto/api'; import { AsyncFailable, Fail, HasFailed } from 'picsur-shared/dist/types'; +import { strictValidate } from 'picsur-shared/dist/util/validate'; import { MultiPartRequest } from '../../models/multi-part-request'; import { KeyService } from './key.service'; @@ -31,7 +28,7 @@ export class ApiService { data: object ): AsyncFailable { const sendClass = plainToClass(sendType, data); - const errors = await validate(sendClass); + const errors = await strictValidate(sendClass); if (errors.length > 0) { this.logger.warn(errors); return Fail('Something went wrong'); @@ -68,14 +65,15 @@ export class ApiService { ApiSuccessResponse, ApiSuccessResponse >(ApiSuccessResponse, result); - const resultErrors = await validate(resultClass); + + const resultErrors = await strictValidate(resultClass); if (resultErrors.length > 0) { this.logger.warn('result', resultErrors); return Fail('Something went wrong'); } const dataClass = plainToClass(type, result.data); - const dataErrors = await validate(dataClass); + const dataErrors = await strictValidate(dataClass); if (dataErrors.length > 0) { this.logger.warn('data', dataErrors); return Fail('Something went wrong'); diff --git a/frontend/src/app/services/api/user.service.ts b/frontend/src/app/services/api/user.service.ts index c78bd2e..93f14bc 100644 --- a/frontend/src/app/services/api/user.service.ts +++ b/frontend/src/app/services/api/user.service.ts @@ -1,14 +1,17 @@ import { Injectable } from '@angular/core'; import { plainToClass } from 'class-transformer'; -import { validate } from 'class-validator'; import jwt_decode from 'jwt-decode'; import { UserLoginRequest, - UserLoginResponse, UserMeResponse, UserRegisterRequest, UserRegisterResponse + UserLoginResponse, + UserMeResponse, + UserRegisterRequest, + UserRegisterResponse } from 'picsur-shared/dist/dto/api/user.dto'; import { JwtDataDto } from 'picsur-shared/dist/dto/jwt.dto'; import { EUser } from 'picsur-shared/dist/entities/user.entity'; import { AsyncFailable, Fail, HasFailed } from 'picsur-shared/dist/types'; +import { strictValidate } from 'picsur-shared/dist/util/validate'; import { BehaviorSubject } from 'rxjs'; import { ApiService } from './api.service'; import { KeyService } from './key.service'; @@ -60,7 +63,10 @@ export class UserService { return user; } - public async register(username: string, password: string): AsyncFailable { + public async register( + username: string, + password: string + ): AsyncFailable { const request: UserRegisterRequest = { username, password, @@ -119,7 +125,7 @@ export class UserService { } const jwtData = plainToClass(JwtDataDto, decoded); - const errors = await validate(jwtData); + const errors = await strictValidate(jwtData); if (errors.length > 0) { this.logger.warn(errors); return Fail('Invalid token data'); diff --git a/shared/src/dto/api/api.dto.ts b/shared/src/dto/api/api.dto.ts index 455b595..1d5b262 100644 --- a/shared/src/dto/api/api.dto.ts +++ b/shared/src/dto/api/api.dto.ts @@ -5,8 +5,7 @@ import { IsNotEmpty, IsString, Max, - Min, - ValidateNested, + Min } from 'class-validator'; class BaseApiResponse { @@ -24,7 +23,7 @@ class BaseApiResponse { @IsNotEmpty() timestamp: string; - @ValidateNested() + //@ValidateNested() @IsDefined() data: T; } diff --git a/shared/src/dto/api/user.dto.ts b/shared/src/dto/api/user.dto.ts index 07aa825..8477794 100644 --- a/shared/src/dto/api/user.dto.ts +++ b/shared/src/dto/api/user.dto.ts @@ -1,15 +1,11 @@ import { Type } from 'class-transformer'; import { IsArray, IsDefined, - IsEnum, - IsInt, - IsNotEmpty, IsPositive, - IsString, + IsEnum, IsNotEmpty, IsString, ValidateNested } from 'class-validator'; import { EUser } from '../../entities/user.entity'; import { Permissions, PermissionsList } from '../permissions'; -import { Roles } from '../roles.dto'; // Api @@ -43,38 +39,6 @@ export class UserRegisterRequest { export class UserRegisterResponse extends EUser {} -// UserDelete -export class UserDeleteRequest { - @IsString() - @IsNotEmpty() - username: string; -} - -export class UserDeleteResponse extends EUser {} - -// UserInfo -export class UserInfoRequest { - @IsString() - @IsNotEmpty() - username: string; -} - -export class UserInfoResponse extends EUser {} - -// UserList -export class UserListResponse { - @IsArray() - @IsDefined() - @ValidateNested() - @Type(() => EUser) - users: EUser[]; - - @IsInt() - @IsPositive() - @IsDefined() - total: number; -} - // UserMe export class UserMeResponse { @IsDefined() @@ -94,17 +58,3 @@ export class UserMePermissionsResponse { @IsEnum(PermissionsList, { each: true }) permissions: Permissions; } - -// UserUpdateRoles -export class UserUpdateRolesRequest { - @IsString() - @IsNotEmpty() - username: string; - - @IsArray() - @IsDefined() - @IsString({ each: true }) - roles: Roles; -} - -export class UserUpdateRolesResponse extends EUser {} diff --git a/shared/src/dto/api/usermanage.dto.ts b/shared/src/dto/api/usermanage.dto.ts new file mode 100644 index 0000000..60fdcce --- /dev/null +++ b/shared/src/dto/api/usermanage.dto.ts @@ -0,0 +1,50 @@ +import { Type } from 'class-transformer'; +import { IsArray, IsDefined, IsInt, IsNotEmpty, IsPositive, IsString, ValidateNested } from 'class-validator'; +import { EUser } from '../../entities/user.entity'; +import { Roles } from '../roles.dto'; + +// UserDelete +export class UserDeleteRequest { + @IsString() + @IsNotEmpty() + username: string; +} + +export class UserDeleteResponse extends EUser {} + +// UserInfo +export class UserInfoRequest { + @IsString() + @IsNotEmpty() + username: string; +} + +export class UserInfoResponse extends EUser {} + +// UserList +export class UserListResponse { + @IsArray() + @IsDefined() + @ValidateNested() + @Type(() => EUser) + users: EUser[]; + + @IsInt() + @IsPositive() + @IsDefined() + total: number; +} + +// UserUpdateRoles +export class UserUpdateRolesRequest { + @IsString() + @IsNotEmpty() + username: string; + + @IsArray() + @IsDefined() + @IsString({ each: true }) + roles: Roles; +} + +export class UserUpdateRolesResponse extends EUser {} diff --git a/shared/src/util/validate.ts b/shared/src/util/validate.ts new file mode 100644 index 0000000..88db690 --- /dev/null +++ b/shared/src/util/validate.ts @@ -0,0 +1,12 @@ +import { validate } from 'class-validator'; + +export const ValidateOptions = { + disableErrorMessages: true, + forbidNonWhitelisted: true, + forbidUnknownValues: true, + stopAtFirstError: true, + whitelist: true, +}; + +export const strictValidate = (object: object) => + validate(object, ValidateOptions);