Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/apps/job-launcher/server/src/common/constants/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,3 +216,13 @@ export enum ErrorQualification {
export enum ErrorEncryption {
MissingPrivateKey = 'Encryption private key cannot be empty, when it is enabled',
}

/**
* Represents error messages associated to storage.
*/
export enum ErrorStorage {
FailedToDownload = 'Failed to download file',
NotFound = 'File not found',
InvalidUrl = 'Invalid file URL',
FileNotUploaded = 'File not uploaded',
}
11 changes: 0 additions & 11 deletions packages/apps/job-launcher/server/src/common/errors/base.ts

This file was deleted.

13 changes: 0 additions & 13 deletions packages/apps/job-launcher/server/src/common/errors/controlled.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import { QueryFailedError } from 'typeorm';
import { DatabaseError } from '.';
import { PostgresErrorCodes } from '../enums/database';

export class DatabaseError extends Error {
constructor(message: string, stack: string) {
super(message);
this.stack = stack;
}
}

export function handleQueryFailedError(error: QueryFailedError): DatabaseError {
const stack = error.stack || '';
let message = error.message;
Expand Down
55 changes: 55 additions & 0 deletions packages/apps/job-launcher/server/src/common/errors/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
export * from './database';

export class BaseError extends Error {
constructor(message: string, stack?: string) {
super(message);
this.name = this.constructor.name;
if (stack) {
this.stack = stack;
} else {
Error.captureStackTrace(this, this.constructor);
}
}
}

export class ValidationError extends BaseError {
constructor(message: string, stack?: string) {
super(message, stack);
}
}

export class AuthError extends BaseError {
constructor(message: string, stack?: string) {
super(message, stack);
}
}

export class ForbiddenError extends BaseError {
constructor(message: string, stack?: string) {
super(message, stack);
}
}

export class NotFoundError extends BaseError {
constructor(message: string, stack?: string) {
super(message, stack);
}
}

export class ConflictError extends BaseError {
constructor(message: string, stack?: string) {
super(message, stack);
}
}

export class ServerError extends BaseError {
constructor(message: string, stack?: string) {
super(message, stack);
}
}

export class DatabaseError extends BaseError {
constructor(message: string, stack?: string) {
super(message, stack);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,44 +6,53 @@ import {
Logger,
} from '@nestjs/common';
import { Request, Response } from 'express';
import { DatabaseError } from '../errors/database';
import { ControlledError } from '../errors/controlled';
import {
ValidationError,
AuthError,
ForbiddenError,
NotFoundError,
ConflictError,
ServerError,
DatabaseError,
} from '../errors';

@Catch()
export class ExceptionFilter implements IExceptionFilter {
private logger = new Logger(ExceptionFilter.name);

private getStatus(exception: any): number {
if (exception instanceof ValidationError) {
return HttpStatus.BAD_REQUEST;
} else if (exception instanceof AuthError) {
return HttpStatus.UNAUTHORIZED;
} else if (exception instanceof ForbiddenError) {
return HttpStatus.FORBIDDEN;
} else if (exception instanceof NotFoundError) {
return HttpStatus.NOT_FOUND;
} else if (exception instanceof ConflictError) {
return HttpStatus.CONFLICT;
} else if (exception instanceof ServerError) {
return HttpStatus.UNPROCESSABLE_ENTITY;
} else if (exception instanceof DatabaseError) {
return HttpStatus.CONFLICT;
} else if (exception.statusCode) {
return exception.statusCode;
}
return HttpStatus.INTERNAL_SERVER_ERROR;
}

catch(exception: any, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();

let status = HttpStatus.INTERNAL_SERVER_ERROR;
let message = 'Internal server error';
const status = this.getStatus(exception);
const message = exception.message || 'Internal server error';

if (exception instanceof ControlledError) {
status = exception.status;
message = exception.message;

this.logger.error(`Job Launcher error: ${message}`, exception.stack);
} else if (exception instanceof DatabaseError) {
status = HttpStatus.UNPROCESSABLE_ENTITY;
message = exception.message;

this.logger.error(
`Database error: ${exception.message}`,
exception.stack,
);
} else {
if (exception.statusCode === HttpStatus.BAD_REQUEST) {
status = exception.statusCode;
message = exception.message;
}
this.logger.error(
`Unhandled exception: ${exception.message}`,
exception.stack,
);
}
this.logger.error(
`Exception caught: ${message}`,
exception.stack || 'No stack trace available',
);

response.status(status).json({
statusCode: status,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { ExecutionContext, HttpStatus } from '@nestjs/common';
import { ExecutionContext } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { ApiKeyGuard } from './apikey.auth';
import { AuthService } from '../../modules/auth/auth.service';
import { UserEntity } from '../../modules/user/user.entity';
import { ControlledError } from '../errors/controlled';
import { AuthError } from '../errors';
import { ApiKeyGuard } from './apikey.auth';

describe('ApiKeyGuard', () => {
let guard: ApiKeyGuard;
Expand Down Expand Up @@ -71,7 +71,7 @@ describe('ApiKeyGuard', () => {
.mockResolvedValue(null);

await expect(guard.canActivate(context)).rejects.toThrow(
new ControlledError('Unauthorized', HttpStatus.UNAUTHORIZED),
new AuthError('Unauthorized'),
);
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import {
Injectable,
ExecutionContext,
CanActivate,
HttpStatus,
} from '@nestjs/common';
import { Injectable, ExecutionContext, CanActivate } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { AuthService } from '../../modules/auth/auth.service';
import { ControlledError } from '../errors/controlled';
import { AuthError } from '../errors';

@Injectable()
export class ApiKeyGuard implements CanActivate {
Expand Down Expand Up @@ -35,9 +30,9 @@ export class ApiKeyGuard implements CanActivate {
return true;
}
} else {
throw new ControlledError('Invalid API Key', HttpStatus.UNAUTHORIZED);
throw new AuthError('Invalid API Key');
}
}
throw new ControlledError('Unauthorized', HttpStatus.UNAUTHORIZED);
throw new AuthError('Unauthorized');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import {
} from '@nestjs/common';
import { ModuleRef, Reflector } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
import { AuthError, ForbiddenError } from '../errors';
import { ApiKeyGuard } from './apikey.auth';
import { ControlledError } from '../errors/controlled';

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt-http') implements CanActivate {
Expand All @@ -33,7 +33,7 @@ export class JwtAuthGuard extends AuthGuard('jwt-http') implements CanActivate {
}
}

throw new ControlledError('Unauthorized', HttpStatus.UNAUTHORIZED);
throw new AuthError('Unauthorized');
}

public async canActivate(context: ExecutionContext): Promise<boolean> {
Expand All @@ -58,11 +58,11 @@ export class JwtAuthGuard extends AuthGuard('jwt-http') implements CanActivate {
return this.handleApiKeyAuthentication(context);
case HttpStatus.FORBIDDEN:
if (jwtError?.response?.message === 'Forbidden') {
throw new ControlledError('Forbidden', HttpStatus.FORBIDDEN);
throw new ForbiddenError('Forbidden');
}
break;
default:
throw new ControlledError('Unauthorized', HttpStatus.UNAUTHORIZED);
throw new AuthError('Unauthorized');
}

return false;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ExecutionContext, HttpStatus } from '@nestjs/common';
import { SignatureAuthGuard } from './signature.auth';
import { verifySignature } from '../utils/signature';
import { ChainId, EscrowUtils } from '@human-protocol/sdk';
import { ExecutionContext } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { MOCK_ADDRESS } from '../../../test/constants';
import { Role } from '../enums/role';
import { ControlledError } from '../errors/controlled';
import { verifySignature } from '../utils/signature';
import { SignatureAuthGuard } from './signature.auth';
import { AuthError } from '../errors';

jest.mock('../../common/utils/signature');

Expand Down Expand Up @@ -82,14 +82,14 @@ describe('SignatureAuthGuard', () => {
(verifySignature as jest.Mock).mockReturnValue(false);

await expect(guard.canActivate(context as any)).rejects.toThrow(
new ControlledError('Unauthorized', HttpStatus.UNAUTHORIZED),
new AuthError('Unauthorized'),
);
});

it('should throw unauthorized exception for unrecognized oracle type', async () => {
mockRequest.originalUrl = '/some/random/path';
await expect(guard.canActivate(context as any)).rejects.toThrow(
new ControlledError('Unauthorized', HttpStatus.UNAUTHORIZED),
new AuthError('Unauthorized'),
);
});
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
import {
CanActivate,
ExecutionContext,
HttpStatus,
Injectable,
} from '@nestjs/common';
import { verifySignature } from '../utils/signature';
import { HEADER_SIGNATURE_KEY } from '../constants';
import { EscrowUtils } from '@human-protocol/sdk';
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { HEADER_SIGNATURE_KEY } from '../constants';
import { Role } from '../enums/role';
import { ControlledError } from '../errors/controlled';
import { AuthError } from '../errors';
import { verifySignature } from '../utils/signature';

@Injectable()
export class SignatureAuthGuard implements CanActivate {
Expand Down Expand Up @@ -50,6 +45,6 @@ export class SignatureAuthGuard implements CanActivate {
console.error(error);
}

throw new ControlledError('Unauthorized', HttpStatus.UNAUTHORIZED);
throw new AuthError('Unauthorized');
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { ExecutionContext, HttpStatus } from '@nestjs/common';
import { ExecutionContext } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { WhitelistAuthGuard } from './whitelist.auth';
import { WhitelistService } from '../../modules/whitelist/whitelist.service';
import { ControlledError } from '../errors/controlled';
import { AuthError } from '../errors';
import { WhitelistAuthGuard } from './whitelist.auth';

describe('WhitelistAuthGuard', () => {
let guard: WhitelistAuthGuard;
Expand Down Expand Up @@ -36,9 +36,7 @@ describe('WhitelistAuthGuard', () => {

await expect(
guard.canActivate(mockContext as ExecutionContext),
).rejects.toThrow(
new ControlledError('User not found.', HttpStatus.UNAUTHORIZED),
);
).rejects.toThrow(new AuthError('User not found.'));
});

it('should throw an error if the user is not whitelisted', async () => {
Expand All @@ -54,9 +52,7 @@ describe('WhitelistAuthGuard', () => {

await expect(
guard.canActivate(mockContext as ExecutionContext),
).rejects.toThrow(
new ControlledError('Unauthorized.', HttpStatus.UNAUTHORIZED),
);
).rejects.toThrow(new AuthError('Unauthorized.'));
});

it('should return true if the user is whitelisted', async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import {
CanActivate,
ExecutionContext,
HttpStatus,
Injectable,
} from '@nestjs/common';
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { WhitelistService } from '../../modules/whitelist/whitelist.service';
import { ControlledError } from '../errors/controlled';
import { AuthError } from '../errors';

@Injectable()
export class WhitelistAuthGuard implements CanActivate {
Expand All @@ -16,14 +11,14 @@ export class WhitelistAuthGuard implements CanActivate {
const user = request.user;

if (!user) {
throw new ControlledError('User not found.', HttpStatus.UNAUTHORIZED);
throw new AuthError('User not found.');
}

const isWhitelisted = await this.whitelistService.isUserWhitelisted(
user.id,
);
if (!isWhitelisted) {
throw new ControlledError('Unauthorized.', HttpStatus.UNAUTHORIZED);
throw new AuthError('Unauthorized.');
}

return true;
Expand Down
Loading