整合資料庫、完成登入註冊忘記密碼功能
This commit is contained in:
131
lib/services/password-reset-service.ts
Normal file
131
lib/services/password-reset-service.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
// =====================================================
|
||||
// 密碼重設服務
|
||||
// =====================================================
|
||||
|
||||
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<PasswordResetToken> {
|
||||
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<PasswordResetToken | null> {
|
||||
const sql = `
|
||||
SELECT * FROM password_reset_tokens
|
||||
WHERE token = ? AND expires_at > NOW() AND used_at IS NULL
|
||||
`;
|
||||
|
||||
const result = await db.queryOne<PasswordResetToken>(sql, [token]);
|
||||
return result;
|
||||
}
|
||||
|
||||
// 使用密碼重設 token
|
||||
static async useResetToken(token: string, newPassword: string): Promise<boolean> {
|
||||
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<number> {
|
||||
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<PasswordResetToken[]> {
|
||||
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<PasswordResetToken>(sql, [userId]);
|
||||
}
|
||||
|
||||
// 撤銷用戶的所有重設 tokens
|
||||
static async revokeUserTokens(userId: string): Promise<number> {
|
||||
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;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user