Skip to content

Commit

Permalink
refactor: SignatureAuthGuard to accept at least one role
Browse files Browse the repository at this point in the history
  • Loading branch information
dnechay committed Feb 26, 2025
1 parent 52232f0 commit 0fb47fb
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 59 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,8 @@ import {
ExecutionContextMock,
} from '../../../test/mock-creators/nest';
import { signMessage } from '../../utils/web3';
import { AuthSignatureRole } from '../enums/role';

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

describe('SignatureAuthGuard', () => {
describe('canActivate', () => {
Expand All @@ -38,18 +37,31 @@ describe('SignatureAuthGuard', () => {
};
});

it('should throw if empty roles provided in constructor', async () => {
let thrownError;
try {
new SignatureAuthGuard([]);
} catch (error) {
thrownError = error;
}
expect(thrownError).toBeInstanceOf(Error);
expect(thrownError.message).toBe(
'At least one auth signature role should be provided',
);
});

it.each([
{
name: 'launcher',
role: AuthSignatureRole.JobLauncher,
role: AuthSignatureRole.JOB_LAUNCHER,
},
{
name: 'exchangeOracle',
role: AuthSignatureRole.Exchange,
role: AuthSignatureRole.EXCHANGE_ORACLE,
},
{
name: 'recordingOracle',
role: AuthSignatureRole.Recording,
role: AuthSignatureRole.RECORDING_ORACLE,
},
])(
'should return true if signature is verified for "$role" role',
Expand Down Expand Up @@ -84,46 +96,21 @@ describe('SignatureAuthGuard', () => {
);

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(
executionContextMock as unknown as ExecutionContext,
);
} catch (error) {
catchedError = error;
}
expect(catchedError).toBeInstanceOf(HttpException);
expect(catchedError).toHaveProperty('message', 'Invalid web3 signature');
expect(catchedError).toHaveProperty('status', HttpStatus.UNAUTHORIZED);
});

it('should throw unauthorized exception for unrecognized oracle type', async () => {
const guard = new SignatureAuthGuard([]);
const guard = new SignatureAuthGuard([
AuthSignatureRole.JOB_LAUNCHER,
AuthSignatureRole.EXCHANGE_ORACLE,
AuthSignatureRole.RECORDING_ORACLE,
]);

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

const signature = await signMessage(body, privateKey);
const { privateKey: differentSignerPrivateKey } = generateEthWallet();
const signature = await signMessage(body, differentSignerPrivateKey);

const request = {
headers: {
Expand All @@ -133,17 +120,17 @@ describe('SignatureAuthGuard', () => {
};
executionContextMock.__getRequest.mockReturnValueOnce(request);

let catchedError;
let thrownError;
try {
await guard.canActivate(
executionContextMock as unknown as ExecutionContext,
);
} catch (error) {
catchedError = error;
thrownError = error;
}
expect(catchedError).toBeInstanceOf(HttpException);
expect(catchedError).toHaveProperty('message', 'Invalid web3 signature');
expect(catchedError).toHaveProperty('status', HttpStatus.UNAUTHORIZED);
expect(thrownError).toBeInstanceOf(HttpException);
expect(thrownError.message).toBe('Invalid web3 signature');
expect(thrownError.status).toBe(HttpStatus.UNAUTHORIZED);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,24 @@ import {
} from '@nestjs/common';
import { verifySignature } from '../../utils/web3';
import { HEADER_SIGNATURE_KEY } from '../constants';
import { AuthSignatureRole } from '../enums/role';

export enum AuthSignatureRole {
JOB_LAUNCHER = 'job_launcher',
EXCHANGE_ORACLE = 'exchange',
RECORDING_ORACLE = 'recording',
}

@Injectable()
export class SignatureAuthGuard implements CanActivate {
constructor(private role: AuthSignatureRole[]) {}
private readonly authorizedSignerRoles: AuthSignatureRole[];

constructor(roles: AuthSignatureRole[]) {
if (roles.length === 0) {
throw new Error('At least one auth signature role should be provided');
}

this.authorizedSignerRoles = roles;
}

public async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
Expand All @@ -25,19 +38,19 @@ export class SignatureAuthGuard implements CanActivate {
data.escrow_address,
);
if (
this.role.includes(AuthSignatureRole.JobLauncher) &&
this.authorizedSignerRoles.includes(AuthSignatureRole.JOB_LAUNCHER) &&
escrowData.launcher.length
) {
oracleAdresses.push(escrowData.launcher);
}
if (
this.role.includes(AuthSignatureRole.Exchange) &&
this.authorizedSignerRoles.includes(AuthSignatureRole.EXCHANGE_ORACLE) &&
escrowData.exchangeOracle?.length
) {
oracleAdresses.push(escrowData.exchangeOracle);
}
if (
this.role.includes(AuthSignatureRole.Recording) &&
this.authorizedSignerRoles.includes(AuthSignatureRole.RECORDING_ORACLE) &&
escrowData.recordingOracle?.length
) {
oracleAdresses.push(escrowData.recordingOracle);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ import {
} from '@nestjs/swagger';
import { HEADER_SIGNATURE_KEY } from '../../common/constants';
import { Public } from '../../common/decorators';
import { AuthSignatureRole } from '../../common/enums/role';
import { SignatureAuthGuard } from '../../common/guards';
import { AuthSignatureRole, SignatureAuthGuard } from '../../common/guards';

import { WebhookIncomingService } from './webhook-incoming.service';
import { IncomingWebhookDto } from './webhook.dto';
Expand Down Expand Up @@ -46,7 +45,7 @@ export class WebhookController {
status: 202,
description: 'Incoming webhook accepted successfully',
})
@UseGuards(new SignatureAuthGuard([AuthSignatureRole.Recording]))
@UseGuards(new SignatureAuthGuard([AuthSignatureRole.RECORDING_ORACLE]))
@Post('/')
@HttpCode(202)
public async createIncomingWebhook(
Expand Down

0 comments on commit 0fb47fb

Please sign in to comment.