@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.
@@ -0,0 +1,203 @@
1
+ import { base64URLStringToBuffer, bufferToBase64URLString } from './base64urlAndBuffer.js';
2
+ import { _browserSupportsWebAuthnInternals, browserSupportsWebAuthn } from './browserSupportsWebAuthn.js';
3
+ import { _browserSupportsWebAuthnAutofillInternals, browserSupportsWebAuthnAutofill } from './browserSupportsWebAuthnAutofill.js';
4
+ import { identifyAuthenticationError } from './identifyAuthenticationError.js';
5
+ import { identifyRegistrationError } from './identifyRegistrationError.js';
6
+ import { isValidDomain } from './isValidDomain.js';
7
+ import { platformAuthenticatorIsAvailable } from './platformAuthenticatorIsAvailable.js';
8
+ import { toAuthenticatorAttachment } from './toAuthenticatorAttachment.js';
9
+ import { toPublicKeyCredentialDescriptor } from './toPublicKeyCredentialDescriptor.js';
10
+ import { WebAuthnAbortService } from './WebAuthnAbortService.js';
11
+ import { WebAuthnError } from './webAuthnError.js';
12
+
13
+ // ================================ base64urlAndBuffer.js ================================
14
+ /**
15
+ * ```js
16
+ * // 文件导出内容
17
+ * base64URLStringToBuffer(); // 将 Base64URL 编码的字符串转换为 Array Buffer;
18
+ * bufferToBase64URLString(); // 将给定的数组缓冲区转换为 Base64URL 编码的字符串;
19
+ * ```
20
+ * >查看定义:@see {@link base64URLStringToBuffer}、{@link bufferToBase64URLString}
21
+ */
22
+ declare module './base64urlAndBuffer.js' {
23
+ export * from './base64urlAndBuffer.js';
24
+ }
25
+
26
+ // ================================ browserSupportsWebAuthn.js ================================
27
+ /**
28
+ * ```js
29
+ * // 文件导出内容
30
+ * const _browserSupportsWebAuthnInternals={}; // 测试期间对返回值进行桩(stub)处理
31
+ * browserSupportsWebAuthn(); // 判断当前浏览器是否支持 WebAuthn
32
+ * ```
33
+ * >查看定义:@see {@link _browserSupportsWebAuthnInternals}、{@link browserSupportsWebAuthn}
34
+ */
35
+ declare module './browserSupportsWebAuthn.js' {
36
+ export * from './browserSupportsWebAuthn.js';
37
+ }
38
+
39
+ // ================================ browserSupportsWebAuthnAutofill.js ================================
40
+ /**
41
+ * ```js
42
+ * // 文件导出内容
43
+ * const _browserSupportsWebAuthnAutofillInternals={}; // 测试期间模拟返回值;
44
+ * browserSupportsWebAuthnAutofill(); // 判断浏览器是否支持条件式UI;
45
+ * ```
46
+ * >查看定义:@see {@link _browserSupportsWebAuthnAutofillInternals}、{@link browserSupportsWebAuthnAutofill}
47
+ */
48
+ declare module './browserSupportsWebAuthnAutofill.js' {
49
+ export * from './browserSupportsWebAuthnAutofill.js';
50
+ }
51
+
52
+ // ================================ identifyAuthenticationError.js ================================
53
+ /**
54
+ * ```js
55
+ * // 文件导出内容
56
+ * identifyAuthenticationError(); // 尝试推断调用 `navigator.credentials.get()` 后引发错误的原因;
57
+ * ```
58
+ * >查看定义:@see {@link identifyAuthenticationError}
59
+ */
60
+ declare module './identifyAuthenticationError.js' {
61
+ export * from './identifyAuthenticationError.js';
62
+ }
63
+
64
+ // ================================ identifyRegistrationError.js ================================
65
+ /**
66
+ * ```js
67
+ * // 文件导出内容
68
+ * identifyRegistrationError(); // 尝试推断调用 `navigator.credentials.create()` 后引发错误的原因;
69
+ * ```
70
+ * >查看定义:@see {@link identifyRegistrationError}
71
+ */
72
+ declare module './identifyRegistrationError.js' {
73
+ export * from './identifyRegistrationError.js';
74
+ }
75
+
76
+ // ================================ isValidDomain.js ================================
77
+ /**
78
+ * ```js
79
+ * // 文件导出内容
80
+ * isValidDomain(); // 判断主机名是否符合验证规范;
81
+ * ```
82
+ * >查看定义:@see {@link isValidDomain}
83
+ */
84
+ declare module './isValidDomain.js' {
85
+ export * from './isValidDomain.js';
86
+ }
87
+
88
+ // ================================ platformAuthenticatorIsAvailable.js ================================
89
+ /**
90
+ * ```js
91
+ * // 文件导出内容
92
+ * platformAuthenticatorIsAvailable(); // 判断浏览器是否能够与内置身份验证器通信;
93
+ * ```
94
+ * >查看定义:@see {@link platformAuthenticatorIsAvailable}
95
+ */
96
+ declare module './platformAuthenticatorIsAvailable.js' {
97
+ export * from './platformAuthenticatorIsAvailable.js';
98
+ }
99
+
100
+ // ================================ toAuthenticatorAttachment.js ================================
101
+ /**
102
+ * ```js
103
+ * // 文件导出内容
104
+ * toAuthenticatorAttachment(); // 尝试将 `string` 值强制转换为已知的 `AuthenticatorAttachment` 类型
105
+ * ```
106
+ * >查看定义:@see {@link toAuthenticatorAttachment}
107
+ */
108
+ declare module './toAuthenticatorAttachment.js' {
109
+ export * from './toAuthenticatorAttachment.js';
110
+ }
111
+
112
+ // ================================ toPublicKeyCredentialDescriptor.js ================================
113
+ /**
114
+ * ```js
115
+ * // 文件导出内容
116
+ * toPublicKeyCredentialDescriptor(); // 将描述符中的 Base64URL 字符串 `id` 转换为 ArrayBuffer,以适配 WebAuthn API;
117
+ * ```
118
+ * >查看定义:@see {@link toPublicKeyCredentialDescriptor}
119
+ */
120
+ declare module './toPublicKeyCredentialDescriptor.js' {
121
+ export * from './toPublicKeyCredentialDescriptor.js';
122
+ }
123
+
124
+ // ================================ webAuthnAbortService.js ================================
125
+ /**
126
+ * ```js
127
+ * // 文件导出内容
128
+ * const WebAuthnAbortService = new BaseWebAuthnAbortService(); // 服务单例,用于确保同一时间只有一个 WebAuthn 仪式处于活动状态;
129
+ * ```
130
+ * >查看定义:@see {@link WebAuthnAbortService}
131
+ */
132
+ declare module './webAuthnAbortService.js' {
133
+ export * from './webAuthnAbortService.js';
134
+ }
135
+
136
+ // ================================ webAuthnError.js ================================
137
+ /**
138
+ * ```js
139
+ * // 文件导出内容
140
+ * class WebAuthnError{}; // 自定义错误,用于更细致地说明规范中八种错误被抛出的原因;
141
+ * ```
142
+ * >查看定义:@see {@link WebAuthnError}
143
+ */
144
+ declare module './webAuthnError.js' {
145
+ export type WebAuthnErrorCode =
146
+ | 'ERROR_CEREMONY_ABORTED' | 'ERROR_INVALID_DOMAIN' | 'ERROR_INVALID_RP_ID' | 'ERROR_INVALID_USER_ID_LENGTH'
147
+ | 'ERROR_MALFORMED_PUBKEYCREDPARAMS' | 'ERROR_AUTHENTICATOR_GENERAL_ERROR'
148
+ | 'ERROR_AUTHENTICATOR_MISSING_DISCOVERABLE_CREDENTIAL_SUPPORT'
149
+ | 'ERROR_AUTHENTICATOR_MISSING_USER_VERIFICATION_SUPPORT'
150
+ | 'ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED' | 'ERROR_AUTHENTICATOR_NO_SUPPORTED_PUBKEYCREDPARAMS_ALG'
151
+ | 'ERROR_AUTO_REGISTER_USER_VERIFICATION_FAILURE' | 'ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY';
152
+ export * from './webAuthnError.js';
153
+ }
154
+
155
+ // ================================ 导出入口 ================================
156
+ /**
157
+ * 模块导出内容:
158
+ * ```js
159
+ * // 类型
160
+ * type WebAuthnErrorCode;
161
+ *
162
+ * // 常量
163
+ * const _browserSupportsWebAuthnInternals={}; // 测试期间对返回值进行桩(stub)处理
164
+ * const _browserSupportsWebAuthnAutofillInternals={}; // 测试期间模拟返回值;
165
+ * const WebAuthnAbortService = new BaseWebAuthnAbortService(); // 服务单例,用于确保同一时间只有一个 WebAuthn 仪式处于活动状态;
166
+ *
167
+ * // 函数
168
+ * base64URLStringToBuffer(); // 将 Base64URL 编码的字符串转换为 Array Buffer;
169
+ * bufferToBase64URLString(); // 将给定的数组缓冲区转换为 Base64URL 编码的字符串;
170
+ * browserSupportsWebAuthn(); // 判断当前浏览器是否支持 WebAuthn
171
+ * browserSupportsWebAuthnAutofill(); // 判断浏览器是否支持条件式UI;
172
+ * identifyAuthenticationError(); // 尝试推断调用 `navigator.credentials.get()` 后引发错误的原因;
173
+ * identifyRegistrationError(); // 尝试推断调用 `navigator.credentials.create()` 后引发错误的原因;
174
+ * isValidDomain(); // 判断主机名是否符合验证规范;
175
+ * platformAuthenticatorIsAvailable(); // 判断浏览器是否能够与内置身份验证器通信;
176
+ * toAuthenticatorAttachment(); // 尝试将 `string` 值强制转换为已知的 `AuthenticatorAttachment` 类型
177
+ * toPublicKeyCredentialDescriptor(); // 将描述符中的Base64URL字符串`id`转换为ArrayBuffer,以适配 WebAuthn API;
178
+ *
179
+ * // 类
180
+ * class WebAuthnError{}; // 自定义错误,用于更细致地说明规范中八种错误被抛出的原因;
181
+ * ```
182
+ * >查看定义:@see
183
+ * - 类型 {@link WebAuthnErrorCode}
184
+ * - 常量 {@link _browserSupportsWebAuthnInternals}、{@link _browserSupportsWebAuthnAutofillInternals}、{@link WebAuthnAbortService}
185
+ * - 函数 {@link base64URLStringToBuffer}、{@link bufferToBase64URLString}、 {@link browserSupportsWebAuthn}、
186
+ * {@link browserSupportsWebAuthnAutofill}、{@link identifyAuthenticationError}、{@link identifyRegistrationError}、
187
+ * {@link isValidDomain}、{@link platformAuthenticatorIsAvailable}、{@link toAuthenticatorAttachment}、
188
+ * {@link toPublicKeyCredentialDescriptor}
189
+ * - 类 {@link WebAuthnError}
190
+ */
191
+ declare module './index.js' {
192
+ export * from './base64urlAndBuffer.js';
193
+ export * from './browserSupportsWebAuthn.js';
194
+ export * from './browserSupportsWebAuthnAutofill.js';
195
+ export * from './identifyAuthenticationError.js';
196
+ export * from './identifyRegistrationError.js';
197
+ export * from './isValidDomain.js';
198
+ export * from './platformAuthenticatorIsAvailable.js';
199
+ export * from './toAuthenticatorAttachment.js';
200
+ export * from './toPublicKeyCredentialDescriptor.js';
201
+ export * from './webAuthnAbortService.js';
202
+ export * from './webAuthnError.js';
203
+ }
@@ -0,0 +1,11 @@
1
+ export * from './base64urlAndBuffer.js';
2
+ export * from './browserSupportsWebAuthn.js';
3
+ export * from './browserSupportsWebAuthnAutofill.js';
4
+ export * from './identifyAuthenticationError.js';
5
+ export * from './identifyRegistrationError.js';
6
+ export * from './isValidDomain.js';
7
+ export * from './platformAuthenticatorIsAvailable.js';
8
+ export * from './toAuthenticatorAttachment.js';
9
+ export * from './toPublicKeyCredentialDescriptor.js';
10
+ export * from './WebAuthnAbortService.js';
11
+ export * from './WebAuthnError.js';
@@ -0,0 +1,18 @@
1
+ /**
2
+ * 一个简单的测试,用于判断主机名是否为格式正确的域名
3
+ * - 查看定义:@see {@link isValidDomain}
4
+ * - “有效域名”的定义参见:https://url.spec.whatwg.org/#valid-domain
5
+ *
6
+ * - 正则表达式最初来源于此处,后经改编以增加 punycode 支持:
7
+ * https://www.oreilly.com/library/view/regular-expressions-cookbook/9781449327453/ch08s15.html
8
+ *
9
+ * @param {string} hostname - 待验证的主机名
10
+ * @returns {boolean} 如果主机名是 localhost 或符合有效域名格式则返回 true,否则 false
11
+ */
12
+ const isValidDomain = hostname => {
13
+ return (
14
+ // 将 localhost 视为有效,因为它在安全上下文方面是没问题的,支持 punycode (ACE) 或 ASCII 标签和域名
15
+ hostname === 'localhost' || /^((xn--[a-z0-9-]+|[a-z0-9]+(-[a-z0-9]+)*)\.)+([a-z]{2,}|xn--[a-z0-9-]+)$/i.test(hostname));
16
+ }
17
+
18
+ export { isValidDomain };
@@ -0,0 +1,14 @@
1
+ import { browserSupportsWebAuthn } from './browserSupportsWebAuthn.js';
2
+
3
+ /**
4
+ * 判断浏览器是否能够与内置身份验证器(如 Touch ID、Android 指纹扫描器或 Windows Hello)通信;
5
+ * 该方法无法告知平台身份验证器的具体名称;
6
+ * - 查看定义:@see {@link platformAuthenticatorIsAvailable}
7
+ * @returns {Promise<boolean>} 如果平台身份验证器可用,则解析为 true,否则为 false;
8
+ */
9
+ const platformAuthenticatorIsAvailable = () => {
10
+ if (!browserSupportsWebAuthn()) return new Promise(resolve => resolve(false));
11
+ return PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
12
+ }
13
+
14
+ export { platformAuthenticatorIsAvailable };
@@ -0,0 +1,15 @@
1
+ const attachments = ['cross-platform', 'platform'];
2
+
3
+ /**
4
+ * 尝试将 `string` 值强制转换为已知的 `AuthenticatorAttachment` 类型
5
+ * - 查看定义:@see {@link toAuthenticatorAttachment}
6
+ * @param {string} attachment - 待转换的字符串
7
+ * @returns {'cross-platform' | 'platform' | undefined} 如果是合法的认证器附着类型则返回对应字符串,否则返回 undefined;
8
+ */
9
+ const toAuthenticatorAttachment = attachment => {
10
+ if (!attachment) return;
11
+ if (attachments.indexOf(attachment) < 0) return;
12
+ return attachment;
13
+ }
14
+
15
+ export { toAuthenticatorAttachment };
@@ -0,0 +1,23 @@
1
+ import { base64URLStringToBuffer } from './base64urlAndBuffer.js';
2
+
3
+ /**
4
+ * 将描述符中的 Base64URL 字符串 `id` 转换为 ArrayBuffer,以适配 WebAuthn API;
5
+ * - 查看定义:@see {@link toPublicKeyCredentialDescriptor}
6
+ * @param {Object} descriptor - 包含 `id` (Base64URL 字符串) 的凭证描述符;
7
+ * @param {string} descriptor.id - 凭证 ID(Base64URL 编码)
8
+ * @param {string[]} [descriptor.transports] - 可选的传输方式列表(如 ['usb', 'nfc'])
9
+ * @returns {PublicKeyCredentialDescriptor} 转换后的描述符,可直接用于 WebAuthn;
10
+ */
11
+ const toPublicKeyCredentialDescriptor = descriptor => {
12
+ const { id } = descriptor;
13
+ return {
14
+ ...descriptor, id: base64URLStringToBuffer(id),
15
+ /**
16
+ * `descriptor.transports` 是一个包含较新传输方式的数组,这些传输方式属于我们的 `AuthenticatorTransportFuture` 类型,
17
+ * 而 TypeScript 的 DOM 库对此尚不了解;让 TS 相信我们的传输方式列表可以传递给 WebAuthn,因为浏览器会识别这些新值;
18
+ */
19
+ transports: descriptor.transports,
20
+ };
21
+ }
22
+
23
+ export { toPublicKeyCredentialDescriptor };
@@ -0,0 +1,51 @@
1
+ /**
2
+ * WebAuthn 中止服务的基础类
3
+ */
4
+ class BaseWebAuthnAbortService {
5
+ constructor() {
6
+ /**
7
+ * 当前活动的 AbortController 实例
8
+ * @type {AbortController | undefined}
9
+ */
10
+ this.controller = undefined;
11
+ }
12
+
13
+ /**
14
+ * 创建新的 AbortSignal,并中止任何进行中的 WebAuthn 仪式
15
+ *
16
+ * @returns {AbortSignal} 可用于 `navigator.credentials.create()` 或 `navigator.credentials.get()` 的 `signal` 选项
17
+ */
18
+ createNewAbortSignal() {
19
+ // 中止任何现有的 navigator.credentials.create() 或 navigator.credentials.get() 调用
20
+ if (this.controller) {
21
+ const abortError = new Error('取消当前 WebAuthn API 调用,使用新的调用');
22
+ abortError.name = 'AbortError', this.controller.abort(abortError);
23
+ }
24
+ const newController = new AbortController();
25
+ this.controller = newController;
26
+ return newController.signal;
27
+ }
28
+
29
+ /**
30
+ * 手动取消当前正在进行的 WebAuthn 仪式
31
+ * @returns {void}
32
+ */
33
+ cancelCeremony() {
34
+ if (this.controller) {
35
+ const abortError = new Error('手动取消现有的 WebAuthn API 调用');
36
+ abortError.name = 'AbortError', this.controller.abort(abortError), this.controller = undefined;
37
+ }
38
+ }
39
+ }
40
+
41
+ /**
42
+ * 服务单例,用于确保同一时间只有一个 WebAuthn 仪式处于活动状态;
43
+ * **@flun/webauthn-browser** 的使用者通常不需要使用此服务,但它可以帮助
44
+ * 使用客户端路由的项目开发者更好地控制其用户体验以响应路由导航事件;
45
+ * - 查看定义:@see {@link WebAuthnAbortService}
46
+ *
47
+ * @type {BaseWebAuthnAbortService}
48
+ */
49
+ const WebAuthnAbortService = new BaseWebAuthnAbortService();
50
+
51
+ export { BaseWebAuthnAbortService, WebAuthnAbortService };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * 自定义错误,用于在调用 `navigator.credentials.create()` 或 `navigator.credentials.get()` 后,
3
+ * 更细致地说明规范中描述的八种错误之一被抛出的原因:
4
+ *
5
+ * - `AbortError`
6
+ * - `ConstraintError`
7
+ * - `InvalidStateError`
8
+ * - `NotAllowedError`
9
+ * - `NotSupportedError`
10
+ * - `SecurityError`
11
+ * - `TypeError`
12
+ * - `UnknownError`
13
+ *
14
+ * 错误消息通过研究规范确定,以了解在哪些场景下会抛出特定错误;
15
+ * - 查看定义:@see {@link WebAuthnError}
16
+ */
17
+ class WebAuthnError extends Error {
18
+ /**
19
+ * @param {Object} params - 错误配置参数
20
+ * @param {string} params.message - 错误消息
21
+ * @param {string} params.code - 错误码(规范中定义的错误名称之一)
22
+ * @param {Error} [params.cause] - 导致此错误的原始错误对象
23
+ * @param {string} [params.name] - 自定义错误名称,默认使用 cause 的 name
24
+ */
25
+ constructor({ message, code, cause, name }) {
26
+ super(message, { cause }), this.name = name ?? cause.name, this.code = code;
27
+ }
28
+ }
29
+
30
+ export { WebAuthnError };
package/index.d.ts ADDED
@@ -0,0 +1,42 @@
1
+ import {
2
+ bufferToBase64URLString, base64URLStringToBuffer, _browserSupportsWebAuthnInternals, browserSupportsWebAuthn,
3
+ _browserSupportsWebAuthnAutofillInternals, browserSupportsWebAuthnAutofill, platformAuthenticatorIsAvailable,
4
+ WebAuthnAbortService, WebAuthnError
5
+ } from './helpers/index.js';
6
+ import { startAuthentication, startRegistration } from './methods/index.js';
7
+
8
+ /**
9
+ * 模块导出内容:
10
+ * ```js
11
+ * // 登录和注册处理函数
12
+ * startAuthentication(); // 通过 WebAuthn 断言开始身份验证器“登录”
13
+ * startRegistration(); // 通过 WebAuthn 证明开始认证器“注册”
14
+ *
15
+ * // 工具
16
+ * const _browserSupportsWebAuthnInternals={}; // 测试期间对返回值进行桩(stub)处理
17
+ * const _browserSupportsWebAuthnAutofillInternals={}; // 测试期间模拟返回值;
18
+ * const WebAuthnAbortService = new BaseWebAuthnAbortService(); // 服务单例,用于确保同一时间只有一个 WebAuthn 仪式处于活动状态;
19
+ *
20
+ * base64URLStringToBuffer(); // 将 Base64URL 编码的字符串转换为 Array Buffer;
21
+ * bufferToBase64URLString(); // 将给定的数组缓冲区转换为 Base64URL 编码的字符串;
22
+ * browserSupportsWebAuthn(); // 判断当前浏览器是否支持 WebAuthn
23
+ * browserSupportsWebAuthnAutofill(); // 判断浏览器是否支持条件式UI;
24
+ * platformAuthenticatorIsAvailable(); // 判断浏览器是否能够与内置身份验证器通信;
25
+ *
26
+ * class WebAuthnError{}; // 自定义错误,用于更细致地说明规范中八种错误被抛出的原因;
27
+ * ```
28
+ * >查看定义:@see
29
+ *- 登录和注册处理: {@link startAuthentication}、{@link startRegistration}
30
+ *- 工具: {@link _browserSupportsWebAuthnInternals}、{@link _browserSupportsWebAuthnAutofillInternals}、{@link WebAuthnAbortService}、
31
+ * {@link base64URLStringToBuffer}、{@link bufferToBase64URLString}、 {@link browserSupportsWebAuthn}、
32
+ * {@link browserSupportsWebAuthnAutofill}、{@link platformAuthenticatorIsAvailable}
33
+ * >{@link WebAuthnError}
34
+ */
35
+ declare module './index.js' {
36
+ export {
37
+ bufferToBase64URLString, base64URLStringToBuffer, _browserSupportsWebAuthnInternals, browserSupportsWebAuthn,
38
+ _browserSupportsWebAuthnAutofillInternals, browserSupportsWebAuthnAutofill, platformAuthenticatorIsAvailable,
39
+ WebAuthnAbortService, WebAuthnError
40
+ } from './helpers/index.js';
41
+ export * from './methods/index.js';
42
+ }
package/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export {
2
+ bufferToBase64URLString, base64URLStringToBuffer, browserSupportsWebAuthn, browserSupportsWebAuthnAutofill,
3
+ platformAuthenticatorIsAvailable, WebAuthnAbortService, WebAuthnError
4
+ } from './helpers/index.js';
5
+ export * from './methods/index.js';
@@ -0,0 +1,40 @@
1
+ import { startAuthentication } from './startAuthentication.js';
2
+ import { startRegistration } from './startRegistration.js';
3
+
4
+ // =================================== startAuthentication.js ===================================
5
+ /**
6
+ * ```js
7
+ * // 文件导出内容
8
+ * startAuthentication(); // 通过 WebAuthn 断言开始身份验证器“登录”
9
+ * ```
10
+ * >查看定义:@see {@link startAuthentication}
11
+ */
12
+ declare module './startAuthentication.js' {
13
+ export * from './startAuthentication.js';
14
+ }
15
+
16
+ // =================================== startRegistration.js ===================================
17
+ /**
18
+ * ```js
19
+ * // 文件导出内容
20
+ * startRegistration(); // 通过 WebAuthn 证明开始认证器“注册”
21
+ * ```
22
+ * >查看定义:@see {@link startRegistration}
23
+ */
24
+ declare module './startRegistration.js' {
25
+ export * from './startRegistration.js';
26
+ }
27
+
28
+ // ================================ 导出入口 ================================
29
+ /**
30
+ * 模块导出内容:
31
+ * ```js
32
+ * startAuthentication(); // 通过 WebAuthn 断言开始身份验证器“登录”
33
+ * startRegistration(); // 通过 WebAuthn 证明开始认证器“注册”
34
+ * ```
35
+ * >查看定义:@see {@link startAuthentication}、{@link startRegistration}
36
+ */
37
+ declare module './index.js' {
38
+ export * from './startAuthentication.js';
39
+ export * from './startRegistration.js';
40
+ }
@@ -0,0 +1,2 @@
1
+ export * from './startAuthentication.js';
2
+ export * from './startRegistration.js';
@@ -0,0 +1,94 @@
1
+ import {
2
+ bufferToBase64URLString, base64URLStringToBuffer, browserSupportsWebAuthn, browserSupportsWebAuthnAutofill,
3
+ identifyAuthenticationError, toAuthenticatorAttachment, toPublicKeyCredentialDescriptor, WebAuthnAbortService
4
+ } from '../helpers/index.js';
5
+
6
+ /**
7
+ * 通过 WebAuthn 断言开始身份验证器“登录”
8
+ * - 查看定义:@see {@link startAuthentication}
9
+ * @param {Object} options - 配置选项
10
+ * @param {Object} options.optionsJSON - 来自 **@flun/webauthn-server** 的 `generateAuthenticationOptions()` 的输出
11
+ * @param {boolean} [options.useBrowserAutofill=false] - 初始化条件式 UI,以支持通过浏览器自动填充提示进行登录
12
+ * @param {boolean} [options.verifyBrowserAutofillInput=true] - 当 `useBrowserAutofill` 为 `true` 时,确保存在合适的 `<input>` 元素
13
+ * @returns {Promise<{
14
+ * id: string,
15
+ * rawId: string,
16
+ * response: {
17
+ * authenticatorData: string,
18
+ * clientDataJSON: string,
19
+ * signature: string,
20
+ * userHandle?: string
21
+ * },
22
+ * type: string,
23
+ * clientExtensionResults: AuthenticationExtensionsClientOutputs,
24
+ * authenticatorAttachment: string
25
+ * }>}
26
+ */
27
+ const startAuthentication = async options => {
28
+ // 有意检查旧的调用结构,以警告不正确的 API 调用
29
+ if (!options.optionsJSON && options.challenge) {
30
+ console.warn("startAuthentication() 调用方式不正确;它将尝试使用提供的选项继续执行,但应重构此调用以使用预期的调用结构;");
31
+ options = { optionsJSON: options }; // 将作为位置参数传入的 options 重新赋值给预期变量
32
+ }
33
+ if (!browserSupportsWebAuthn()) throw new Error('此浏览器不支持 WebAuthn');
34
+
35
+ const { optionsJSON, useBrowserAutofill = false, verifyBrowserAutofillInput = true } = options;
36
+ // 需要避免传递空数组,以免阻止公钥的检索
37
+ let allowCredentials;
38
+ if (optionsJSON.allowCredentials?.length !== 0)
39
+ allowCredentials = optionsJSON.allowCredentials?.map(toPublicKeyCredentialDescriptor);
40
+
41
+ // 在将凭证传递给 navigator 之前,需要将某些值转换为 Uint8Array
42
+ const publicKey = { ...optionsJSON, challenge: base64URLStringToBuffer(optionsJSON.challenge), allowCredentials }
43
+ , getOptions = {}; // 为 `.get()` 准备选项
44
+
45
+ /**
46
+ * 设置页面,通过浏览器的输入自动填充机制提示用户选择用于身份验证的凭证;
47
+ */
48
+ if (useBrowserAutofill) {
49
+ if (!(await browserSupportsWebAuthnAutofill())) throw Error('浏览器不支持 WebAuthn 自动填充');
50
+
51
+ // 检查是否存在 `autocomplete` 属性中包含 "webauthn" 的 <input>
52
+ const eligibleInputs = document.querySelectorAll("input[autocomplete$='webauthn']");
53
+
54
+ // WebAuthn 自动填充要求至少有一个有效的输入框
55
+ if (eligibleInputs.length < 1 && verifyBrowserAutofillInput)
56
+ throw Error('未检测到任何 `autocomplete` 属性中包含 "webauthn"(作为唯一值或最后一个值)的 <input> 元素');
57
+
58
+ // 截至 typescript@4.6.3,`CredentialMediationRequirement` 尚不识别 "conditional"
59
+ getOptions.mediation = 'conditional', publicKey.allowCredentials = []; // 条件式 UI 要求 allowCredentials 为空列表
60
+ }
61
+
62
+ getOptions.publicKey = publicKey; // 最终确定选项
63
+ getOptions.signal = WebAuthnAbortService.createNewAbortSignal(); // 设置取消此请求的能力,以防用户尝试另一个请求
64
+
65
+ // 等待用户完成断言
66
+ let credential;
67
+ try {
68
+ credential = await navigator.credentials.get(getOptions);
69
+ } catch (err) {
70
+ throw identifyAuthenticationError({ error: err, options: getOptions });
71
+ }
72
+ if (!credential) throw new Error('身份验证未完成');
73
+
74
+ const { id, rawId, response, type } = credential;
75
+ let userHandle = undefined;
76
+ if (response.userHandle) userHandle = bufferToBase64URLString(response.userHandle);
77
+
78
+ // 将值转换为 base64,以便更容易发送回服务器
79
+ return {
80
+ id,
81
+ rawId: bufferToBase64URLString(rawId),
82
+ response: {
83
+ authenticatorData: bufferToBase64URLString(response.authenticatorData),
84
+ clientDataJSON: bufferToBase64URLString(response.clientDataJSON),
85
+ signature: bufferToBase64URLString(response.signature),
86
+ userHandle,
87
+ },
88
+ type,
89
+ clientExtensionResults: credential.getClientExtensionResults(),
90
+ authenticatorAttachment: toAuthenticatorAttachment(credential.authenticatorAttachment),
91
+ };
92
+ }
93
+
94
+ export { startAuthentication };