// ===================================================== // 密碼重設服務 // ===================================================== import { db } from '../database'; import { v4 as uuidv4 } from 'uuid'; import bcrypt from 'bcryptjs'; export interface PasswordResetToken { id: string; user_id: string; token: string; expires_at: string; used_at?: string; created_at: string; updated_at: string; } export class PasswordResetService { // 創建密碼重設 token static async createResetToken(userId: string): Promise { const token = uuidv4() + '-' + Date.now(); const expiresAt = new Date(Date.now() + 60 * 60 * 1000); // 1 小時後過期 const sql = ` INSERT INTO password_reset_tokens (id, user_id, token, expires_at) VALUES (?, ?, ?, ?) `; const params = [uuidv4(), userId, token, expiresAt]; await db.insert(sql, params); return { id: params[0], user_id: userId, token, expires_at: expiresAt.toISOString(), created_at: new Date().toISOString(), updated_at: new Date().toISOString() }; } // 驗證密碼重設 token static async validateResetToken(token: string): Promise { const sql = ` SELECT * FROM password_reset_tokens WHERE token = ? AND expires_at > NOW() AND used_at IS NULL `; const result = await db.queryOne(sql, [token]); return result; } // 使用密碼重設 token static async useResetToken(token: string, newPassword: string): Promise { const connection = await db.getConnection(); try { await connection.beginTransaction(); // 獲取 token 信息 const tokenInfo = await this.validateResetToken(token); if (!tokenInfo) { throw new Error('無效或已過期的重設 token'); } // 加密新密碼 const saltRounds = 12; const passwordHash = await bcrypt.hash(newPassword, saltRounds); // 更新用戶密碼 const updateUserSql = ` UPDATE users SET password_hash = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? `; await connection.execute(updateUserSql, [passwordHash, tokenInfo.user_id]); // 標記 token 為已使用 const updateTokenSql = ` UPDATE password_reset_tokens SET used_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP WHERE token = ? `; await connection.execute(updateTokenSql, [token]); await connection.commit(); return true; } catch (error) { await connection.rollback(); throw error; } finally { connection.release(); } } // 清理過期的 tokens static async cleanupExpiredTokens(): Promise { const sql = ` DELETE FROM password_reset_tokens WHERE expires_at < NOW() OR used_at IS NOT NULL `; const result = await db.update(sql, []); return result.affectedRows; } // 獲取用戶的活躍重設 tokens static async getUserActiveTokens(userId: string): Promise { const sql = ` SELECT * FROM password_reset_tokens WHERE user_id = ? AND expires_at > NOW() AND used_at IS NULL ORDER BY created_at DESC `; return await db.query(sql, [userId]); } // 撤銷用戶的所有重設 tokens static async revokeUserTokens(userId: string): Promise { const sql = ` UPDATE password_reset_tokens SET used_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP WHERE user_id = ? AND used_at IS NULL `; const result = await db.update(sql, [userId]); return result.affectedRows; } }