Skip to content

Commit 70da36d

Browse files
committed
feat(web-demo): add PasskeyDemo component with passkey lifecycle UI
- Register, attach, derive PRF key, send funds, remove passkey flows - Right-column panels: registered passkeys + associated wallets with refresh - Quick-action buttons to select passkey/wallet for attach/remove steps - Config fields (env, enterpriseId, coin) persisted to localStorage - Coin registered via coinFactory before SDK use - Staging environment option added TICKET: WCN-188
1 parent 5697169 commit 70da36d

6 files changed

Lines changed: 955 additions & 1 deletion

File tree

modules/passkey-crypto/src/derivePasskeyPrfKey.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export async function derivePasskeyPrfKey(params: {
2323

2424
// Fetch the wallet's user keychain to get webauthnDevices
2525
const keychain = await wallet.getEncryptedUserKeychain();
26-
const devices = keychain.webauthnDevices;
26+
const devices = (keychain as any).webauthnDevices ?? (keychain as any).webAuthnDevices;
2727

2828
if (!devices || devices.length === 0) {
2929
throw new Error('No passkey devices available');
@@ -41,10 +41,18 @@ export async function derivePasskeyPrfKey(params: {
4141
.get(bitgo.url('/user/otp/webauthn/assertion', 2))
4242
.result()) as AssertionChallengeResponse;
4343

44+
// Build allowCredentials from the evalByCredential map so the browser
45+
// knows which credentials are valid for the PRF extension.
46+
const allowCredentials = Object.keys(evalByCredential).map((credId) => ({
47+
type: 'public-key' as const,
48+
id: Buffer.from(credId.replace(/-/g, '+').replace(/_/g, '/'), 'base64').buffer,
49+
}));
50+
4451
// Trigger WebAuthn assertion with PRF evaluation via the provider (navigator layer)
4552
const result = await provider.get({
4653
publicKey: {
4754
challenge: Buffer.from(challenge, 'base64'),
55+
allowCredentials,
4856
} as PublicKeyCredentialRequestOptions,
4957
evalByCredential,
5058
});

modules/web-demo/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
"@bitgo/sdk-coin-xtz": "^2.10.7",
6161
"@bitgo/sdk-coin-zec": "^2.8.7",
6262
"@bitgo/sdk-core": "^36.44.0",
63+
"@bitgo/passkey-crypto": "*",
6364
"@bitgo/sdk-hmac": "^1.9.0",
6465
"@bitgo/sdk-lib-mpc": "^10.12.0",
6566
"@bitgo/sdk-opensslbytes": "^2.1.0",

modules/web-demo/src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const EcdsaChallengeComponent = lazy(
1414
() => import('@components/EcdsaChallenge'),
1515
);
1616
const WebCryptoAuthComponent = lazy(() => import('@components/WebCryptoAuth'));
17+
const PasskeyDemo = lazy(() => import('@components/PasskeyDemo'));
1718

1819
const Loading = () => <div>Loading route...</div>;
1920

@@ -40,6 +41,7 @@ const App = () => {
4041
path="/webcrypto-auth"
4142
element={<WebCryptoAuthComponent />}
4243
/>
44+
<Route path="/passkey-demo" element={<PasskeyDemo />} />
4345
</Routes>
4446
</Suspense>
4547
</Layout>

modules/web-demo/src/components/Navbar/index.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ const Navbar = () => {
5656
>
5757
WebCrypto Auth
5858
</NavItem>
59+
<NavItem
60+
activeRoute={pathname === '/passkey-demo'}
61+
onClick={() => navigate('/passkey-demo')}
62+
>
63+
Passkey Demo
64+
</NavItem>
5965
</NavbarContainer>
6066
);
6167
};

0 commit comments

Comments
 (0)