mirror of
https://github.com/CaramelFur/Picsur.git
synced 2025-11-13 15:25:39 +01:00
change validation to be stricter
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { plainToClass } from 'class-transformer';
|
import { plainToClass } from 'class-transformer';
|
||||||
import { validate } from 'class-validator';
|
|
||||||
import { Permissions } from 'picsur-shared/dist/dto/permissions';
|
import { Permissions } from 'picsur-shared/dist/dto/permissions';
|
||||||
import {
|
import {
|
||||||
ImmuteableRolesList,
|
ImmuteableRolesList,
|
||||||
@@ -14,6 +13,7 @@ import {
|
|||||||
HasFailed,
|
HasFailed,
|
||||||
HasSuccess
|
HasSuccess
|
||||||
} from 'picsur-shared/dist/types';
|
} from 'picsur-shared/dist/types';
|
||||||
|
import { strictValidate } from 'picsur-shared/dist/util/validate';
|
||||||
import { In, Repository } from 'typeorm';
|
import { In, Repository } from 'typeorm';
|
||||||
import { ERoleBackend } from '../../models/entities/role.entity';
|
import { ERoleBackend } from '../../models/entities/role.entity';
|
||||||
|
|
||||||
@@ -172,7 +172,7 @@ export class RolesService {
|
|||||||
return await this.findOne(user);
|
return await this.findOne(user);
|
||||||
} else {
|
} else {
|
||||||
user = plainToClass(ERoleBackend, user);
|
user = plainToClass(ERoleBackend, user);
|
||||||
const errors = await validate(user, { forbidUnknownValues: true });
|
const errors = await strictValidate(user);
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
this.logger.warn(errors);
|
this.logger.warn(errors);
|
||||||
return Fail('Invalid role');
|
return Fail('Invalid role');
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { plainToClass } from 'class-transformer';
|
import { plainToClass } from 'class-transformer';
|
||||||
import { validate } from 'class-validator';
|
|
||||||
import {
|
import {
|
||||||
InternalSysprefRepresentation,
|
InternalSysprefRepresentation,
|
||||||
SysPreferences,
|
SysPreferences,
|
||||||
@@ -14,6 +13,7 @@ import {
|
|||||||
Failable,
|
Failable,
|
||||||
HasFailed
|
HasFailed
|
||||||
} from 'picsur-shared/dist/types';
|
} from 'picsur-shared/dist/types';
|
||||||
|
import { strictValidate } from 'picsur-shared/dist/util/validate';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { ESysPreferenceBackend } from '../../models/entities/syspreference.entity';
|
import { ESysPreferenceBackend } from '../../models/entities/syspreference.entity';
|
||||||
import { SysPreferenceDefaultsService } from './syspreferencedefaults.service';
|
import { SysPreferenceDefaultsService } from './syspreferencedefaults.service';
|
||||||
@@ -81,9 +81,7 @@ export class SysPreferenceService {
|
|||||||
ESysPreferenceBackend,
|
ESysPreferenceBackend,
|
||||||
foundSysPreference,
|
foundSysPreference,
|
||||||
);
|
);
|
||||||
const errors = await validate(foundSysPreference, {
|
const errors = await strictValidate(foundSysPreference);
|
||||||
forbidUnknownValues: true,
|
|
||||||
});
|
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
this.logger.warn(errors);
|
this.logger.warn(errors);
|
||||||
return Fail('Invalid preference');
|
return Fail('Invalid preference');
|
||||||
@@ -183,9 +181,7 @@ export class SysPreferenceService {
|
|||||||
verifySysPreference.value = validatedValue;
|
verifySysPreference.value = validatedValue;
|
||||||
|
|
||||||
// Just to be sure
|
// Just to be sure
|
||||||
const errors = await validate(verifySysPreference, {
|
const errors = await strictValidate(verifySysPreference);
|
||||||
forbidUnknownValues: true,
|
|
||||||
});
|
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
this.logger.warn(errors);
|
this.logger.warn(errors);
|
||||||
return Fail('Invalid preference');
|
return Fail('Invalid preference');
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { Injectable, Logger } from '@nestjs/common';
|
|||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import * as bcrypt from 'bcrypt';
|
import * as bcrypt from 'bcrypt';
|
||||||
import { plainToClass } from 'class-transformer';
|
import { plainToClass } from 'class-transformer';
|
||||||
import { validate } from 'class-validator';
|
|
||||||
import { Permissions } from 'picsur-shared/dist/dto/permissions';
|
import { Permissions } from 'picsur-shared/dist/dto/permissions';
|
||||||
import { PermanentRolesList, Roles } from 'picsur-shared/dist/dto/roles.dto';
|
import { PermanentRolesList, Roles } from 'picsur-shared/dist/dto/roles.dto';
|
||||||
import {
|
import {
|
||||||
@@ -11,6 +10,7 @@ import {
|
|||||||
HasFailed,
|
HasFailed,
|
||||||
HasSuccess
|
HasSuccess
|
||||||
} from 'picsur-shared/dist/types';
|
} from 'picsur-shared/dist/types';
|
||||||
|
import { strictValidate } from 'picsur-shared/dist/util/validate';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { EUserBackend } from '../../models/entities/user.entity';
|
import { EUserBackend } from '../../models/entities/user.entity';
|
||||||
import { GetCols } from '../collectionutils';
|
import { GetCols } from '../collectionutils';
|
||||||
@@ -170,7 +170,7 @@ export class UsersService {
|
|||||||
return await this.findOne(user);
|
return await this.findOne(user);
|
||||||
} else {
|
} else {
|
||||||
user = plainToClass(EUserBackend, user);
|
user = plainToClass(EUserBackend, user);
|
||||||
const errors = await validate(user, { forbidUnknownValues: true });
|
const errors = await strictValidate(user);
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
this.logger.warn(errors);
|
this.logger.warn(errors);
|
||||||
return Fail('Invalid user');
|
return Fail('Invalid user');
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import {
|
|||||||
PipeTransform,
|
PipeTransform,
|
||||||
Scope
|
Scope
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { validate } from 'class-validator';
|
|
||||||
import { FastifyRequest } from 'fastify';
|
import { FastifyRequest } from 'fastify';
|
||||||
import { MultipartFields, MultipartFile } from 'fastify-multipart';
|
import { MultipartFields, MultipartFile } from 'fastify-multipart';
|
||||||
import { Newable } from 'picsur-shared/dist/types';
|
import { Newable } from 'picsur-shared/dist/types';
|
||||||
|
import { strictValidate } from 'picsur-shared/dist/util/validate';
|
||||||
import { MultipartConfigService } from '../config/multipart.config.service';
|
import { MultipartConfigService } from '../config/multipart.config.service';
|
||||||
import {
|
import {
|
||||||
MultiPartFieldDto,
|
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) {
|
if (errors.length > 0) {
|
||||||
this.logger.warn(errors);
|
this.logger.warn(errors);
|
||||||
throw new BadRequestException('Invalid file');
|
throw new BadRequestException('Invalid file');
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
NestFastifyApplication
|
NestFastifyApplication
|
||||||
} from '@nestjs/platform-fastify';
|
} from '@nestjs/platform-fastify';
|
||||||
import * as multipart from 'fastify-multipart';
|
import * as multipart from 'fastify-multipart';
|
||||||
|
import { ValidateOptions } from 'picsur-shared/dist/util/validate';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
import { UsersService } from './collections/userdb/userdb.service';
|
import { UsersService } from './collections/userdb/userdb.service';
|
||||||
import { HostConfigService } from './config/host.config.service';
|
import { HostConfigService } from './config/host.config.service';
|
||||||
@@ -28,12 +29,7 @@ async function bootstrap() {
|
|||||||
);
|
);
|
||||||
app.useGlobalFilters(new MainExceptionFilter());
|
app.useGlobalFilters(new MainExceptionFilter());
|
||||||
app.useGlobalInterceptors(new SuccessInterceptor());
|
app.useGlobalInterceptors(new SuccessInterceptor());
|
||||||
app.useGlobalPipes(
|
app.useGlobalPipes(new ValidationPipe(ValidateOptions));
|
||||||
new ValidationPipe({
|
|
||||||
disableErrorMessages: true,
|
|
||||||
forbidUnknownValues: true,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
app.useGlobalGuards(
|
app.useGlobalGuards(
|
||||||
new MainAuthGuard(app.get(Reflector), app.get(UsersService)),
|
new MainAuthGuard(app.get(Reflector), app.get(UsersService)),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { JwtService } from '@nestjs/jwt';
|
import { JwtService } from '@nestjs/jwt';
|
||||||
import { instanceToPlain, plainToClass } from 'class-transformer';
|
import { instanceToPlain, plainToClass } from 'class-transformer';
|
||||||
import { validate } from 'class-validator';
|
|
||||||
import { JwtDataDto } from 'picsur-shared/dist/dto/jwt.dto';
|
import { JwtDataDto } from 'picsur-shared/dist/dto/jwt.dto';
|
||||||
|
import { strictValidate } from 'picsur-shared/dist/util/validate';
|
||||||
import { EUserBackend } from '../../models/entities/user.entity';
|
import { EUserBackend } from '../../models/entities/user.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -16,7 +16,7 @@ export class AuthManagerService {
|
|||||||
user,
|
user,
|
||||||
});
|
});
|
||||||
|
|
||||||
const errors = await validate(jwtData, { forbidUnknownValues: true });
|
const errors = await strictValidate(jwtData);
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
this.logger.warn(errors);
|
this.logger.warn(errors);
|
||||||
throw new Error('Invalid jwt token generated');
|
throw new Error('Invalid jwt token generated');
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
Logger
|
Logger
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { plainToClass } from 'class-transformer';
|
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';
|
import { EUserBackend } from '../../../models/entities/user.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -20,7 +20,7 @@ export class AdminGuard implements CanActivate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const user = plainToClass(EUserBackend, request.user);
|
const user = plainToClass(EUserBackend, request.user);
|
||||||
const errors = await validate(user, { forbidUnknownValues: true });
|
const errors = await strictValidate(user);
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
this.logger.warn(errors);
|
this.logger.warn(errors);
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import {
|
|||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { PassportStrategy } from '@nestjs/passport';
|
import { PassportStrategy } from '@nestjs/passport';
|
||||||
import { plainToClass } from 'class-transformer';
|
import { plainToClass } from 'class-transformer';
|
||||||
import { validate } from 'class-validator';
|
|
||||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||||
import { JwtDataDto } from 'picsur-shared/dist/dto/jwt.dto';
|
import { JwtDataDto } from 'picsur-shared/dist/dto/jwt.dto';
|
||||||
|
import { strictValidate } from 'picsur-shared/dist/util/validate';
|
||||||
import { EUserBackend } from '../../../models/entities/user.entity';
|
import { EUserBackend } from '../../../models/entities/user.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -26,9 +26,7 @@ export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
|
|||||||
async validate(payload: any): Promise<EUserBackend> {
|
async validate(payload: any): Promise<EUserBackend> {
|
||||||
const jwt = plainToClass(JwtDataDto, payload);
|
const jwt = plainToClass(JwtDataDto, payload);
|
||||||
|
|
||||||
const errors = await validate(jwt, {
|
const errors = await strictValidate(jwt);
|
||||||
forbidUnknownValues: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
this.logger.warn(errors);
|
this.logger.warn(errors);
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ import {
|
|||||||
import { Reflector } from '@nestjs/core';
|
import { Reflector } from '@nestjs/core';
|
||||||
import { AuthGuard } from '@nestjs/passport';
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
import { plainToClass } from 'class-transformer';
|
import { plainToClass } from 'class-transformer';
|
||||||
import { validate } from 'class-validator';
|
|
||||||
import {
|
import {
|
||||||
Permissions
|
Permissions
|
||||||
} from 'picsur-shared/dist/dto/permissions';
|
} from 'picsur-shared/dist/dto/permissions';
|
||||||
import { Fail, Failable, HasFailed } from 'picsur-shared/dist/types';
|
import { Fail, Failable, HasFailed } from 'picsur-shared/dist/types';
|
||||||
import { isPermissionsArray } from 'picsur-shared/dist/util/permissions';
|
import { isPermissionsArray } from 'picsur-shared/dist/util/permissions';
|
||||||
|
import { strictValidate } from 'picsur-shared/dist/util/validate';
|
||||||
import { UsersService } from '../../../collections/userdb/userdb.service';
|
import { UsersService } from '../../../collections/userdb/userdb.service';
|
||||||
import { EUserBackend } from '../../../models/entities/user.entity';
|
import { EUserBackend } from '../../../models/entities/user.entity';
|
||||||
|
|
||||||
@@ -77,9 +77,7 @@ export class MainAuthGuard extends AuthGuard(['jwt', 'guest']) {
|
|||||||
|
|
||||||
private async validateUser(user: EUserBackend): Promise<EUserBackend> {
|
private async validateUser(user: EUserBackend): Promise<EUserBackend> {
|
||||||
const userClass = plainToClass(EUserBackend, user);
|
const userClass = plainToClass(EUserBackend, user);
|
||||||
const errors = await validate(userClass, {
|
const errors = await strictValidate(userClass);
|
||||||
forbidUnknownValues: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { UserApiModule } from './auth/user.module';
|
|
||||||
import { ExperimentModule } from './experiment/experiment.module';
|
import { ExperimentModule } from './experiment/experiment.module';
|
||||||
import { InfoModule } from './info/info.module';
|
import { InfoModule } from './info/info.module';
|
||||||
import { PrefModule } from './pref/pref.module';
|
import { PrefModule } from './pref/pref.module';
|
||||||
import { RolesApiModule } from './roles/roles.module';
|
import { RolesApiModule } from './roles/roles.module';
|
||||||
|
import { UserApiModule } from './user/user.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
|||||||
@@ -8,18 +8,11 @@ import {
|
|||||||
Request
|
Request
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import {
|
import {
|
||||||
UserDeleteRequest,
|
|
||||||
UserDeleteResponse,
|
|
||||||
UserInfoRequest,
|
|
||||||
UserInfoResponse,
|
|
||||||
UserListResponse,
|
|
||||||
UserLoginResponse,
|
UserLoginResponse,
|
||||||
UserMePermissionsResponse,
|
UserMePermissionsResponse,
|
||||||
UserMeResponse,
|
UserMeResponse,
|
||||||
UserRegisterRequest,
|
UserRegisterRequest,
|
||||||
UserRegisterResponse,
|
UserRegisterResponse
|
||||||
UserUpdateRolesRequest,
|
|
||||||
UserUpdateRolesResponse
|
|
||||||
} from 'picsur-shared/dist/dto/api/user.dto';
|
} from 'picsur-shared/dist/dto/api/user.dto';
|
||||||
import { Permission } from 'picsur-shared/dist/dto/permissions';
|
import { Permission } from 'picsur-shared/dist/dto/permissions';
|
||||||
import { HasFailed } from 'picsur-shared/dist/types';
|
import { HasFailed } from 'picsur-shared/dist/types';
|
||||||
@@ -34,7 +27,7 @@ import AuthFasityRequest from '../../../models/dto/authrequest.dto';
|
|||||||
|
|
||||||
@Controller('api/user')
|
@Controller('api/user')
|
||||||
export class UserController {
|
export class UserController {
|
||||||
private readonly logger = new Logger('AuthController');
|
private readonly logger = new Logger('UserController');
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private usersService: UsersService,
|
private usersService: UsersService,
|
||||||
@@ -66,65 +59,6 @@ export class UserController {
|
|||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('delete')
|
|
||||||
@RequiredPermissions(Permission.UserManage)
|
|
||||||
async delete(
|
|
||||||
@Body() deleteData: UserDeleteRequest,
|
|
||||||
): Promise<UserDeleteResponse> {
|
|
||||||
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<UserUpdateRolesResponse> {
|
|
||||||
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<UserInfoResponse> {
|
|
||||||
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<UserListResponse> {
|
|
||||||
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')
|
@Get('me')
|
||||||
@RequiredPermissions(Permission.UserMe)
|
@RequiredPermissions(Permission.UserMe)
|
||||||
async me(@Request() req: AuthFasityRequest): Promise<UserMeResponse> {
|
async me(@Request() req: AuthFasityRequest): Promise<UserMeResponse> {
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { AuthManagerModule } from '../../../managers/auth/auth.module';
|
import { AuthManagerModule } from '../../../managers/auth/auth.module';
|
||||||
import { UserController } from './user.controller';
|
import { UserController } from './user.controller';
|
||||||
|
import { UserManageController } from './usermanage.controller';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [AuthManagerModule],
|
imports: [AuthManagerModule],
|
||||||
controllers: [UserController],
|
controllers: [UserController, UserManageController],
|
||||||
})
|
})
|
||||||
export class UserApiModule {}
|
export class UserApiModule {}
|
||||||
85
backend/src/routes/api/user/usermanage.controller.ts
Normal file
85
backend/src/routes/api/user/usermanage.controller.ts
Normal file
@@ -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<UserListResponse> {
|
||||||
|
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<UserDeleteResponse> {
|
||||||
|
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<UserUpdateRolesResponse> {
|
||||||
|
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<UserInfoResponse> {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
<h1>Users</h1>
|
||||||
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
templateUrl: './settings-users.component.html',
|
||||||
|
})
|
||||||
|
export class SettingsUsersComponent implements OnInit {
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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 {}
|
||||||
@@ -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 {}
|
||||||
@@ -7,6 +7,7 @@ import { SidebarResolverService } from 'src/app/services/sidebar-resolver/sideba
|
|||||||
import { SettingsGeneralRouteModule } from './settings-general/settings-general.module';
|
import { SettingsGeneralRouteModule } from './settings-general/settings-general.module';
|
||||||
import { SettingsSidebarComponent } from './settings-sidebar/settings-sidebar.component';
|
import { SettingsSidebarComponent } from './settings-sidebar/settings-sidebar.component';
|
||||||
import { SettingsSysprefRouteModule } from './settings-syspref/settings-syspref.module';
|
import { SettingsSysprefRouteModule } from './settings-syspref/settings-syspref.module';
|
||||||
|
import { SettingsUsersRouteModule } from './settings-users/settings-users.module';
|
||||||
|
|
||||||
const SettingsRoutes: PRoutes = [
|
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],
|
canActivate: [PermissionGuard],
|
||||||
canActivateChild: [PermissionGuard],
|
canActivateChild: [PermissionGuard],
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { ClassConstructor, plainToClass } from 'class-transformer';
|
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 { AsyncFailable, Fail, HasFailed } from 'picsur-shared/dist/types';
|
||||||
|
import { strictValidate } from 'picsur-shared/dist/util/validate';
|
||||||
import { MultiPartRequest } from '../../models/multi-part-request';
|
import { MultiPartRequest } from '../../models/multi-part-request';
|
||||||
import { KeyService } from './key.service';
|
import { KeyService } from './key.service';
|
||||||
|
|
||||||
@@ -31,7 +28,7 @@ export class ApiService {
|
|||||||
data: object
|
data: object
|
||||||
): AsyncFailable<W> {
|
): AsyncFailable<W> {
|
||||||
const sendClass = plainToClass(sendType, data);
|
const sendClass = plainToClass(sendType, data);
|
||||||
const errors = await validate(sendClass);
|
const errors = await strictValidate(sendClass);
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
this.logger.warn(errors);
|
this.logger.warn(errors);
|
||||||
return Fail('Something went wrong');
|
return Fail('Something went wrong');
|
||||||
@@ -68,14 +65,15 @@ export class ApiService {
|
|||||||
ApiSuccessResponse<T>,
|
ApiSuccessResponse<T>,
|
||||||
ApiSuccessResponse<T>
|
ApiSuccessResponse<T>
|
||||||
>(ApiSuccessResponse, result);
|
>(ApiSuccessResponse, result);
|
||||||
const resultErrors = await validate(resultClass);
|
|
||||||
|
const resultErrors = await strictValidate(resultClass);
|
||||||
if (resultErrors.length > 0) {
|
if (resultErrors.length > 0) {
|
||||||
this.logger.warn('result', resultErrors);
|
this.logger.warn('result', resultErrors);
|
||||||
return Fail('Something went wrong');
|
return Fail('Something went wrong');
|
||||||
}
|
}
|
||||||
|
|
||||||
const dataClass = plainToClass(type, result.data);
|
const dataClass = plainToClass(type, result.data);
|
||||||
const dataErrors = await validate(dataClass);
|
const dataErrors = await strictValidate(dataClass);
|
||||||
if (dataErrors.length > 0) {
|
if (dataErrors.length > 0) {
|
||||||
this.logger.warn('data', dataErrors);
|
this.logger.warn('data', dataErrors);
|
||||||
return Fail('Something went wrong');
|
return Fail('Something went wrong');
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { plainToClass } from 'class-transformer';
|
import { plainToClass } from 'class-transformer';
|
||||||
import { validate } from 'class-validator';
|
|
||||||
import jwt_decode from 'jwt-decode';
|
import jwt_decode from 'jwt-decode';
|
||||||
import {
|
import {
|
||||||
UserLoginRequest,
|
UserLoginRequest,
|
||||||
UserLoginResponse, UserMeResponse, UserRegisterRequest, UserRegisterResponse
|
UserLoginResponse,
|
||||||
|
UserMeResponse,
|
||||||
|
UserRegisterRequest,
|
||||||
|
UserRegisterResponse
|
||||||
} from 'picsur-shared/dist/dto/api/user.dto';
|
} from 'picsur-shared/dist/dto/api/user.dto';
|
||||||
import { JwtDataDto } from 'picsur-shared/dist/dto/jwt.dto';
|
import { JwtDataDto } from 'picsur-shared/dist/dto/jwt.dto';
|
||||||
import { EUser } from 'picsur-shared/dist/entities/user.entity';
|
import { EUser } from 'picsur-shared/dist/entities/user.entity';
|
||||||
import { AsyncFailable, Fail, HasFailed } from 'picsur-shared/dist/types';
|
import { AsyncFailable, Fail, HasFailed } from 'picsur-shared/dist/types';
|
||||||
|
import { strictValidate } from 'picsur-shared/dist/util/validate';
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
import { ApiService } from './api.service';
|
import { ApiService } from './api.service';
|
||||||
import { KeyService } from './key.service';
|
import { KeyService } from './key.service';
|
||||||
@@ -60,7 +63,10 @@ export class UserService {
|
|||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async register(username: string, password: string): AsyncFailable<EUser> {
|
public async register(
|
||||||
|
username: string,
|
||||||
|
password: string
|
||||||
|
): AsyncFailable<EUser> {
|
||||||
const request: UserRegisterRequest = {
|
const request: UserRegisterRequest = {
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
@@ -119,7 +125,7 @@ export class UserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const jwtData = plainToClass(JwtDataDto, decoded);
|
const jwtData = plainToClass(JwtDataDto, decoded);
|
||||||
const errors = await validate(jwtData);
|
const errors = await strictValidate(jwtData);
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
this.logger.warn(errors);
|
this.logger.warn(errors);
|
||||||
return Fail('Invalid token data');
|
return Fail('Invalid token data');
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ import {
|
|||||||
IsNotEmpty,
|
IsNotEmpty,
|
||||||
IsString,
|
IsString,
|
||||||
Max,
|
Max,
|
||||||
Min,
|
Min
|
||||||
ValidateNested,
|
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
|
|
||||||
class BaseApiResponse<T extends Object, W extends boolean> {
|
class BaseApiResponse<T extends Object, W extends boolean> {
|
||||||
@@ -24,7 +23,7 @@ class BaseApiResponse<T extends Object, W extends boolean> {
|
|||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
timestamp: string;
|
timestamp: string;
|
||||||
|
|
||||||
@ValidateNested()
|
//@ValidateNested()
|
||||||
@IsDefined()
|
@IsDefined()
|
||||||
data: T;
|
data: T;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
import { Type } from 'class-transformer';
|
import { Type } from 'class-transformer';
|
||||||
import {
|
import {
|
||||||
IsArray, IsDefined,
|
IsArray, IsDefined,
|
||||||
IsEnum,
|
IsEnum, IsNotEmpty, IsString,
|
||||||
IsInt,
|
|
||||||
IsNotEmpty, IsPositive,
|
|
||||||
IsString,
|
|
||||||
ValidateNested
|
ValidateNested
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
import { EUser } from '../../entities/user.entity';
|
import { EUser } from '../../entities/user.entity';
|
||||||
import { Permissions, PermissionsList } from '../permissions';
|
import { Permissions, PermissionsList } from '../permissions';
|
||||||
import { Roles } from '../roles.dto';
|
|
||||||
|
|
||||||
// Api
|
// Api
|
||||||
|
|
||||||
@@ -43,38 +39,6 @@ export class UserRegisterRequest {
|
|||||||
|
|
||||||
export class UserRegisterResponse extends EUser {}
|
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
|
// UserMe
|
||||||
export class UserMeResponse {
|
export class UserMeResponse {
|
||||||
@IsDefined()
|
@IsDefined()
|
||||||
@@ -94,17 +58,3 @@ export class UserMePermissionsResponse {
|
|||||||
@IsEnum(PermissionsList, { each: true })
|
@IsEnum(PermissionsList, { each: true })
|
||||||
permissions: Permissions;
|
permissions: Permissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserUpdateRoles
|
|
||||||
export class UserUpdateRolesRequest {
|
|
||||||
@IsString()
|
|
||||||
@IsNotEmpty()
|
|
||||||
username: string;
|
|
||||||
|
|
||||||
@IsArray()
|
|
||||||
@IsDefined()
|
|
||||||
@IsString({ each: true })
|
|
||||||
roles: Roles;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UserUpdateRolesResponse extends EUser {}
|
|
||||||
|
|||||||
50
shared/src/dto/api/usermanage.dto.ts
Normal file
50
shared/src/dto/api/usermanage.dto.ts
Normal file
@@ -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 {}
|
||||||
12
shared/src/util/validate.ts
Normal file
12
shared/src/util/validate.ts
Normal file
@@ -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);
|
||||||
Reference in New Issue
Block a user