141 lines
5.4 KiB
TypeScript
141 lines
5.4 KiB
TypeScript
/**
|
|
* 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);
|