PluginProbe ʕ •ᴥ•ʔ
WP 2FA – Two-factor authentication for WordPress / 4.0.0
WP 2FA – Two-factor authentication for WordPress v4.0.0
4.0.0 1.7.1 2.0.0 2.0.1 2.1.0 2.2.0 2.2.1 2.3.0 2.4.0 2.4.1 2.4.2 2.5.0 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.7.0 2.8.0 2.9.0 2.9.1 2.9.2 2.9.3 3.0.0 3.0.1 3.1.0 3.1.1 3.1.1.2 trunk 1.2.0 1.3.0 1.4.0 1.4.1 1.4.2 1.5.0 1.5.1 1.5.2 1.6.0 1.6.1 1.6.2 1.7.0
wp-2fa / includes / classes / Admin / Methods / passkeys / assets / js / index.js
wp-2fa / includes / classes / Admin / Methods / passkeys / assets / js Last commit date
index.js 5 days ago user-login-ajax.js 5 days ago user-login.js 5 days ago user-profile-ajax.js 5 days ago user-profile.js 5 days ago
index.js
364 lines
1 /* [@simplewebauthn/browser@7.4.0] */
2 function utf8StringToBuffer(value) {
3 return new TextEncoder().encode(value);
4 }
5
6 function bufferToBase64URLString(buffer) {
7 const bytes = new Uint8Array(buffer);
8 let str = '';
9 for (const charCode of bytes) {
10 str += String.fromCharCode(charCode);
11 }
12 const base64String = btoa(str);
13 return base64String.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
14 }
15
16 function base64URLStringToBuffer(base64URLString) {
17 const base64 = base64URLString.replace(/-/g, '+').replace(/_/g, '/');
18 const padLength = (4 - (base64.length % 4)) % 4;
19 const padded = base64.padEnd(base64.length + padLength, '=');
20 const binary = atob(padded);
21 const buffer = new ArrayBuffer(binary.length);
22 const bytes = new Uint8Array(buffer);
23 for (let i = 0; i < binary.length; i++) {
24 bytes[i] = binary.charCodeAt(i);
25 }
26 return buffer;
27 }
28
29 function browserSupportsWebAuthn() {
30 return (window?.PublicKeyCredential !== undefined && typeof window.PublicKeyCredential === 'function');
31 }
32
33 function toPublicKeyCredentialDescriptor(descriptor) {
34 const { id } = descriptor;
35 return {
36 ...descriptor,
37 id: base64URLStringToBuffer(id),
38 transports: descriptor.transports,
39 };
40 }
41
42 function isValidDomain(hostname) {
43 return (hostname === 'localhost' || /^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/i.test(hostname));
44 }
45
46 class WebAuthnError extends Error {
47 code;
48 constructor({ message, code, cause, name, }) {
49 super(message, { cause });
50 this.name = name ?? cause.name;
51 this.code = code;
52 }
53 }
54
55 function identifyRegistrationError({ error, options, }) {
56 const { publicKey } = options;
57 if (!publicKey) {
58 throw Error('options was missing required publicKey property');
59 }
60 if (error.name === 'AbortError') {
61 if (options.signal instanceof AbortSignal) {
62 return new WebAuthnError({
63 message: 'Registration ceremony was sent an abort signal',
64 code: 'ERROR_CEREMONY_ABORTED',
65 cause: error,
66 });
67 }
68 }
69 else if (error.name === 'ConstraintError') {
70 if (publicKey.authenticatorSelection?.requireResidentKey === true) {
71 return new WebAuthnError({
72 message: 'Discoverable credentials were required but no available authenticator supported it',
73 code: 'ERROR_AUTHENTICATOR_MISSING_DISCOVERABLE_CREDENTIAL_SUPPORT',
74 cause: error,
75 });
76 }
77 else if (publicKey.authenticatorSelection?.userVerification === 'required') {
78 return new WebAuthnError({
79 message: 'User verification was required but no available authenticator supported it',
80 code: 'ERROR_AUTHENTICATOR_MISSING_USER_VERIFICATION_SUPPORT',
81 cause: error,
82 });
83 }
84 }
85 else if (error.name === 'InvalidStateError') {
86 return new WebAuthnError({
87 message: 'The authenticator was previously registered',
88 code: 'ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED',
89 cause: error
90 });
91 }
92 else if (error.name === 'NotAllowedError') {
93 return new WebAuthnError({
94 message: error.message,
95 code: 'ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY',
96 cause: error,
97 });
98 }
99 else if (error.name === 'NotSupportedError') {
100 const validPubKeyCredParams = publicKey.pubKeyCredParams.filter(param => param.type === 'public-key');
101 if (validPubKeyCredParams.length === 0) {
102 return new WebAuthnError({
103 message: 'No entry in pubKeyCredParams was of type "public-key"',
104 code: 'ERROR_MALFORMED_PUBKEYCREDPARAMS',
105 cause: error,
106 });
107 }
108 return new WebAuthnError({
109 message: 'No available authenticator supported any of the specified pubKeyCredParams algorithms',
110 code: 'ERROR_AUTHENTICATOR_NO_SUPPORTED_PUBKEYCREDPARAMS_ALG',
111 cause: error,
112 });
113 }
114 else if (error.name === 'SecurityError') {
115 const effectiveDomain = window.location.hostname;
116 if (!isValidDomain(effectiveDomain)) {
117 return new WebAuthnError({
118 message: `${window.location.hostname} is an invalid domain`,
119 code: 'ERROR_INVALID_DOMAIN',
120 cause: error
121 });
122 }
123 else if (publicKey.rp.id !== effectiveDomain) {
124 return new WebAuthnError({
125 message: `The RP ID "${publicKey.rp.id}" is invalid for this domain`,
126 code: 'ERROR_INVALID_RP_ID',
127 cause: error,
128 });
129 }
130 }
131 else if (error.name === 'TypeError') {
132 if (publicKey.user.id.byteLength < 1 || publicKey.user.id.byteLength > 64) {
133 return new WebAuthnError({
134 message: 'User ID was not between 1 and 64 characters',
135 code: 'ERROR_INVALID_USER_ID_LENGTH',
136 cause: error,
137 });
138 }
139 }
140 else if (error.name === 'UnknownError') {
141 return new WebAuthnError({
142 message: 'The authenticator was unable to process the specified options, or could not create a new credential',
143 code: 'ERROR_AUTHENTICATOR_GENERAL_ERROR',
144 cause: error,
145 });
146 }
147 return error;
148 }
149
150 class WebAuthnAbortService {
151 controller;
152 createNewAbortSignal() {
153 if (this.controller) {
154 const abortError = new Error('Cancelling existing WebAuthn API call for new one');
155 abortError.name = 'AbortError';
156 this.controller.abort(abortError);
157 }
158 const newController = new AbortController();
159 this.controller = newController;
160 return newController.signal;
161 }
162 }
163 const webauthnAbortService = new WebAuthnAbortService();
164
165 const attachments = ['cross-platform', 'platform'];
166 function toAuthenticatorAttachment(attachment) {
167 if (!attachment) {
168 return;
169 }
170 if (attachments.indexOf(attachment) < 0) {
171 return;
172 }
173 return attachment;
174 }
175
176 async function startRegistration(creationOptionsJSON) {
177 if (!browserSupportsWebAuthn()) {
178 throw new Error('WebAuthn is not supported in this browser');
179 }
180 const publicKey = {
181 ...creationOptionsJSON,
182 challenge: base64URLStringToBuffer(creationOptionsJSON.challenge),
183 user: {
184 ...creationOptionsJSON.user,
185 id: utf8StringToBuffer(creationOptionsJSON.user.id),
186 },
187 excludeCredentials: creationOptionsJSON.excludeCredentials?.map(toPublicKeyCredentialDescriptor),
188 };
189 const options = { publicKey };
190
191 options.signal = webauthnAbortService.createNewAbortSignal();
192 let credential;
193 try {
194 credential = (await navigator.credentials.create(options));
195 }
196 catch (err) {
197 throw identifyRegistrationError({ error: err, options });
198 }
199 if (!credential) {
200 throw new Error('Registration was not completed');
201 }
202 const { id, rawId, response, type } = credential;
203 let transports = undefined;
204 if (typeof response.getTransports === 'function') {
205 transports = response.getTransports();
206 }
207 let responsePublicKeyAlgorithm = undefined;
208 if (typeof response.getPublicKeyAlgorithm === 'function') {
209 responsePublicKeyAlgorithm = response.getPublicKeyAlgorithm();
210 }
211 let responsePublicKey = undefined;
212 if (typeof response.getPublicKey === 'function') {
213 const _publicKey = response.getPublicKey();
214 if (_publicKey !== null) {
215 responsePublicKey = bufferToBase64URLString(_publicKey);
216 }
217 }
218 let responseAuthenticatorData;
219 if (typeof response.getAuthenticatorData === 'function') {
220 responseAuthenticatorData = bufferToBase64URLString(response.getAuthenticatorData());
221 }
222 return {
223 id,
224 rawId: bufferToBase64URLString(rawId),
225 response: {
226 attestationObject: bufferToBase64URLString(response.attestationObject),
227 clientDataJSON: bufferToBase64URLString(response.clientDataJSON),
228 transports,
229 publicKeyAlgorithm: responsePublicKeyAlgorithm,
230 publicKey: responsePublicKey,
231 authenticatorData: responseAuthenticatorData,
232 },
233 type,
234 clientExtensionResults: credential.getClientExtensionResults(),
235 authenticatorAttachment: toAuthenticatorAttachment(credential.authenticatorAttachment),
236 };
237 }
238
239 function bufferToUTF8String(value) {
240 return new TextDecoder('utf-8').decode(value);
241 }
242
243 async function browserSupportsWebAuthnAutofill() {
244 const globalPublicKeyCredential = window.PublicKeyCredential;
245 return (globalPublicKeyCredential.isConditionalMediationAvailable !== undefined &&
246 globalPublicKeyCredential.isConditionalMediationAvailable());
247 }
248
249 function identifyAuthenticationError({ error, options, }) {
250 const { publicKey } = options;
251 if (!publicKey) {
252 throw Error('options was missing required publicKey property');
253 }
254 if (error.name === 'AbortError') {
255 if (options.signal instanceof AbortSignal) {
256 return new WebAuthnError({
257 message: 'Authentication ceremony was sent an abort signal',
258 code: 'ERROR_CEREMONY_ABORTED',
259 cause: error,
260 });
261 }
262 }
263 else if (error.name === 'NotAllowedError') {
264 return new WebAuthnError({
265 message: error.message,
266 code: 'ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY',
267 cause: error,
268 });
269 }
270 else if (error.name === 'SecurityError') {
271 const effectiveDomain = window.location.hostname;
272 if (!isValidDomain(effectiveDomain)) {
273 return new WebAuthnError({
274 message: `${window.location.hostname} is an invalid domain`,
275 code: 'ERROR_INVALID_DOMAIN',
276 cause: error,
277 });
278 }
279 else if (publicKey.rpId !== effectiveDomain) {
280 return new WebAuthnError({
281 message: `The RP ID "${publicKey.rpId}" is invalid for this domain`,
282 code: 'ERROR_INVALID_RP_ID',
283 cause: error,
284 });
285 }
286 }
287 else if (error.name === 'UnknownError') {
288 return new WebAuthnError({
289 message: 'The authenticator was unable to process the specified options, or could not create a new assertion signature',
290 code: 'ERROR_AUTHENTICATOR_GENERAL_ERROR',
291 cause: error,
292 });
293 }
294 return error;
295 }
296
297 async function startAuthentication(requestOptionsJSON, useBrowserAutofill = false) {
298 if (!browserSupportsWebAuthn()) {
299 throw new Error('WebAuthn is not supported in this browser');
300 }
301 let allowCredentials;
302 if (requestOptionsJSON.allowCredentials?.length !== 0) {
303 allowCredentials = requestOptionsJSON.allowCredentials?.map(toPublicKeyCredentialDescriptor);
304 }
305
306 const publicKey = {
307 ...requestOptionsJSON,
308 challenge: base64URLStringToBuffer(requestOptionsJSON.challenge),
309 allowCredentials,
310 };
311 const options = {};
312 if (useBrowserAutofill) {
313 if (!(await browserSupportsWebAuthnAutofill())) {
314 throw Error('Browser does not support WebAuthn autofill');
315 }
316 const eligibleInputs = document.querySelectorAll("input[autocomplete*='webauthn']");
317 if (eligibleInputs.length < 1) {
318 throw Error('No <input> with `"webauthn"` in its `autocomplete` attribute was detected');
319 }
320 options.mediation = 'conditional';
321 publicKey.allowCredentials = [];
322 }
323 options.publicKey = publicKey;
324 options.signal = webauthnAbortService.createNewAbortSignal();
325 let credential;
326 try {
327 credential = (await navigator.credentials.get(options));
328 }
329 catch (err) {
330 throw identifyAuthenticationError({ error: err, options });
331 }
332 if (!credential) {
333 throw new Error('Authentication was not completed');
334 }
335 const { id, rawId, response, type } = credential;
336
337 let userHandle = undefined;
338 if (response.userHandle) {
339 userHandle = bufferToUTF8String(response.userHandle);
340 }
341 return {
342 id,
343 rawId: bufferToBase64URLString(rawId),
344 response: {
345 authenticatorData: bufferToBase64URLString(response.authenticatorData),
346 clientDataJSON: bufferToBase64URLString(response.clientDataJSON),
347 signature: bufferToBase64URLString(response.signature),
348 userHandle,
349 },
350 type,
351 clientExtensionResults: credential.getClientExtensionResults(),
352 authenticatorAttachment: toAuthenticatorAttachment(credential.authenticatorAttachment),
353 };
354 }
355
356 async function platformAuthenticatorIsAvailable() {
357 if (!browserSupportsWebAuthn()) {
358 return false;
359 }
360 return PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
361 }
362
363 export { browserSupportsWebAuthn, browserSupportsWebAuthnAutofill, platformAuthenticatorIsAvailable, startAuthentication, startRegistration };
364