mirror of
https://github.com/we-promise/sure.git
synced 2026-05-07 21:04:12 +00:00
feat(auth): add WebAuthn MFA credentials (#1628)
* feat(auth): add WebAuthn MFA credentials * fix(auth): harden WebAuthn MFA review paths * fix(auth): polish WebAuthn error handling * fix(auth): handle duplicate WebAuthn credential races * fix(auth): permit WebAuthn credential params * fix(auth): trim WebAuthn registration controller cleanup * fix(auth): tighten WebAuthn MFA handling * fix(auth): pin WebAuthn relying party config
This commit is contained in:
85
app/javascript/utils/webauthn.js
Normal file
85
app/javascript/utils/webauthn.js
Normal file
@@ -0,0 +1,85 @@
|
||||
function bufferToBase64url(buffer) {
|
||||
const bytes = new Uint8Array(buffer);
|
||||
const binary = String.fromCharCode(...bytes);
|
||||
|
||||
return btoa(binary)
|
||||
.replace(/\+/g, "-")
|
||||
.replace(/\//g, "_")
|
||||
.replace(/=+$/g, "");
|
||||
}
|
||||
|
||||
function base64urlToBuffer(value) {
|
||||
const base64 = value.replace(/-/g, "+").replace(/_/g, "/");
|
||||
const padded = base64.padEnd(
|
||||
base64.length + ((4 - (base64.length % 4)) % 4),
|
||||
"=",
|
||||
);
|
||||
const binary = atob(padded);
|
||||
const bytes = new Uint8Array(binary.length);
|
||||
|
||||
for (let i = 0; i < binary.length; i += 1) {
|
||||
bytes[i] = binary.charCodeAt(i);
|
||||
}
|
||||
|
||||
return bytes.buffer;
|
||||
}
|
||||
|
||||
export function prepareCredentialCreationOptions(options) {
|
||||
options.challenge = base64urlToBuffer(options.challenge);
|
||||
options.user.id = base64urlToBuffer(options.user.id);
|
||||
options.excludeCredentials = (options.excludeCredentials || []).map(
|
||||
(credential) => ({
|
||||
...credential,
|
||||
id: base64urlToBuffer(credential.id),
|
||||
}),
|
||||
);
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
export function prepareCredentialRequestOptions(options) {
|
||||
options.challenge = base64urlToBuffer(options.challenge);
|
||||
options.allowCredentials = (options.allowCredentials || []).map(
|
||||
(credential) => ({
|
||||
...credential,
|
||||
id: base64urlToBuffer(credential.id),
|
||||
}),
|
||||
);
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
export function serializePublicKeyCredential(credential) {
|
||||
const serialized = {
|
||||
id: credential.id,
|
||||
rawId: bufferToBase64url(credential.rawId),
|
||||
type: credential.type,
|
||||
authenticatorAttachment: credential.authenticatorAttachment,
|
||||
clientExtensionResults: credential.getClientExtensionResults(),
|
||||
};
|
||||
|
||||
if (credential.response.attestationObject) {
|
||||
serialized.response = {
|
||||
attestationObject: bufferToBase64url(
|
||||
credential.response.attestationObject,
|
||||
),
|
||||
clientDataJSON: bufferToBase64url(credential.response.clientDataJSON),
|
||||
transports: credential.response.getTransports
|
||||
? credential.response.getTransports()
|
||||
: [],
|
||||
};
|
||||
} else {
|
||||
serialized.response = {
|
||||
authenticatorData: bufferToBase64url(
|
||||
credential.response.authenticatorData,
|
||||
),
|
||||
clientDataJSON: bufferToBase64url(credential.response.clientDataJSON),
|
||||
signature: bufferToBase64url(credential.response.signature),
|
||||
userHandle: credential.response.userHandle
|
||||
? bufferToBase64url(credential.response.userHandle)
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
return serialized;
|
||||
}
|
||||
Reference in New Issue
Block a user