69 lines
2.6 KiB
TypeScript
69 lines
2.6 KiB
TypeScript
// Browser-side WebAuthn helpers. The server sends and expects base64url;
|
|
// the browser's credential APIs need ArrayBuffers.
|
|
|
|
function b64urlToBuffer(value: string): ArrayBuffer {
|
|
const padded = value.replace(/-/g, "+").replace(/_/g, "/");
|
|
const binary = atob(padded.padEnd(padded.length + ((4 - (padded.length % 4)) % 4), "="));
|
|
const bytes = new Uint8Array(binary.length);
|
|
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
return bytes.buffer;
|
|
}
|
|
|
|
function bufferToB64url(buffer: ArrayBuffer): string {
|
|
const bytes = new Uint8Array(buffer);
|
|
let binary = "";
|
|
for (const b of bytes) binary += String.fromCharCode(b);
|
|
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
}
|
|
|
|
export function isPasskeySupported(): boolean {
|
|
return typeof window !== "undefined" && !!window.PublicKeyCredential;
|
|
}
|
|
|
|
export function toCreateOptions(options: any): PublicKeyCredentialCreationOptions {
|
|
return {
|
|
...options,
|
|
challenge: b64urlToBuffer(options.challenge),
|
|
user: { ...options.user, id: b64urlToBuffer(options.user.id) },
|
|
excludeCredentials: (options.excludeCredentials ?? []).map((c: any) => ({
|
|
...c,
|
|
id: b64urlToBuffer(c.id),
|
|
})),
|
|
};
|
|
}
|
|
|
|
export function toRequestOptions(options: any): PublicKeyCredentialRequestOptions {
|
|
return {
|
|
...options,
|
|
challenge: b64urlToBuffer(options.challenge),
|
|
allowCredentials: (options.allowCredentials ?? []).map((c: any) => ({
|
|
...c,
|
|
id: b64urlToBuffer(c.id),
|
|
})),
|
|
};
|
|
}
|
|
|
|
// credentialToJSON produces the shape go-webauthn's parsers read.
|
|
export function credentialToJSON(cred: PublicKeyCredential): unknown {
|
|
const response = cred.response as AuthenticatorAttestationResponse & AuthenticatorAssertionResponse;
|
|
const json: any = {
|
|
id: cred.id,
|
|
rawId: bufferToB64url(cred.rawId),
|
|
type: cred.type,
|
|
clientExtensionResults: cred.getClientExtensionResults(),
|
|
response: { clientDataJSON: bufferToB64url(response.clientDataJSON) },
|
|
};
|
|
if (response.attestationObject) {
|
|
json.response.attestationObject = bufferToB64url(response.attestationObject);
|
|
if (typeof response.getTransports === "function") {
|
|
json.response.transports = response.getTransports();
|
|
}
|
|
}
|
|
if (response.authenticatorData) {
|
|
json.response.authenticatorData = bufferToB64url(response.authenticatorData);
|
|
json.response.signature = bufferToB64url(response.signature);
|
|
json.response.userHandle = response.userHandle ? bufferToB64url(response.userHandle) : null;
|
|
}
|
|
return json;
|
|
}
|