kneadly/src/lib/auth.js
King Omar aa45ebc769 Kneadly — online booking platform for massage shops
Fresha-style booking: shops sign up, add services/therapists/hours, and get a
public booking page they can drop into their Google Business Profile so
customers book from Maps. Deposits via Stripe stop no-shows.

Stack: Hono + Cloudflare Workers + D1 + Stripe, server-rendered HTML.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 19:09:21 +10:00

38 lines
1.8 KiB
JavaScript

export async function hashPassword(password) {
const enc = new TextEncoder()
const salt = crypto.getRandomValues(new Uint8Array(16))
const key = await crypto.subtle.importKey('raw', enc.encode(password), 'PBKDF2', false, ['deriveBits'])
const bits = await crypto.subtle.deriveBits({ name: 'PBKDF2', salt, iterations: 100000, hash: 'SHA-256' }, key, 256)
const toHex = buf => Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('')
return toHex(salt.buffer) + ':' + toHex(bits)
}
export async function verifyPassword(password, hash) {
const [saltHex, hashHex] = hash.split(':')
const salt = new Uint8Array(saltHex.match(/.{2}/g).map(h => parseInt(h, 16)))
const enc = new TextEncoder()
const key = await crypto.subtle.importKey('raw', enc.encode(password), 'PBKDF2', false, ['deriveBits'])
const bits = await crypto.subtle.deriveBits({ name: 'PBKDF2', salt, iterations: 100000, hash: 'SHA-256' }, key, 256)
const computed = Array.from(new Uint8Array(bits)).map(b => b.toString(16).padStart(2, '0')).join('')
return computed === hashHex
}
export const genId = () => crypto.randomUUID().replace(/-/g, '')
export async function createSession(db, userId) {
const id = genId()
const expires = Math.floor(Date.now() / 1000) + 86400 * 30
await db.prepare('INSERT INTO sessions (id, user_id, expires_at) VALUES (?, ?, ?)').bind(id, userId, expires).run()
return id
}
export async function getSession(db, sessionId) {
if (!sessionId) return null
return db.prepare(
'SELECT s.user_id, u.email, u.name FROM sessions s JOIN users u ON u.id = s.user_id WHERE s.id = ? AND s.expires_at > ?'
).bind(sessionId, Math.floor(Date.now() / 1000)).first()
}
export async function deleteSession(db, sessionId) {
await db.prepare('DELETE FROM sessions WHERE id = ?').bind(sessionId).run()
}