Skip to content

Commit

Permalink
Switch to ethers
Browse files Browse the repository at this point in the history
- Removed ganache from dependencies and CI
- Removed web3
- Removed nevermined
- Removed hdwallet
- Signing and verifying now work without the need of a provider
  • Loading branch information
r-marques committed Apr 13, 2022
1 parent 542d093 commit b8035c4
Show file tree
Hide file tree
Showing 9 changed files with 8,923 additions and 22,473 deletions.
3 changes: 0 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ jobs:
run: |
npm run build
npm run lint
- name: Run ganache
run: |
npx ganache &
- name: Run tests
run: |
npm run setup:dev
Expand Down
31,284 changes: 8,889 additions & 22,395 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 2 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,9 @@
"@nestjs/platform-express": "^8.4.1",
"@nestjs/swagger": "^5.2.0",
"@nestjs/typeorm": "^8.0.3",
"@nevermined-io/nevermined-sdk-js": "^0.19.12",
"@sideway/address": "^4.1.3",
"@sideway/formula": "^3.0.0",
"@sideway/pinpoint": "^2.0.0",
"@truffle/hdwallet-provider": "^2.0.5",
"cache-manager": "^3.6.0",
"class-transformer": "0.5.1",
"class-validator": "^0.13.2",
Expand All @@ -63,8 +61,7 @@
"ts-node": "^10.7.0",
"tsconfig-paths": "^3.13.0",
"typeorm": "^0.2.45",
"typescript": "^4.6.2",
"web3": "^1.7.1"
"typescript": "^4.6.2"
},
"devDependencies": {
"@faker-js/faker": "^6.0.0-beta.0",
Expand All @@ -84,7 +81,6 @@
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jsdoc": "^38.0.3",
"eslint-plugin-prefer-arrow": "^1.2.3",
"ganache": "^7.0.4",
"jest": "^27.5.1",
"jest-date-mock": "^1.0.8",
"lint-staged": "^12.3.5",
Expand Down Expand Up @@ -135,4 +131,4 @@
},
"author": "Keyko",
"license": "ISC"
}
}
19 changes: 6 additions & 13 deletions src/auth/auth.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { Test, TestingModule } from '@nestjs/testing';
import { Account } from '@nevermined-io/nevermined-sdk-js';
import HDWalletProvider from '@truffle/hdwallet-provider';
import Web3 from 'web3';
import { ethers } from 'ethers';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { jwtConstants } from './constants';
Expand All @@ -12,15 +10,10 @@ import { EthSignJWT } from './jwt.utils';

describe('AuthController', () => {
let authController: AuthController;
let account: Account;
let web3: Web3;
let wallet: ethers.Wallet;

beforeAll(() => {
const seedphrase = 'taxi music thumb unique chat sand crew more leg another off lamp';
const provider = new HDWalletProvider(seedphrase, 'http://localhost:8545', 0, 10);
const address: string = provider.getAddresses()[0];
account = new Account(address);
web3 = new Web3(provider);
wallet = ethers.Wallet.createRandom();
});

beforeEach(async () => {
Expand All @@ -41,15 +34,15 @@ describe('AuthController', () => {

it('should get an access_token', async () => {
const clientAssertion = await new EthSignJWT({
iss: web3.utils.toChecksumAddress(account.getId())
iss: wallet.address
})
.setProtectedHeader({ alg: 'ES256K' })
.setIssuedAt()
.setExpirationTime('60m')
.ethSign(account, web3);
.ethSign(wallet);
const clientAssertionType = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer';

const response = await authController.login(clientAssertionType, clientAssertion);
const response = authController.login(clientAssertionType, clientAssertion);
expect(response).toHaveProperty('access_token');
});
});
4 changes: 2 additions & 2 deletions src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ export class AuthController {
constructor(private authService: AuthService) { }
@Public()
@Post('login')
async login(
login(
@Body('client_assertion_type') clientAssertionType: string,
@Body('client_assertion') clientAssertion: string,
): Promise<LoginDto> {
): LoginDto {
return this.authService.validateClaim(clientAssertionType, clientAssertion);
}

Expand Down
19 changes: 5 additions & 14 deletions src/auth/auth.integration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,19 @@ import request from 'supertest';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { Test, TestingModule } from '@nestjs/testing';
import { Account } from '@nevermined-io/nevermined-sdk-js';
import HDWalletProvider from '@truffle/hdwallet-provider';
import Web3 from 'web3';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { jwtConstants } from './constants';
import { JwtStrategy } from './jwt.strategy';
import { EthSignJWT } from './jwt.utils';
import { ethers } from 'ethers';

describe('AuthController', () => {
let app: INestApplication;
let account: Account;
let web3: Web3;
let accessToken: string;
let wallet: ethers.Wallet;

beforeAll(() => {
const seedphrase = 'taxi music thumb unique chat sand crew more leg another off lamp';
const provider = new HDWalletProvider(seedphrase, 'http://localhost:8545', 0, 10);
const address: string = provider.getAddresses()[0];
account = new Account(address);
web3 = new Web3(provider);
wallet = ethers.Wallet.createRandom();
});

beforeEach(async () => {
Expand All @@ -45,12 +37,12 @@ describe('AuthController', () => {

it('/POST login', async () => {
const clientAssertion = await new EthSignJWT({
iss: web3.utils.toChecksumAddress(account.getId())
iss: wallet.address
})
.setProtectedHeader({ alg: 'ES256K' })
.setIssuedAt()
.setExpirationTime('60m')
.ethSign(account, web3);
.ethSign(wallet);

const clientAssertionType = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer';
const response = await request(app.getHttpServer())
Expand All @@ -59,6 +51,5 @@ describe('AuthController', () => {

expect(response.statusCode).toBe(201);
expect(response.body).toHaveProperty('access_token');

});
});
27 changes: 8 additions & 19 deletions src/auth/auth.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,15 @@ import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { jwtConstants } from './constants';
import { JwtStrategy } from './jwt.strategy';
import HDWalletProvider from '@truffle/hdwallet-provider';
import { Account } from '@nevermined-io/nevermined-sdk-js';
import { EthSignJWT } from './jwt.utils';
import Web3 from 'web3';
import { ethers } from 'ethers';

describe('AuthService', () => {
let service: AuthService;
let web3: Web3;
let account: Account;
let wallet: ethers.Wallet;

beforeAll(() => {
const seedphrase = 'taxi music thumb unique chat sand crew more leg another off lamp';
const provider = new HDWalletProvider(seedphrase, 'http://localhost:8545', 0, 10);
const address: string = provider.getAddresses()[0];
account = new Account(address);
web3 = new Web3(provider);
wallet = ethers.Wallet.createRandom();
});

beforeEach(async () => {
Expand All @@ -40,28 +33,24 @@ describe('AuthService', () => {
service = module.get<AuthService>(AuthService);
});

it('should be defined', () => {
expect(service).toBeDefined();
});

it('should get a token with web3 signing', async () => {
it('should get an access token with an ethereum signed claim', async () => {

const clientAssertion = await new EthSignJWT({
iss: web3.utils.toChecksumAddress(account.getId())
iss: wallet.address
})
.setProtectedHeader({ alg: 'ES256K' })
.setIssuedAt()
.setExpirationTime('60m')
.ethSign(account, web3);
.ethSign(wallet);

const result = await service.validateClaim(
const result = service.validateClaim(
'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
clientAssertion
);
expect(result).toHaveProperty('access_token');

const payload = decodeJwt(result.access_token);
expect(payload.iss).toEqual(web3.utils.toChecksumAddress(account.getId()));
expect(payload.iss).toEqual(wallet.address);
});

});
4 changes: 2 additions & 2 deletions src/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ export class AuthService {
// - the size of the signature. ethereum adds an extra byte to the signature to help
// with recovering the public key that create the signature
// - the hash function used. ES256K uses sha-256 while ethereum uses keccak
async validateClaim(clientAssertionType: string, clientAssertion: string): Promise<LoginDto> {
validateClaim(clientAssertionType: string, clientAssertion: string): LoginDto {
if (clientAssertionType !== CLIENT_ASSERTION_TYPE) {
throw new UnauthorizedException('Invalid "client_assertion_type"');
}

let payload: JWTPayload;
try {
payload = await jwtEthVerify(clientAssertion);
payload = jwtEthVerify(clientAssertion);
} catch (error) {
throw new UnauthorizedException(`The 'client_assertion' is invalid: ${(error as Error).message}`);
}
Expand Down
28 changes: 9 additions & 19 deletions src/auth/jwt.utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { Account } from "@nevermined-io/nevermined-sdk-js";
import HDWalletProvider from "@truffle/hdwallet-provider";
import {
decodeJwt,
decodeProtectedHeader,
Expand All @@ -8,7 +6,6 @@ import {
ProtectedHeaderParameters,
SignJWT
} from "jose";
import Web3 from "web3";
import { ethers } from "ethers";

// TODO: Used only for testing and copied from the sdk
Expand All @@ -22,8 +19,7 @@ export class EthSignJWT extends SignJWT {
}

public async ethSign(
account: Account,
web3: Web3
wallet: ethers.Wallet
): Promise<string> {
const encoder = new TextEncoder();
const decoder = new TextDecoder();
Expand All @@ -36,11 +32,9 @@ export class EthSignJWT extends SignJWT {
);
const data = this.concat(encodedHeader, encoder.encode('.'), encodedPayload);

const sign = await web3.eth.personal.sign(decoder.decode(data), account.getId(), undefined);
const sign = await wallet.signMessage(decoder.decode(data));

const signed = this.base64url(
Uint8Array.from(web3.utils.hexToBytes(sign))
);
const signed = this.base64url(ethers.utils.arrayify(sign));
const grantToken = `${decoder.decode(encodedHeader)}.${decoder.decode(
encodedPayload
)}.${signed}`;
Expand Down Expand Up @@ -68,7 +62,7 @@ export class EthSignJWT extends SignJWT {
}
}

export const recoverPublicKey = async (protectedHeader: string, payload: string, signature: string) => {
export const recoverPublicKey = (protectedHeader: string, payload: string, signature: string) => {
const signatureInput = `${protectedHeader}.${payload}`;
const signatureDecoded = `0x${Buffer.from(signature, 'base64').toString('hex')}`;

Expand All @@ -78,8 +72,8 @@ export const recoverPublicKey = async (protectedHeader: string, payload: string,

// TODO: A lot of this functionality should maybe be turned
// into a passport strategy
// Verify a jwt signed by a web3 provider
export const jwtEthVerify = async (
// Verify a jwt with an ethereum signature
export const jwtEthVerify = (
jwt: string,
) => {
const { 0: protectedHeader, 1: payload, 2: signature, length } = jwt.split('.');
Expand All @@ -103,7 +97,7 @@ export const jwtEthVerify = async (
// This is the de-facto signature validation
let publicKey: string;
try {
publicKey = await recoverPublicKey(protectedHeader, payload, signature);
publicKey = recoverPublicKey(protectedHeader, payload, signature);
} catch (error) {
throw new Error(`Signature: Failed to validate signature (${(error as Error).message})`);
}
Expand All @@ -118,12 +112,8 @@ export const jwtEthVerify = async (
if (!parsedPayload.iss) {
throw new Error('Payload: `iss` field is required');
}
let isValidAddress: boolean;
try {
isValidAddress = Web3.utils.checkAddressChecksum(parsedPayload.iss);
} catch (error) {
throw new Error(`Payload: 'iss' field must be a valid checksum ethereum address (${(error as Error).message})`);
}

const isValidAddress = ethers.utils.isAddress(parsedPayload.iss);
if (!isValidAddress) {
throw new Error('Payload: `iss` field must be a valid checksum ethereum address');
}
Expand Down

0 comments on commit b8035c4

Please sign in to comment.