Skip to content

Commit

Permalink
adding recovery integration tests (#74)
Browse files Browse the repository at this point in the history
* first cut

* try workflow

* run daemon

* print logs

* restart on failure

* try this

* try

* try

* try

* try

* cross fingers

* fix

* works hopefully

* fix

* add back

* cleanup

* image versions and readme
  • Loading branch information
acharb authored Nov 27, 2023
1 parent db60e08 commit d512ac7
Show file tree
Hide file tree
Showing 7 changed files with 311 additions and 0 deletions.
19 changes: 19 additions & 0 deletions .github/workflows/integrationTest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: Recovery Signer Integration Test
on: [pull_request]
jobs:
test-ci:
name: integration test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Start docker
run: docker-compose -f test/docker/docker-compose.yml up -d
- uses: actions/setup-node@v2
with:
node-version: 18
- run: yarn install
- run: yarn build
- run: yarn test:integration:ci
- name: Print Docker Logs
if: always() # This ensures that the logs are printed even if the tests fail
run: docker-compose -f test/docker/docker-compose.yml logs
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ module.exports = {
"^.+\\.(ts|tsx)?$": "ts-jest",
"^.+\\.(js|jsx)$": "babel-jest",
},
testPathIgnorePatterns: ["/node_modules/", "integration.test.ts"],
};
9 changes: 9 additions & 0 deletions jest.integration.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module.exports = {
rootDir: "./",
preset: "ts-jest",
transform: {
"^.+\\.(ts|tsx)?$": "ts-jest",
"^.+\\.(js|jsx)$": "babel-jest",
},
testMatch: ["**/*integration.test.ts"],
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"prepare": "husky install",
"test": "jest --watchAll",
"test:ci": "jest --ci",
"test:integration:ci": "jest --config jest.integration.config.js --ci",
"build:web": "webpack --config webpack.config.js",
"build:node": "webpack --env NODE=true --config webpack.config.js",
"build": "run-p build:web build:node",
Expand Down
18 changes: 18 additions & 0 deletions test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Recovery Integration Tests

## How it works

The recovery integration tests run different recovery scenarios against recovery
signer and webauth servers. 2 recovery signer and 2 webauth servers are started
in a docker-compose file (see test/docker/docker-compose.yml), to simulate a
wallet interacting with 2 separate recovery servers.

## To run tests locally:

```
// start servers using docker
$ docker-compose -f test/docker/docker-compose.yml up
// run tests
$ yarn test:integration:ci
```
97 changes: 97 additions & 0 deletions test/docker/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
version: "3"
services:
recovery-signer-migrate1:
image: stellar/recoverysigner:latest
depends_on:
postgres1:
condition: service_healthy
restart: on-failure
command: ["db", "migrate", "up"]
environment:
DB_URL: "postgresql://postgres:pg_password@postgres1:5432/pg_database1?sslmode=disable"

recovery-signer-migrate2:
image: stellar/recoverysigner:latest
depends_on:
postgres2:
condition: service_healthy
restart: on-failure
command: ["db", "migrate", "up"]
environment:
DB_URL: "postgresql://postgres:pg_password@postgres2:5432/pg_database2?sslmode=disable"

recovery-signer1:
image: stellar/recoverysigner:latest
ports:
- "8000:8000"
depends_on:
- postgres1
environment:
DB_URL: "postgresql://postgres:pg_password@postgres1:5432/pg_database1?sslmode=disable"
SIGNING_KEY: SAQFNCKPZ3ON5TSSEURAF4NPTZONPA37JPHQNHSLSRUNFP43MMT5LNH6
FIREBASE_PROJECT_ID: "none"
SEP10_JWKS: '{"keys":[{"kty":"EC","crv":"P-256","alg":"ES256","x":"dzqvhrMYwbmv7kcZK6L1oOATMFXG9wLFlnKfHf3E7FM","y":"Vb_wmcX-Zq2Hg2LFoXCEVWMwdJ01q41pSnxC3psunUY"}]}'
PORT: 8000

recovery-signer2:
image: stellar/recoverysigner:latest
ports:
- "8002:8002"
depends_on:
- postgres2
environment:
DB_URL: "postgresql://postgres:pg_password@postgres2:5432/pg_database2?sslmode=disable"
SIGNING_KEY: SA3Y2KQCPN6RAKLUISMY252QABWPQ3A65FBMZO2JJFKJ7O7VJNQ2PRYH # Use a different key for the second recovery signer
FIREBASE_PROJECT_ID: "none"
SEP10_JWKS: '{"keys":[{"kty":"EC","crv":"P-256","alg":"ES256","x":"dzqvhrMYwbmv7kcZK6L1oOATMFXG9wLFlnKfHf3E7FM","y":"Vb_wmcX-Zq2Hg2LFoXCEVWMwdJ01q41pSnxC3psunUY"}]}'
PORT: 8002

web-auth1:
image: stellar/webauth:latest
ports:
- "8001:8001"
environment:
SIGNING_KEY: SDYHSG4V2JP5H66N2CXBFCOBTAUFWXGJVPKWY6OXSIPMYW743N62QX6U
JWK: '{"kty":"EC","crv":"P-256","alg":"ES256","x":"dzqvhrMYwbmv7kcZK6L1oOATMFXG9wLFlnKfHf3E7FM","y":"Vb_wmcX-Zq2Hg2LFoXCEVWMwdJ01q41pSnxC3psunUY","d":"ivOMB4Wscz8ShvhwWDRyd-JJVfSMsjsz1oU3sNc-XJo"}'
DOMAIN: test-domain
AUTH_HOME_DOMAIN: test-domain
JWT_ISSUER: test
PORT: 8001

web-auth2:
image: stellar/webauth:latest
ports:
- "8003:8003"
environment:
SIGNING_KEY: SCAS7BUKVDL44A2BAP23RVAM6XXHB24YRCANQGDTP24HP7T6LPUFIGGU # Use a different key for the second web auth server
JWK: '{"kty":"EC","crv":"P-256","alg":"ES256","x":"dzqvhrMYwbmv7kcZK6L1oOATMFXG9wLFlnKfHf3E7FM","y":"Vb_wmcX-Zq2Hg2LFoXCEVWMwdJ01q41pSnxC3psunUY","d":"ivOMB4Wscz8ShvhwWDRyd-JJVfSMsjsz1oU3sNc-XJo"}'
DOMAIN: test-domain
AUTH_HOME_DOMAIN: test-domain
JWT_ISSUER: test
PORT: 8003

postgres1:
image: postgres:14
environment:
POSTGRES_PASSWORD: pg_password
POSTGRES_DB: pg_database1
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5

postgres2:
image: postgres:14
environment:
POSTGRES_PASSWORD: pg_password
POSTGRES_DB: pg_database2
ports:
- "5433:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
166 changes: 166 additions & 0 deletions test/integration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import axios from "axios";
import { Wallet } from "../src";

import {
RecoveryServer,
RecoveryServerKey,
RecoveryServerMap,
RecoverableWalletConfig,
RecoveryRole,
RecoveryAccountIdentity,
RecoveryType,
} from "../src/walletSdk/Types/recovery";

describe("Recovery Integration Tests", () => {
it("should work", async () => {
const wallet = Wallet.TestNet();
const stellar = wallet.stellar();
const accountService = stellar.account();

const server1Key: RecoveryServerKey = "server1";
const server1: RecoveryServer = {
endpoint: "http://localhost:8000",
authEndpoint: "http://localhost:8001",
homeDomain: "test-domain",
};

const server2Key: RecoveryServerKey = "server2";
const server2: RecoveryServer = {
endpoint: "http://localhost:8002",
authEndpoint: "http://localhost:8003",
homeDomain: "test-domain",
};

const servers: RecoveryServerMap = {
[server1Key]: server1,
[server2Key]: server2,
};

const recovery = wallet.recovery({ servers });

// Create accounts

const accountKp = accountService.createKeypair();
const deviceKp = accountService.createKeypair();
const recoveryKp = accountService.createKeypair();

try {
await stellar.server.loadAccount(accountKp.publicKey);
await stellar.server.loadAccount(deviceKp.publicKey);
await stellar.server.loadAccount(recoveryKp.publicKey);
} catch (e) {
await axios.get(
"https://friendbot.stellar.org/?addr=" + accountKp.publicKey,
);
await axios.get(
"https://friendbot.stellar.org/?addr=" + deviceKp.publicKey,
);
await axios.get(
"https://friendbot.stellar.org/?addr=" + recoveryKp.publicKey,
);
}

// Create SEP-30 identities

const identity1: RecoveryAccountIdentity = {
role: RecoveryRole.OWNER,
authMethods: [
{
type: RecoveryType.STELLAR_ADDRESS,
value: recoveryKp.publicKey,
},
],
};

const identity2: RecoveryAccountIdentity = {
role: RecoveryRole.OWNER,
authMethods: [
{
type: RecoveryType.STELLAR_ADDRESS,
value: recoveryKp.publicKey,
},
{
type: RecoveryType.EMAIL,
value: "[email protected]",
},
],
};

// Create recoverable wallet

const config: RecoverableWalletConfig = {
accountAddress: accountKp,
deviceAddress: deviceKp,
accountThreshold: { low: 10, medium: 10, high: 10 },
accountIdentity: { [server1Key]: [identity1], [server2Key]: [identity2] },
signerWeight: { device: 10, recoveryServer: 5 },
};
const recoverableWallet = await recovery.createRecoverableWallet(config);

// Sign and submit

recoverableWallet.transaction.sign(accountKp.keypair);
await stellar.submitTransaction(recoverableWallet.transaction);

let resp = await stellar.server.loadAccount(accountKp.publicKey);

expect(resp.signers.map((obj) => obj.weight).sort((a, b) => a - b)).toEqual(
[0, 5, 5, 10],
);
expect(
resp.signers.find((obj) => obj.key === accountKp.publicKey).weight,
).toBe(0);
expect(
resp.signers.find((obj) => obj.key === deviceKp.publicKey).weight,
).toBe(10);

// Get Account Info

const authToken1 = await recovery
.sep10Auth(server1Key)
.authenticate({ accountKp: recoveryKp });

const authMap = { [server1Key]: authToken1 };

const accountResp = await recovery.getAccountInfo(accountKp, authMap);
expect(accountResp[server1Key].address).toBe(accountKp.publicKey);
expect(accountResp[server1Key].identities[0].role).toBe("owner");
expect(accountResp[server1Key].signers.length).toBe(1);

// Recover Wallet

const authToken2 = await recovery
.sep10Auth(server2Key)
.authenticate({ accountKp: recoveryKp });

const recoverySignerAddress1 = recoverableWallet.signers[0];
const recoverySignerAddress2 = recoverableWallet.signers[1];
const newKp = accountService.createKeypair();
const signerMap = {
[server1Key]: {
signerAddress: recoverySignerAddress1,
authToken: authToken1,
},
[server2Key]: {
signerAddress: recoverySignerAddress2,
authToken: authToken2,
},
};
const recoverTxn = await recovery.replaceDeviceKey(
accountKp,
newKp,
signerMap,
);

await stellar.submitTransaction(recoverTxn);

resp = await stellar.server.loadAccount(accountKp.publicKey);

expect(
resp.signers.find((obj) => obj.key === deviceKp.publicKey),
).toBeFalsy();
expect(resp.signers.find((obj) => obj.key === newKp.publicKey).weight).toBe(
10,
);
}, 60000);
});

0 comments on commit d512ac7

Please sign in to comment.