Vibe passkey
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
// WebAuthn helpers — base64url encoding compatible with Fido2NetLib
|
||||
|
||||
function base64urlToBuffer(base64url) {
|
||||
const base64 = base64url.replace(/-/g, '+').replace(/_/g, '/');
|
||||
const binary = atob(base64);
|
||||
return Uint8Array.from(binary, c => c.charCodeAt(0)).buffer;
|
||||
}
|
||||
|
||||
function bufferToBase64url(buffer) {
|
||||
const bytes = new Uint8Array(buffer);
|
||||
let binary = '';
|
||||
for (const b of bytes) binary += String.fromCharCode(b);
|
||||
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
||||
}
|
||||
|
||||
function prepareCreationOptions(options) {
|
||||
options.challenge = base64urlToBuffer(options.challenge);
|
||||
options.user.id = base64urlToBuffer(options.user.id);
|
||||
if (options.excludeCredentials) {
|
||||
options.excludeCredentials = options.excludeCredentials.map(c => ({
|
||||
...c,
|
||||
id: base64urlToBuffer(c.id),
|
||||
}));
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
function prepareRequestOptions(options) {
|
||||
options.challenge = base64urlToBuffer(options.challenge);
|
||||
if (options.allowCredentials) {
|
||||
options.allowCredentials = options.allowCredentials.map(c => ({
|
||||
...c,
|
||||
id: base64urlToBuffer(c.id),
|
||||
}));
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
function serializeAttestation(credential) {
|
||||
return {
|
||||
id: credential.id,
|
||||
rawId: bufferToBase64url(credential.rawId),
|
||||
type: credential.type,
|
||||
response: {
|
||||
attestationObject: bufferToBase64url(credential.response.attestationObject),
|
||||
clientDataJSON: bufferToBase64url(credential.response.clientDataJSON),
|
||||
transports: credential.response.getTransports ? credential.response.getTransports() : [],
|
||||
},
|
||||
extensions: credential.getClientExtensionResults(),
|
||||
};
|
||||
}
|
||||
|
||||
function serializeAssertion(credential) {
|
||||
return {
|
||||
id: credential.id,
|
||||
rawId: bufferToBase64url(credential.rawId),
|
||||
type: credential.type,
|
||||
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,
|
||||
},
|
||||
extensions: credential.getClientExtensionResults(),
|
||||
};
|
||||
}
|
||||
|
||||
window.passkeyEnroll = async function () {
|
||||
const optRes = await fetch('/api/auth/register/options', { method: 'POST' });
|
||||
if (!optRes.ok) throw new Error(await optRes.text());
|
||||
const options = prepareCreationOptions(await optRes.json());
|
||||
|
||||
const credential = await navigator.credentials.create({ publicKey: options });
|
||||
|
||||
const completeRes = await fetch('/api/auth/register/complete', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(serializeAttestation(credential)),
|
||||
});
|
||||
if (!completeRes.ok) throw new Error(await completeRes.text());
|
||||
return await completeRes.json();
|
||||
};
|
||||
|
||||
window.passkeyLogin = async function () {
|
||||
const optRes = await fetch('/api/auth/login/options', { method: 'POST' });
|
||||
if (!optRes.ok) throw new Error(await optRes.text());
|
||||
const options = prepareRequestOptions(await optRes.json());
|
||||
|
||||
const credential = await navigator.credentials.get({ publicKey: options });
|
||||
|
||||
const completeRes = await fetch('/api/auth/login/complete', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(serializeAssertion(credential)),
|
||||
});
|
||||
if (!completeRes.ok) throw new Error(await completeRes.text());
|
||||
return await completeRes.json();
|
||||
};
|
||||
Reference in New Issue
Block a user