| 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)); |
| } |
|
|