/** * End-to-end test for Passkey authentication against amiPro FIDO2 server. * * Tests: * 1. AmiProFIDO2Client connectivity * 2. PasskeyPlatformService flow * 3. PasskeyServer interactive ceremony (opens browser) * * Usage: npx tsx test-passkey.ts [register|authenticate] */ import { AmiProFIDO2Client } from './src/passkey/amipro-fido2-client.js'; import { PasskeyPlatformService } from './src/passkey/platform-service.js'; import { PasskeyServer } from './src/passkey/passkey-server.js'; const FIDO2_SERVER_URL = 'https://fido2.epk.amipro.me'; const RP_ID = 'a2a-demo.mcplet.ai'; const TEST_USER = 'a2a-test-user'; async function testAmiProClient() { console.log('\n=== Test 1: AmiProFIDO2Client Connectivity ===\n'); const client = new AmiProFIDO2Client(FIDO2_SERVER_URL, RP_ID); // Test attestation options (registration) console.log('[1a] Getting attestation options...'); try { const attOpts = await client.getAttestationOptions(TEST_USER, 'A2A Test User'); console.log(' status:', attOpts.status); if (attOpts.status !== 'failed') { console.log(' challenge:', attOpts.challenge?.substring(0, 30) + '...'); console.log(' rp:', JSON.stringify(attOpts.rp)); console.log(' user.id:', attOpts.user?.id?.substring(0, 20) + '...'); console.log(' pubKeyCredParams:', JSON.stringify(attOpts.pubKeyCredParams)); console.log(' timeout:', attOpts.timeout); } else { console.log(' errorMessage:', attOpts.errorMessage); } } catch (err) { console.error(' ERROR:', (err as Error).message); } // Test assertion options (authentication) console.log('\n[1b] Getting assertion options...'); try { const assertOpts = await client.getAssertionOptions(TEST_USER); console.log(' status:', assertOpts.status); if (assertOpts.status === 'ok') { console.log(' challenge:', assertOpts.challenge?.substring(0, 30) + '...'); console.log(' rpId:', assertOpts.rpId); console.log(' timeout:', assertOpts.timeout); console.log(' allowCredentials:', assertOpts.allowCredentials?.length, 'entries'); console.log(' userVerification:', assertOpts.userVerification); } else { console.log(' errorMessage:', assertOpts.errorMessage); console.log(' (Expected: user not registered yet)'); } } catch (err) { console.error(' ERROR:', (err as Error).message); } } async function testPlatformService() { console.log('\n=== Test 2: PasskeyPlatformService ===\n'); const service = new PasskeyPlatformService(RP_ID, FIDO2_SERVER_URL); // Test get attestation options console.log('[2a] Getting attestation options via service...'); const attResult = await service.getAttestationOptions(TEST_USER, 'A2A Test User'); console.log(' success:', attResult.success); if (attResult.success) { console.log(' challenge present:', !!attResult.options?.challenge); console.log(' rp:', JSON.stringify(attResult.options?.rp)); } else { console.log(' error:', attResult.error); } // Test get assertion options console.log('\n[2b] Getting assertion options via service...'); const assertResult = await service.getAssertionOptions(TEST_USER); console.log(' success:', assertResult.success); if (assertResult.success) { console.log(' challenge present:', !!assertResult.options?.challenge); console.log(' allowCredentials:', assertResult.options?.allowCredentials?.length, 'entries'); } else { console.log(' error:', assertResult.error); console.log(' (Expected if user not registered yet)'); } } async function testInteractiveCeremony(username?: string) { console.log(`\n=== Test 3: Interactive Passkey Ceremony ===\n`); console.log(`Opens a browser page that auto-detects register vs authenticate.`); console.log(`rpId: ${RP_ID}, FIDO2 server: ${FIDO2_SERVER_URL}`); if (username) console.log(`Pre-filled username: ${username}`); console.log(); const server = new PasskeyServer(RP_ID, FIDO2_SERVER_URL, 180_000); try { const payload = await server.startCeremony( `この操作には Passkey 認証が必要です`, username, ); console.log('\n Ceremony succeeded!'); console.log(' type:', payload.type); console.log(' challenge:', payload.challenge?.substring(0, 30) + '...'); console.log(' session:', (payload as any).session); console.log(' username:', (payload as any).username); } catch (err) { console.error('\n Ceremony failed:', (err as Error).message); } } // ─── Main ────────────────────────────────────────────────────────────── const arg = process.argv[2]; await testAmiProClient(); await testPlatformService(); if (arg === 'test') { // Interactive test: opens browser with unified flow // Pass a username to pre-fill, or omit to let user enter it const username = process.argv[3] || undefined; await testInteractiveCeremony(username); } else if (arg === 'new') { // Test with a brand new user (no pre-fill) await testInteractiveCeremony(); } else { console.log('\n=== Interactive test skipped ==='); console.log('Usage:'); console.log(' npx tsx test-passkey.ts test [username] — unified flow (auto register+auth)'); console.log(' npx tsx test-passkey.ts new — new user (empty username field)'); } console.log('\nDone.'); process.exit(0);