Skip to content

Commit c822495

Browse files
refactor(webauthn): improve error handling and reduce code duplication
- Add getCDPSession() helper to centralize CDP session access - Add handleWebAuthnError() for user-friendly error messages - Wrap all CDP calls in try/catch blocks - Add specific error handling for addCredential (userHandle, privateKey) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 5b6939e commit c822495

1 file changed

Lines changed: 127 additions & 88 deletions

File tree

src/tools/webauthn.ts

Lines changed: 127 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,37 @@ import type {CDPSession} from '../third_party/index.js';
88
import {zod} from '../third_party/index.js';
99

1010
import {ToolCategory} from './categories.js';
11+
import type {Context} from './ToolDefinition.js';
1112
import {defineTool} from './ToolDefinition.js';
1213

14+
/**
15+
* Gets the CDP session from the current page context.
16+
*/
17+
function getCDPSession(context: Context): CDPSession {
18+
const page = context.getSelectedPage();
19+
// @ts-expect-error _client is internal Puppeteer API
20+
return page._client() as CDPSession;
21+
}
22+
23+
/**
24+
* Wraps CDP errors with more helpful messages.
25+
*/
26+
function handleWebAuthnError(error: unknown): never {
27+
const message = error instanceof Error ? error.message : String(error);
28+
29+
if (message.includes('not been enabled')) {
30+
throw new Error(
31+
'WebAuthn virtual authenticator environment not enabled. Call webauthn_enable first.',
32+
);
33+
}
34+
if (message.includes('authenticator')) {
35+
throw new Error(
36+
`Invalid or unknown authenticator ID. Use webauthn_add_authenticator to create one. Original error: ${message}`,
37+
);
38+
}
39+
throw error;
40+
}
41+
1342
export const enableWebAuthn = defineTool({
1443
name: 'webauthn_enable',
1544
description:
@@ -20,9 +49,7 @@ export const enableWebAuthn = defineTool({
2049
},
2150
schema: {},
2251
handler: async (_request, response, context) => {
23-
const page = context.getSelectedPage();
24-
// @ts-expect-error _client is internal Puppeteer API
25-
const session = page._client() as CDPSession;
52+
const session = getCDPSession(context);
2653
await session.send('WebAuthn.enable');
2754
response.appendResponseLine(
2855
'WebAuthn virtual authenticator environment enabled.',
@@ -58,10 +85,7 @@ export const addVirtualAuthenticator = defineTool({
5885
.describe('Whether user verification is currently enabled/verified.'),
5986
},
6087
handler: async (request, response, context) => {
61-
const page = context.getSelectedPage();
62-
// @ts-expect-error _client is internal Puppeteer API
63-
const session = page._client() as CDPSession;
64-
88+
const session = getCDPSession(context);
6589
const {
6690
protocol,
6791
transport,
@@ -70,19 +94,22 @@ export const addVirtualAuthenticator = defineTool({
7094
isUserVerified,
7195
} = request.params;
7296

73-
const result = await session.send('WebAuthn.addVirtualAuthenticator', {
74-
options: {
75-
protocol,
76-
transport,
77-
hasResidentKey: hasResidentKey ?? false,
78-
hasUserVerification: hasUserVerification ?? false,
79-
isUserVerified: isUserVerified ?? false,
80-
},
81-
});
82-
83-
response.appendResponseLine(
84-
`Added virtual authenticator (authenticatorId: ${result.authenticatorId})`,
85-
);
97+
try {
98+
const result = await session.send('WebAuthn.addVirtualAuthenticator', {
99+
options: {
100+
protocol,
101+
transport,
102+
hasResidentKey: hasResidentKey ?? false,
103+
hasUserVerification: hasUserVerification ?? false,
104+
isUserVerified: isUserVerified ?? false,
105+
},
106+
});
107+
response.appendResponseLine(
108+
`Added virtual authenticator (authenticatorId: ${result.authenticatorId})`,
109+
);
110+
} catch (error) {
111+
handleWebAuthnError(error);
112+
}
86113
},
87114
});
88115

@@ -99,17 +126,17 @@ export const removeVirtualAuthenticator = defineTool({
99126
.describe('The ID of the authenticator to remove.'),
100127
},
101128
handler: async (request, response, context) => {
102-
const page = context.getSelectedPage();
103-
// @ts-expect-error _client is internal Puppeteer API
104-
const session = page._client() as CDPSession;
105-
106-
await session.send('WebAuthn.removeVirtualAuthenticator', {
107-
authenticatorId: request.params.authenticatorId,
108-
});
109-
110-
response.appendResponseLine(
111-
`Removed virtual authenticator (authenticatorId: ${request.params.authenticatorId})`,
112-
);
129+
const session = getCDPSession(context);
130+
try {
131+
await session.send('WebAuthn.removeVirtualAuthenticator', {
132+
authenticatorId: request.params.authenticatorId,
133+
});
134+
response.appendResponseLine(
135+
`Removed virtual authenticator (authenticatorId: ${request.params.authenticatorId})`,
136+
);
137+
} catch (error) {
138+
handleWebAuthnError(error);
139+
}
113140
},
114141
});
115142

@@ -126,25 +153,26 @@ export const getCredentials = defineTool({
126153
.describe('The ID of the authenticator to get credentials from.'),
127154
},
128155
handler: async (request, response, context) => {
129-
const page = context.getSelectedPage();
130-
// @ts-expect-error _client is internal Puppeteer API
131-
const session = page._client() as CDPSession;
156+
const session = getCDPSession(context);
157+
try {
158+
const result = await session.send('WebAuthn.getCredentials', {
159+
authenticatorId: request.params.authenticatorId,
160+
});
132161

133-
const result = await session.send('WebAuthn.getCredentials', {
134-
authenticatorId: request.params.authenticatorId,
135-
});
136-
137-
if (result.credentials.length === 0) {
138-
response.appendResponseLine('No credentials registered.');
139-
} else {
140-
response.appendResponseLine(
141-
`Found ${result.credentials.length} credential(s):`,
142-
);
143-
for (const cred of result.credentials) {
162+
if (result.credentials.length === 0) {
163+
response.appendResponseLine('No credentials registered.');
164+
} else {
144165
response.appendResponseLine(
145-
`- credentialId: ${cred.credentialId}, rpId: ${cred.rpId}, signCount: ${cred.signCount}`,
166+
`Found ${result.credentials.length} credential(s):`,
146167
);
168+
for (const cred of result.credentials) {
169+
response.appendResponseLine(
170+
`- credentialId: ${cred.credentialId}, rpId: ${cred.rpId}, signCount: ${cred.signCount}`,
171+
);
172+
}
147173
}
174+
} catch (error) {
175+
handleWebAuthnError(error);
148176
}
149177
},
150178
});
@@ -175,10 +203,7 @@ export const addCredential = defineTool({
175203
signCount: zod.number().int().optional().describe('The signature counter.'),
176204
},
177205
handler: async (request, response, context) => {
178-
const page = context.getSelectedPage();
179-
// @ts-expect-error _client is internal Puppeteer API
180-
const session = page._client() as CDPSession;
181-
206+
const session = getCDPSession(context);
182207
const {
183208
authenticatorId,
184209
credentialId,
@@ -189,21 +214,35 @@ export const addCredential = defineTool({
189214
signCount,
190215
} = request.params;
191216

192-
await session.send('WebAuthn.addCredential', {
193-
authenticatorId,
194-
credential: {
195-
credentialId,
196-
isResidentCredential,
197-
rpId,
198-
privateKey,
199-
userHandle,
200-
signCount: signCount ?? 0,
201-
},
202-
});
203-
204-
response.appendResponseLine(
205-
`Added credential (credentialId: ${credentialId}) to authenticator ${authenticatorId}`,
206-
);
217+
try {
218+
await session.send('WebAuthn.addCredential', {
219+
authenticatorId,
220+
credential: {
221+
credentialId,
222+
isResidentCredential,
223+
rpId,
224+
privateKey,
225+
userHandle,
226+
signCount: signCount ?? 0,
227+
},
228+
});
229+
response.appendResponseLine(
230+
`Added credential (credentialId: ${credentialId}) to authenticator ${authenticatorId}`,
231+
);
232+
} catch (error) {
233+
const message = error instanceof Error ? error.message : String(error);
234+
if (message.includes('User Handle is required')) {
235+
throw new Error(
236+
'Resident credentials require a userHandle. Provide userHandle parameter.',
237+
);
238+
}
239+
if (message.includes('error occurred trying to create')) {
240+
throw new Error(
241+
'Failed to create credential. Ensure privateKey is a valid PKCS#8 EC P-256 key (base64 encoded).',
242+
);
243+
}
244+
handleWebAuthnError(error);
245+
}
207246
},
208247
});
209248

@@ -220,17 +259,17 @@ export const clearCredentials = defineTool({
220259
.describe('The ID of the authenticator to clear credentials from.'),
221260
},
222261
handler: async (request, response, context) => {
223-
const page = context.getSelectedPage();
224-
// @ts-expect-error _client is internal Puppeteer API
225-
const session = page._client() as CDPSession;
226-
227-
await session.send('WebAuthn.clearCredentials', {
228-
authenticatorId: request.params.authenticatorId,
229-
});
230-
231-
response.appendResponseLine(
232-
`Cleared all credentials from authenticator ${request.params.authenticatorId}`,
233-
);
262+
const session = getCDPSession(context);
263+
try {
264+
await session.send('WebAuthn.clearCredentials', {
265+
authenticatorId: request.params.authenticatorId,
266+
});
267+
response.appendResponseLine(
268+
`Cleared all credentials from authenticator ${request.params.authenticatorId}`,
269+
);
270+
} catch (error) {
271+
handleWebAuthnError(error);
272+
}
234273
},
235274
});
236275

@@ -249,17 +288,17 @@ export const setUserVerified = defineTool({
249288
.describe('Whether user verification should succeed.'),
250289
},
251290
handler: async (request, response, context) => {
252-
const page = context.getSelectedPage();
253-
// @ts-expect-error _client is internal Puppeteer API
254-
const session = page._client() as CDPSession;
255-
256-
await session.send('WebAuthn.setUserVerified', {
257-
authenticatorId: request.params.authenticatorId,
258-
isUserVerified: request.params.isUserVerified,
259-
});
260-
261-
response.appendResponseLine(
262-
`Set user verification to ${request.params.isUserVerified} for authenticator ${request.params.authenticatorId}`,
263-
);
291+
const session = getCDPSession(context);
292+
try {
293+
await session.send('WebAuthn.setUserVerified', {
294+
authenticatorId: request.params.authenticatorId,
295+
isUserVerified: request.params.isUserVerified,
296+
});
297+
response.appendResponseLine(
298+
`Set user verification to ${request.params.isUserVerified} for authenticator ${request.params.authenticatorId}`,
299+
);
300+
} catch (error) {
301+
handleWebAuthnError(error);
302+
}
264303
},
265304
});

0 commit comments

Comments
 (0)