Skip to content

Commit

Permalink
feat: automatically add public key for Fynbos wallets (#643)
Browse files Browse the repository at this point in the history
  • Loading branch information
sidvishnoi authored Oct 14, 2024
1 parent 7244a3d commit ea33b28
Show file tree
Hide file tree
Showing 11 changed files with 411 additions and 15 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/nightly-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ jobs:
TEST_WALLET_KEY_ID: ${{ vars.E2E_CONNECT_KEY_ID }}
TEST_WALLET_PUBLIC_KEY: ${{ secrets.E2E_CONNECT_PUBLIC_KEY }}
TEST_WALLET_PRIVATE_KEY: ${{ secrets.E2E_CONNECT_PRIVATE_KEY }}
FYNBOS_WALLET_ADDRESS_URL: ${{ vars.E2E_FYNBOS_WALLET_ADDRESS_URL }}
FYNBOS_USERNAME: ${{ vars.E2E_FYNBOS_USERNAME }}
FYNBOS_PASSWORD: ${{ secrets.E2E_FYNBOS_PASSWORD }}

- name: Encrypt report
shell: bash
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/tests-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ jobs:
TEST_WALLET_KEY_ID: ${{ vars.E2E_CONNECT_KEY_ID }}
TEST_WALLET_PUBLIC_KEY: ${{ secrets.E2E_CONNECT_PUBLIC_KEY }}
TEST_WALLET_PRIVATE_KEY: ${{ secrets.E2E_CONNECT_PRIVATE_KEY }}
FYNBOS_WALLET_ADDRESS_URL: ${{ vars.E2E_FYNBOS_WALLET_ADDRESS_URL }}
FYNBOS_USERNAME: ${{ vars.E2E_FYNBOS_USERNAME }}
FYNBOS_PASSWORD: ${{ secrets.E2E_FYNBOS_PASSWORD }}

- name: Encrypt report
shell: bash
Expand Down
21 changes: 12 additions & 9 deletions docs/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,18 @@ Make sure you run `pnpm build chrome` before running tests.
1. Copy `tests/e2e/.env.example` to `tests/e2e/.env`
2. Update `tests/e2e/.env` with your secrets.

| Environment Variable | Description | Secret? |
| ------------------------- | ----------------------------------------------------------- | ------- |
| `TEST_WALLET_ORIGIN` | URL origin of the test wallet (e.g. https://rafiki.money) | - |
| `TEST_WALLET_USERNAME` | -- Login email for the test wallet | - |
| `TEST_WALLET_PASSWORD` | -- Login password for the test wallet | Yes |
| `TEST_WALLET_ADDRESS_URL` | Your wallet address URL that will be connected to extension | - |
| `TEST_WALLET_KEY_ID` | ID of the key that will be connected to extension (UUID v4) | - |
| `TEST_WALLET_PRIVATE_KEY` | Private key (hex-encoded Ed25519 private key) | Yes |
| `TEST_WALLET_PUBLIC_KEY` | Public key (base64-encoded Ed25519 public key) | - |
| Environment Variable | Description | Secret? | Optional? |
| --------------------------- | ----------------------------------------------------------- | ------- | --------- |
| `TEST_WALLET_ORIGIN` | URL origin of the test wallet (e.g. https://rafiki.money) | - | - |
| `TEST_WALLET_USERNAME` | -- Login email for the test wallet | - | - |
| `TEST_WALLET_PASSWORD` | -- Login password for the test wallet | Yes | - |
| `TEST_WALLET_ADDRESS_URL` | Your wallet address URL that will be connected to extension | - | - |
| `TEST_WALLET_KEY_ID` | ID of the key that will be connected to extension (UUID v4) | - | - |
| `TEST_WALLET_PRIVATE_KEY` | Private key (hex-encoded Ed25519 private key) | Yes | - |
| `TEST_WALLET_PUBLIC_KEY` | Public key (base64-encoded Ed25519 public key) | - | - |
| `FYNBOS_WALLET_ADDRESS_URL` | Fynbos wallet address (used for Fynbos specific tests only) | - | Yes |
| `FYNBOS_USERNAME` | -- Login email for Fynbos wallet | - | Yes |
| `FYNBOS_PASSWORD` | -- Login password for Fynbos wallet | Yes | Yes |

To get the `TEST_WALLET_KEY_ID`, `TEST_WALLET_PRIVATE_KEY` and `TEST_WALLET_PUBLIC_KEY`:

Expand Down
16 changes: 12 additions & 4 deletions esbuild/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import path from 'node:path';
import { readdirSync } from 'node:fs';
import type { BuildOptions } from 'esbuild';
import type { Manifest } from 'webextension-polyfill';

Expand All @@ -10,6 +11,13 @@ export const SRC_DIR = path.resolve(ROOT_DIR, 'src');
export const DEV_DIR = path.resolve(ROOT_DIR, 'dev');
export const DIST_DIR = path.resolve(ROOT_DIR, 'dist');

const KEY_AUTO_ADD_TARGETS = readdirSync(
path.join(SRC_DIR, 'content', 'keyAutoAdd'),
{ withFileTypes: true },
)
.filter((e) => e.isFile())
.map(({ name }) => path.basename(name, path.extname(name)));

export type Target = (typeof TARGETS)[number];
export type Channel = (typeof CHANNELS)[number];
export type BuildArgs = {
Expand All @@ -28,10 +36,10 @@ export const options: BuildOptions = {
in: path.join(SRC_DIR, 'content', 'index.ts'),
out: path.join('content', 'content'),
},
{
in: path.join(SRC_DIR, 'content', 'keyAutoAdd', 'testWallet.ts'),
out: path.join('content', 'keyAutoAdd', 'testWallet'),
},
...KEY_AUTO_ADD_TARGETS.map((name) => ({
in: path.join(SRC_DIR, 'content', 'keyAutoAdd', `${name}.ts`),
out: path.join('content', 'keyAutoAdd', name),
})),
{
in: path.join(SRC_DIR, 'content', 'polyfill.ts'),
out: path.join('polyfill', 'polyfill'),
Expand Down
6 changes: 4 additions & 2 deletions src/background/services/keyAutoAdd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,10 @@ export function walletAddressToProvider(walletAddress: WalletAddress): {
switch (host) {
case 'ilp.rafiki.money':
return { url: 'https://rafiki.money/settings/developer-keys' };
// case 'eu1.fynbos.me': // fynbos dev
// case 'fynbos.me': // fynbos production
case 'eu1.fynbos.me':
return { url: 'https://eu1.fynbos.dev/settings/keys' };
case 'fynbos.me':
return { url: 'https://wallet.fynbos.app/settings/keys' };
default:
throw new ErrorWithKey('connectWalletKeyService_error_notImplemented');
}
Expand Down
124 changes: 124 additions & 0 deletions src/content/keyAutoAdd/fynbos.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// cSpell:ignore nextjs
import { errorWithKey, ErrorWithKey, sleep } from '@/shared/helpers';
import {
KeyAutoAdd,
LOGIN_WAIT_TIMEOUT,
type StepRun as Run,
} from './lib/keyAutoAdd';
import { isTimedOut, waitForURL } from './lib/helpers';
// #region: Steps

type IndexRouteResponse = {
isUser: boolean;
walletInfo: {
walletID: string;
url: string;
};
};

const waitForLogin: Run<void> = async (
{ keyAddUrl },
{ skip, setNotificationSize },
) => {
let alreadyLoggedIn = window.location.href.startsWith(keyAddUrl);
if (!alreadyLoggedIn) setNotificationSize('notification');
try {
await sleep(2000);
alreadyLoggedIn = await waitForURL(
(url) => (url.origin + url.pathname).startsWith(keyAddUrl),
{ timeout: LOGIN_WAIT_TIMEOUT },
);
setNotificationSize('fullscreen');
} catch (error) {
if (isTimedOut(error)) {
throw new ErrorWithKey('connectWalletKeyService_error_timeoutLogin');
}
throw new Error(error);
}

if (alreadyLoggedIn) {
skip(errorWithKey('connectWalletKeyService_error_skipAlreadyLoggedIn'));
}
};

const findWallet: Run<void> = async (
{ walletAddressUrl },
{ setNotificationSize },
) => {
setNotificationSize('fullscreen');
const url = `/?_data=${encodeURIComponent('routes/_index')}`;
const res = await fetch(url, {
headers: { accept: 'application/json' },
credentials: 'include',
}).catch((error) => {
return Response.json(null, { status: 599, statusText: error.message });
});
if (!res.ok) {
throw new Error(`Failed to get wallet details (${res.statusText})`);
}
const data: IndexRouteResponse = await res.json();
if (data?.walletInfo?.url !== walletAddressUrl) {
throw new ErrorWithKey('connectWalletKeyService_error_accountNotFound');
}
};

const addKey: Run<void> = async ({ nickName, publicKey }) => {
const url = `/settings/keys/add-public?_data=${encodeURIComponent('routes/settings_.keys_.add-public')}`;
const csrfToken = await getCSRFToken(url);

const res = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
},
credentials: 'include',
body: toFormUrlEncoded({
csrfToken: csrfToken,
applicationName: nickName,
publicKey: publicKey,
}),
}).catch((error) => {
return Response.json(null, { status: 599, statusText: error.message });
});

if (!res.ok) {
throw new Error(`Failed to upload public key (${res.statusText})`);
}
};
// #endregion

// #region: Helpers
const getCSRFToken = async (url: string): Promise<string> => {
const res = await fetch(url, {
headers: { accept: 'application/json' },
credentials: 'include',
}).catch((error) => {
return Response.json(null, { status: 599, statusText: error.message });
});
if (!res.ok) {
throw new Error(`Failed to retrieve CSRF token (${res.statusText})`);
}

const { csrfToken }: { csrfToken: string } = await res.json();

return csrfToken;
};

const toFormUrlEncoded = (data: Record<string, string>) => {
return Object.entries(data)
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
.join('&');
};
// #endregion

// #region: Main
new KeyAutoAdd([
{
name: 'Waiting for you to login',
run: waitForLogin,
maxDuration: LOGIN_WAIT_TIMEOUT,
},
{ name: 'Finding wallet', run: findWallet },
{ name: 'Adding key', run: addKey },
]).init();
// #endregion
5 changes: 5 additions & 0 deletions src/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@
"matches": ["https://rafiki.money/*/*"],
"js": ["content/keyAutoAdd/testWallet.js"],
"run_at": "document_end"
},
{
"matches": ["https://eu1.fynbos.dev/*", "https://wallet.fynbos.app/*"],
"js": ["content/keyAutoAdd/fynbos.js"],
"run_at": "document_end"
}
],
"background": {
Expand Down
7 changes: 7 additions & 0 deletions tests/e2e/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,10 @@ TEST_WALLET_ADDRESS_URL="https://ilp.rafiki.money/something"
TEST_WALLET_KEY_ID=uuid-v4-key-id
TEST_WALLET_PUBLIC_KEY="base-64-public-key=="
TEST_WALLET_PRIVATE_KEY="hex-encoded-private-key"

## If following are not provided, tests that use these will be skipped.

# Fynbos specific tests, using https://eu1.fynbos.dev
FYNBOS_WALLET_ADDRESS_URL=
FYNBOS_USERNAME=
FYNBOS_PASSWORD=
Loading

0 comments on commit ea33b28

Please sign in to comment.