electron-webauthn 0.0.17 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -28,11 +28,21 @@ This package provides JavaScript bindings to Apple's AuthenticationServices fram
28
28
 
29
29
  ## Installation
30
30
 
31
+ ### Prerequisites
32
+
33
+ - Node.js / Bun
34
+ - Xcode Command Line Tools (Run `xcode-select --install` to install)
35
+ - `pkg-config` from Homebrew (Run `brew install pkgconf` to install)
36
+
37
+ ### Install using npm, bun, pnpm, or yarn
38
+
31
39
  ```bash
32
40
  npm install electron-webauthn
33
41
  # or
34
42
  bun add electron-webauthn
35
43
  # or
44
+ pnpm add electron-webauthn
45
+ # or
36
46
  yarn add electron-webauthn
37
47
  ```
38
48
 
@@ -44,234 +54,15 @@ yarn add electron-webauthn
44
54
 
45
55
  ## Quick Start
46
56
 
47
- > **💡 Drop-in Compatibility:** Simply plug any `publicKeyOptions` from the standard `navigator.credentials.create()` or `navigator.credentials.get()` APIs directly into these functions. They follow the W3C WebAuthn specification exactly.
48
-
49
- ### Creating a Credential (Registration)
50
-
51
- ```typescript
52
- import { createCredential } from "electron-webauthn";
53
- import { BrowserWindow } from "electron";
54
-
55
- // In your Electron main process or preload script
56
- async function register(window: BrowserWindow, challenge: ArrayBuffer) {
57
- // Get the native window handle from your BrowserWindow
58
- const nativeWindowHandle = window.getNativeWindowHandle();
59
-
60
- // Call createCredential with W3C WebAuthn-compliant options
61
- // You can plug any publicKeyOptions from navigator.credentials.create() here
62
- const result = await createCredential(
63
- {
64
- challenge: challenge,
65
- rp: {
66
- name: "Example App",
67
- id: "example.com",
68
- },
69
- user: {
70
- id: new Uint8Array(16), // Random user ID
71
- name: "user@example.com",
72
- displayName: "User Name",
73
- },
74
- pubKeyCredParams: [
75
- { type: "public-key", alg: -7 }, // ES256
76
- { type: "public-key", alg: -257 }, // RS256
77
- ],
78
- timeout: 60000, // Optional: 60 seconds
79
- attestation: "none", // Optional: "none" | "indirect" | "direct"
80
- authenticatorSelection: {
81
- userVerification: "preferred", // Optional: "preferred" | "required" | "discouraged"
82
- residentKey: "preferred", // Optional: "discouraged" | "preferred" | "required"
83
- },
84
- },
85
- {
86
- currentOrigin: "https://example.com",
87
- topFrameOrigin: "https://example.com",
88
- nativeWindowHandle: nativeWindowHandle,
89
- }
90
- );
91
-
92
- if (result.success) {
93
- console.log("Registration successful!");
94
- console.log("Credential ID:", result.data.credentialId);
95
- console.log("Public Key:", result.data.publicKey);
96
- // Result contains base64url-encoded strings ready to send to server
97
- } else {
98
- console.error("Registration failed:", result.error);
99
- }
100
- }
101
- ```
102
-
103
- ### Authenticating with a Credential
104
-
105
- ```typescript
106
- import { getCredential } from "electron-webauthn";
107
- import { BrowserWindow } from "electron";
108
-
109
- // In your Electron main process or preload script
110
- async function authenticate(window: BrowserWindow, challenge: ArrayBuffer) {
111
- // Get the native window handle from your BrowserWindow
112
- const nativeWindowHandle = window.getNativeWindowHandle();
113
-
114
- // Call getCredential with W3C WebAuthn-compliant options
115
- // You can plug any publicKeyOptions from navigator.credentials.get() here
116
- const result = await getCredential(
117
- {
118
- challenge: challenge,
119
- rpId: "example.com",
120
- timeout: 60000, // Optional: 60 seconds
121
- userVerification: "preferred", // Optional: "preferred" | "required" | "discouraged"
122
- allowCredentials: [], // Optional: restrict to specific credentials
123
- },
124
- {
125
- currentOrigin: "https://example.com",
126
- topFrameOrigin: "https://example.com", // Use for iframe support
127
- nativeWindowHandle: nativeWindowHandle,
128
- }
129
- );
130
-
131
- if (result.success) {
132
- console.log("Authentication successful!");
133
- console.log("Credential ID:", result.data.credentialId);
134
- console.log("Signature:", result.data.signature);
135
- // Result contains base64url-encoded strings ready to send to server
136
- } else {
137
- console.error("Authentication failed:", result.error);
138
- }
139
- }
140
- ```
57
+ See the [Quick Start Guide](./docs/quick-start.md) for detailed examples on credential creation and authentication.
141
58
 
142
59
  ## API Reference
143
60
 
144
- ### `createCredential(publicKeyOptions, additionalOptions)`
145
-
146
- Creates and registers a new WebAuthn credential using available platform and cross-platform authenticators.
147
-
148
- **Note:** You can plug any `publicKeyOptions` from the standard `navigator.credentials.create({ publicKey: ... })` directly into this function.
149
-
150
- #### Parameters
151
-
152
- ##### `publicKeyOptions: PublicKeyCredentialCreationOptions`
153
-
154
- Standard W3C WebAuthn credential creation options. See the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredentialCreationOptions) for more details.
155
-
156
- ##### `additionalOptions: WebauthnCreateRequestOptions`
157
-
158
- Additional options specific to the Electron environment:
159
-
160
- - **`currentOrigin: string`** (required) - The origin of the requesting document (e.g., "https://example.com")
161
- - **`topFrameOrigin: string | undefined`** - The origin of the top frame (for iframe support). Set to `currentOrigin` if not in an iframe
162
- - **`nativeWindowHandle: Buffer`** (required) - Native window handle from `BrowserWindow.getNativeWindowHandle()`, or a pointer to a NSView object
163
- - **`isPublicSuffix?: (domain: string) => boolean`** - Optional function to check if a domain is a public suffix (e.g., "com", "co.uk"). Strongly recommended for security
164
-
165
- #### Returns
166
-
167
- `Promise<CreateCredentialResult>` - Resolves with the registration result (that you can transform into a `PublicKeyCredential` object) or error
168
-
169
- #### Result Types
170
-
171
- ```typescript
172
- type CreateCredentialResult =
173
- | CreateCredentialSuccessResult
174
- | CreateCredentialErrorResult;
175
-
176
- interface CreateCredentialSuccessResult {
177
- success: true;
178
- data: {
179
- credentialId: string; // Base64url-encoded credential ID
180
- clientDataJSON: string; // Base64url-encoded client data
181
- attestationObject: string; // Base64url-encoded attestation object
182
- authData: string; // Base64url-encoded authenticator data
183
- publicKey: string; // Base64url-encoded public key (COSE format)
184
- publicKeyAlgorithm: number; // COSE algorithm identifier (e.g., -7 for ES256)
185
- transports: string[]; // Available transports (e.g., ["internal", "usb"])
186
- extensions: {
187
- credProps?: {
188
- rk: boolean; // True if credential is a resident key
189
- };
190
- prf?: {
191
- enabled?: boolean; // True if PRF is supported
192
- results?: {
193
- first?: string; // Base64url-encoded PRF output
194
- second?: string; // Base64url-encoded PRF output (if provided)
195
- };
196
- };
197
- largeBlob?: {
198
- supported?: boolean; // True if large blob is supported
199
- };
200
- };
201
- };
202
- }
203
-
204
- interface CreateCredentialErrorResult {
205
- success: false;
206
- error:
207
- | "TypeError"
208
- | "AbortError"
209
- | "NotAllowedError"
210
- | "SecurityError"
211
- | "InvalidStateError";
212
- }
213
- ```
214
-
215
- ### `getCredential(publicKeyOptions, additionalOptions)`
61
+ See the [API Reference](./docs/api-reference.md) for detailed documentation on all available functions and types.
216
62
 
217
- Performs a WebAuthn assertion (authentication) using available platform and cross-platform authenticators.
63
+ ## Advanced Examples
218
64
 
219
- **Note:** You can plug any `publicKeyOptions` from the standard `navigator.credentials.get({ publicKey: ... })` directly into this function.
220
-
221
- #### Parameters
222
-
223
- ##### `publicKeyOptions: PublicKeyCredentialRequestOptions`
224
-
225
- Standard W3C WebAuthn credential request options. See the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredentialRequestOptions) for more details.
226
-
227
- ##### `additionalOptions: WebauthnGetRequestOptions`
228
-
229
- Additional options specific to the Electron environment:
230
-
231
- - **`currentOrigin: string`** (required) - The origin of the requesting document (e.g., "https://example.com")
232
- - **`topFrameOrigin: string | undefined`** - The origin of the top frame (for iframe support). Set to `currentOrigin` if not in an iframe
233
- - **`nativeWindowHandle: Buffer`** (required) - Native window handle from `BrowserWindow.getNativeWindowHandle()`, or a pointer to a NSView object
234
- - **`isPublicSuffix?: (domain: string) => boolean`** - Optional function to check if a domain is a public suffix (e.g., "com", "co.uk"). Strongly recommended for security. Use a library like `tldts` for implementation
235
-
236
- #### Returns
237
-
238
- `Promise<GetCredentialResult>` - Resolves with the assertion result (that you can transform into a `PublicKeyCredential` object) or error
239
-
240
- #### Result Types
241
-
242
- ```typescript
243
- type GetCredentialResult =
244
- | GetCredentialSuccessResult
245
- | GetCredentialErrorResult;
246
-
247
- interface GetCredentialSuccessResult {
248
- success: true;
249
- data: {
250
- credentialId: string; // Base64url-encoded credential ID
251
- clientDataJSON: string; // Base64url-encoded client data
252
- authenticatorData: string; // Base64url-encoded authenticator data
253
- signature: string; // Base64url-encoded signature
254
- userHandle: string; // Base64url-encoded user handle
255
- extensions?: {
256
- prf?: {
257
- results?: {
258
- first: string; // Base64url-encoded PRF output
259
- second?: string; // Base64url-encoded PRF output (if provided)
260
- };
261
- };
262
- largeBlob?: {
263
- blob?: string; // Base64url-encoded blob data (if read)
264
- written?: boolean; // True if write succeeded (if write was requested)
265
- };
266
- };
267
- };
268
- }
269
-
270
- interface GetCredentialErrorResult {
271
- success: false;
272
- error: "TypeError" | "AbortError" | "NotAllowedError" | "SecurityError";
273
- }
274
- ```
65
+ See the [Advanced Examples](./docs/advanced-examples.md) for detailed examples on how to use the library with more complex use cases.
275
66
 
276
67
  ## Architecture
277
68
 
@@ -283,417 +74,6 @@ This library implements the W3C WebAuthn standard using Apple's native Authentic
283
74
  - Properly manages WebAuthn extensions (PRF, Large Blob)
284
75
  - Returns base64url-encoded values ready to send to your server for verification
285
76
 
286
- ## Usage Examples
287
-
288
- ### Basic Registration
289
-
290
- ```typescript
291
- import { createCredential } from "electron-webauthn";
292
- import { app, BrowserWindow } from "electron";
293
-
294
- let mainWindow: BrowserWindow;
295
-
296
- app.on("ready", () => {
297
- mainWindow = new BrowserWindow({
298
- width: 800,
299
- height: 600,
300
- webPreferences: {
301
- nodeIntegration: false,
302
- contextIsolation: true,
303
- preload: "./preload.js",
304
- },
305
- });
306
- mainWindow.loadURL("https://myapp.com");
307
- });
308
-
309
- // In your preload script or main process
310
- export async function registerUser(
311
- challenge: ArrayBuffer,
312
- userId: ArrayBuffer,
313
- userName: string,
314
- userDisplayName: string
315
- ) {
316
- const nativeHandle = mainWindow.getNativeWindowHandle();
317
-
318
- const result = await createCredential(
319
- {
320
- challenge: challenge,
321
- rp: {
322
- name: "My App",
323
- id: "myapp.com",
324
- },
325
- user: {
326
- id: userId,
327
- name: userName,
328
- displayName: userDisplayName,
329
- },
330
- pubKeyCredParams: [
331
- { type: "public-key", alg: -7 }, // ES256
332
- { type: "public-key", alg: -257 }, // RS256
333
- ],
334
- authenticatorSelection: {
335
- userVerification: "preferred",
336
- },
337
- },
338
- {
339
- currentOrigin: "https://myapp.com",
340
- topFrameOrigin: "https://myapp.com",
341
- nativeWindowHandle: nativeHandle,
342
- }
343
- );
344
-
345
- return result;
346
- }
347
- ```
348
-
349
- ### Basic Authentication
350
-
351
- ```typescript
352
- import { getCredential } from "electron-webauthn";
353
- import { app, BrowserWindow } from "electron";
354
-
355
- let mainWindow: BrowserWindow;
356
-
357
- app.on("ready", () => {
358
- mainWindow = new BrowserWindow({
359
- width: 800,
360
- height: 600,
361
- webPreferences: {
362
- nodeIntegration: false,
363
- contextIsolation: true,
364
- preload: "./preload.js",
365
- },
366
- });
367
- mainWindow.loadURL("https://myapp.com");
368
- });
369
-
370
- // In your preload script or main process
371
- export async function authenticateUser(challenge: ArrayBuffer) {
372
- const nativeHandle = mainWindow.getNativeWindowHandle();
373
-
374
- const result = await getCredential(
375
- {
376
- challenge: challenge,
377
- rpId: "myapp.com",
378
- userVerification: "preferred",
379
- },
380
- {
381
- currentOrigin: "https://myapp.com",
382
- topFrameOrigin: "https://myapp.com",
383
- nativeWindowHandle: nativeHandle,
384
- }
385
- );
386
-
387
- return result;
388
- }
389
- ```
390
-
391
- ### Restricting to Specific Credentials
392
-
393
- ```typescript
394
- // Only allow specific registered credentials
395
- const result = await getCredential(
396
- {
397
- challenge: challenge,
398
- rpId: "myapp.com",
399
- allowCredentials: [
400
- { type: "public-key", id: credentialId1 },
401
- { type: "public-key", id: credentialId2 },
402
- ],
403
- },
404
- {
405
- currentOrigin: "https://myapp.com",
406
- topFrameOrigin: "https://myapp.com",
407
- nativeWindowHandle: nativeHandle,
408
- }
409
- );
410
- ```
411
-
412
- ### With Required User Verification
413
-
414
- ```typescript
415
- // Require user verification (e.g., for sensitive operations)
416
- const result = await getCredential(
417
- {
418
- challenge: challenge,
419
- rpId: "myapp.com",
420
- userVerification: "required", // Force biometric or PIN
421
- },
422
- {
423
- currentOrigin: "https://myapp.com",
424
- topFrameOrigin: "https://myapp.com",
425
- nativeWindowHandle: nativeHandle,
426
- }
427
- );
428
- ```
429
-
430
- ### Creating Resident Keys (Discoverable Credentials)
431
-
432
- ```typescript
433
- // Create a discoverable credential that can be used without specifying allowCredentials
434
- const result = await createCredential(
435
- {
436
- challenge: challenge,
437
- rp: { name: "My App", id: "myapp.com" },
438
- user: {
439
- id: userId,
440
- name: userName,
441
- displayName: userDisplayName,
442
- },
443
- pubKeyCredParams: [
444
- { type: "public-key", alg: -7 },
445
- { type: "public-key", alg: -257 },
446
- ],
447
- authenticatorSelection: {
448
- residentKey: "required", // Require resident key
449
- userVerification: "required", // Usually combined with resident keys
450
- },
451
- },
452
- {
453
- currentOrigin: "https://myapp.com",
454
- topFrameOrigin: "https://myapp.com",
455
- nativeWindowHandle: nativeHandle,
456
- }
457
- );
458
- ```
459
-
460
- ### Preventing Duplicate Registrations
461
-
462
- ```typescript
463
- // Prevent user from registering the same authenticator multiple times
464
- const result = await createCredential(
465
- {
466
- challenge: challenge,
467
- rp: { name: "My App", id: "myapp.com" },
468
- user: {
469
- id: userId,
470
- name: userName,
471
- displayName: userDisplayName,
472
- },
473
- pubKeyCredParams: [
474
- { type: "public-key", alg: -7 },
475
- { type: "public-key", alg: -257 },
476
- ],
477
- excludeCredentials: [
478
- // List of credentials already registered for this user
479
- { type: "public-key", id: existingCredentialId1 },
480
- { type: "public-key", id: existingCredentialId2 },
481
- ],
482
- },
483
- {
484
- currentOrigin: "https://myapp.com",
485
- topFrameOrigin: "https://myapp.com",
486
- nativeWindowHandle: nativeHandle,
487
- }
488
- );
489
-
490
- // If user tries to use an excluded authenticator, you'll get:
491
- // { success: false, error: "InvalidStateError" }
492
- ```
493
-
494
- ### Creating Credentials with PRF Extension
495
-
496
- ```typescript
497
- // Register a credential with PRF support and immediately evaluate it
498
- const prfSalt = crypto.getRandomValues(new Uint8Array(32));
499
-
500
- const result = await createCredential(
501
- {
502
- challenge: challenge,
503
- rp: { name: "My App", id: "myapp.com" },
504
- user: {
505
- id: userId,
506
- name: userName,
507
- displayName: userDisplayName,
508
- },
509
- pubKeyCredParams: [
510
- { type: "public-key", alg: -7 },
511
- { type: "public-key", alg: -257 },
512
- ],
513
- extensions: {
514
- prf: {
515
- eval: {
516
- first: prfSalt,
517
- },
518
- },
519
- },
520
- },
521
- {
522
- currentOrigin: "https://myapp.com",
523
- topFrameOrigin: "https://myapp.com",
524
- nativeWindowHandle: nativeHandle,
525
- }
526
- );
527
-
528
- if (result.success && result.data.extensions?.prf?.results?.first) {
529
- console.log(
530
- "PRF is supported and evaluated:",
531
- result.data.extensions.prf.results.first
532
- );
533
- // Use this PRF output as an encryption key
534
- }
535
- ```
536
-
537
- ### Using PRF Extension (Authentication)
538
-
539
- The PRF (Pseudo-Random Function) extension allows you to derive cryptographic secrets from credentials:
540
-
541
- ```typescript
542
- // Generate a random salt for PRF
543
- const prfSalt = crypto.getRandomValues(new Uint8Array(32));
544
-
545
- const result = await getCredential(
546
- {
547
- challenge: challenge,
548
- rpId: "myapp.com",
549
- extensions: {
550
- prf: {
551
- eval: {
552
- first: prfSalt,
553
- // second: optionalSecondSalt, // Optional second output
554
- },
555
- },
556
- },
557
- },
558
- {
559
- currentOrigin: "https://myapp.com",
560
- topFrameOrigin: "https://myapp.com",
561
- nativeWindowHandle: nativeHandle,
562
- }
563
- );
564
-
565
- if (result.success && result.data.extensions?.prf?.results) {
566
- const prfOutput = result.data.extensions.prf.results.first;
567
- // Use prfOutput as a cryptographic key for encryption, etc.
568
- console.log("PRF output:", prfOutput);
569
- }
570
- ```
571
-
572
- ### Using PRF with Per-Credential Evaluation
573
-
574
- ```typescript
575
- import { bufferToBase64Url } from "./helpers"; // You'll need to implement this
576
-
577
- const result = await getCredential(
578
- {
579
- challenge: challenge,
580
- rpId: "myapp.com",
581
- allowCredentials: [
582
- { type: "public-key", id: credentialId1 },
583
- { type: "public-key", id: credentialId2 },
584
- ],
585
- extensions: {
586
- prf: {
587
- evalByCredential: {
588
- [bufferToBase64Url(credentialId1)]: {
589
- first: new Uint8Array(32), // Different salt for credential 1
590
- },
591
- [bufferToBase64Url(credentialId2)]: {
592
- first: new Uint8Array(32), // Different salt for credential 2
593
- },
594
- },
595
- },
596
- },
597
- },
598
- {
599
- currentOrigin: "https://myapp.com",
600
- topFrameOrigin: "https://myapp.com",
601
- nativeWindowHandle: nativeHandle,
602
- }
603
- );
604
- ```
605
-
606
- ### Reading and Writing Large Blobs
607
-
608
- ```typescript
609
- // Reading a large blob
610
- const readResult = await getCredential(
611
- {
612
- challenge: challenge,
613
- rpId: "myapp.com",
614
- extensions: {
615
- largeBlob: {
616
- read: true,
617
- },
618
- },
619
- },
620
- {
621
- currentOrigin: "https://myapp.com",
622
- topFrameOrigin: "https://myapp.com",
623
- nativeWindowHandle: nativeHandle,
624
- }
625
- );
626
-
627
- if (readResult.success && readResult.data.extensions?.largeBlob?.blob) {
628
- console.log("Large blob data:", readResult.data.extensions.largeBlob.blob);
629
- }
630
-
631
- // Writing a large blob
632
- const dataToWrite = new TextEncoder().encode("Secret data");
633
- const writeResult = await getCredential(
634
- {
635
- challenge: challenge,
636
- rpId: "myapp.com",
637
- extensions: {
638
- largeBlob: {
639
- write: dataToWrite,
640
- },
641
- },
642
- },
643
- {
644
- currentOrigin: "https://myapp.com",
645
- topFrameOrigin: "https://myapp.com",
646
- nativeWindowHandle: nativeHandle,
647
- }
648
- );
649
-
650
- if (writeResult.success && writeResult.data.extensions?.largeBlob?.written) {
651
- console.log("Large blob written successfully");
652
- }
653
- ```
654
-
655
- ### With Public Suffix List Validation
656
-
657
- For enhanced security, use a public suffix list library like `tldts`:
658
-
659
- ```typescript
660
- import { getPublicSuffix } from "tldts";
661
-
662
- const result = await getCredential(
663
- {
664
- challenge: challenge,
665
- rpId: "myapp.com",
666
- },
667
- {
668
- currentOrigin: "https://myapp.com",
669
- topFrameOrigin: "https://myapp.com",
670
- nativeWindowHandle: nativeHandle,
671
- isPublicSuffix: (domain) => {
672
- const suffix = getPublicSuffix(domain);
673
- return suffix === domain;
674
- },
675
- }
676
- );
677
- ```
678
-
679
- ### Supporting Cross-Origin Iframes
680
-
681
- If your app loads WebAuthn requests from an iframe:
682
-
683
- ```typescript
684
- const result = await getCredential(
685
- {
686
- challenge: challenge,
687
- rpId: "myapp.com",
688
- },
689
- {
690
- currentOrigin: "https://auth.myapp.com", // The iframe's origin
691
- topFrameOrigin: "https://myapp.com", // The parent page's origin
692
- nativeWindowHandle: nativeHandle,
693
- }
694
- );
695
- ```
696
-
697
77
  ## Error Handling
698
78
 
699
79
  Both `createCredential` and `getCredential` functions return a result object with a `success` field. Always check this field:
@@ -778,54 +158,9 @@ console.log("Credential ID:", result.data.credentialId);
778
158
  - ❌ Conditional UI (autofill/conditional mediation)
779
159
  - ❌ Other WebAuthn extensions (credProtect, minPinLength, hmac-secret, etc.)
780
160
 
781
- ## Best Practices
782
-
783
161
  ### Security Recommendations
784
162
 
785
- 1. **Always use HTTPS origins** in production (unless testing on `localhost`)
786
- 2. **Implement public suffix list validation** using `tldts` or similar library
787
- 3. **Validate rpId carefully** - it should match your domain
788
- 4. **Generate strong challenges** - use at least 32 random bytes from a CSPRNG
789
- 5. **Verify assertions on your server** - never trust client-side validation alone
790
- 6. **Store credential public keys securely** - needed for signature verification
791
- 7. **Implement proper timeout handling** - don't leave prompts open indefinitely
792
-
793
- ### Performance Tips
794
-
795
- 1. **Cache native window handles** - no need to get them on every call
796
- 2. **Reuse challenge buffers** when possible
797
- 3. **Set appropriate timeouts** - shorter for better UX, longer for hardware keys
798
- 4. **Handle user cancellation gracefully** - don't retry automatically
799
-
800
- ### TypeScript Types
801
-
802
- This library exports all necessary TypeScript types. Import them for type safety:
803
-
804
- ```typescript
805
- import type {
806
- // Registration types
807
- CreateCredentialResult,
808
- CreateCredentialSuccessData,
809
- PublicKeyCredentialCreationOptions,
810
-
811
- // Authentication types
812
- GetCredentialResult,
813
- GetCredentialSuccessData,
814
- PublicKeyCredentialRequestOptions,
815
-
816
- // Shared types
817
- AuthenticationExtensionsClientInputs,
818
- PRFInput,
819
- } from "electron-webauthn";
820
- ```
821
-
822
- ## Debugging
823
-
824
- Enable detailed logging by checking the console output. The library logs warnings for:
825
-
826
- - PRF extension enabled but no input values provided
827
- - Large blob write enabled but no data provided
828
- - Authorization errors with native error messages
163
+ - **Implement public suffix list validation** using `tldts` or similar library
829
164
 
830
165
  ## Known Limitations
831
166
 
@@ -4,7 +4,6 @@ import { NSArrayFromObjects } from "../objc/foundation/nsarray.js";
4
4
  import { NSStringFromString } from "../objc/foundation/nsstring.js";
5
5
  import { createASCPublicKeyCredentialDescriptor } from "../objc/authentication-services/as-authorization-c-public-key-credential-descriptor.js";
6
6
  import { NSNumberFromInteger } from "../objc/foundation/nsinteger.js";
7
- import { isNumber, isObject } from "../helpers/validation.js";
8
7
  const createControllerState = new Map();
9
8
  function getObjectPointerString(self) {
10
9
  return getPointer(self).toBase64();
@@ -32,7 +31,13 @@ export const WebauthnCreateController = NobjcClass.define({
32
31
  const context = NobjcClass.super(self, "_requestContextWithRequests$error$", requests, outError);
33
32
  const selfPointer = getObjectPointerString(self);
34
33
  if (context && createControllerState.has(selfPointer)) {
35
- const registrationOptions = context.platformKeyCredentialCreationOptions();
34
+ let isSecurityKey = false;
35
+ let registrationOptions = context.platformKeyCredentialCreationOptions();
36
+ if (!registrationOptions) {
37
+ registrationOptions =
38
+ context.securityKeyCredentialCreationOptions();
39
+ isSecurityKey = true;
40
+ }
36
41
  const [clientDataHash, pubKeyCredParams, residentKeyRequired, excludeCredentials,] = createControllerState.get(selfPointer);
37
42
  registrationOptions.setClientDataHash$(NSDataFromBuffer(clientDataHash));
38
43
  registrationOptions.setChallenge$(null);
@@ -45,7 +50,9 @@ export const WebauthnCreateController = NobjcClass.define({
45
50
  if (supportedAlgos.length > 0) {
46
51
  registrationOptions.setSupportedAlgorithmIdentifiers$(NSArrayFromObjects(supportedAlgos));
47
52
  }
48
- registrationOptions.setShouldRequireResidentKey$(residentKeyRequired);
53
+ if (!isSecurityKey) {
54
+ registrationOptions.setShouldRequireResidentKey$(residentKeyRequired);
55
+ }
49
56
  const excludeList = [];
50
57
  for (const cred of excludeCredentials) {
51
58
  const transports = [];
@@ -1,3 +1,4 @@
1
+ export type CreateCredentialErrorCodes = "TypeError" | "AbortError" | "NotAllowedError" | "SecurityError" | "InvalidStateError";
1
2
  export interface CreateCredentialSuccessData {
2
3
  credentialId: string;
3
4
  clientDataJSON: string;
@@ -34,7 +35,8 @@ interface CreateCredentialSuccessResult {
34
35
  }
35
36
  interface CreateCredentialErrorResult {
36
37
  success: false;
37
- error: "TypeError" | "AbortError" | "NotAllowedError" | "SecurityError" | "InvalidStateError";
38
+ error: CreateCredentialErrorCodes;
39
+ errorObject?: Error;
38
40
  }
39
41
  export type CreateCredentialResult = CreateCredentialSuccessResult | CreateCredentialErrorResult;
40
42
  export declare function createCredential(publicKeyOptions: PublicKeyCredentialCreationOptions | undefined, additionalOptions: WebauthnCreateRequestOptions): Promise<CreateCredentialResult>;
@@ -108,6 +108,16 @@ export async function createCredential(publicKeyOptions, additionalOptions) {
108
108
  return { success: false, error: "TypeError" };
109
109
  }
110
110
  }
111
+ if (supportedAlgorithmIdentifiers.length === 0) {
112
+ supportedAlgorithmIdentifiers.push({
113
+ type: "public-key",
114
+ algorithm: -7,
115
+ });
116
+ supportedAlgorithmIdentifiers.push({
117
+ type: "public-key",
118
+ algorithm: -257,
119
+ });
120
+ }
111
121
  const excludeCredentials = [];
112
122
  if (publicKeyOptions.excludeCredentials &&
113
123
  Array.isArray(publicKeyOptions.excludeCredentials)) {
@@ -128,6 +138,7 @@ export async function createCredential(publicKeyOptions, additionalOptions) {
128
138
  const { extensions, largeBlobSupport, prf } = getExtensionsConfiguration(publicKeyOptions.extensions);
129
139
  let residentKeyRequired = false;
130
140
  let userVerificationPreference = "preferred";
141
+ let preferredAuthenticatorAttachment = "all";
131
142
  if (publicKeyOptions.authenticatorSelection) {
132
143
  if (publicKeyOptions.authenticatorSelection.residentKey === "required") {
133
144
  residentKeyRequired = true;
@@ -145,6 +156,13 @@ export async function createCredential(publicKeyOptions, additionalOptions) {
145
156
  else {
146
157
  userVerificationPreference = "preferred";
147
158
  }
159
+ const attachment = publicKeyOptions.authenticatorSelection.authenticatorAttachment;
160
+ if (attachment === "cross-platform") {
161
+ preferredAuthenticatorAttachment = "cross-platform";
162
+ }
163
+ else if (attachment === "platform") {
164
+ preferredAuthenticatorAttachment = "platform";
165
+ }
148
166
  }
149
167
  const { currentOrigin, topFrameOrigin, isPublicSuffix, nativeWindowHandle } = additionalOptions;
150
168
  const isRpIdAllowed = isRpIdAllowedForOrigin(currentOrigin, rpId, {
@@ -153,13 +171,13 @@ export async function createCredential(publicKeyOptions, additionalOptions) {
153
171
  if (!isRpIdAllowed.ok) {
154
172
  return { success: false, error: "NotAllowedError" };
155
173
  }
156
- const result = await createCredentialInternal(rpId, challenge, userName, userID, nativeWindowHandle, currentOrigin, extensions, attestationPreference, supportedAlgorithmIdentifiers, excludeCredentials, residentKeyRequired, userVerificationPreference, {
174
+ let errorResult = null;
175
+ const result = await createCredentialInternal(rpId, challenge, userName, userID, nativeWindowHandle, currentOrigin, timeout, extensions, attestationPreference, supportedAlgorithmIdentifiers, excludeCredentials, residentKeyRequired, preferredAuthenticatorAttachment, userVerificationPreference, {
157
176
  topFrameOrigin,
158
177
  largeBlobSupport,
159
178
  prf,
160
179
  }).catch((error) => {
161
- console.error("Error creating credential", error);
162
- console.log("error.message", error.message);
180
+ errorResult = error;
163
181
  if (error.message.includes("(com.apple.AuthenticationServices.AuthorizationError error 1006.)")) {
164
182
  return "InvalidStateError";
165
183
  }
@@ -169,7 +187,7 @@ export async function createCredential(publicKeyOptions, additionalOptions) {
169
187
  return null;
170
188
  });
171
189
  if (typeof result === "string") {
172
- return { success: false, error: result };
190
+ return { success: false, error: result, errorObject: errorResult };
173
191
  }
174
192
  const data = {
175
193
  credentialId: bufferToBase64Url(result.credentialId),
@@ -1,5 +1,6 @@
1
1
  import { type PRFInput } from "../helpers/prf.js";
2
2
  import { type PublicKeyCredentialParams } from "./authorization-controller.js";
3
+ export type AuthenticatorAttachmentWithExtra = AuthenticatorAttachment | "all";
3
4
  export interface CreateCredentialResult {
4
5
  credentialId: Buffer;
5
6
  clientDataJSON: Buffer;
@@ -30,5 +31,5 @@ export interface ExcludeCredential {
30
31
  id: Buffer;
31
32
  transports?: string[];
32
33
  }
33
- declare function createCredentialInternal(rpid: string, challenge: Buffer, username: string, userID: Buffer, nativeWindowHandle: Buffer, origin: string, enabledExtensions: CredentialCreationExtensions[], attestation: CredentialAttestationPreference, supportedAlgorithmIdentifiers: PublicKeyCredentialParams[], excludeCredentials: ExcludeCredential[], residentKeyRequired?: boolean, userVerification?: CredentialUserVerificationPreference, additionalOptions?: CreateCredentialAdditionalOptions): Promise<CreateCredentialResult>;
34
+ declare function createCredentialInternal(rpid: string, challenge: Buffer, username: string, userID: Buffer, nativeWindowHandle: Buffer, origin: string, timeout: number, enabledExtensions: CredentialCreationExtensions[], attestation: CredentialAttestationPreference, supportedAlgorithmIdentifiers: PublicKeyCredentialParams[], excludeCredentials: ExcludeCredential[], residentKeyRequired?: boolean, preferredAuthenticatorAttachment?: AuthenticatorAttachmentWithExtra, userVerification?: CredentialUserVerificationPreference, additionalOptions?: CreateCredentialAdditionalOptions): Promise<CreateCredentialResult>;
34
35
  export { createCredentialInternal };
@@ -17,16 +17,11 @@ import { NSStringFromString } from "../objc/foundation/nsstring.js";
17
17
  import { removeControllerState, setControllerState, WebauthnCreateController, } from "./authorization-controller.js";
18
18
  import { parseAttestationObject } from "@oslojs/webauthn";
19
19
  import { ASAuthorizationPublicKeyCredentialAttachment } from "../objc/authentication-services/enums/as-authorization-public-key-credential-attachment.js";
20
+ import { createSecurityKeyPublicKeyCredentialProvider } from "../objc/authentication-services/as-authorization-security-key-public-key-credential-provider.js";
21
+ import { createASAuthorizationPublicKeyCredentialParameters, } from "../objc/authentication-services/as-authorization-public-key-credential-parameters.js";
20
22
  const VALID_EXTENSIONS = ["largeBlob", "prf"];
21
- function createCredentialInternal(rpid, challenge, username, userID, nativeWindowHandle, origin, enabledExtensions, attestation = "none", supportedAlgorithmIdentifiers = [], excludeCredentials, residentKeyRequired = false, userVerification = "preferred", additionalOptions = {}) {
22
- const { promise, resolve, reject } = PromiseWithResolvers();
23
- const NS_rpID = NSStringFromString(rpid);
24
- const NS_challenge = NSDataFromBuffer(challenge);
25
- const NS_username = NSStringFromString(username);
26
- const NS_userID = NSDataFromBuffer(userID);
27
- const platformProvider = createPlatformPublicKeyCredentialProvider(NS_rpID);
28
- const platformKeyRequest = platformProvider.createCredentialRegistrationRequestWithChallenge$name$userID$(NS_challenge, NS_username, NS_userID);
29
- if (enabledExtensions.includes("largeBlob")) {
23
+ function setupPublicKeyCredentialRegistrationRequest(type, keyRequest, attestation, enabledExtensions, userVerification, pubKeyCredParams, additionalOptions) {
24
+ if (type === "platform" && enabledExtensions.includes("largeBlob")) {
30
25
  let supportMode;
31
26
  const largeBlobSupport = additionalOptions.largeBlobSupport;
32
27
  if (largeBlobSupport === "required") {
@@ -42,11 +37,25 @@ function createCredentialInternal(rpid, challenge, username, userID, nativeWindo
42
37
  }
43
38
  if (supportMode) {
44
39
  const largeBlobInput = createASAuthorizationPublicKeyCredentialLargeBlobRegistrationInput(supportMode);
45
- platformKeyRequest.setLargeBlob$(largeBlobInput);
40
+ keyRequest.setLargeBlob$(largeBlobInput);
46
41
  }
47
42
  }
48
43
  let attestationPreference = ASAuthorizationPublicKeyCredentialAttestationKind.None;
49
- platformKeyRequest.setAttestationPreference$(NSStringFromString(attestationPreference));
44
+ if (type === "security-key") {
45
+ if (attestation === "direct") {
46
+ attestationPreference =
47
+ ASAuthorizationPublicKeyCredentialAttestationKind.Direct;
48
+ }
49
+ else if (attestation === "enterprise") {
50
+ attestationPreference =
51
+ ASAuthorizationPublicKeyCredentialAttestationKind.Enterprise;
52
+ }
53
+ else if (attestation === "indirect") {
54
+ attestationPreference =
55
+ ASAuthorizationPublicKeyCredentialAttestationKind.Indirect;
56
+ }
57
+ }
58
+ keyRequest.setAttestationPreference$(NSStringFromString(attestationPreference));
50
59
  let userVerificationPreference = ASAuthorizationPublicKeyCredentialUserVerificationPreference.Preferred;
51
60
  if (userVerification === "required") {
52
61
  userVerificationPreference =
@@ -56,28 +65,67 @@ function createCredentialInternal(rpid, challenge, username, userID, nativeWindo
56
65
  userVerificationPreference =
57
66
  ASAuthorizationPublicKeyCredentialUserVerificationPreference.Discouraged;
58
67
  }
59
- platformKeyRequest.setUserVerificationPreference$(NSStringFromString(userVerificationPreference));
60
- if (additionalOptions.userDisplayName) {
68
+ keyRequest.setUserVerificationPreference$(NSStringFromString(userVerificationPreference));
69
+ if (type === "platform" && additionalOptions.userDisplayName) {
61
70
  const userDisplayName = NSStringFromString(additionalOptions.userDisplayName);
62
- platformKeyRequest.setDisplayName$(userDisplayName);
71
+ keyRequest.setDisplayName$(userDisplayName);
63
72
  }
64
- if (enabledExtensions.includes("prf")) {
73
+ if (type === "security-key") {
74
+ const credentialParameters = [];
75
+ for (const param of pubKeyCredParams) {
76
+ if (param.type === "public-key") {
77
+ credentialParameters.push(createASAuthorizationPublicKeyCredentialParameters(param.algorithm));
78
+ }
79
+ }
80
+ const nsCredentialParameters = NSArrayFromObjects(credentialParameters);
81
+ keyRequest.setCredentialParameters$(nsCredentialParameters);
82
+ }
83
+ if (type === "platform" && enabledExtensions.includes("prf")) {
65
84
  if (additionalOptions.prf) {
66
85
  const inputValues = createPRFInput(additionalOptions.prf);
67
86
  const prfInput = createASAuthorizationPublicKeyCredentialPRFRegistrationInput(inputValues);
68
- platformKeyRequest.setPrf$(prfInput);
87
+ keyRequest.setPrf$(prfInput);
69
88
  }
70
89
  else {
71
- platformKeyRequest.setPrf$(ASAuthorizationPublicKeyCredentialPRFRegistrationInput.checkForSupport());
90
+ keyRequest.setPrf$(ASAuthorizationPublicKeyCredentialPRFRegistrationInput.checkForSupport());
72
91
  }
73
92
  }
74
- const requestsArray = NSArrayFromObjects([platformKeyRequest]);
93
+ }
94
+ function createCredentialInternal(rpid, challenge, username, userID, nativeWindowHandle, origin, timeout, enabledExtensions, attestation = "none", supportedAlgorithmIdentifiers = [], excludeCredentials, residentKeyRequired = false, preferredAuthenticatorAttachment = "all", userVerification = "preferred", additionalOptions = {}) {
95
+ const { promise, resolve, reject } = PromiseWithResolvers();
96
+ const NS_rpID = NSStringFromString(rpid);
97
+ const NS_challenge = NSDataFromBuffer(challenge);
98
+ const NS_username = NSStringFromString(username);
99
+ const NS_userID = NSDataFromBuffer(userID);
100
+ const requestArrayInput = [];
101
+ if (preferredAuthenticatorAttachment === "all" ||
102
+ preferredAuthenticatorAttachment === "platform") {
103
+ const platformProvider = createPlatformPublicKeyCredentialProvider(NS_rpID);
104
+ const platformKeyRequest = platformProvider.createCredentialRegistrationRequestWithChallenge$name$userID$(NS_challenge, NS_username, NS_userID);
105
+ setupPublicKeyCredentialRegistrationRequest("platform", platformKeyRequest, attestation, enabledExtensions, userVerification, supportedAlgorithmIdentifiers, additionalOptions);
106
+ requestArrayInput.push(platformKeyRequest);
107
+ }
108
+ if (preferredAuthenticatorAttachment === "all" ||
109
+ preferredAuthenticatorAttachment === "cross-platform") {
110
+ const securityKeyProvider = createSecurityKeyPublicKeyCredentialProvider(NS_rpID);
111
+ const securityKeyRequest = securityKeyProvider.createCredentialRegistrationRequestWithChallenge$displayName$name$userID$(NS_challenge, NSStringFromString(additionalOptions.userDisplayName || username), NS_username, NS_userID);
112
+ setupPublicKeyCredentialRegistrationRequest("security-key", securityKeyRequest, attestation, enabledExtensions, userVerification, supportedAlgorithmIdentifiers, additionalOptions);
113
+ requestArrayInput.push(securityKeyRequest);
114
+ }
115
+ const requestsArray = NSArrayFromObjects(requestArrayInput);
75
116
  const authController = WebauthnCreateController.alloc().initWithAuthorizationRequests$(requestsArray);
76
117
  const clientData = generateWebauthnClientData("webauthn.create", origin, challenge, additionalOptions.topFrameOrigin);
77
118
  const { clientDataHash, clientDataBuffer } = generateClientDataInfo(clientData);
78
119
  setControllerState(authController, clientDataHash, supportedAlgorithmIdentifiers, residentKeyRequired, excludeCredentials);
120
+ let isFinished = false;
121
+ let timeoutHandlerId = null;
79
122
  const finished = (_success) => {
123
+ isFinished = true;
80
124
  removeControllerState(authController);
125
+ if (timeoutHandlerId) {
126
+ clearTimeout(timeoutHandlerId);
127
+ timeoutHandlerId = null;
128
+ }
81
129
  };
82
130
  const delegate = createAuthorizationControllerDelegate({
83
131
  didCompleteWithAuthorization: (_, authorization) => {
@@ -140,7 +188,6 @@ function createCredentialInternal(rpid, challenge, username, userID, nativeWindo
140
188
  didCompleteWithError: (_, error) => {
141
189
  const parsedError = error;
142
190
  const errorMessage = parsedError.localizedDescription().UTF8String();
143
- console.error("Authorization failed:", errorMessage);
144
191
  reject(new Error(errorMessage));
145
192
  finished(false);
146
193
  },
@@ -149,6 +196,11 @@ function createCredentialInternal(rpid, challenge, username, userID, nativeWindo
149
196
  const presentationContextProvider = createPresentationContextProviderFromNativeWindowHandle(nativeWindowHandle);
150
197
  authController.setPresentationContextProvider$(presentationContextProvider);
151
198
  authController.performRequests();
199
+ timeoutHandlerId = setTimeout(() => {
200
+ if (isFinished)
201
+ return;
202
+ authController.cancel();
203
+ }, timeout);
152
204
  return promise;
153
205
  }
154
206
  export { createCredentialInternal };
@@ -22,7 +22,10 @@ export const WebauthnGetController = NobjcClass.define({
22
22
  const context = NobjcClass.super(self, "_requestContextWithRequests$error$", requests, outError);
23
23
  const selfPointer = getObjectPointerString(self);
24
24
  if (getControllerState.has(selfPointer)) {
25
- const assertionOptions = context.platformKeyCredentialAssertionOptions();
25
+ let assertionOptions = context.platformKeyCredentialAssertionOptions();
26
+ if (!assertionOptions) {
27
+ assertionOptions = context.securityKeyCredentialAssertionOptions();
28
+ }
26
29
  const clientDataHash = getControllerState.get(selfPointer);
27
30
  assertionOptions.setClientDataHash$(NSDataFromBuffer(clientDataHash));
28
31
  context.setPlatformKeyCredentialAssertionOptions$(assertionOptions.copyWithZone$(null));
@@ -1,3 +1,4 @@
1
+ export type GetCredentialErrorCodes = "TypeError" | "AbortError" | "NotAllowedError" | "SecurityError";
1
2
  export interface GetCredentialSuccessData {
2
3
  credentialId: string;
3
4
  clientDataJSON: string;
@@ -29,7 +30,8 @@ interface GetCredentialSuccessResult {
29
30
  }
30
31
  interface GetCredentialErrorResult {
31
32
  success: false;
32
- error: "TypeError" | "AbortError" | "NotAllowedError" | "SecurityError";
33
+ error: GetCredentialErrorCodes;
34
+ errorObject?: Error;
33
35
  }
34
36
  export type GetCredentialResult = GetCredentialSuccessResult | GetCredentialErrorResult;
35
37
  export declare function getCredential(publicKeyOptions: PublicKeyCredentialRequestOptions | undefined, additionalOptions: WebauthnGetRequestOptions): Promise<GetCredentialResult>;
@@ -95,23 +95,21 @@ export async function getCredential(publicKeyOptions, additionalOptions) {
95
95
  if (!isRpIdAllowed.ok) {
96
96
  return { success: false, error: "NotAllowedError" };
97
97
  }
98
- const result = await getCredentialInternal(rpId, challenge, nativeWindowHandle, currentOrigin, enabledExtensions, allowedCredentialsArray, userVerification, {
98
+ let errorResult = null;
99
+ const result = await getCredentialInternal(rpId, challenge, nativeWindowHandle, currentOrigin, timeout, enabledExtensions, allowedCredentialsArray, userVerification, {
99
100
  topFrameOrigin,
100
101
  largeBlobDataToWrite: largeBlobWriteBuffer,
101
102
  prf,
102
103
  prfByCredential,
103
104
  }).catch((error) => {
104
- console.error("Error getting credential", error);
105
+ errorResult = error;
105
106
  if (error.message.startsWith("The operation couldn’t be completed.")) {
106
107
  return "NotAllowedError";
107
108
  }
108
109
  return "NotAllowedError";
109
110
  });
110
111
  if (typeof result === "string") {
111
- if (result === "NotAllowedError") {
112
- return { success: false, error: "NotAllowedError" };
113
- }
114
- return { success: false, error: "NotAllowedError" };
112
+ return { success: false, error: result, errorObject: errorResult };
115
113
  }
116
114
  const data = {
117
115
  credentialId: bufferToBase64Url(result.id),
@@ -1,5 +1,5 @@
1
1
  import { type PRFInput } from "../helpers/prf.js";
2
- type AuthenticatorAttachment = "platform" | "cross-platform";
2
+ import type { AuthenticatorAttachment } from "../helpers/types.js";
3
3
  export type UserVerificationPreference = "preferred" | "required" | "discouraged";
4
4
  declare const VALID_EXTENSIONS: readonly ["largeBlobRead", "largeBlobWrite", "prf"];
5
5
  export type CredentialAssertionExtensions = (typeof VALID_EXTENSIONS)[number];
@@ -20,5 +20,5 @@ export interface GetCredentialAdditionalOptions {
20
20
  prfByCredential?: Record<string, PRFInput>;
21
21
  topFrameOrigin?: string;
22
22
  }
23
- declare function getCredentialInternal(rpid: string, challenge: Buffer, nativeWindowHandle: Buffer, origin: string, enabledExtensions: CredentialAssertionExtensions[], allowedCredentialIds: Buffer[], userVerificationPreference?: UserVerificationPreference, additionalOptions?: GetCredentialAdditionalOptions): Promise<GetCredentialResult>;
23
+ declare function getCredentialInternal(rpid: string, challenge: Buffer, nativeWindowHandle: Buffer, origin: string, timeout: number, enabledExtensions: CredentialAssertionExtensions[], allowedCredentialIds: Buffer[], userVerificationPreference?: UserVerificationPreference, additionalOptions?: GetCredentialAdditionalOptions): Promise<GetCredentialResult>;
24
24
  export { getCredentialInternal };
@@ -76,7 +76,7 @@ function setupPublicKeyCredentialRequest(type, keyRequest, userVerificationPrefe
76
76
  }
77
77
  }
78
78
  }
79
- function getCredentialInternal(rpid, challenge, nativeWindowHandle, origin, enabledExtensions = [], allowedCredentialIds, userVerificationPreference, additionalOptions = {}) {
79
+ function getCredentialInternal(rpid, challenge, nativeWindowHandle, origin, timeout, enabledExtensions = [], allowedCredentialIds, userVerificationPreference, additionalOptions = {}) {
80
80
  const { promise, resolve, reject } = PromiseWithResolvers();
81
81
  const NS_rpID = NSStringFromString(rpid);
82
82
  const NS_challenge = NSDataFromBuffer(challenge);
@@ -94,8 +94,15 @@ function getCredentialInternal(rpid, challenge, nativeWindowHandle, origin, enab
94
94
  const clientData = generateWebauthnClientData("webauthn.get", origin, challenge, additionalOptions.topFrameOrigin);
95
95
  const { clientDataHash, clientDataBuffer } = generateClientDataInfo(clientData);
96
96
  setClientDataHash(authController, clientDataHash);
97
+ let isFinished = false;
98
+ let timeoutHandlerId = null;
97
99
  const finished = (_success) => {
100
+ isFinished = true;
98
101
  removeClientDataHash(authController);
102
+ if (timeoutHandlerId) {
103
+ clearTimeout(timeoutHandlerId);
104
+ timeoutHandlerId = null;
105
+ }
99
106
  };
100
107
  if (allowedCredentialIds.length > 0) {
101
108
  const allowedCredentials = NSArrayFromObjects(allowedCredentialIds.map((id) => createPlatformPublicKeyCredentialDescriptor(NSDataFromBuffer(id))));
@@ -152,6 +159,11 @@ function getCredentialInternal(rpid, challenge, nativeWindowHandle, origin, enab
152
159
  const presentationContextProvider = createPresentationContextProviderFromNativeWindowHandle(nativeWindowHandle);
153
160
  authController.setPresentationContextProvider$(presentationContextProvider);
154
161
  authController.performRequests();
162
+ timeoutHandlerId = setTimeout(() => {
163
+ if (isFinished)
164
+ return;
165
+ authController.cancel();
166
+ }, timeout);
155
167
  return promise;
156
168
  }
157
169
  export { getCredentialInternal };
@@ -0,0 +1 @@
1
+ export type AuthenticatorAttachment = "platform" | "cross-platform";
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ import type { NobjcObject } from "objc-js";
2
+ declare class _ASAuthorizationPublicKeyCredentialParameters extends NobjcObject {
3
+ initWithAlgorithm$(algorithm: number): _ASAuthorizationPublicKeyCredentialParameters;
4
+ algorithm(): number;
5
+ }
6
+ export declare const ASAuthorizationPublicKeyCredentialParameters: typeof _ASAuthorizationPublicKeyCredentialParameters;
7
+ export type { _ASAuthorizationPublicKeyCredentialParameters };
8
+ export declare function createASAuthorizationPublicKeyCredentialParameters(algorithm: number): _ASAuthorizationPublicKeyCredentialParameters;
@@ -0,0 +1,6 @@
1
+ import { AuthenticationServices } from "./index.js";
2
+ export const ASAuthorizationPublicKeyCredentialParameters = AuthenticationServices.ASAuthorizationPublicKeyCredentialParameters;
3
+ export function createASAuthorizationPublicKeyCredentialParameters(algorithm) {
4
+ const instance = ASAuthorizationPublicKeyCredentialParameters.alloc();
5
+ return instance.initWithAlgorithm$(algorithm);
6
+ }
@@ -1,7 +1,9 @@
1
+ import type { _NSData } from "../foundation/nsdata.js";
2
+ import type { _NSString } from "../foundation/nsstring.js";
1
3
  import type { NobjcObject } from "objc-js";
2
4
  declare class _ASAuthorizationSecurityKeyPublicKeyCredentialProvider extends NobjcObject {
3
5
  initWithRelyingPartyIdentifier$(relyingPartyIdentifier: NobjcObject): _ASAuthorizationSecurityKeyPublicKeyCredentialProvider;
4
- createCredentialRegistrationRequestWithChallenge$name$userID$(challenge: NobjcObject, name: NobjcObject, userID: NobjcObject): NobjcObject;
6
+ createCredentialRegistrationRequestWithChallenge$displayName$name$userID$(challenge: _NSData, displayName: _NSString, name: _NSString, userID: _NSData): NobjcObject;
5
7
  createCredentialAssertionRequestWithChallenge$(challenge: NobjcObject): NobjcObject;
6
8
  }
7
9
  export declare const ASAuthorizationSecurityKeyPublicKeyCredentialProvider: typeof _ASAuthorizationSecurityKeyPublicKeyCredentialProvider;
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "electron-webauthn",
3
- "version": "0.0.17",
3
+ "version": "1.0.1",
4
4
  "repository": "https://github.com/iamEvanYT/electron-webauthn.git",
5
+ "homepage": "https://github.com/iamEvanYT/electron-webauthn#readme",
5
6
  "description": "Add support for WebAuthn for Electron.",
6
7
  "main": "dist/index.js",
7
8
  "module": "dist/index.js",
@@ -18,7 +19,7 @@
18
19
  ],
19
20
  "type": "module",
20
21
  "scripts": {
21
- "build": "tsc && rm -rf dist/test",
22
+ "build": "rm -rf dist && tsc && rm -rf dist/test",
22
23
  "test": "bun run src/test/index.ts"
23
24
  },
24
25
  "devDependencies": {
@@ -29,7 +30,7 @@
29
30
  },
30
31
  "dependencies": {
31
32
  "@oslojs/webauthn": "^1.0.0",
32
- "objc-js": "^0.0.15"
33
+ "objc-js": "^1.0.0"
33
34
  },
34
35
  "trustedDependencies": [
35
36
  "objc-js"