Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Reputation Oracle] refactor: web3service and dependent configs #3115

Merged
merged 12 commits into from
Feb 27, 2025
38 changes: 21 additions & 17 deletions packages/apps/reputation-oracle/server/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ import { QualificationModule } from './modules/qualification/qualification.modul
import { EscrowCompletionModule } from './modules/escrow-completion/escrow-completion.module';
import { WebhookIncomingModule } from './modules/webhook/webhook-incoming.module';
import { WebhookOutgoingModule } from './modules/webhook/webhook-outgoing.module';
import { UserModule } from './modules/user/user.module';
import { EmailModule } from './modules/email/module';
import { StorageModule } from './modules/storage/storage.module';
import Environment from './utils/environment';

@Module({
Expand All @@ -42,35 +44,37 @@ import Environment from './utils/environment';
],
imports: [
ScheduleModule.forRoot(),
ServeStaticModule.forRoot({
rootPath: join(
__dirname,
'../../../../../../',
'node_modules/swagger-ui-dist',
),
}),
ConfigModule.forRoot({
/**
* First value found takes precendece
*/
envFilePath: [`.env.${Environment.name}`, '.env'],
validationSchema: envValidator,
}),
EnvConfigModule,
DatabaseModule,
HealthModule,
ReputationModule,
WebhookIncomingModule,
WebhookOutgoingModule,
Web3Module,
HCaptchaModule,
AuthModule,
KycModule,
ServeStaticModule.forRoot({
rootPath: join(
__dirname,
'../../../../../../',
'node_modules/swagger-ui-dist',
),
}),
CronJobModule,
EmailModule,
EscrowCompletionModule,
HealthModule,
KycModule,
PayoutModule,
EnvConfigModule,
HCaptchaModule,
QualificationModule,
EscrowCompletionModule,
EmailModule,
ReputationModule,
StorageModule,
UserModule,
Web3Module,
WebhookIncomingModule,
WebhookOutgoingModule,
],
})
export class AppModule {}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
export enum Web3Env {
TESTNET = 'testnet',
MAINNET = 'mainnet',
LOCALHOST = 'localhost',
}

export enum SignatureType {
SIGNUP = 'signup',
SIGNIN = 'signin',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,85 +1,110 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ExecutionContext, HttpException, HttpStatus } from '@nestjs/common';
import { SignatureAuthGuard } from './signature.auth';
import { signMessage } from '../../utils/web3';
import { ChainId, EscrowUtils } from '@human-protocol/sdk';
import { MOCK_ADDRESS, MOCK_PRIVATE_KEY } from '../../../test/constants';
import { AuthSignatureRole } from '../enums/role';

jest.mock('@human-protocol/sdk', () => ({
...jest.requireActual('@human-protocol/sdk'),
EscrowUtils: {
getEscrow: jest.fn(),
},
}));

describe('SignatureAuthGuard', () => {
let guard: SignatureAuthGuard;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
{
provide: SignatureAuthGuard,
useValue: new SignatureAuthGuard([
AuthSignatureRole.JobLauncher,
AuthSignatureRole.Exchange,
AuthSignatureRole.Recording,
]),
},
],
}).compile();

guard = module.get<SignatureAuthGuard>(SignatureAuthGuard);
EscrowUtils.getEscrow = jest.fn().mockResolvedValueOnce({
launcher: MOCK_ADDRESS,
exchangeOracle: MOCK_ADDRESS,
reputationOracle: MOCK_ADDRESS,
});
});
import { EscrowUtils } from '@human-protocol/sdk';
import { ExecutionContext, HttpException, HttpStatus } from '@nestjs/common';

it('should be defined', () => {
expect(guard).toBeDefined();
});
import {
generateContractAddress,
generateEthWallet,
generateTestnetChainId,
} from '../../../test/fixtures/web3';
import {
createExecutionContextMock,
ExecutionContextMock,
} from '../../../test/mock-creators/nest';
import { signMessage } from '../../utils/web3';
import { AuthSignatureRole } from '../enums/role';

import { SignatureAuthGuard } from './signature.auth';

describe('SignatureAuthGuard', () => {
describe('canActivate', () => {
let context: ExecutionContext;
let mockRequest: any;
let executionContextMock: ExecutionContextMock;
let body: {
chain_id: number;
escrow_address: string;
};

beforeEach(() => {
mockRequest = {
switchToHttp: jest.fn().mockReturnThis(),
getRequest: jest.fn().mockReturnThis(),
headers: {},
body: {},
originalUrl: '',
executionContextMock = createExecutionContextMock();
body = {
chain_id: generateTestnetChainId(),
escrow_address: generateContractAddress(),
};
context = {
switchToHttp: jest.fn().mockReturnThis(),
getRequest: jest.fn(() => mockRequest),
} as any as ExecutionContext;
});

it('should return true if signature is verified', async () => {
const body = {
escrow_address: MOCK_ADDRESS,
chain_id: ChainId.LOCALHOST,
};
const signature = await signMessage(body, MOCK_PRIVATE_KEY);
mockRequest.headers['human-signature'] = signature;
mockRequest.body = body;
const result = await guard.canActivate(context as any);
expect(result).toBeTruthy();
expect(EscrowUtils.getEscrow).toHaveBeenCalledWith(
ChainId.LOCALHOST,
MOCK_ADDRESS,
);
});
it.each([
{
name: 'launcher',
role: AuthSignatureRole.JobLauncher,
},
{
name: 'exchangeOracle',
role: AuthSignatureRole.Exchange,
},
{
name: 'recordingOracle',
role: AuthSignatureRole.Recording,
},
])(
'should return true if signature is verified for "$role" role',
async ({ name, role }) => {
const guard = new SignatureAuthGuard([role]);

const { privateKey, address } = generateEthWallet();
EscrowUtils.getEscrow = jest.fn().mockResolvedValueOnce({
[name]: address,
});

const signature = await signMessage(body, privateKey);

const request = {
headers: {
'human-signature': signature,
},
body,
};
executionContextMock.__getRequest.mockReturnValueOnce(request);

const result = await guard.canActivate(
executionContextMock as unknown as ExecutionContext,
);

expect(result).toBeTruthy();
expect(EscrowUtils.getEscrow).toHaveBeenCalledWith(
body.chain_id,
body.escrow_address,
);
},
);

it('should throw unauthorized exception if signature is not verified', async () => {
const guard = new SignatureAuthGuard([AuthSignatureRole.JobLauncher]);

EscrowUtils.getEscrow = jest.fn().mockResolvedValueOnce({
launcher: generateEthWallet().address,
});

const signature = await signMessage(body, generateEthWallet().privateKey);

const request = {
headers: {
'human-signature': signature,
},
body,
};
executionContextMock.__getRequest.mockReturnValueOnce(request);

let catchedError;
try {
await guard.canActivate(context as any);
await guard.canActivate(
executionContextMock as unknown as ExecutionContext,
);
} catch (error) {
catchedError = error;
}
Expand All @@ -89,10 +114,30 @@ describe('SignatureAuthGuard', () => {
});

it('should throw unauthorized exception for unrecognized oracle type', async () => {
mockRequest.originalUrl = '/some/random/path';
const guard = new SignatureAuthGuard([]);

const { privateKey, address } = generateEthWallet();
EscrowUtils.getEscrow = jest.fn().mockResolvedValueOnce({
launcher: address,
exachangeOracle: address,
recordingOracle: address,
});

const signature = await signMessage(body, privateKey);

const request = {
headers: {
'human-signature': signature,
},
body,
};
executionContextMock.__getRequest.mockReturnValueOnce(request);

let catchedError;
try {
await guard.canActivate(context as any);
await guard.canActivate(
executionContextMock as unknown as ExecutionContext,
);
} catch (error) {
catchedError = error;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { EscrowUtils } from '@human-protocol/sdk';
import {
CanActivate,
ExecutionContext,
Expand All @@ -7,7 +8,6 @@ import {
} from '@nestjs/common';
import { verifySignature } from '../../utils/web3';
import { HEADER_SIGNATURE_KEY } from '../constants';
import { EscrowUtils } from '@human-protocol/sdk';
import { AuthSignatureRole } from '../enums/role';

@Injectable()
Expand All @@ -27,18 +27,21 @@ export class SignatureAuthGuard implements CanActivate {
if (
this.role.includes(AuthSignatureRole.JobLauncher) &&
escrowData.launcher.length
)
) {
oracleAdresses.push(escrowData.launcher);
}
if (
this.role.includes(AuthSignatureRole.Exchange) &&
escrowData.exchangeOracle?.length
)
) {
oracleAdresses.push(escrowData.exchangeOracle);
}
if (
this.role.includes(AuthSignatureRole.Recording) &&
escrowData.recordingOracle?.length
)
) {
oracleAdresses.push(escrowData.recordingOracle);
}

const isVerified = verifySignature(data, signature, oracleAdresses);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Module, Global } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { ConfigModule } from '@nestjs/config';

import { AuthConfigService } from './auth-config.service';
import { ServerConfigService } from './server-config.service';
Expand All @@ -10,13 +10,12 @@ import { EmailConfigService } from './email-config.service';
import { Web3ConfigService } from './web3-config.service';
import { ReputationConfigService } from './reputation-config.service';
import { KycConfigService } from './kyc-config.service';
import { NetworkConfigService } from './network-config.service';
import { HCaptchaConfigService } from './hcaptcha-config.service';

@Global()
@Module({
imports: [ConfigModule],
providers: [
ConfigService,
ServerConfigService,
AuthConfigService,
DatabaseConfigService,
Expand All @@ -26,11 +25,9 @@ import { HCaptchaConfigService } from './hcaptcha-config.service';
EmailConfigService,
KycConfigService,
PGPConfigService,
NetworkConfigService,
HCaptchaConfigService,
],
exports: [
ConfigService,
ServerConfigService,
AuthConfigService,
DatabaseConfigService,
Expand All @@ -40,7 +37,6 @@ import { HCaptchaConfigService } from './hcaptcha-config.service';
EmailConfigService,
KycConfigService,
PGPConfigService,
NetworkConfigService,
HCaptchaConfigService,
],
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as Joi from 'joi';
import { Web3Network } from './web3-config.service';

export const envValidator = Joi.object({
// General
Expand Down Expand Up @@ -34,7 +35,7 @@ export const envValidator = Joi.object({
POSTGRES_URL: Joi.string(),
POSTGRES_LOGGING: Joi.string(),
// Web3
WEB3_ENV: Joi.string(),
WEB3_ENV: Joi.string().valid(...Object.values(Web3Network)),
WEB3_PRIVATE_KEY: Joi.string().required(),
GAS_PRICE_MULTIPLIER: Joi.number(),
RPC_URL_SEPOLIA: Joi.string(),
Expand Down
Loading