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

adding recovery integration tests #74

Merged
merged 18 commits into from
Nov 27, 2023
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
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"
acharb marked this conversation as resolved.
Show resolved Hide resolved
services:
acharb marked this conversation as resolved.
Show resolved Hide resolved
recovery-signer-migrate1:
image: stellar/recoverysigner
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
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
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
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
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
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
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
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);
});
Loading