no-password

This commit is contained in:
2025-10-30 01:43:07 +01:00
parent b9966f82a0
commit f89c6bb259
11 changed files with 483 additions and 212 deletions
+164
View File
@@ -0,0 +1,164 @@
const base64urlToBase64 = (value = '') => {
const normalized = value.replace(/-/g, '+').replace(/_/g, '/');
const padding = normalized.length % 4;
if (padding === 0) {
return normalized;
}
const padLength = 4 - padding;
return normalized + '='.repeat(padLength);
};
const base64ToBase64url = (value = '') =>
value.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
const decodeBase64 = (value) => {
if (typeof window !== 'undefined' && typeof window.atob === 'function') {
return window.atob(value);
}
const bufferCtor = typeof globalThis !== 'undefined' ? globalThis.Buffer : undefined;
if (bufferCtor) {
return bufferCtor.from(value, 'base64').toString('binary');
}
throw new Error('No base64 decoder available.');
};
const encodeBase64 = (binary) => {
if (typeof window !== 'undefined' && typeof window.btoa === 'function') {
return window.btoa(binary);
}
const bufferCtor = typeof globalThis !== 'undefined' ? globalThis.Buffer : undefined;
if (bufferCtor) {
return bufferCtor.from(binary, 'binary').toString('base64');
}
throw new Error('No base64 encoder available.');
};
export const base64urlToUint8Array = (value) => {
const base64 = base64urlToBase64(value || '');
const binary = decodeBase64(base64);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i += 1) {
bytes[i] = binary.charCodeAt(i);
}
return bytes;
};
export const arrayBufferToBase64url = (buffer) => {
const bytes = new Uint8Array(buffer || []);
let binary = '';
for (let i = 0; i < bytes.length; i += 1) {
binary += String.fromCharCode(bytes[i]);
}
const base64 = encodeBase64(binary);
return base64ToBase64url(base64);
};
export const isWebAuthnAvailable = () =>
typeof window !== 'undefined'
&& typeof navigator !== 'undefined'
&& navigator.credentials
&& typeof navigator.credentials.create === 'function'
&& typeof navigator.credentials.get === 'function';
export const preparePublicKeyCreationOptions = (challengeResponse) => {
if (!challengeResponse || !challengeResponse.publicKey) {
throw new Error('Missing publicKey challenge options.');
}
const publicKey = { ...challengeResponse.publicKey };
if (publicKey.challenge) {
publicKey.challenge = base64urlToUint8Array(publicKey.challenge);
}
if (publicKey.user?.id) {
publicKey.user = {
...publicKey.user,
id: base64urlToUint8Array(publicKey.user.id),
};
}
if (Array.isArray(publicKey.excludeCredentials)) {
publicKey.excludeCredentials = publicKey.excludeCredentials.map((descriptor) => ({
...descriptor,
id: base64urlToUint8Array(descriptor.id),
}));
}
if (publicKey.authenticatorSelection?.residentKey === 'discouraged' && !publicKey.authenticatorSelection.requireResidentKey) {
delete publicKey.authenticatorSelection.requireResidentKey;
}
return publicKey;
};
export const preparePublicKeyRequestOptions = (challengeResponse) => {
if (!challengeResponse || !challengeResponse.publicKey) {
throw new Error('Missing publicKey request options.');
}
const publicKey = { ...challengeResponse.publicKey };
if (publicKey.challenge) {
publicKey.challenge = base64urlToUint8Array(publicKey.challenge);
}
if (Array.isArray(publicKey.allowCredentials)) {
publicKey.allowCredentials = publicKey.allowCredentials.map((descriptor) => ({
...descriptor,
id: base64urlToUint8Array(descriptor.id),
}));
}
return publicKey;
};
export const serializeRegistrationCredential = (credential) => {
if (!credential) {
return null;
}
const transports =
typeof credential?.response?.getTransports === 'function'
? credential.response.getTransports()
: undefined;
return {
id: credential.id,
type: credential.type,
rawId: arrayBufferToBase64url(credential.rawId),
response: {
clientDataJSON: arrayBufferToBase64url(credential.response.clientDataJSON),
attestationObject: arrayBufferToBase64url(credential.response.attestationObject),
transports: transports && transports.length ? Array.from(transports) : undefined,
},
clientExtensionResults:
typeof credential.getClientExtensionResults === 'function'
? credential.getClientExtensionResults()
: {},
};
};
export const serializeAuthenticationCredential = (credential) => {
if (!credential) {
return null;
}
return {
id: credential.id,
type: credential.type,
rawId: arrayBufferToBase64url(credential.rawId),
response: {
clientDataJSON: arrayBufferToBase64url(credential.response.clientDataJSON),
authenticatorData: arrayBufferToBase64url(credential.response.authenticatorData),
signature: arrayBufferToBase64url(credential.response.signature),
userHandle: credential.response.userHandle
? arrayBufferToBase64url(credential.response.userHandle)
: undefined,
},
clientExtensionResults:
typeof credential.getClientExtensionResults === 'function'
? credential.getClientExtensionResults()
: {},
};
};