import { eq } from "drizzle-orm"; import { dbGlobal } from "@/drizzle-pkg/lib/db"; import { users } from "@/drizzle-pkg/lib/schema/auth"; import type { IAuthStrategy, AuthUser } from "../../types/auth"; import { verifyPassword, isLocked, } from "../lib/password"; import { signAccessToken } from "../lib/jwt"; export class PasswordStrategy implements IAuthStrategy { readonly type = "password" as const; async authenticate(credentials: { email: string; password: string; ip?: string; userAgent?: string; }): Promise { const { email, password, ip, userAgent } = credentials; const [user] = await dbGlobal .select() .from(users) .where(eq(users.email, email)) .limit(1); if (!user) { throw { code: "INVALID_CREDENTIALS", message: "邮箱或密码错误" }; } if (user.lockoutUntil && isLocked(user.lockoutUntil)) { throw { code: "ACCOUNT_LOCKED", message: "账户已锁定,请稍后再试" }; } const valid = await verifyPassword(password, user.password); if (!valid) { const attempts = (user.failedLoginAttempts || 0) + 1; const lockoutUntil = attempts >= 5 ? new Date(Date.now() + 15 * 60_000) : null; await dbGlobal .update(users) .set({ failedLoginAttempts: attempts, lockoutUntil: lockoutUntil ?? null, }) .where(eq(users.id, user.id)); throw { code: "INVALID_CREDENTIALS", message: "邮箱或密码错误" }; } await dbGlobal .update(users) .set({ failedLoginAttempts: 0, lockoutUntil: null, lastLoginAt: new Date(), lastLoginIp: ip ?? null, }) .where(eq(users.id, user.id)); return { id: user.id, email: user.email, username: user.username, role: user.role, status: user.status, }; } async generateTokens( user: AuthUser, sessionId: string ): Promise<{ accessToken: string; refreshToken: string }> { const accessToken = await signAccessToken({ userId: user.id, sessionId, role: user.role, }); return { accessToken, refreshToken: "" }; } async revokeSession(sessionId: string): Promise { // delegated to AuthService } }