@flun/webauthn-browser 2.0.2

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/CHANGELOG.md ADDED
@@ -0,0 +1,4 @@
1
+ # 变更日志
2
+ ## [2.0.2] - 2026-04-30 13:40
3
+ ### 新包名应用成功通知:
4
+ - 新包名版本号将延续旧包;
package/LICENSE.md ADDED
@@ -0,0 +1,19 @@
1
+ SC 开源许可声明
2
+ 版权信息
3
+ 衍生开发 Copyright (c) 2026, flun <cn@flun.top>
4
+
5
+ 本项目(软件包)基于 Matthew Miller 作者的原代码进行开发、修改和扩展。
6
+
7
+ 使用条款
8
+ 本软件以“原样”提供,任何人都可以自由地:
9
+
10
+ 使用:用于任何目的,包括商业用途。
11
+
12
+ 修改:根据需要改编代码。
13
+
14
+ 分发:免费或收费地分享原版或修改后的软件。
15
+
16
+ 唯一的条件是:不能从事非法及其它不正当行为,否则由您自行承担全部责任。
17
+
18
+ 免责声明
19
+ 作者对软件的质量不作任何保证。因使用本软件而产生的任何问题(如数据丢失、系统故障等),作者不承担任何责任。
package/README.md ADDED
@@ -0,0 +1,583 @@
1
+ # @flun/webauthn-browser <!-- omit in toc -->
2
+
3
+ ![WebAuthn](https://img.shields.io/badge/WebAuthn-Browser_Simplified-blueviolet?style=for-the-badge&logo=WebAuthn)
4
+ [![npm (scoped)](https://img.shields.io/npm/v/@flun/webauthn-browser?style=for-the-badge&logo=npm)](https://www.npmjs.com/package/@flun/webauthn-browser)
5
+
6
+ **@flun/webauthn-browser** 是一个专为浏览器环境设计的 WebAuthn(含 Passkeys)前端工具库,基于 [@simplewebauthn/browser](https://simplewebauthn.dev/) 核心逻辑重构并增强,它封装了 `navigator.credentials` API 的复杂细节,提供简洁的 Promise 风格接口,并内置了丰富的错误处理与 Base64URL 编解码工具;
7
+
8
+ 本包以 **ESM** 编写,同时提供 **UMD** 打包产物,可通过 npm 模块方式或 `<script>` 全局方式引入,兼容现代浏览器及部分旧版环境;
9
+
10
+ ## 目录
11
+
12
+ - [目录](#目录)
13
+ - [安装](#安装)
14
+ - [NPM 模块方式](#npm-模块方式)
15
+ - [UMD 全局方式](#umd-全局方式)
16
+ - [ES2021(推荐用于现代浏览器)](#es2021推荐用于现代浏览器)
17
+ - [ES5(包含 polyfill,支持 IE11 / 旧版 Edge)](#es5包含-polyfill支持-ie11--旧版-edge)
18
+ - [主要功能](#主要功能)
19
+ - [快速开始](#快速开始)
20
+ - [模块方式使用示例](#模块方式使用示例)
21
+ - [全局方式使用示例](#全局方式使用示例)
22
+ - [API 参考](#api-参考)
23
+ - [核心认证方法](#核心认证方法)
24
+ - [startRegistration(options)](#startregistrationoptions)
25
+ - [startAuthentication(options)](#startauthenticationoptions)
26
+ - [环境检测方法](#环境检测方法)
27
+ - [browserSupportsWebAuthn()](#browsersupportswebauthn)
28
+ - [platformAuthenticatorIsAvailable()](#platformauthenticatorisavailable)
29
+ - [browserSupportsWebAuthnAutofill()](#browsersupportswebauthnautofill)
30
+ - [编解码工具](#编解码工具)
31
+ - [错误类与中止服务](#错误类与中止服务)
32
+ - [WebAuthnError](#webauthnerror)
33
+ - [WebAuthnAbortService](#webauthnabortservice)
34
+ - [内部调试工具](#内部调试工具)
35
+ - [错误处理](#错误处理)
36
+ - [前后端集成示例](#前后端集成示例)
37
+ - [配合 @flun/webauthn-server](#配合-flunwebauthn-server)
38
+ - [许可证](#许可证)
39
+ - [相关链接](#相关链接)
40
+
41
+ ---
42
+
43
+ ## 安装
44
+
45
+ ### NPM 模块方式
46
+
47
+ 适用于使用打包工具(Webpack、Vite、Rollup 等)或 Node.js ≥ 22.12.0 的环境;
48
+
49
+ ```sh
50
+ npm i @flun/webauthn-browser
51
+ ```
52
+
53
+ 然后在代码中按需导入:
54
+
55
+ ```js
56
+ // ES Module
57
+ import { startRegistration, startAuthentication } from '@flun/webauthn-browser';
58
+
59
+ // CommonJS (Node.js ≥ 22.12.0)
60
+ const { startRegistration, startAuthentication } = require('@flun/webauthn-browser');
61
+ ```
62
+
63
+ ### UMD 全局方式
64
+
65
+ 本包通过 **unpkg** 提供 UMD 版本,引入后所有方法将挂载在全局对象 **`flunWebAuthnBrowser`** 上;
66
+
67
+ > 注意:ES5 版本包含针对旧版浏览器的 polyfill,会增加体积,但允许在旧环境中使用 `browserSupportsWebAuthn()` 进行特性检测;
68
+
69
+ #### ES2021(推荐用于现代浏览器)
70
+
71
+ ```html
72
+ <script src="https://unpkg.com/@flun/webauthn-browser/dist/index.js"></script>
73
+ ```
74
+
75
+ #### ES5(包含 polyfill,支持 IE11 / 旧版 Edge)
76
+
77
+ ```html
78
+ <script src="https://unpkg.com/@flun/webauthn-browser/dist/index.es5.js"></script>
79
+ ```
80
+
81
+ ---
82
+
83
+ ## 主要功能
84
+
85
+ - ✅ **简化的 WebAuthn 调用**
86
+ `startRegistration()` 和 `startAuthentication()` 自动处理 Base64URL ↔ ArrayBuffer 转换,支持标准 JSON 格式的入参;
87
+
88
+ - ✅ **内置错误标准化**
89
+ 将浏览器原生错误转换为包含 `code` 和 `message` 的 `WebAuthnError` 对象,便于业务层处理;
90
+
91
+ - ✅ **条件媒介支持(Conditional UI / 自动填充)**
92
+ 支持 `useBrowserAutofill` 实现无感登录体验(需页面存在合适的 `<input>` 元素);
93
+
94
+ - ✅ **完整的环境检测**
95
+ 提供 `browserSupportsWebAuthn()`、`platformAuthenticatorIsAvailable()` 等方法,精确判断设备能力;
96
+
97
+ - ✅ **编解码工具集**
98
+ 导出 `bufferToBase64URLString` / `base64URLStringToBuffer` 等工具函数,方便前后端数据交互;
99
+
100
+ - ✅ **可中止的认证流程**
101
+ 通过 `WebAuthnAbortService` 在必要时取消进行中的认证/注册操作;
102
+
103
+ ---
104
+
105
+ ## 快速开始
106
+
107
+ 以下示例演示了最基本的调用方式,默认与后端使用 JSON 交互;
108
+
109
+ ### 模块方式使用示例
110
+
111
+ ```js
112
+ import { startRegistration, startAuthentication } from '@flun/webauthn-browser';
113
+
114
+ // 注册
115
+ async function register(username) {
116
+ const regOptions = await fetch('/api/register/begin', {
117
+ method: 'POST',
118
+ headers: { 'Content-Type': 'application/json' },
119
+ body: JSON.stringify({ username })
120
+ }).then(r => r.json());
121
+
122
+ const regResponse = await startRegistration({ optionsJSON: regOptions });
123
+
124
+ await fetch('/api/register/complete', {
125
+ method: 'POST',
126
+ headers: { 'Content-Type': 'application/json' },
127
+ body: JSON.stringify({ username, response: regResponse })
128
+ });
129
+ }
130
+
131
+ // 登录
132
+ async function login(username) {
133
+ const authOptions = await fetch('/api/login/begin', {
134
+ method: 'POST',
135
+ headers: { 'Content-Type': 'application/json' },
136
+ body: JSON.stringify({ username })
137
+ }).then(r => r.json());
138
+
139
+ const authResponse = await startAuthentication({ optionsJSON: authOptions }),
140
+ verifyRes = await fetch('/api/login/complete', {
141
+ method: 'POST',
142
+ headers: { 'Content-Type': 'application/json' },
143
+ body: JSON.stringify({ username, response: authResponse })
144
+ });
145
+ return verifyRes.json();
146
+ }
147
+ ```
148
+
149
+ ### 全局方式使用示例
150
+
151
+ 在 HTML 页面中引入 UMD 脚本后,即可通过全局变量 `flunWebAuthnBrowser` 调用所有方法。
152
+
153
+ ```html
154
+ <script src="https://unpkg.com/@flun/webauthn-browser/dist/index.js"></script>
155
+ <script>
156
+ (async () => {
157
+ // 1. 首先检测浏览器是否支持 WebAuthn
158
+ if (!flunWebAuthnBrowser.browserSupportsWebAuthn()) {
159
+ return alert('您的浏览器不支持 WebAuthn 或 Passkey,请升级或使用其他方式登录。');
160
+ }
161
+
162
+ // ---------- 注册示例 ----------
163
+ async function handleRegister(username) {
164
+ const optionsRes = await fetch('/api/register/begin', {
165
+ method: 'POST',
166
+ headers: { 'Content-Type': 'application/json' },
167
+ body: JSON.stringify({ username })
168
+ }), options = await optionsRes.json(),
169
+ response = await flunWebAuthnBrowser.startRegistration({
170
+ optionsJSON: options
171
+ }),
172
+ verifyRes = await fetch('/api/register/complete', {
173
+ method: 'POST',
174
+ headers: { 'Content-Type': 'application/json' },
175
+ body: JSON.stringify({ username, response })
176
+ }), result = await verifyRes.json();
177
+
178
+ console.log('注册结果:', result);
179
+ }
180
+
181
+ // ---------- 登录(认证)示例 ----------
182
+ async function handleLogin(username) {
183
+ const optionsRes = await fetch('/api/login/begin', {
184
+ method: 'POST',
185
+ headers: { 'Content-Type': 'application/json' },
186
+ body: JSON.stringify({ username })
187
+ }), options = await optionsRes.json(),
188
+ response = await flunWebAuthnBrowser.startAuthentication({
189
+ optionsJSON: options
190
+ });
191
+
192
+ const verifyRes = await fetch('/api/login/complete', {
193
+ method: 'POST',
194
+ headers: { 'Content-Type': 'application/json' },
195
+ body: JSON.stringify({ username, response })
196
+ });
197
+ const result = await verifyRes.json();
198
+ if (result.verified) {
199
+ console.log('登录成功');
200
+ window.location.href = '/dashboard';
201
+ } else {
202
+ alert('登录失败,请重试');
203
+ }
204
+ }
205
+
206
+ // ---------- 带自动填充(Conditional UI)的登录示例 ----------
207
+ // 注意:使用自动填充时,页面必须包含类似如下的 input 元素:
208
+ // <input type="text" name="username" autocomplete="username webauthn" />
209
+ async function handleAutofillLogin() {
210
+ if (!(await flunWebAuthnBrowser.browserSupportsWebAuthnAutofill())) {
211
+ return console.warn('当前浏览器不支持 WebAuthn 自动填充');
212
+ }
213
+
214
+ const optionsRes = await fetch('/api/login/begin', {
215
+ method: 'POST',
216
+ headers: { 'Content-Type': 'application/json' },
217
+ body: JSON.stringify({ useAutofill: true })
218
+ }), options = await optionsRes.json();
219
+
220
+ try {
221
+ const response = await flunWebAuthnBrowser.startAuthentication({
222
+ optionsJSON: options,
223
+ useBrowserAutofill: true
224
+ }),
225
+ verifyRes = await fetch('/api/login/complete', {
226
+ method: 'POST',
227
+ headers: { 'Content-Type': 'application/json' },
228
+ body: JSON.stringify({ response })
229
+ }), result = await verifyRes.json();
230
+
231
+ if (result.verified) {
232
+ console.log('自动填充登录成功');
233
+ window.location.href = '/dashboard';
234
+ }
235
+ } catch (error) {
236
+ console.warn('自动填充登录未完成:', error.message);
237
+ }
238
+ }
239
+
240
+ // 绑定按钮事件示例
241
+ document.getElementById('register-btn')?.addEventListener('click', () => {
242
+ const username = document.getElementById('username-input').value;
243
+ handleRegister(username);
244
+ });
245
+
246
+ document.getElementById('login-btn')?.addEventListener('click', () => {
247
+ const username = document.getElementById('username-input').value;
248
+ handleLogin(username);
249
+ });
250
+
251
+ // 自动填充登录通常在页面加载时触发
252
+ window.addEventListener('load', () => {
253
+ handleAutofillLogin();
254
+ });
255
+ })();
256
+ </script>
257
+ ```
258
+
259
+ ---
260
+
261
+ ## API 参考
262
+
263
+ ### 核心认证方法
264
+
265
+ #### startRegistration(options)
266
+
267
+ 发起 WebAuthn 注册流程,创建新凭证。
268
+
269
+ **参数**
270
+
271
+ | 字段名 | 类型 | 默认值 | 描述 |
272
+ | ----------------- | --------- | ------- | --------------------------------------------------------------- |
273
+ | `optionsJSON` | `object` | 必填 | 由服务端调用 `generateRegistrationOptions()` 生成的 JSON 对象 |
274
+ | `useAutoRegister` | `boolean` | `false` | 是否启用条件媒介自动注册(实验性,需浏览器支持 Conditional UI) |
275
+
276
+ **返回值** `Promise<RegistrationResponseJSON>`
277
+
278
+ 返回一个经过标准化处理的 JSON 对象,可直接发送给后端 `verifyRegistrationResponse()` 验证。响应格式如下:
279
+
280
+ ```ts
281
+ interface RegistrationResponseJSON {
282
+ id: string;
283
+ rawId: string;
284
+ response: {
285
+ attestationObject: string;
286
+ clientDataJSON: string;
287
+ transports?: AuthenticatorTransport[];
288
+ publicKeyAlgorithm?: number;
289
+ publicKey?: string;
290
+ authenticatorData?: string;
291
+ };
292
+ type: string;
293
+ clientExtensionResults: AuthenticationExtensionsClientOutputs;
294
+ authenticatorAttachment?: string;
295
+ }
296
+ ```
297
+
298
+ **示例**
299
+
300
+ ```js
301
+ const response = await startRegistration({ optionsJSON });
302
+ // 发送 response 至服务端
303
+ ```
304
+
305
+ #### startAuthentication(options)
306
+
307
+ 发起 WebAuthn 认证(登录)流程。
308
+
309
+ **参数**
310
+
311
+ | 字段名 | 类型 | 默认值 | 描述 |
312
+ | ---------------------------- | --------- | ------- | ------------------------------------------------------------------------------ |
313
+ | `optionsJSON` | `object` | 必填 | 由服务端调用 `generateAuthenticationOptions()` 生成的 JSON 对象 |
314
+ | `useBrowserAutofill` | `boolean` | `false` | 是否启用条件媒介(自动填充),实现无感登录 |
315
+ | `verifyBrowserAutofillInput` | `boolean` | `true` | 启用自动填充时是否检查页面是否存在 `autocomplete="... webauthn"` 的 input 元素 |
316
+
317
+ **返回值** `Promise<AuthenticationResponseJSON>`
318
+
319
+ ```ts
320
+ interface AuthenticationResponseJSON {
321
+ id: string;
322
+ rawId: string;
323
+ response: {
324
+ authenticatorData: string;
325
+ clientDataJSON: string;
326
+ signature: string;
327
+ userHandle?: string;
328
+ };
329
+ type: string;
330
+ clientExtensionResults: AuthenticationExtensionsClientOutputs;
331
+ authenticatorAttachment?: string;
332
+ }
333
+ ```
334
+
335
+ **示例**
336
+
337
+ ```js
338
+ // 普通登录
339
+ const response = await startAuthentication({ optionsJSON });
340
+
341
+ // 自动填充登录(需页面存在 <input autocomplete="webauthn">)
342
+ const response = await startAuthentication({
343
+ optionsJSON,
344
+ useBrowserAutofill: true
345
+ });
346
+ ```
347
+
348
+ ### 环境检测方法
349
+
350
+ #### browserSupportsWebAuthn()
351
+
352
+ 检查当前浏览器是否支持 WebAuthn API。
353
+
354
+ **返回值** `boolean`
355
+
356
+ ```js
357
+ if (flunWebAuthnBrowser.browserSupportsWebAuthn()) {
358
+ // 可以使用 WebAuthn
359
+ }
360
+ ```
361
+
362
+ #### platformAuthenticatorIsAvailable()
363
+
364
+ 检查平台认证器(如 Touch ID、Face ID、Windows Hello)是否可用。
365
+
366
+ **返回值** `Promise<boolean>`
367
+
368
+ ```js
369
+ const available = await flunWebAuthnBrowser.platformAuthenticatorIsAvailable();
370
+ ```
371
+
372
+ #### browserSupportsWebAuthnAutofill()
373
+
374
+ 检查当前浏览器是否支持 WebAuthn 条件媒介(自动填充)。
375
+
376
+ **返回值** `Promise<boolean>`
377
+
378
+ ```js
379
+ if (await flunWebAuthnBrowser.browserSupportsWebAuthnAutofill()) {
380
+ // 可以使用 useBrowserAutofill 选项
381
+ }
382
+ ```
383
+
384
+ ### 编解码工具
385
+
386
+ 这些工具函数直接挂载在导出对象上,便于前后端统一数据格式;
387
+
388
+ | 函数 | 描述 |
389
+ | ---------------------------------------------- | -------------------------------------- |
390
+ | `bufferToBase64URLString(buffer: ArrayBuffer)` | 将 ArrayBuffer 转换为 Base64URL 字符串 |
391
+ | `base64URLStringToBuffer(base64url: string)` | 将 Base64URL 字符串转换为 ArrayBuffer |
392
+
393
+ **示例**
394
+
395
+ ```js
396
+ const { bufferToBase64URLString, base64URLStringToBuffer } = flunWebAuthnBrowser,
397
+ challengeBuffer = base64URLStringToBuffer(optionsJSON.challenge),
398
+ encoded = bufferToBase64URLString(challengeBuffer);
399
+ ```
400
+
401
+ ### 错误类与中止服务
402
+
403
+ #### WebAuthnError
404
+
405
+ 自定义错误类,继承自 `Error`。当认证/注册过程中发生可识别的错误时抛出。
406
+
407
+ **属性**
408
+
409
+ | 属性 | 类型 | 描述 |
410
+ | ------- | -------- | ---------------------------- |
411
+ | `name` | `string` | 错误名称(通常为原始错误名) |
412
+ | `code` | `string` | 标准化的错误码(见下方表格) |
413
+ | `cause` | `Error` | 浏览器抛出的原始错误对象 |
414
+
415
+ **常见错误码**
416
+
417
+ | 错误码 | 描述 |
418
+ | ------------------------------------------------------------- | ------------------------------------------------ |
419
+ | `ERROR_CEREMONY_ABORTED` | 认证/注册流程被中止(如调用 `cancelCeremony()`) |
420
+ | `ERROR_INVALID_DOMAIN` | 当前域名无效 |
421
+ | `ERROR_INVALID_RP_ID` | RP ID 与当前域名不匹配 |
422
+ | `ERROR_AUTHENTICATOR_GENERAL_ERROR` | 认证器通用错误 |
423
+ | `ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED` | 认证器已被注册(注册时) |
424
+ | `ERROR_AUTHENTICATOR_MISSING_DISCOVERABLE_CREDENTIAL_SUPPORT` | 需要可发现凭证但认证器不支持 |
425
+ | `ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY` | 透传错误,需查看 `cause` 属性 |
426
+
427
+ #### WebAuthnAbortService
428
+
429
+ 用于手动取消正在进行的 WebAuthn 操作。
430
+
431
+ **方法**
432
+
433
+ | 方法 | 描述 |
434
+ | ------------------------ | ------------------------------------------ |
435
+ | `createNewAbortSignal()` | 创建新的 AbortSignal,并自动中止之前的操作 |
436
+ | `cancelCeremony()` | 主动取消当前正在进行的认证/注册流程 |
437
+
438
+ ```js
439
+ // 取消当前所有 WebAuthn 操作
440
+ flunWebAuthnBrowser.WebAuthnAbortService.cancelCeremony();
441
+ ```
442
+
443
+ ### 内部调试工具
444
+
445
+ 以下属性暴露了部分内部实现细节,**仅用于高级调试或扩展,不推荐在生产中直接使用**。
446
+
447
+ | 属性 | 描述 |
448
+ | ------------------------------------------- | ---------------------- |
449
+ | `_browserSupportsWebAuthnInternals` | 内部检测函数的桩对象 |
450
+ | `_browserSupportsWebAuthnAutofillInternals` | 自动填充检测的内部实现 |
451
+
452
+ ---
453
+
454
+ ## 错误处理
455
+
456
+ 推荐使用 `try...catch` 捕获 `WebAuthnError` 并根据 `code` 进行差异化处理。
457
+
458
+ ```js
459
+ import { startAuthentication, WebAuthnError } from '@flun/webauthn-browser';
460
+
461
+ try {
462
+ const response = await startAuthentication({ optionsJSON });
463
+ // 发送 response
464
+ } catch (err) {
465
+ if (err instanceof WebAuthnError) {
466
+ switch (err.code) {
467
+ case 'ERROR_CEREMONY_ABORTED':
468
+ console.log('用户取消了操作');
469
+ break;
470
+ case 'ERROR_INVALID_RP_ID':
471
+ alert('配置错误:RP ID 无效');
472
+ break;
473
+ default:
474
+ console.error('认证失败', err.message);
475
+ }
476
+ } else {
477
+ // 处理其他非 WebAuthn 错误
478
+ }
479
+ }
480
+ ```
481
+
482
+ ---
483
+
484
+ ## 前后端集成示例
485
+
486
+ ### 配合 @flun/webauthn-server
487
+
488
+ 以下是一个完整的最小化集成示例,前端使用本库,后端使用 [`@flun/webauthn-server`](https://www.npmjs.com/package/@flun/webauthn-server)。
489
+
490
+ **前端代码**
491
+
492
+ ```js
493
+ import { startRegistration, startAuthentication, browserSupportsWebAuthn} from '@flun/webauthn-browser';
494
+
495
+ async function register(username) {
496
+ // 1. 请求注册选项
497
+ const optionsRes = await fetch('/api/register/begin', {
498
+ method: 'POST',
499
+ headers: { 'Content-Type': 'application/json' },
500
+ body: JSON.stringify({ username })
501
+ }), options = await optionsRes.json(),
502
+ // 2. 启动浏览器注册流程
503
+ response = await startRegistration({ optionsJSON: options }),
504
+ // 3. 将响应发送给服务端验证
505
+ verifyRes = await fetch('/api/register/complete', {
506
+ method: 'POST',
507
+ headers: { 'Content-Type': 'application/json' },
508
+ body: JSON.stringify({ username, response })
509
+ });
510
+ return verifyRes.json();
511
+ }
512
+
513
+ async function login(username) {
514
+ // 1. 请求认证选项
515
+ const optionsRes = await fetch('/api/login/begin', {
516
+ method: 'POST',
517
+ headers: { 'Content-Type': 'application/json' },
518
+ body: JSON.stringify({ username })
519
+ }), options = await optionsRes.json(),
520
+ // 2. 启动浏览器认证流程(可开启自动填充)
521
+ response = await startAuthentication({ optionsJSON: options }),
522
+ // 3. 发送响应验证
523
+ verifyRes = await fetch('/api/login/complete', {
524
+ method: 'POST',
525
+ headers: { 'Content-Type': 'application/json' },
526
+ body: JSON.stringify({ username, response })
527
+ });
528
+ return verifyRes.json();
529
+ }
530
+ ```
531
+
532
+ **后端代码片段**(基于 Express + `@flun/webauthn-server`)
533
+
534
+ ```js
535
+ import {
536
+ generateRegistrationOptions, verifyRegistrationResponse, generateAuthenticationOptions, verifyAuthenticationResponse
537
+ } from '@flun/webauthn-server';
538
+
539
+ app.post('/api/register/begin', async (req, res) => {
540
+ const { username } = req.body, user = getUser(username),
541
+ options = await generateRegistrationOptions({
542
+ rpName: 'My App',
543
+ rpID: 'localhost',
544
+ userName: username,
545
+ userID: user.id,
546
+ });
547
+ storeChallenge(username, options.challenge);
548
+ res.json(options);
549
+ });
550
+
551
+ app.post('/api/register/complete', async (req, res) => {
552
+ const { username, response } = req.body, expectedChallenge = getChallenge(username),
553
+ verification = await verifyRegistrationResponse({
554
+ response,
555
+ expectedChallenge,
556
+ expectedOrigin: 'http://localhost:3000',
557
+ expectedRPID: 'localhost',
558
+ });
559
+ if (verification.verified) {
560
+ saveCredential(username, verification.registrationInfo);
561
+ res.json({ success: true });
562
+ } else {
563
+ res.status(400).json({ error: 'Verification failed' });
564
+ }
565
+ });
566
+
567
+ // 认证接口类似...
568
+ ```
569
+
570
+ ---
571
+
572
+ ## 许可证
573
+
574
+ ISC © [flun](https://github.com/flunGit)
575
+
576
+ ---
577
+
578
+ ## 相关链接
579
+
580
+ - [GitHub 仓库](https://github.com/flunGit/@flun/webauthn-browser)
581
+ - [npm 包页面](https://www.npmjs.com/package/@flun/webauthn-browser)
582
+ - [配套后端库 @flun/webauthn-server](https://www.npmjs.com/package/@flun/webauthn-server)
583
+ - [W3C WebAuthn 规范](https://w3c.github.io/webauthn/)
@@ -0,0 +1 @@
1
+ function e(b) { return e = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (a) { return typeof a } : function (a) { return a && "function" == typeof Symbol && a.constructor === Symbol && a !== Symbol.prototype ? "symbol" : typeof a }, e(b) } !function (a, b) { if ("function" == typeof define && define.amd) define("flunWebAuthnBrowser", ["exports"], b); else if ("undefined" != typeof exports) b(exports); else { var c = { exports: {} }; b(c.exports), a.flunWebAuthnBrowser = c.exports } }("undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : this, (function (n) { function I() { J = function () { return k }; var l, k = {}, s = Object.prototype, p = s.hasOwnProperty, t = Object.defineProperty || function (a, b, c) { a[b] = c.value }, q = "function" == typeof Symbol ? Symbol : {}, v = q.iterator || "@@iterator", z = q.asyncIterator || "@@asyncIterator", B = q.toStringTag || "@@toStringTag"; function w(a, b, c) { return Object.defineProperty(a, b, { value: c, enumerable: !0, configurable: !0, writable: !0 }), a[b] } try { w({}, "") } catch (l) { w = function (a, b, c) { return a[b] = c } } function Z(a, b, c, d) { var f = b && b.prototype instanceof O ? b : O, g = Object.create(f.prototype), i = new P(d || []); return t(g, "_invoke", { value: bt(a, c, i) }), g } function Q(a, b, c) { try { return { type: "normal", arg: a.call(b, c) } } catch (a) { return { type: "throw", arg: a } } } k.wrap = Z; var ba = "suspendedStart", bu = "suspendedYield", bb = "executing", K = "completed", x = {}; function O() { } function L() { } function C() { } var R = {}; w(R, v, (function () { return this })); var S = Object.getPrototypeOf, M = S && S(S(T([]))); M && M !== s && p.call(M, v) && (R = M); var D = C.prototype = O.prototype = Object.create(R); function bc(c) { ["next", "throw", "return"].forEach((function (b) { w(c, b, (function (a) { return this._invoke(b, a) })) })) } function N(j, o) { function r(b, c, d, f) { var g = Q(j[b], j, c); if ("throw" !== g.type) { var i = g.arg, h = i.value; return h && "object" == e(h) && p.call(h, "__await") ? o.resolve(h.__await).then((function (a) { r("next", a, d, f) }), (function (a) { r("throw", a, d, f) })) : o.resolve(h).then((function (a) { i.value = a, d(i) }), (function (a) { return r("throw", a, d, f) })) } f(g.arg) } var y; t(this, "_invoke", { value: function (c, d) { function f() { return new o((function (a, b) { r(c, d, a, b) })) } return y = y ? y.then(f, f) : f() } }) } function bt(g, i, h) { var j = ba; return function (a, b) { if (j === bb) throw Error("Generator is already running"); if (j === K) { if ("throw" === a) throw b; return { value: l, done: !0 } } for (h.method = a, h.arg = b; ;) { var c = h.delegate; if (c) { var d = bd(c, h); if (d) { if (d === x) continue; return d } } if ("next" === h.method) h.sent = h._sent = h.arg; else if ("throw" === h.method) { if (j === ba) throw j = K, h.arg; h.dispatchException(h.arg) } else "return" === h.method && h.abrupt("return", h.arg); j = bb; var f = Q(g, i, h); if ("normal" === f.type) { if (j = h.done ? K : bu, f.arg === x) continue; return { value: f.arg, done: h.done } } "throw" === f.type && (j = K, h.method = "throw", h.arg = f.arg) } } } function bd(a, b) { var c = b.method, d = a.iterator[c]; if (d === l) return b.delegate = null, "throw" === c && a.iterator.return && (b.method = "return", b.arg = l, bd(a, b), "throw" === b.method) || "return" !== c && (b.method = "throw", b.arg = new TypeError("The iterator does not provide a '" + c + "' method")), x; var f = Q(d, a.iterator, b.arg); if ("throw" === f.type) return b.method = "throw", b.arg = f.arg, b.delegate = null, x; var g = f.arg; return g ? g.done ? (b[a.resultName] = g.value, b.next = a.nextLoc, "return" !== b.method && (b.method = "next", b.arg = l), b.delegate = null, x) : g : (b.method = "throw", b.arg = new TypeError("iterator result is not an object"), b.delegate = null, x) } function bv(a) { var b = { tryLoc: a[0] }; 1 in a && (b.catchLoc = a[1]), 2 in a && (b.finallyLoc = a[2], b.afterLoc = a[3]), this.tryEntries.push(b) } function U(a) { var b = a.completion || {}; b.type = "normal", delete b.arg, a.completion = b } function P(a) { this.tryEntries = [{ tryLoc: "root" }], a.forEach(bv, this), this.reset(!0) } function T(a) { if (a || "" === a) { var b = a[v]; if (b) return b.call(a); if ("function" == typeof a.next) return a; if (!isNaN(a.length)) { var c = -1, d = function e() { for (; ++c < a.length;)if (p.call(a, c)) return e.value = a[c], e.done = !1, e; return e.value = l, e.done = !0, e }; return d.next = d } } throw new TypeError(e(a) + " is not iterable") } return L.prototype = C, t(D, "constructor", { value: C, configurable: !0 }), t(C, "constructor", { value: L, configurable: !0 }), L.displayName = w(C, B, "GeneratorFunction"), k.isGeneratorFunction = function (a) { var b = "function" == typeof a && a.constructor; return !!b && (b === L || "GeneratorFunction" === (b.displayName || b.name)) }, k.mark = function (a) { return Object.setPrototypeOf ? Object.setPrototypeOf(a, C) : (a.__proto__ = C, w(a, B, "GeneratorFunction")), a.prototype = Object.create(D), a }, k.awrap = function (a) { return { __await: a } }, bc(N.prototype), w(N.prototype, z, (function () { return this })), k.AsyncIterator = N, k.async = function (b, c, d, f, g) { void 0 === g && (g = Promise); var i = new N(Z(b, c, d, f), g); return k.isGeneratorFunction(c) ? i : i.next().then((function (a) { return a.done ? a.value : i.next() })) }, bc(D), w(D, B, "Generator"), w(D, v, (function () { return this })), w(D, "toString", (function () { return "[object Generator]" })), k.keys = function (b) { var c = Object(b), d = []; for (var f in c) d.push(f); return d.reverse(), function b() { for (; d.length;) { var a = d.pop(); if (a in c) return b.value = a, b.done = !1, b } return b.done = !0, b } }, k.values = T, P.prototype = { constructor: P, reset: function (a) { if (this.prev = 0, this.next = 0, this.sent = this._sent = l, this.done = !1, this.delegate = null, this.method = "next", this.arg = l, this.tryEntries.forEach(U), !a) for (var b in this) "t" === b.charAt(0) && p.call(this, b) && !isNaN(+b.slice(1)) && (this[b] = l) }, stop: function () { this.done = !0; var a = this.tryEntries[0].completion; if ("throw" === a.type) throw a.arg; return this.rval }, dispatchException: function (c) { if (this.done) throw c; var d = this; function f(a, b) { return h.type = "throw", h.arg = c, d.next = a, b && (d.method = "next", d.arg = l), !!b } for (var g = this.tryEntries.length - 1; g >= 0; --g) { var i = this.tryEntries[g], h = i.completion; if ("root" === i.tryLoc) return f("end"); if (i.tryLoc <= this.prev) { var j = p.call(i, "catchLoc"), o = p.call(i, "finallyLoc"); if (j && o) { if (this.prev < i.catchLoc) return f(i.catchLoc, !0); if (this.prev < i.finallyLoc) return f(i.finallyLoc) } else if (j) { if (this.prev < i.catchLoc) return f(i.catchLoc, !0) } else { if (!o) throw Error("try statement without catch or finally"); if (this.prev < i.finallyLoc) return f(i.finallyLoc) } } } }, abrupt: function (a, b) { for (var c = this.tryEntries.length - 1; c >= 0; --c) { var d = this.tryEntries[c]; if (d.tryLoc <= this.prev && p.call(d, "finallyLoc") && this.prev < d.finallyLoc) { var f = d; break } } f && ("break" === a || "continue" === a) && f.tryLoc <= b && b <= f.finallyLoc && (f = null); var g = f ? f.completion : {}; return g.type = a, g.arg = b, f ? (this.method = "next", this.next = f.finallyLoc, x) : this.complete(g) }, complete: function (a, b) { if ("throw" === a.type) throw a.arg; return "break" === a.type || "continue" === a.type ? this.next = a.arg : "return" === a.type ? (this.rval = this.arg = a.arg, this.method = "return", this.next = "end") : "normal" === a.type && b && (this.next = b), x }, finish: function (a) { for (var b = this.tryEntries.length - 1; b >= 0; --b) { var c = this.tryEntries[b]; if (c.finallyLoc === a) return this.complete(c.completion, c.afterLoc), U(c), x } }, catch: function (e) { for (var l = this.tryEntries.length - 1; l >= 0; --l) { var J = this.tryEntries[l]; if (J.tryLoc === e) { var k = J.completion; if ("throw" === k.type) { var s = k.arg; U(J) } return s } } throw Error("illegal catch attempt") }, delegateYield: function (a, b, c) { return this.delegate = { iterator: T(a), resultName: b, nextLoc: c }, "next" === this.method && (this.arg = l), x } }, k } function be(a, b, c, d, f, g, i) { try { var h = a[g](i), j = h.value } catch (a) { return void c(a) } h.done ? b(j) : Promise.resolve(j).then(d, f) } function bf(j) { return function () { var i = this, h = arguments; return new Promise((function (b, c) { var d = j.apply(i, h); function f(a) { be(d, b, c, f, g, "next", a) } function g(a) { be(d, b, c, f, g, "throw", a) } f(void 0) })) } } function bg(a, b) { for (var c = 0; c < b.length; c++) { var d = b[c]; d.enumerable = d.enumerable || !1, d.configurable = !0, "value" in d && (d.writable = !0), Object.defineProperty(a, bh(d.key), d) } } function bi(a, b, c) { return b && bg(a.prototype, b), c && bg(a, c), Object.defineProperty(a, "prototype", { writable: !1 }), a } function bj(a, b) { if (!(a instanceof b)) throw new TypeError("Cannot call a class as a function") } function bw(d, f, g) { return f = E(f), function (b, c) { if (c && ("object" == e(c) || "function" == typeof c)) return c; if (void 0 !== c) throw new TypeError("Derived constructors may only return object or undefined"); return function (a) { if (void 0 === a) throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); return a }(b) }(d, V() ? Reflect.construct(f, g || [], E(d).constructor) : f.apply(d, g)) } function W(h) { var j = "function" == typeof Map ? new Map : void 0; return W = function (g) { if (null === g || !function (g) { try { return -1 !== Function.toString.call(g).indexOf("[native code]") } catch (j) { return "function" == typeof g } }(g)) return g; if ("function" != typeof g) throw new TypeError("Super expression must either be null or a function"); if (void 0 !== j) { if (j.has(g)) return j.get(g); j.set(g, i) } function i() { return function (a, b, c) { if (V()) return Reflect.construct.apply(null, arguments); var d = [null]; d.push.apply(d, b); var f = new (a.bind.apply(a, d)); return c && F(f, c.prototype), f }(g, arguments, E(this).constructor) } return i.prototype = Object.create(g.prototype, { constructor: { value: i, enumerable: !1, writable: !0, configurable: !0 } }), F(i, g) }, W(h) } function V() { try { var a = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], (function () { }))) } catch (a) { } return (V = function () { return !!a })() } function F(c, d) { return F = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (a, b) { return a.__proto__ = b, a }, F(c, d) } function E(b) { return E = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (a) { return a.__proto__ || Object.getPrototypeOf(a) }, E(b) } function bk(b, c) { var d = Object.keys(b); if (Object.getOwnPropertySymbols) { var f = Object.getOwnPropertySymbols(b); c && (f = f.filter((function (a) { return Object.getOwnPropertyDescriptor(b, a).enumerable }))), d.push.apply(d, f) } return d } function A(b) { for (var c = 1; c < arguments.length; c++) { var d = null != arguments[c] ? arguments[c] : {}; c % 2 ? bk(Object(d), !0).forEach((function (a) { bx(b, a, d[a]) })) : Object.getOwnPropertyDescriptors ? Object.defineProperties(b, Object.getOwnPropertyDescriptors(d)) : bk(Object(d)).forEach((function (a) { Object.defineProperty(b, a, Object.getOwnPropertyDescriptor(d, a)) })) } return b } function bx(a, b, c) { return (b = bh(b)) in a ? Object.defineProperty(a, b, { value: c, enumerable: !0, configurable: !0, writable: !0 }) : a[b] = c, a } function bh(f) { var g = function (a, b) { if ("object" != e(a) || !a) return a; var c = a[Symbol.toPrimitive]; if (void 0 !== c) { var d = c.call(a, b || "default"); if ("object" != e(d)) return d; throw new TypeError("@@toPrimitive must return a primitive value.") } return ("string" === b ? String : Number)(a) }(f, "string"); return "symbol" == e(g) ? g : g + "" } function by(b, c) { var d = "undefined" != typeof Symbol && b[Symbol.iterator] || b["@@iterator"]; if (!d) { if (Array.isArray(b) || (d = function (b, c) { if (b) { if ("string" == typeof b) return bl(b, c); var d = {}.toString.call(b).slice(8, -1); return "Object" === d && b.constructor && (d = b.constructor.name), "Map" === d || "Set" === d ? Array.from(b) : "Arguments" === d || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(d) ? bl(b, c) : void 0 } }(b)) || c && b && "number" == typeof b.length) { d && (b = d); var f = 0, g = function () { }; return { s: g, n: function () { return f >= b.length ? { done: !0 } : { done: !1, value: b[f++] } }, e: function (a) { throw a }, f: g } } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.") } var i, h = !0, j = !1; return { s: function () { d = d.call(b) }, n: function () { var a = d.next(); return h = a.done, a }, e: function (a) { j = !0, i = a }, f: function () { try { h || null == d.return || d.return() } finally { if (j) throw i } } } } function bl(a, b) { (null == b || b > a.length) && (b = a.length); for (var c = 0, d = Array(b); c < b; c++)d[c] = a[c]; return d } function u(a) { var b, c = "", d = by(new Uint8Array(a)); try { for (d.s(); !(b = d.n()).done;) { var f = b.value; c += String.fromCharCode(f) } } catch (a) { d.e(a) } finally { d.f() } return btoa(c).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "") } function G(a) { for (var b = a.replace(/-/g, "+").replace(/_/g, "/"), c = (4 - b.length % 4) % 4, d = b.padEnd(b.length + c, "="), f = atob(d), g = new ArrayBuffer(f.length), i = new Uint8Array(g), h = 0; h < f.length; h++)i[h] = f.charCodeAt(h); return g } function H() { var a; return bz.stubThis(void 0 !== (null === (a = window) || void 0 === a ? void 0 : a.PublicKeyCredential) && "function" == typeof window.PublicKeyCredential) } Object.defineProperty(n, "__esModule", { value: !0 }), n._browserSupportsWebAuthnInternals = n._browserSupportsWebAuthnAutofillInternals = n.WebAuthnError = n.WebAuthnAbortService = void 0, n.base64URLStringToBuffer = G, n.browserSupportsWebAuthn = H, n.browserSupportsWebAuthnAutofill = bm, n.bufferToBase64URLString = u, n.platformAuthenticatorIsAvailable = function () { if (!H()) return new Promise((function (e) { return e(!1) })); return PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable() }, n.startAuthentication = function (a) { return bn.apply(this, arguments) }, n.startRegistration = function (a) { return bo.apply(this, arguments) }; var bz = n._browserSupportsWebAuthnInternals = { stubThis: function (a) { return a } }; function bp(a) { var b = a.id; return A(A({}, a), {}, { id: G(b), transports: a.transports }) } function bq(a) { return "localhost" === a || /^((xn--[a-z0-9-]+|[a-z0-9]+(-[a-z0-9]+)*)\.)+([a-z]{2,}|xn--[a-z0-9-]+)$/i.test(a) } var m = n.WebAuthnError = function (i) { function h(a) { var b, c = a.message, d = a.code, f = a.cause, g = a.name; return bj(this, h), b = bw(this, h, [c, { cause: f }]), Object.defineProperty(b, "code", { enumerable: !0, configurable: !0, writable: !0, value: void 0 }), b.name = null != g ? g : f.name, b.code = d, b } return function (a, b) { if ("function" != typeof b && null !== b) throw new TypeError("Super expression must either be null or a function"); a.prototype = Object.create(b && b.prototype, { constructor: { value: a, writable: !0, configurable: !0 } }), Object.defineProperty(a, "prototype", { writable: !1 }), b && F(a, b) }(h, i), bi(h) }(W(Error)); function bA(a) { var b = a.error, c = a.options, d = c.publicKey; if (!d) throw Error("options was missing required publicKey property"); if ("AbortError" === b.name) { if (c.signal instanceof AbortSignal) return new m({ message: "注册流程收到了中止信号", code: "ERROR_CEREMONY_ABORTED", cause: b }) } else if ("ConstraintError" === b.name) { var f, g, i; if (!0 === (null === (f = d.authenticatorSelection) || void 0 === f ? void 0 : f.requireResidentKey)) return new m({ message: "要求使用可发现凭证,但可用的验证器不支持", code: "ERROR_AUTHENTICATOR_MISSING_DISCOVERABLE_CREDENTIAL_SUPPORT", cause: b }); if ("conditional" === c.mediation && "required" === (null === (g = d.authenticatorSelection) || void 0 === g ? void 0 : g.userVerification)) return new m({ message: "自动注册时要求用户验证,但无法执行", code: "ERROR_AUTO_REGISTER_USER_VERIFICATION_FAILURE", cause: b }); if ("required" === (null === (i = d.authenticatorSelection) || void 0 === i ? void 0 : i.userVerification)) return new m({ message: "要求用户验证,但可用的验证器不支持", code: "ERROR_AUTHENTICATOR_MISSING_USER_VERIFICATION_SUPPORT", cause: b }) } else { if ("InvalidStateError" === b.name) return new m({ message: "该验证器已被注册过", code: "ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED", cause: b }); if ("NotAllowedError" === b.name) return new m({ message: b.message, code: "ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY", cause: b }); if ("NotSupportedError" === b.name) return 0 === d.pubKeyCredParams.filter((function (a) { return "public-key" === a.type })).length ? new m({ message: 'pubKeyCredParams 中没有 type 为 "public-key" 的条目', code: "ERROR_MALFORMED_PUBKEYCREDPARAMS", cause: b }) : new m({ message: "没有可用的验证器支持指定的任何 pubKeyCredParams 算法", code: "ERROR_AUTHENTICATOR_NO_SUPPORTED_PUBKEYCREDPARAMS_ALG", cause: b }); if ("SecurityError" === b.name) { var h = window.location.hostname; if (!bq(h)) return new m({ message: "".concat(window.location.hostname, " 是无效域名"), code: "ERROR_INVALID_DOMAIN", cause: b }); if (d.rp.id !== h) return new m({ message: 'RP ID "'.concat(d.rp.id, '" 对于当前域名无效'), code: "ERROR_INVALID_RP_ID", cause: b }) } else if ("TypeError" === b.name) { if (d.user.id.byteLength < 1 || d.user.id.byteLength > 64) return new m({ message: "用户 ID 长度不在1~64字节之间", code: "ERROR_INVALID_USER_ID_LENGTH", cause: b }) } else if ("UnknownError" === b.name) return new m({ message: "验证器无法处理指定的选项或创建新的凭证", code: "ERROR_AUTHENTICATOR_GENERAL_ERROR", cause: b }) } return b } var bB = function () { return bi((function c() { bj(this, c), Object.defineProperty(this, "controller", { enumerable: !0, configurable: !0, writable: !0, value: void 0 }) }), [{ key: "createNewAbortSignal", value: function () { if (this.controller) { var a = new Error("正在取消已有的 WebAuthn API 调用,然后进行新的调用"); a.name = "AbortError", this.controller.abort(a) } var b = new AbortController; return this.controller = b, b.signal } }, { key: "cancelCeremony", value: function () { if (this.controller) { var a = new Error("正在取消 WebAuthn API 调用"); a.name = "AbortError", this.controller.abort(a), this.controller = void 0 } } }]) }(), br = n.WebAuthnAbortService = new bB, bC = ["cross-platform", "platform"]; function bs(a) { if (a && !(bC.indexOf(a) < 0)) return a } function bo() { return (bo = bf(I().mark((function B(b) { var c, d, f, g, i, h, j, o, r, y, l, k, s, p, t, q, v, z; return I().wrap((function (a) { for (; ;)switch (a.prev = a.next) { case 0: if (!b.optionsJSON && b.challenge && (console.warn("startRegistration() 调用方式不正确;将尝试继续使用提供的选项,但建议按照正确的调用结构重构;"), b = { optionsJSON: b }), f = (d = b).optionsJSON, g = d.useAutoRegister, i = void 0 !== g && g, H()) { a.next = 4; break } throw new Error("当前浏览器不支持 WebAuthn"); case 4: return h = A(A({}, f), {}, { challenge: G(f.challenge), user: A(A({}, f.user), {}, { id: G(f.user.id) }), excludeCredentials: null === (c = f.excludeCredentials) || void 0 === c ? void 0 : c.map(bp) }), j = {}, i && (j.mediation = "conditional"), j.publicKey = h, j.signal = br.createNewAbortSignal(), a.prev = 9, a.next = 12, navigator.credentials.create(j); case 12: o = a.sent, a.next = 18; break; case 15: throw a.prev = 15, a.t0 = a.catch(9), bA({ error: a.t0, options: j }); case 18: if (o) { a.next = 20; break } throw new Error("注册未完成"); case 20: if (y = (r = o).id, l = r.rawId, k = r.response, s = r.type, p = void 0, "function" == typeof k.getTransports && (p = k.getTransports()), t = void 0, "function" == typeof k.getPublicKeyAlgorithm) try { t = k.getPublicKeyAlgorithm() } catch (a) { X("getPublicKeyAlgorithm()", a) } if (q = void 0, "function" == typeof k.getPublicKey) try { null !== (v = k.getPublicKey()) && (q = u(v)) } catch (a) { X("getPublicKey()", a) } if ("function" == typeof k.getAuthenticatorData) try { z = u(k.getAuthenticatorData()) } catch (a) { X("getAuthenticatorData()", a) } return a.abrupt("return", { id: y, rawId: u(l), response: { attestationObject: u(k.attestationObject), clientDataJSON: u(k.clientDataJSON), transports: p, publicKeyAlgorithm: t, publicKey: q, authenticatorData: z }, type: s, clientExtensionResults: o.getClientExtensionResults(), authenticatorAttachment: bs(o.authenticatorAttachment) }); case 29: case "end": return a.stop() } }), B, null, [[9, 15]]) })))).apply(this, arguments) } function X(a, b) { console.warn("拦截此 WebAuthn API 调用的浏览器扩展错误地实现了 ".concat(a, ";请向扩展开发者报告此问题;\n"), b) } function bm() { if (!H()) return Y.stubThis(new Promise((function (b) { return b(!1) }))); var b = window.PublicKeyCredential; return void 0 === (null == b ? void 0 : b.isConditionalMediationAvailable) ? Y.stubThis(new Promise((function (a) { return a(!1) }))) : Y.stubThis(b.isConditionalMediationAvailable()) } var Y = n._browserSupportsWebAuthnAutofillInternals = { stubThis: function (a) { return a } }; function bD(a) { var b = a.error, c = a.options, d = c.publicKey; if (!d) throw Error("options was missing required publicKey property"); if ("AbortError" === b.name) { if (c.signal instanceof AbortSignal) return new m({ message: "认证流程收到了中止信号", code: "ERROR_CEREMONY_ABORTED", cause: b }) } else { if ("NotAllowedError" === b.name) return new m({ message: b.message, code: "ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY", cause: b }); if ("SecurityError" === b.name) { var f = window.location.hostname; if (!bq(f)) return new m({ message: "".concat(window.location.hostname, " 是无效域名"), code: "ERROR_INVALID_DOMAIN", cause: b }); if (d.rpId !== f) return new m({ message: 'RP ID "'.concat(d.rpId, '" 对于当前域名无效'), code: "ERROR_INVALID_RP_ID", cause: b }) } else if ("UnknownError" === b.name) return new m({ message: "验证器无法处理指定的选项或创建新的断言签名", code: "ERROR_AUTHENTICATOR_GENERAL_ERROR", cause: b }) } return b } function bn() { return (bn = bf(I().mark((function B(b) { var c, d, f, g, i, h, j, o, r, y, l, k, s, p, t, q, v, z; return I().wrap((function (a) { for (; ;)switch (a.prev = a.next) { case 0: if (!b.optionsJSON && b.challenge && (console.warn("startAuthentication() 调用方式不正确;将尝试继续使用提供的选项,但建议按照正确的调用结构重构;"), b = { optionsJSON: b }), f = (d = b).optionsJSON, g = d.useBrowserAutofill, i = void 0 !== g && g, h = d.verifyBrowserAutofillInput, j = void 0 === h || h, H()) { a.next = 4; break } throw new Error("当前浏览器不支持 WebAuthn"); case 4: if (0 !== (null === (c = f.allowCredentials) || void 0 === c ? void 0 : c.length) && (o = null === (r = f.allowCredentials) || void 0 === r ? void 0 : r.map(bp)), y = A(A({}, f), {}, { challenge: G(f.challenge), allowCredentials: o }), l = {}, !i) { a.next = 17; break } return a.next = 10, bm(); case 10: if (a.sent) { a.next = 12; break } throw Error("当前浏览器不支持 WebAuthn 自动填充"); case 12: if (!(document.querySelectorAll("input[autocomplete$='webauthn']").length < 1 && j)) { a.next = 15; break } throw Error('未检测到任何 `autocomplete` 属性值以 "webauthn" 结尾的 <input> 元素'); case 15: l.mediation = "conditional", y.allowCredentials = []; case 17: return l.publicKey = y, l.signal = br.createNewAbortSignal(), a.prev = 19, a.next = 22, navigator.credentials.get(l); case 22: k = a.sent, a.next = 28; break; case 25: throw a.prev = 25, a.t0 = a.catch(19), bD({ error: a.t0, options: l }); case 28: if (k) { a.next = 30; break } throw new Error("认证未完成"); case 30: return p = (s = k).id, t = s.rawId, q = s.response, v = s.type, z = void 0, q.userHandle && (z = u(q.userHandle)), a.abrupt("return", { id: p, rawId: u(t), response: { authenticatorData: u(q.authenticatorData), clientDataJSON: u(q.clientDataJSON), signature: u(q.signature), userHandle: z }, type: v, clientExtensionResults: k.getClientExtensionResults(), authenticatorAttachment: bs(k.authenticatorAttachment) }); case 34: case "end": return a.stop() } }), B, null, [[19, 25]]) })))).apply(this, arguments) } }));