notes / auth.mjs
ghuser1's picture
Upload 11 files
163c686 verified
Raw
History Blame Contribute Delete
1.97 kB
const encoder = new TextEncoder();
const iterations = 310000;
export async function hashPassword(password, salt = crypto.getRandomValues(new Uint8Array(16))) {
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(password),
"PBKDF2",
false,
["deriveBits"]
);
const bits = await crypto.subtle.deriveBits(
{
name: "PBKDF2",
hash: "SHA-256",
salt,
iterations
},
key,
256
);
return `pbkdf2_sha256$${iterations}$${base64url(salt)}$${base64url(new Uint8Array(bits))}`;
}
export async function verifyPassword(password, storedHash) {
const [scheme, storedIterations, saltText, hashText] = String(storedHash || "").split("$");
if (scheme !== "pbkdf2_sha256" || Number(storedIterations) !== iterations) return false;
const candidate = await hashPassword(password, fromBase64url(saltText));
return timingSafeEqual(candidate, storedHash);
}
export async function hashSessionToken(token) {
const digest = await crypto.subtle.digest("SHA-256", encoder.encode(token));
return base64url(new Uint8Array(digest));
}
export function createSessionToken() {
const bytes = crypto.getRandomValues(new Uint8Array(32));
return base64url(bytes);
}
function timingSafeEqual(a, b) {
const left = encoder.encode(a);
const right = encoder.encode(b);
if (left.length !== right.length) return false;
let result = 0;
for (let index = 0; index < left.length; index += 1) {
result |= left[index] ^ right[index];
}
return result === 0;
}
function base64url(bytes) {
const binary = Array.from(bytes, (byte) => String.fromCharCode(byte)).join("");
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
}
function fromBase64url(value) {
const padded = value.replaceAll("-", "+").replaceAll("_", "/").padEnd(Math.ceil(value.length / 4) * 4, "=");
const binary = atob(padded);
return Uint8Array.from(binary, (char) => char.charCodeAt(0));
}