-
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit d91fbb6
Showing
52 changed files
with
20,476 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
*.js | ||
/node_modules/ | ||
/vendor/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"extends": [ "plugin:@wordpress/eslint-plugin/recommended" ] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
*.sh text eol=lf | ||
*.xml.dist export-ignore | ||
/.* export-ignore | ||
/assets/*.ts export-ignore | ||
/assets/*.map export-ignore | ||
/bin/ export-ignore | ||
/lang/*.po export-ignore | ||
/lang/*.pot export-ignore | ||
/lang/Makefile export-ignore | ||
/stubs/ export-ignore | ||
/docker-compose.yml export-ignore | ||
/rollup.config.mjs export-ignore | ||
/package-lock.json export-ignore | ||
/package.json export-ignore | ||
/tsconfig.json export-ignore |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
/node_modules | ||
/vendor | ||
/.wp-env.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
module.exports = { | ||
semi: true, | ||
trailingComma: 'all', | ||
singleQuote: true, | ||
printWidth: 120, | ||
tabWidth: 4, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
import { | ||
L_NOT_ALLOWED_ERROR, | ||
L_SECURITY_ERROR, | ||
L_NOT_SUPPORTED_ERROR, | ||
L_ABORT_ERROR, | ||
L_UNKNOWN_KEY, | ||
L_KEY_ALREADY_REGISTERED, | ||
} from './lang'; | ||
interface PublicKeyCredentialUserEntityPlain extends PublicKeyCredentialEntity { | ||
displayName: string; | ||
id: string; | ||
} | ||
|
||
interface PublicKeyCredentialDescriptorPlain { | ||
id: string; | ||
transports?: AuthenticatorTransport[]; | ||
type: PublicKeyCredentialType; | ||
} | ||
|
||
export interface PublicKeyCredentialCreationOptionsPlain { | ||
attestation?: AttestationConveyancePreference; | ||
authenticatorSelection?: AuthenticatorSelectionCriteria; | ||
challenge: string; | ||
excludeCredentials?: PublicKeyCredentialDescriptorPlain[]; | ||
extensions?: AuthenticationExtensionsClientInputs; | ||
pubKeyCredParams: PublicKeyCredentialParameters[]; | ||
rp: PublicKeyCredentialRpEntity; | ||
timeout?: number; | ||
user: PublicKeyCredentialUserEntityPlain; | ||
} | ||
|
||
export interface PublicKeyCredentialRequestOptionsPlain { | ||
allowCredentials?: PublicKeyCredentialDescriptorPlain[]; | ||
challenge: string; | ||
extensions?: AuthenticationExtensionsClientInputs; | ||
rpId?: string; | ||
timeout?: number; | ||
userVerification?: UserVerificationRequirement; | ||
} | ||
|
||
interface AuthenticatorResponsePlain { | ||
clientDataJSON: string; | ||
} | ||
|
||
interface AuthenticatorAttestationResponsePlain extends AuthenticatorResponsePlain { | ||
attestationObject: string; | ||
} | ||
|
||
interface AuthenticatorAssertionResponsePlain extends AuthenticatorResponsePlain { | ||
authenticatorData: string; | ||
signature: string; | ||
userHandle: string | null | undefined; | ||
} | ||
|
||
export interface PublicKeyCredentialPlain extends Credential { | ||
rawId: string; | ||
response: Partial<AuthenticatorAttestationResponsePlain & AuthenticatorAssertionResponsePlain>; | ||
clientExtensionResults: AuthenticationExtensionsClientOutputs; | ||
} | ||
|
||
export function arrayToBase64String(a: Uint8Array): string { | ||
return window.btoa(String.fromCharCode(...a)); | ||
} | ||
|
||
export function base64UrlDecode(input: string): string { | ||
return window.atob(input.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat(3 - ((3 + input.length) % 4))); | ||
} | ||
|
||
export function stringToBufferMapper(c: string): number { | ||
return c.charCodeAt(0); | ||
} | ||
|
||
export function preparePublicKeyCreationOptions( | ||
publicKey: PublicKeyCredentialCreationOptionsPlain, | ||
): PublicKeyCredentialCreationOptions { | ||
return { | ||
...publicKey, | ||
user: { | ||
...publicKey.user, | ||
id: Uint8Array.from(base64UrlDecode(publicKey.user.id), stringToBufferMapper), | ||
}, | ||
challenge: Uint8Array.from(base64UrlDecode(publicKey.challenge), stringToBufferMapper), | ||
excludeCredentials: publicKey.excludeCredentials?.map( | ||
(data: PublicKeyCredentialDescriptorPlain): PublicKeyCredentialDescriptor => ({ | ||
...data, | ||
id: Uint8Array.from(base64UrlDecode(data.id), stringToBufferMapper), | ||
}), | ||
), | ||
}; | ||
} | ||
|
||
export function preparePublicKeyCredentialRequestOptions( | ||
publicKey: PublicKeyCredentialRequestOptionsPlain, | ||
): PublicKeyCredentialRequestOptions { | ||
return { | ||
...publicKey, | ||
challenge: Uint8Array.from(base64UrlDecode(publicKey.challenge), stringToBufferMapper).buffer, | ||
allowCredentials: publicKey.allowCredentials?.map( | ||
(data: PublicKeyCredentialDescriptorPlain): PublicKeyCredentialDescriptor => ({ | ||
...data, | ||
id: Uint8Array.from(base64UrlDecode(data.id), stringToBufferMapper).buffer, | ||
}), | ||
), | ||
}; | ||
} | ||
|
||
export function preparePublicKeyCredential(data: PublicKeyCredential): PublicKeyCredentialPlain { | ||
const response = data.response as AuthenticatorAssertionResponse | AuthenticatorAttestationResponse; | ||
return { | ||
id: data.id, | ||
type: data.type, | ||
rawId: arrayToBase64String(new Uint8Array(data.rawId)), | ||
clientExtensionResults: data.getClientExtensionResults(), | ||
response: { | ||
attestationObject: | ||
'attestationObject' in response | ||
? arrayToBase64String(new Uint8Array(response.attestationObject)) | ||
: undefined, | ||
authenticatorData: | ||
'authenticatorData' in response | ||
? arrayToBase64String(new Uint8Array(response.authenticatorData)) | ||
: undefined, | ||
signature: 'signature' in response ? arrayToBase64String(new Uint8Array(response.signature)) : undefined, | ||
userHandle: | ||
'userHandle' in response && response.userHandle | ||
? arrayToBase64String(new Uint8Array(response.userHandle)) | ||
: undefined, | ||
clientDataJSON: arrayToBase64String(new Uint8Array(data.response.clientDataJSON)), | ||
}, | ||
}; | ||
} | ||
|
||
export function decodeDOMException(e: DOMException, isAuth: boolean): string { | ||
switch (e.name) { | ||
case 'NotAllowedError': | ||
return L_NOT_ALLOWED_ERROR; | ||
|
||
case 'SecurityError': | ||
return L_SECURITY_ERROR; | ||
|
||
case 'NotSupportedError': | ||
return L_NOT_SUPPORTED_ERROR; | ||
|
||
case 'AbortError': | ||
return L_ABORT_ERROR; | ||
|
||
case 'InvalidStateError': | ||
return isAuth ? L_UNKNOWN_KEY : L_KEY_ALREADY_REGISTERED; | ||
|
||
default: | ||
return e.message; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { __ } from '@wordpress/i18n'; | ||
|
||
export const L_WEBAUTHN_NOT_SUPPORTED = __('WebAuthn is not supported by the browser.', '2fa-wa-js'); | ||
export const L_UNABLE_TO_GET_PK_CREDENTIAL = __('Unable to get a public key credential.', '2fa-wa-js'); | ||
export const L_NOT_ALLOWED_ERROR = __('The request is not allowed.', '2fa-wa-js'); | ||
export const L_SECURITY_ERROR = __('The operation is insecure.', '2fa-wa-js'); | ||
export const L_NOT_SUPPORTED_ERROR = __('The operation is not supported.', '2fa-wa-js'); | ||
export const L_ABORT_ERROR = __('The operation was canceled.', '2fa-wa-js'); | ||
export const L_UNKNOWN_KEY = __('You cannot use this key to log in.', '2fa-wa-js'); | ||
export const L_KEY_ALREADY_REGISTERED = __('This key is already registered.', '2fa-wa-js'); | ||
export const L_UNKNOWN_ERROR = __('This key is already registered.', '2fa-wa-js'); | ||
export const L_FETCHING_REG_INFO = __('Fetching registration information…', '2fa-wa-js'); | ||
export const L_GENERATING_CREDENTIALS = __('Generating credentials…', '2fa-wa-js'); | ||
export const L_REGISTERING_CREDENTIALS = __('Registering credentials…', '2fa-wa-js'); | ||
export const L_FAILED_TO_CREATE_CREDENTIALS = __('Unable to create public key credentials', '2fa-wa-js'); | ||
export const L_KEY_REGISTERED = __('The key has been registered.', '2fa-wa-js'); | ||
export const L_SENDING_REQUEST = __('Sending request…', '2fa-wa-js'); | ||
export const L_KEY_REVOKED = __('The key has been revoked.', '2fa-wa-js'); | ||
export const L_KEY_RENAMED = __('The key has been renamed.', '2fa-wa-js'); |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import { | ||
PublicKeyCredentialRequestOptionsPlain, | ||
preparePublicKeyCredentialRequestOptions, | ||
preparePublicKeyCredential, | ||
decodeDOMException, | ||
} from './common'; | ||
import { L_WEBAUTHN_NOT_SUPPORTED, L_UNABLE_TO_GET_PK_CREDENTIAL } from './lang'; | ||
|
||
// eslint-disable-next-line camelcase | ||
declare let tfa_webauthn: { | ||
options: PublicKeyCredentialRequestOptionsPlain; | ||
}; | ||
|
||
function showError(error: string): void { | ||
const el = document.getElementById('login_error'); | ||
if (el && el.parentNode) { | ||
el.parentNode.removeChild(el); | ||
} | ||
|
||
const form = document.getElementById('loginform'); | ||
if (form) { | ||
form.insertAdjacentHTML('beforebegin', '<div id="login_error" role="alert"><p>' + error + '</p></div>'); | ||
} | ||
} | ||
|
||
function startAuthentication(): void { | ||
const loginForm = document.getElementById('loginform') as HTMLFormElement; | ||
const publicKey = preparePublicKeyCredentialRequestOptions(tfa_webauthn.options); | ||
( | ||
navigator.credentials.get({ | ||
publicKey, | ||
}) as Promise<PublicKeyCredential | null> | ||
) | ||
.then((credential) => { | ||
if (credential) { | ||
(document.getElementById('webauthn_response') as HTMLInputElement).value = JSON.stringify( | ||
preparePublicKeyCredential(credential), | ||
); | ||
loginForm.submit(); | ||
} else { | ||
throw new Error(L_UNABLE_TO_GET_PK_CREDENTIAL); | ||
} | ||
}) | ||
.catch((e: Error) => { | ||
const message = e instanceof DOMException ? decodeDOMException(e, true) : e.message; | ||
showError(message); | ||
(document.getElementById('webauthn-retry') as HTMLDivElement).removeAttribute('hidden'); | ||
}); | ||
} | ||
|
||
const callback = (): void => { | ||
const retryButton = document.querySelector('#webauthn-retry .button') as HTMLButtonElement; | ||
retryButton.addEventListener('click', (e) => { | ||
(document.getElementById('webauthn-retry') as HTMLDivElement).setAttribute('hidden', 'hidden'); | ||
startAuthentication(); | ||
}); | ||
|
||
if ('credentials' in navigator) { | ||
startAuthentication(); | ||
} else { | ||
showError(L_WEBAUTHN_NOT_SUPPORTED); | ||
} | ||
}; | ||
|
||
if (document.readyState === 'loading') { | ||
document.addEventListener('DOMContentLoaded', callback); | ||
} else { | ||
callback(); | ||
} |
Oops, something went wrong.