Partly refactor errors

This commit is contained in:
rubikscraft 2022-07-19 14:54:02 +02:00
parent c8722d8944
commit d507fcfaf0
No known key found for this signature in database
GPG Key ID: 1463EBE9200A5CD4
15 changed files with 138 additions and 95 deletions

View File

@ -113,7 +113,8 @@ export class SysPreferenceService {
): AsyncFailable<PrefValueType> {
let pref = await this.getPreference(key);
if (HasFailed(pref)) return pref;
if (pref.type !== type) return Fail(FT.UsrValidation, 'Invalid preference type');
if (pref.type !== type)
return Fail(FT.UsrValidation, 'Invalid preference type');
return pref.value;
}

View File

@ -1,10 +1,9 @@
import {
ArgumentMetadata,
BadRequestException,
Injectable,
PipeTransform,
ArgumentMetadata, Injectable,
PipeTransform
} from '@nestjs/common';
import { Ext2Mime } from 'picsur-shared/dist/dto/mimes.dto';
import { Fail, FT } from 'picsur-shared/dist/types';
import { UUIDRegex } from 'picsur-shared/dist/util/common-regex';
import { ImageFullId } from '../../models/constants/image-full-id.const';
@ -15,23 +14,23 @@ export class ImageFullIdPipe implements PipeTransform<string, ImageFullId> {
if (split.length === 2) {
const [id, ext] = split;
if (!UUIDRegex.test(id))
throw new BadRequestException('Invalid image identifier');
throw Fail(FT.UsrValidation, 'Invalid image identifier');
const mime = Ext2Mime(ext);
if (mime === undefined)
throw new BadRequestException('Invalid image identifier');
throw Fail(FT.UsrValidation, 'Invalid image identifier');
return { type: 'normal', id, ext, mime };
} else if (split.length === 1) {
const [id] = split;
if (!UUIDRegex.test(id))
throw new BadRequestException('Invalid image identifier');
throw Fail(FT.UsrValidation, 'Invalid image identifier');
return { type: 'original', id, ext: null, mime: null };
} else {
throw new BadRequestException('Invalid image identifier');
throw Fail(FT.UsrValidation, 'Invalid image identifier');
}
}
}

View File

@ -1,15 +1,14 @@
import {
ArgumentMetadata,
BadRequestException,
Injectable,
PipeTransform,
ArgumentMetadata, Injectable,
PipeTransform
} from '@nestjs/common';
import { Fail, FT } from 'picsur-shared/dist/types';
import { UUIDRegex } from 'picsur-shared/dist/util/common-regex';
@Injectable()
export class ImageIdPipe implements PipeTransform<string, string> {
transform(value: string, metadata: ArgumentMetadata): string {
if (UUIDRegex.test(value)) return value;
throw new BadRequestException('Invalid image id');
throw Fail(FT.UsrValidation, 'Invalid image id');
}
}

View File

@ -1,15 +1,12 @@
import { MultipartFields, MultipartFile } from '@fastify/multipart';
import {
ArgumentMetadata,
BadRequestException,
Injectable,
InternalServerErrorException,
ArgumentMetadata, Injectable,
Logger,
PipeTransform,
Scope
} from '@nestjs/common';
import { FastifyRequest } from 'fastify';
import { HasFailed } from 'picsur-shared/dist/types';
import { Fail, FT, HasFailed } from 'picsur-shared/dist/types';
import { ZodDtoStatic } from 'picsur-shared/dist/util/create-zod-dto';
import { MultipartConfigService } from '../../config/early/multipart.config.service';
import {
@ -32,11 +29,11 @@ export class MultiPartPipe implements PipeTransform {
let zodSchema = (metadata?.metatype as ZodDtoStatic)?.zodSchema;
if (!zodSchema) {
this.logger.error('Invalid scheme on multipart body');
throw new InternalServerErrorException('Invalid scheme on backend');
throw Fail(FT.Internal, 'Invalid scheme on backend');
}
let multipartData = {};
if (!req.isMultipart()) throw new BadRequestException('Invalid file');
if (!req.isMultipart()) throw Fail(FT.UsrValidation, 'Invalid file');
// Fetch all fields from the request
let fields: MultipartFields | null = null;
@ -49,7 +46,7 @@ export class MultiPartPipe implements PipeTransform {
} catch (e) {
this.logger.warn(e);
}
if (!fields) throw new BadRequestException('Invalid file');
if (!fields) throw Fail(FT.UsrValidation, 'Invalid file');
// Loop over every formfield that was sent
for (const key of Object.keys(fields)) {
@ -66,10 +63,7 @@ export class MultiPartPipe implements PipeTransform {
);
} else {
const file = await CreateMultiPartFileDto(fields[key] as MultipartFile);
if (HasFailed(file)) {
this.logger.error(file.getReason());
throw new InternalServerErrorException('Invalid file');
}
if (HasFailed(file)) throw file;
(multipartData as any)[key] = file;
}
}
@ -78,7 +72,7 @@ export class MultiPartPipe implements PipeTransform {
const result = zodSchema.safeParse(multipartData);
if (!result.success) {
this.logger.warn(result.error);
throw new BadRequestException('Invalid file');
throw Fail(FT.UsrValidation, 'Invalid file');
}
return result.data;

View File

@ -1,12 +1,12 @@
import { Multipart } from '@fastify/multipart';
import {
BadRequestException,
Injectable,
Logger,
PipeTransform,
Scope
} from '@nestjs/common';
import { FastifyRequest } from 'fastify';
import { Fail, FT } from 'picsur-shared/dist/types';
import { MultipartConfigService } from '../../config/early/multipart.config.service';
@Injectable({ scope: Scope.REQUEST })
@ -18,7 +18,7 @@ export class PostFilePipe implements PipeTransform {
) {}
async transform({ req }: { req: FastifyRequest }) {
if (!req.isMultipart()) throw new BadRequestException('Invalid file');
if (!req.isMultipart()) throw Fail(FT.UsrValidation, 'Invalid file');
// Only one file is allowed
const file = await req.file({
@ -27,7 +27,7 @@ export class PostFilePipe implements PipeTransform {
files: 1,
},
});
if (file === undefined) throw new BadRequestException('Invalid file');
if (file === undefined) throw Fail(FT.UsrValidation, 'Invalid file');
// Remove empty fields
const allFields: Multipart[] = Object.values(file.fields).filter(
@ -37,14 +37,14 @@ export class PostFilePipe implements PipeTransform {
// Remove non-file fields
const files = allFields.filter((entry) => entry.file !== undefined);
if (files.length !== 1) throw new BadRequestException('Invalid file');
if (files.length !== 1) throw Fail(FT.UsrValidation, 'Invalid file');
// Return a buffer of the file
try {
return await files[0].toBuffer();
} catch (e) {
this.logger.warn(e);
throw new BadRequestException('Invalid file');
throw Fail(FT.Internal, 'Invalid file');
}
}
}

View File

@ -25,23 +25,27 @@ export class MainExceptionFilter implements ExceptionFilter {
return;
}
const status = exception.getCode();
const type = exception.getType();
const message = exception.getReason();
const logmessage =
message +
(exception.getDebugMessage() ? ' - ' + exception.getDebugMessage() : '');
if (exception.isImportant()) {
MainExceptionFilter.logger.error(
`${traceString} ${exception.getName()}: ${exception.getReason()}`,
`${traceString} ${exception.getName()}: ${logmessage}`,
);
if (exception.getStack()) {
MainExceptionFilter.logger.debug(exception.getStack());
}
} else {
MainExceptionFilter.logger.warn(
`${traceString} ${exception.getName()}: ${exception.getReason()}`,
`${traceString} ${exception.getName()}: ${logmessage}`,
);
}
const status = exception.getCode();
const type = exception.getType();
const message = exception.getReason();
const toSend: ApiErrorResponse = {
success: false,
statusCode: status,

View File

@ -1,14 +1,13 @@
import {
CallHandler,
ExecutionContext,
Injectable,
InternalServerErrorException,
Logger,
Injectable, Logger,
NestInterceptor,
Optional
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ApiAnySuccessResponse } from 'picsur-shared/dist/dto/api/api.dto';
import { Fail, FT } from 'picsur-shared/dist/types';
import { ZodDtoStatic } from 'picsur-shared/dist/util/create-zod-dto';
import { map, Observable } from 'rxjs';
@ -55,24 +54,24 @@ export class SuccessInterceptor<T> implements NestInterceptor {
);
if (!schemaStatic) {
this.logger.warn(
throw Fail(
FT.Internal,
"Couldn't find schema",
`No zodSchema found on handler ${context.getHandler().name}`,
);
throw new InternalServerErrorException("Couldn't find schema");
}
let schema = schemaStatic.zodSchema;
const parseResult = schema.safeParse(data);
if (!parseResult.success) {
this.logger.warn(
throw Fail(
FT.Internal,
'Server produced invalid response',
`Function ${context.getHandler().name} failed validation: ${
parseResult.error
}`,
);
throw new InternalServerErrorException(
'Server produced invalid response',
);
}
return parseResult.data;

View File

@ -5,11 +5,11 @@
import {
ArgumentMetadata,
BadRequestException,
Injectable,
Optional,
PipeTransform,
PipeTransform
} from '@nestjs/common';
import { Fail, FT } from 'picsur-shared/dist/types';
import { ZodDtoStatic } from 'picsur-shared/dist/util/create-zod-dto';
export interface ZodValidationPipeOptions {
@ -36,7 +36,11 @@ export class ZodValidationPipe implements PipeTransform {
const parseResult = zodSchema.safeParse(value);
if (!parseResult.success) {
throw new BadRequestException();
throw Fail(
FT.UsrValidation,
'Invalid data',
parseResult.error
);
}
return parseResult.data;

View File

@ -19,13 +19,13 @@ export class AuthManagerService {
// in case of any failures
const result = JwtDataSchema.safeParse(jwtData);
if (!result.success) {
return Fail(FT.SysValidation, 'Invalid JWT: ' + result.error);
return Fail(FT.SysValidation, undefined, 'Invalid JWT: ' + result.error);
}
try {
return await this.jwtService.signAsync(result.data);
} catch (e) {
return Fail(FT.Internal, "Couldn't create JWT: " + e);
return Fail(FT.Internal, undefined, "Couldn't create JWT: " + e);
}
}
}

View File

@ -1,4 +1,4 @@
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-local';
import { EUser } from 'picsur-shared/dist/entities/user.entity';
@ -15,9 +15,7 @@ export class LocalAuthStrategy extends PassportStrategy(Strategy, 'local') {
async validate(username: string, password: string): AsyncFailable<EUser> {
// All this does is call the usersservice authenticate for authentication
const user = await this.usersService.authenticate(username, password);
if (HasFailed(user)) {
throw new UnauthorizedException();
}
if (HasFailed(user)) throw user;
return EUserBackend2EUser(user);
}

View File

@ -1,8 +1,4 @@
import {
ExecutionContext, Injectable,
InternalServerErrorException,
Logger
} from '@nestjs/common';
import { ExecutionContext, Injectable, Logger } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
import { EUser, EUserSchema } from 'picsur-shared/dist/entities/user.entity';
@ -30,34 +26,42 @@ export class MainAuthGuard extends AuthGuard(['jwt', 'guest']) {
// Sanity check
const result = await super.canActivate(context);
if (result !== true) {
this.logger.error('Main Auth has denied access, this should not happen');
throw new InternalServerErrorException();
throw Fail(
FT.Internal,
undefined,
'Main Auth has denied access, this should not happen',
);
}
const user = await this.validateUser(
context.switchToHttp().getRequest().user,
);
if (!user.id) {
this.logger.error('User has no id, this should not happen');
throw new InternalServerErrorException();
throw Fail(
FT.Internal,
undefined,
'User has no id, this should not happen',
);
}
// These are the permissions required to access the route
const permissions = this.extractPermissions(context);
if (HasFailed(permissions)) {
this.logger.error(
throw Fail(
FT.Internal,
undefined,
'Fetching route permission failed: ' + permissions.getReason(),
);
throw new InternalServerErrorException();
}
// These are the permissions the user has
const userPermissions = await this.usersService.getPermissions(user.id);
if (HasFailed(userPermissions)) {
this.logger.warn(
throw Fail(
FT.Internal,
undefined,
'Fetching user permissions failed: ' + userPermissions.getReason(),
);
throw new InternalServerErrorException();
}
context.switchToHttp().getRequest().userPermissions = userPermissions;
@ -78,12 +82,14 @@ export class MainAuthGuard extends AuthGuard(['jwt', 'guest']) {
if (permissions === undefined)
return Fail(
FT.Internal,
undefined,
`${handlerName} does not have any permissions defined, denying access`,
);
if (!isPermissionsArray(permissions))
return Fail(
FT.Internal,
undefined,
`Permissions for ${handlerName} is not a string array`,
);
@ -93,10 +99,11 @@ export class MainAuthGuard extends AuthGuard(['jwt', 'guest']) {
private async validateUser(user: EUser): Promise<EUser> {
const result = EUserSchema.safeParse(user);
if (!result.success) {
this.logger.warn(
throw Fail(
FT.Internal,
undefined,
`Invalid user object, where it should always be valid: ${result.error}`,
);
throw new InternalServerErrorException();
}
return result.data;

View File

@ -1,13 +1,14 @@
import { Controller, Get, Request } from '@nestjs/common';
import { UserInfoResponse } from 'picsur-shared/dist/dto/api/user-manage.dto';
import { Permission } from 'picsur-shared/dist/dto/permissions.enum';
import { RequiredPermissions } from '../../../decorators/permissions.decorator';
import { Fail, FT } from 'picsur-shared/dist/types';
import { NoPermissions, RequiredPermissions } from '../../../decorators/permissions.decorator';
import { ReqUserID } from '../../../decorators/request-user.decorator';
import { Returns } from '../../../decorators/returns.decorator';
import type AuthFasityRequest from '../../../models/interfaces/authrequest.dto';
@Controller('api/experiment')
//@NoPermissions()
@NoPermissions()
@RequiredPermissions(Permission.Settings)
export class ExperimentController {
@Get()
@ -16,6 +17,7 @@ export class ExperimentController {
@Request() req: AuthFasityRequest,
@ReqUserID() thing: string,
): Promise<UserInfoResponse> {
throw Fail(FT.NotFound, new Error("hello"));
return req.user;
}
}

View File

@ -58,8 +58,11 @@ export class ApiService {
const validateResult = sendSchema.safeParse(data);
if (!validateResult.success) {
this.logger.error(validateResult.error);
return Fail(FT.SysValidation, 'Something went wrong');
return Fail(
FT.SysValidation,
'Something went wrong',
validateResult.error,
);
}
return this.fetchSafeJson(receiveType, url, {
@ -92,8 +95,11 @@ export class ApiService {
const validateResult = resultSchema.safeParse(result);
if (!validateResult.success) {
this.logger.error(validateResult.error);
return Fail(FT.SysValidation, 'Something went wrong');
return Fail(
FT.SysValidation,
'Something went wrong',
validateResult.error,
);
}
if (validateResult.data.success === false)
@ -113,8 +119,7 @@ export class ApiService {
try {
return await response.json();
} catch (e) {
this.logger.error(e);
return Fail(FT.Internal, 'Something went wrong');
return Fail(FT.Internal, e);
}
}
@ -150,8 +155,7 @@ export class ApiService {
name,
};
} catch (e) {
this.logger.error(e);
return Fail(FT.Internal, 'Something went wrong');
return Fail(FT.Internal, e);
}
}
@ -187,7 +191,7 @@ export class ApiService {
error: e,
url,
});
return Fail(FT.Network, 'Network Error');
return Fail(FT.Network, e);
}
}
}

View File

@ -10,7 +10,7 @@ export enum FT {
SysValidation = 'sysvalidation',
UsrValidation = 'usrvalidation',
Permission = 'permission',
NotFound = 'notFound',
NotFound = 'notfound',
Conflict = 'conflict',
Internal = 'internal',
Authentication = 'authentication',
@ -21,6 +21,7 @@ export enum FT {
interface FTProp {
important: boolean;
code: number;
message: string;
}
const FTProps: {
@ -29,56 +30,68 @@ const FTProps: {
[FT.Unknown]: {
important: false,
code: 500,
message: 'An unkown error occurred',
},
[FT.Internal]: {
important: true,
code: 500,
message: 'An internal error occurred',
},
[FT.Database]: {
important: true,
code: 500,
message: 'A database error occurred',
},
[FT.Network]: {
important: true,
code: 500,
message: 'A network error occurred',
},
[FT.SysValidation]: {
important: true,
code: 500,
message: 'Validation of internal items failed',
},
[FT.UsrValidation]: {
important: false,
code: 400,
message: 'Validation of user input failed',
},
[FT.Permission]: {
important: false,
code: 403,
message: 'Permission denied',
},
[FT.NotFound]: {
important: false,
code: 404,
message: 'Item(s) could not be found',
},
[FT.Conflict]: {
important: false,
code: 409,
message: 'There was a conflict',
},
[FT.Authentication]: {
important: false,
code: 200,
} ,
message: 'Authentication failed',
},
[FT.Impossible]: {
important: true,
code: 422,
} ,
message: 'What you are doing is impossible',
},
};
export class Failure {
private __68351953531423479708__id_failure = 1148363914;
constructor(
private readonly type: FT = FT.Unknown,
private readonly reason?: string,
private readonly stack?: string,
private readonly type: FT = FT.Unknown,
private readonly debugMessage?: string,
) {}
getReason(): string {
@ -89,6 +102,10 @@ export class Failure {
return this.stack;
}
getDebugMessage(): string | undefined {
return this.debugMessage;
}
getType(): FT {
return this.type;
}
@ -112,19 +129,34 @@ export class Failure {
throw new Error('Invalid failure data');
}
return new Failure(data.reason, data.stack, data.type);
return new Failure(data.type, data.reason, data.stack, data.debugMessage);
}
}
export function Fail(type: FT, reason: any): Failure {
if (typeof reason === 'string') {
return new Failure(reason, undefined, type);
} else if (reason instanceof Error) {
return new Failure(reason.message, reason.stack, type);
} else if (reason instanceof Failure) {
export function Fail(type: FT, reason?: any, dbgReason?: any): Failure {
const strReason = reason.toString();
if (typeof dbgReason === 'string') {
return new Failure(type, strReason, undefined, dbgReason);
} else if (dbgReason instanceof Error) {
return new Failure(type, strReason, dbgReason.stack, dbgReason.message);
} else if (dbgReason instanceof Failure) {
throw new Error('Cannot fail with a failure, just return it');
} else {
return new Failure('Unkown reason', undefined, type);
if (typeof reason === 'string') {
return new Failure(type, strReason, undefined, undefined);
} else if (reason instanceof Error) {
return new Failure(
type,
FTProps[type].message,
reason.stack,
reason.message,
);
} else if (dbgReason instanceof Failure) {
throw new Error('Cannot fail with a failure, just return it');
} else {
return new Failure(type, FTProps[type].message, undefined, undefined);
}
}
}

View File

@ -13,5 +13,5 @@ export function ParseMime(mime: string): Failable<FullMime> {
if (SupportedAnimMimes.includes(mime))
return { mime, type: SupportedMimeCategory.Animation };
return Fail(FT.Validation, 'Unsupported mime type');
return Fail(FT.UsrValidation, 'Unsupported mime type');
}