57 lines
1.8 KiB
TypeScript
57 lines
1.8 KiB
TypeScript
const encoder = new TextEncoder();
|
|
|
|
export function defaultRandomBytes(length: number): Uint8Array {
|
|
if (!Number.isInteger(length) || length < 1)
|
|
throw new RangeError("random byte length must be positive");
|
|
const bytes = new Uint8Array(length);
|
|
crypto.getRandomValues(bytes);
|
|
return bytes;
|
|
}
|
|
|
|
export function bytesToBase64Url(bytes: Uint8Array): string {
|
|
let binary = "";
|
|
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
}
|
|
|
|
export function randomId(randomBytes = defaultRandomBytes, length = 24): string {
|
|
return bytesToBase64Url(randomBytes(length));
|
|
}
|
|
|
|
function bytesToHex(bytes: Uint8Array): string {
|
|
let out = "";
|
|
for (const byte of bytes) out += byte.toString(16).padStart(2, "0");
|
|
return out;
|
|
}
|
|
|
|
export async function sha256(value: string): Promise<string> {
|
|
const digest = await crypto.subtle.digest("SHA-256", encoder.encode(value));
|
|
return bytesToHex(new Uint8Array(digest));
|
|
}
|
|
|
|
export async function hmacSha256(secret: string, value: string): Promise<string> {
|
|
const key = await crypto.subtle.importKey(
|
|
"raw",
|
|
encoder.encode(secret),
|
|
{ name: "HMAC", hash: "SHA-256" },
|
|
false,
|
|
["sign"],
|
|
);
|
|
const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(value));
|
|
return bytesToHex(new Uint8Array(signature));
|
|
}
|
|
|
|
export function constantTimeEqual(left: string, right: string): boolean {
|
|
if (left.length !== right.length) return false;
|
|
let difference = 0;
|
|
for (let index = 0; index < left.length; index++) {
|
|
difference |= left.charCodeAt(index) ^ right.charCodeAt(index);
|
|
}
|
|
return difference === 0;
|
|
}
|
|
|
|
export async function bindingHash(value: string | undefined): Promise<string | undefined> {
|
|
if (!value) return undefined;
|
|
return sha256(value);
|
|
}
|