馃 distributed transcription service thistle.dunkirk.sh
1import db from "../db/schema"; 2 3const SESSION_DURATION = 7 * 24 * 60 * 60; // 7 days in seconds 4 5export interface User { 6 id: number; 7 email: string; 8 name: string | null; 9 avatar: string; 10 created_at: number; 11} 12 13export interface Session { 14 id: string; 15 user_id: number; 16 ip_address: string | null; 17 user_agent: string | null; 18 created_at: number; 19 expires_at: number; 20} 21 22export function createSession( 23 userId: number, 24 ipAddress?: string, 25 userAgent?: string, 26): string { 27 const sessionId = crypto.randomUUID(); 28 const expiresAt = Math.floor(Date.now() / 1000) + SESSION_DURATION; 29 30 db.run( 31 "INSERT INTO sessions (id, user_id, ip_address, user_agent, expires_at) VALUES (?, ?, ?, ?, ?)", 32 [sessionId, userId, ipAddress ?? null, userAgent ?? null, expiresAt], 33 ); 34 35 return sessionId; 36} 37 38export function getSession(sessionId: string): Session | null { 39 const now = Math.floor(Date.now() / 1000); 40 41 const session = db 42 .query<Session, [string, number]>( 43 "SELECT id, user_id, ip_address, user_agent, created_at, expires_at FROM sessions WHERE id = ? AND expires_at > ?", 44 ) 45 .get(sessionId, now); 46 47 return session ?? null; 48} 49 50export function getUserBySession(sessionId: string): User | null { 51 const session = getSession(sessionId); 52 if (!session) return null; 53 54 const user = db 55 .query<User, [number]>( 56 "SELECT id, email, name, avatar, created_at FROM users WHERE id = ?", 57 ) 58 .get(session.user_id); 59 60 return user ?? null; 61} 62 63export function deleteSession(sessionId: string): void { 64 db.run("DELETE FROM sessions WHERE id = ?", [sessionId]); 65} 66 67export function cleanupExpiredSessions(): void { 68 const now = Math.floor(Date.now() / 1000); 69 db.run("DELETE FROM sessions WHERE expires_at <= ?", [now]); 70} 71 72export async function createUser( 73 email: string, 74 password: string, 75 name?: string, 76): Promise<User> { 77 const result = db.run( 78 "INSERT INTO users (email, password_hash, name) VALUES (?, ?, ?)", 79 [email, password, name ?? null], 80 ); 81 82 const user = db 83 .query<User, [number]>( 84 "SELECT id, email, name, avatar, created_at FROM users WHERE id = ?", 85 ) 86 .get(Number(result.lastInsertRowid)); 87 88 if (!user) { 89 throw new Error("Failed to create user"); 90 } 91 92 return user; 93} 94 95export async function authenticateUser( 96 email: string, 97 password: string, 98): Promise<User | null> { 99 const result = db 100 .query< 101 { 102 id: number; 103 email: string; 104 name: string | null; 105 avatar: string; 106 password_hash: string; 107 created_at: number; 108 }, 109 [string] 110 >( 111 "SELECT id, email, name, avatar, password_hash, created_at FROM users WHERE email = ?", 112 ) 113 .get(email); 114 115 if (!result) { 116 // Dummy comparison to prevent timing-based account enumeration 117 const dummyHash = "0".repeat(64); 118 password === dummyHash; 119 return null; 120 } 121 122 if (password !== result.password_hash) return null; 123 124 return { 125 id: result.id, 126 email: result.email, 127 name: result.name, 128 avatar: result.avatar, 129 created_at: result.created_at, 130 }; 131} 132 133export function getUserSessionsForUser(userId: number): Session[] { 134 const now = Math.floor(Date.now() / 1000); 135 136 const sessions = db 137 .query<Session, [number, number]>( 138 "SELECT id, user_id, ip_address, user_agent, created_at, expires_at FROM sessions WHERE user_id = ? AND expires_at > ? ORDER BY created_at DESC", 139 ) 140 .all(userId, now); 141 142 return sessions; 143} 144 145export function getSessionFromRequest(req: Request): string | null { 146 const cookie = req.headers.get("cookie"); 147 if (!cookie) return null; 148 149 const match = cookie.match(/session=([^;]+)/); 150 return match?.[1] ?? null; 151} 152 153export function deleteUser(userId: number): void { 154 db.run("DELETE FROM users WHERE id = ?", [userId]); 155} 156 157export function updateUserEmail(userId: number, newEmail: string): void { 158 db.run("UPDATE users SET email = ? WHERE id = ?", [newEmail, userId]); 159} 160 161export function updateUserName(userId: number, newName: string): void { 162 db.run("UPDATE users SET name = ? WHERE id = ?", [newName, userId]); 163} 164 165export function updateUserAvatar(userId: number, avatar: string): void { 166 db.run("UPDATE users SET avatar = ? WHERE id = ?", [avatar, userId]); 167} 168 169export async function updateUserPassword( 170 userId: number, 171 newPassword: string, 172): Promise<void> { 173 db.run("UPDATE users SET password_hash = ? WHERE id = ?", [ 174 newPassword, 175 userId, 176 ]); 177 db.run("DELETE FROM sessions WHERE user_id = ?", [userId]); 178}