mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-08-05 06:50:10 +00:00
feat: add UUID normalization functions and enhance WebAuthn response handling
This commit is contained in:
@@ -32,6 +32,44 @@ function textBytes(value: string): Uint8Array {
|
|||||||
return new TextEncoder().encode(value);
|
return new TextEncoder().encode(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hexByte(value: number): string {
|
||||||
|
return value.toString(16).padStart(2, '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
function dotNetGuidBytesToUuid(bytes: Uint8Array): string | null {
|
||||||
|
if (bytes.length !== 16) return null;
|
||||||
|
return [
|
||||||
|
[bytes[3], bytes[2], bytes[1], bytes[0]].map(hexByte).join(''),
|
||||||
|
[bytes[5], bytes[4]].map(hexByte).join(''),
|
||||||
|
[bytes[7], bytes[6]].map(hexByte).join(''),
|
||||||
|
[bytes[8], bytes[9]].map(hexByte).join(''),
|
||||||
|
Array.from(bytes.slice(10, 16)).map(hexByte).join(''),
|
||||||
|
].join('-');
|
||||||
|
}
|
||||||
|
|
||||||
|
function uuidToDotNetGuidBytes(value: string): Uint8Array | null {
|
||||||
|
const match = String(value || '').trim().match(
|
||||||
|
/^([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})$/i
|
||||||
|
);
|
||||||
|
if (!match) return null;
|
||||||
|
const hex = match.slice(1).join('');
|
||||||
|
const bytes = new Uint8Array(16);
|
||||||
|
for (let i = 0; i < 16; i += 1) {
|
||||||
|
bytes[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
||||||
|
}
|
||||||
|
return new Uint8Array([
|
||||||
|
bytes[3], bytes[2], bytes[1], bytes[0],
|
||||||
|
bytes[5], bytes[4],
|
||||||
|
bytes[7], bytes[6],
|
||||||
|
bytes[8], bytes[9],
|
||||||
|
bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeWebAuthnBase64(value: unknown): string {
|
||||||
|
return String(value || '').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
async function importHmacKey(secret: string): Promise<CryptoKey> {
|
async function importHmacKey(secret: string): Promise<CryptoKey> {
|
||||||
return crypto.subtle.importKey('raw', textBytes(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign', 'verify']);
|
return crypto.subtle.importKey('raw', textBytes(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign', 'verify']);
|
||||||
}
|
}
|
||||||
@@ -141,13 +179,16 @@ export function getAccountPasskeyRpConfig(request: Request, env: Env): { rpId: s
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function userIdToWebAuthnUserId(userId: string): Uint8Array {
|
export function userIdToWebAuthnUserId(userId: string): Uint8Array {
|
||||||
return textBytes(userId);
|
return uuidToDotNetGuidBytes(userId) || textBytes(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function userHandleToUserId(userHandle: string | undefined): string | null {
|
export function userHandleToUserId(userHandle: string | undefined): string | null {
|
||||||
if (!userHandle) return null;
|
if (!userHandle) return null;
|
||||||
try {
|
try {
|
||||||
const decoded = new TextDecoder().decode(base64UrlToBytes(userHandle));
|
const bytes = base64UrlToBytes(userHandle);
|
||||||
|
const officialGuid = dotNetGuidBytesToUuid(bytes);
|
||||||
|
if (officialGuid) return officialGuid;
|
||||||
|
const decoded = new TextDecoder().decode(bytes);
|
||||||
return decoded.trim() || null;
|
return decoded.trim() || null;
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
@@ -209,17 +250,17 @@ export function normalizeRegistrationResponse(raw: unknown): RegistrationRespons
|
|||||||
const clientDataJSON = response.clientDataJSON || response.clientDataJson;
|
const clientDataJSON = response.clientDataJSON || response.clientDataJson;
|
||||||
if (!input.id || !input.rawId || !clientDataJSON || !response.attestationObject) return null;
|
if (!input.id || !input.rawId || !clientDataJSON || !response.attestationObject) return null;
|
||||||
return {
|
return {
|
||||||
id: String(input.id),
|
id: normalizeWebAuthnBase64(input.id),
|
||||||
rawId: String(input.rawId),
|
rawId: normalizeWebAuthnBase64(input.rawId),
|
||||||
type: 'public-key',
|
type: 'public-key',
|
||||||
authenticatorAttachment: input.authenticatorAttachment,
|
authenticatorAttachment: input.authenticatorAttachment,
|
||||||
clientExtensionResults: input.clientExtensionResults || input.extensions || {},
|
clientExtensionResults: input.clientExtensionResults || input.extensions || {},
|
||||||
response: {
|
response: {
|
||||||
attestationObject: String(response.attestationObject),
|
attestationObject: normalizeWebAuthnBase64(response.attestationObject),
|
||||||
clientDataJSON: String(clientDataJSON),
|
clientDataJSON: normalizeWebAuthnBase64(clientDataJSON),
|
||||||
authenticatorData: response.authenticatorData ? String(response.authenticatorData) : undefined,
|
authenticatorData: response.authenticatorData ? normalizeWebAuthnBase64(response.authenticatorData) : undefined,
|
||||||
transports: Array.isArray(response.transports) ? response.transports.map(String) as AuthenticatorTransportFuture[] : undefined,
|
transports: Array.isArray(response.transports) ? response.transports.map(String) as AuthenticatorTransportFuture[] : undefined,
|
||||||
publicKey: response.publicKey ? String(response.publicKey) : undefined,
|
publicKey: response.publicKey ? normalizeWebAuthnBase64(response.publicKey) : undefined,
|
||||||
publicKeyAlgorithm: typeof response.publicKeyAlgorithm === 'number' ? response.publicKeyAlgorithm : undefined,
|
publicKeyAlgorithm: typeof response.publicKeyAlgorithm === 'number' ? response.publicKeyAlgorithm : undefined,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -232,16 +273,16 @@ export function normalizeAuthenticationResponse(raw: unknown): AuthenticationRes
|
|||||||
const clientDataJSON = response.clientDataJSON || response.clientDataJson;
|
const clientDataJSON = response.clientDataJSON || response.clientDataJson;
|
||||||
if (!input.id || !input.rawId || !clientDataJSON || !response.authenticatorData || !response.signature) return null;
|
if (!input.id || !input.rawId || !clientDataJSON || !response.authenticatorData || !response.signature) return null;
|
||||||
return {
|
return {
|
||||||
id: String(input.id),
|
id: normalizeWebAuthnBase64(input.id),
|
||||||
rawId: String(input.rawId),
|
rawId: normalizeWebAuthnBase64(input.rawId),
|
||||||
type: 'public-key',
|
type: 'public-key',
|
||||||
authenticatorAttachment: input.authenticatorAttachment,
|
authenticatorAttachment: input.authenticatorAttachment,
|
||||||
clientExtensionResults: input.clientExtensionResults || input.extensions || {},
|
clientExtensionResults: input.clientExtensionResults || input.extensions || {},
|
||||||
response: {
|
response: {
|
||||||
authenticatorData: String(response.authenticatorData),
|
authenticatorData: normalizeWebAuthnBase64(response.authenticatorData),
|
||||||
clientDataJSON: String(clientDataJSON),
|
clientDataJSON: normalizeWebAuthnBase64(clientDataJSON),
|
||||||
signature: String(response.signature),
|
signature: normalizeWebAuthnBase64(response.signature),
|
||||||
userHandle: response.userHandle ? String(response.userHandle) : undefined,
|
userHandle: response.userHandle ? normalizeWebAuthnBase64(response.userHandle) : undefined,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user