Skip to content

Commit

Permalink
refactor: User module error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
Dzeranov committed Jan 24, 2025
1 parent 2ad0673 commit 888d269
Show file tree
Hide file tree
Showing 12 changed files with 179 additions and 246 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,22 +36,6 @@ export enum ErrorSignature {
InvalidSignature = 'Invalid signature',
}

/**
* Represents error messages related to user.
*/
export enum ErrorUser {
NotFound = 'User not found.',
AccountCannotBeRegistered = 'Account cannot be registered.',
BalanceCouldNotBeRetreived = 'User balance could not be retrieved.',
InvalidCredentials = 'Invalid credentials.',
AlreadyAssigned = 'User already has an address assigned.',
NoWalletAddresRegistered = 'No wallet address registered on your account.',
KycNotApproved = 'KYC not approved.',
LabelingEnableFailed = 'Failed to enable labeling for this account.',
InvalidType = 'User has invalid type.',
DuplicatedAddress = 'The address you are trying to use already exists. Please check that the address is correct or use a different address.',
}

/**
* Represents error messages related to send grid.
*/
Expand All @@ -77,11 +61,3 @@ export enum ErrorWeb3 {
InvalidChainId = 'Invalid chain id provided for the configured environment',
GasPriceError = 'Error calculating gas price',
}

/**
* Represents error messages related to operator.
*/
export enum ErrorOperator {
OperatorAlreadyActive = 'Operator is already active',
OperatorNotActive = 'Operator not active',
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,5 @@ export enum SignatureType {
SIGNIN = 'signin',
ENABLE_OPERATOR = 'enable_operator',
DISABLE_OPERATOR = 'disable_operator',
CERTIFICATE_AUTHENTICATION = 'certificate_authentication',
REGISTER_ADDRESS = 'register_address',
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,23 @@ export function recoverSigner(
export function generateNonce(): string {
return Buffer.from(ethers.randomBytes(16)).toString('hex');
}

type SignatureBody = {
from: string;
to: string;
contents: string;
nonce?: string;
};
export function prepareSignatureBody({
from,
to,
contents,
nonce,
}: SignatureBody): SignatureBody {
return {
from: from.toLowerCase(),
to: to.toLowerCase(),
contents,
nonce: nonce ?? undefined,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ import { Request, Response } from 'express';

import {
AuthError,
DuplicatedUserError,
DuplicatedUserEmailError,
InvalidOperatorSignupDataError,
} from './auth.errors';

type AuthControllerError =
| AuthError
| DuplicatedUserError
| DuplicatedUserEmailError
| InvalidOperatorSignupDataError;

@Catch(AuthError, DuplicatedUserError, InvalidOperatorSignupDataError)
@Catch(AuthError, DuplicatedUserEmailError, InvalidOperatorSignupDataError)
export class AuthControllerErrorsFilter implements ExceptionFilter {
private logger = new Logger(AuthControllerErrorsFilter.name);
catch(exception: AuthControllerError, host: ArgumentsHost) {
Expand All @@ -28,7 +28,7 @@ export class AuthControllerErrorsFilter implements ExceptionFilter {
let status = HttpStatus.UNAUTHORIZED;

let logContext: string | undefined;
if (exception instanceof DuplicatedUserError) {
if (exception instanceof DuplicatedUserEmailError) {
status = HttpStatus.CONFLICT;
logContext = exception.email;
} else if (exception instanceof InvalidOperatorSignupDataError) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export enum AuthErrorMessage {
INVALID_REFRESH_TOKEN = 'Refresh token is not valid',
REFRESH_TOKEN_EXPIRED = 'Refresh token expired',
INVALID_WEB3_SIGNATURE = 'Invalid signature',
INVALID_ADDRESS = 'Invalid address',
}

export class AuthError extends BaseError {
Expand Down Expand Up @@ -43,10 +44,18 @@ export class InvalidOperatorJobTypesError extends InvalidOperatorSignupDataError
}
}

export class DuplicatedUserError extends BaseError {
export class DuplicatedUserEmailError extends BaseError {
constructor(public readonly email: string) {
super(
'The email you are trying to use already exists. Please check that the email is correct or use a different email.',
);
}
}

export class DuplicatedUserAddressError extends BaseError {
constructor(public readonly address: string) {
super(
'The address you are trying to use already exists. Please, use a different address.',
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
Role,
} from '@human-protocol/sdk';
import { SignatureType, Web3Env } from '../../common/enums/web3';
import { prepareSignatureBody } from '../../common/utils/signature';
import { UserRepository } from '../user/user.repository';
import { AuthConfigService } from '../../common/config/auth-config.service';
import { ServerConfigService } from '../../common/config/server-config.service';
Expand All @@ -43,7 +44,8 @@ import { SiteKeyType } from '../../common/enums';
import {
AuthError,
AuthErrorMessage,
DuplicatedUserError,
DuplicatedUserAddressError,
DuplicatedUserEmailError,
InvalidOperatorFeeError,
InvalidOperatorJobTypesError,
InvalidOperatorRoleError,
Expand Down Expand Up @@ -82,7 +84,7 @@ export class AuthService {
public async signup(data: UserCreateDto): Promise<UserEntity> {
const storedUser = await this.userRepository.findOneByEmail(data.email);
if (storedUser) {
throw new DuplicatedUserError(data.email);
throw new DuplicatedUserEmailError(data.email);
}
const userEntity = await this.userService.create(data);

Expand Down Expand Up @@ -337,10 +339,11 @@ export class AuthService {
}

public async web3Signup(data: Web3SignUpDto): Promise<AuthDto> {
const preSignUpData = await this.userService.prepareSignatureBody(
SignatureType.SIGNUP,
data.address,
);
const preSignUpData = prepareSignatureBody({
from: data.address,
to: this.web3Service.getOperatorAddress(),
contents: SignatureType.SIGNUP,
});

const verified = verifySignature(preSignUpData, data.signature, [
data.address,
Expand Down Expand Up @@ -409,6 +412,11 @@ export class AuthService {
throw new InvalidOperatorJobTypesError(jobTypes);
}

const user = await this.userRepository.findOneByAddress(data.address);

if (user) {
throw new DuplicatedUserAddressError(data.address);
}
const userEntity = await this.userService.createWeb3User(data.address);

await kvstore.set(data.address.toLowerCase(), OperatorStatus.ACTIVE);
Expand All @@ -417,16 +425,21 @@ export class AuthService {
}

public async web3Signin(data: Web3SignInDto): Promise<AuthDto> {
const userEntity = await this.userService.getByAddress(data.address);
const userEntity = await this.userRepository.findOneByAddress(data.address);

const verified = verifySignature(
await this.userService.prepareSignatureBody(
SignatureType.SIGNIN,
data.address,
),
data.signature,
[data.address],
);
if (!userEntity) {
throw new AuthError(AuthErrorMessage.INVALID_ADDRESS);
}

const preSigninData = prepareSignatureBody({
from: data.address,
to: this.web3Service.getOperatorAddress(),
contents: SignatureType.SIGNIN,
nonce: (await this.userRepository.findOneByAddress(data.address))?.nonce,
});
const verified = verifySignature(preSigninData, data.signature, [
data.address,
]);

if (!verified) {
throw new AuthError(AuthErrorMessage.INVALID_WEB3_SIGNATURE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,15 @@ import { CredentialRepository } from './credential.repository';
import { CredentialEntity } from './credential.entity';
import { CredentialStatus } from '../../common/enums/credential';
import { Web3Service } from '../web3/web3.service';
import { verifySignature } from '../../common/utils/signature';
import { ErrorSignature } from '../../common/constants/errors';
import { ChainId, KVStoreClient } from '@human-protocol/sdk';
import { SignatureType, Web3Env } from '../../common/enums/web3';
import { Web3ConfigService } from '../../common/config/web3-config.service';
import { EscrowClient } from '@human-protocol/sdk';
import { UserService } from '../user/user.service';
import { ControlledError } from '../../common/errors/controlled';

@Injectable()
export class CredentialService {
private readonly logger = new Logger(CredentialService.name);

constructor(
private readonly credentialRepository: CredentialRepository,
private readonly web3Service: Web3Service,
private readonly userService: UserService,
private readonly web3ConfigService: Web3ConfigService,
) {}
constructor(private readonly credentialRepository: CredentialRepository) {}

/**
* Create a new credential based on provided data.
Expand Down Expand Up @@ -117,55 +107,4 @@ export class CredentialService {

this.logger.log(`Credential ${reference} validated successfully.`);
}

public async addCredentialOnChain(
reference: string,
workerAddress: string,
signature: string,
chainId: ChainId,
escrowAddress: string,
): Promise<void> {
let signer = this.web3Service.getSigner(chainId);
const escrowClient = await EscrowClient.build(signer);

const reputationOracleAddress =
await escrowClient.getReputationOracleAddress(escrowAddress);

const signatureBody = await this.userService.prepareSignatureBody(
SignatureType.CERTIFICATE_AUTHENTICATION,
reputationOracleAddress,
{
reference: reference,
workerAddress: workerAddress,
},
);

if (!verifySignature(signatureBody.contents, signature, [workerAddress])) {
throw new ControlledError(
ErrorSignature.InvalidSignature,
HttpStatus.UNAUTHORIZED,
);
}

const currentWeb3Env = this.web3ConfigService.env;
if (currentWeb3Env === Web3Env.MAINNET) {
signer = this.web3Service.getSigner(ChainId.POLYGON);
} else if (currentWeb3Env === Web3Env.TESTNET) {
signer = this.web3Service.getSigner(ChainId.POLYGON_AMOY);
} else {
signer = this.web3Service.getSigner(ChainId.LOCALHOST);
}
const kvstore = await KVStoreClient.build(signer);
const key = `${reference}-${reputationOracleAddress}`;
const value = JSON.stringify({
signature,
contents: signatureBody.contents,
});

await kvstore.set(key, value);

this.logger.log(
`Credential added to the blockchain for reference: ${reference}`,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,23 @@ import {
import { JwtAuthGuard } from '../../common/guards';
import { HCaptchaGuard } from '../../common/guards/hcaptcha';
import { RequestWithUser } from '../../common/types';
import { prepareSignatureBody } from '../../common/utils/signature';
import { UserService } from './user.service';
import { Public } from '../../common/decorators';
import { KycSignedAddressDto } from '../kyc/kyc.dto';
import { Web3Service } from '../web3/web3.service';
import { UserRepository } from './user.repository';
import { SignatureType } from 'src/common/enums/web3';

@ApiTags('User')
@Controller('/user')
@ApiBearerAuth()
export class UserController {
constructor(private readonly userService: UserService) {}
constructor(
private readonly userService: UserService,
private readonly web3Service: Web3Service,
private readonly userRepository: UserRepository,
) {}

@Post('/register-labeler')
@HttpCode(200)
Expand Down Expand Up @@ -170,7 +178,16 @@ export class UserController {
public async prepareSignature(
@Body() data: PrepareSignatureDto,
): Promise<SignatureBodyDto> {
return await this.userService.prepareSignatureBody(data.type, data.address);
let nonce;
if (data.type === SignatureType.SIGNIN) {
nonce = (await this.userRepository.findOneByAddress(data.address))?.nonce;
}
return prepareSignatureBody({
from: data.address,
to: this.web3Service.getOperatorAddress(),
contents: data.type,
nonce,
});
}

@Post('/exchange-oracle-registration')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,9 @@ export class SignatureBodyDto {
public contents: string;

@ApiProperty()
@IsOptional()
@IsString()
public nonce: string | undefined;
public nonce?: string | undefined;
}

export class PrepareSignatureDto {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpStatus,
Logger,
} from '@nestjs/common';
import { Request, Response } from 'express';

import { UserError, DuplicatedWalletAddressError } from './user.error';

type UserControllerError = UserError | DuplicatedWalletAddressError;

@Catch(UserError, DuplicatedWalletAddressError)
export class UserErrorFilter implements ExceptionFilter {
private logger = new Logger(UserErrorFilter.name);
catch(exception: UserControllerError, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
let status = HttpStatus.BAD_REQUEST;

if (exception instanceof DuplicatedWalletAddressError) {
status = HttpStatus.CONFLICT;
}

this.logger.error(exception.message, exception.stack, exception.userId);

return response.status(status).json({
message: exception.message,
timestamp: new Date().toISOString(),
path: request.url,
});
}
}
Loading

0 comments on commit 888d269

Please sign in to comment.