My personal photography website steve.phot
portfolio photography svelte sveltekit
at main 91 lines 2.3 kB view raw
1const encoder = new TextEncoder(); 2 3export async function hashPassword( 4 password: string, 5 secret: string, 6): Promise<string> { 7 const key = await crypto.subtle.importKey( 8 "raw", 9 encoder.encode(secret), 10 { name: "HMAC", hash: "SHA-256" }, 11 false, 12 ["sign"], 13 ); 14 const signature = await crypto.subtle.sign( 15 "HMAC", 16 key, 17 encoder.encode(password), 18 ); 19 return arrayBufferToHex(signature); 20} 21 22export async function verifyPassword( 23 password: string, 24 hash: string, 25 secret: string, 26): Promise<boolean> { 27 const computed = await hashPassword(password, secret); 28 return timingSafeEqual(computed, hash); 29} 30 31export async function createSession(secret: string): Promise<string> { 32 const sessionId = crypto.randomUUID(); 33 const timestamp = Date.now().toString(); 34 const data = `${sessionId}.${timestamp}`; 35 36 const key = await crypto.subtle.importKey( 37 "raw", 38 encoder.encode(secret), 39 { name: "HMAC", hash: "SHA-256" }, 40 false, 41 ["sign"], 42 ); 43 const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(data)); 44 const sig = arrayBufferToHex(signature); 45 46 return `${data}.${sig}`; 47} 48 49export async function verifySession( 50 token: string, 51 secret: string, 52): Promise<boolean> { 53 const parts = token.split("."); 54 if (parts.length !== 3) return false; 55 56 const [sessionId, timestamp, providedSig] = parts; 57 const data = `${sessionId}.${timestamp}`; 58 59 // Check if session is expired (24 hours) 60 const sessionTime = parseInt(timestamp, 10); 61 if (isNaN(sessionTime) || Date.now() - sessionTime > 24 * 60 * 60 * 1000) { 62 return false; 63 } 64 65 const key = await crypto.subtle.importKey( 66 "raw", 67 encoder.encode(secret), 68 { name: "HMAC", hash: "SHA-256" }, 69 false, 70 ["sign"], 71 ); 72 const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(data)); 73 const expectedSig = arrayBufferToHex(signature); 74 75 return timingSafeEqual(providedSig, expectedSig); 76} 77 78function arrayBufferToHex(buffer: ArrayBuffer): string { 79 return Array.from(new Uint8Array(buffer)) 80 .map((b) => b.toString(16).padStart(2, "0")) 81 .join(""); 82} 83 84function timingSafeEqual(a: string, b: string): boolean { 85 if (a.length !== b.length) return false; 86 let result = 0; 87 for (let i = 0; i < a.length; i++) { 88 result |= a.charCodeAt(i) ^ b.charCodeAt(i); 89 } 90 return result === 0; 91}