Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | 'use server'; import { cookies, headers } from 'next/headers'; import { redirect } from 'next/navigation'; import { prisma } from '@/lib/db'; import { SESSION_COOKIE, SESSION_MAX_AGE, signSession, verifyPassword, } from '@/lib/auth'; import { checkRateLimit, resetRateLimit } from '@/lib/rate-limit'; export type LoginState = { error?: string }; const LOGIN_MAX_ATTEMPTS = 5; const LOGIN_WINDOW_SECONDS = 15 * 60; async function getClientIp(): Promise<string> { const h = await headers(); const xff = h.get('x-forwarded-for'); if (xff) return xff.split(',')[0].trim(); return h.get('x-real-ip') ?? 'unknown'; } export async function loginAction( _prev: LoginState, formData: FormData, ): Promise<LoginState> { const username = String(formData.get('username') ?? '').trim(); const password = String(formData.get('password') ?? ''); const next = String(formData.get('next') ?? '/dashboard'); if (!username || !password) { return { error: 'กรุณากรอกข้อมูลให้ครบถ้วน' }; } const ip = await getClientIp(); const limitKey = `login:${ip}:${username.toLowerCase()}`; const rl = await checkRateLimit({ key: limitKey, limit: LOGIN_MAX_ATTEMPTS, windowSeconds: LOGIN_WINDOW_SECONDS, }); if (!rl.allowed) { return { error: `พยายามเข้าสู่ระบบเกินจำนวนที่กำหนด ลองใหม่ในอีก ${Math.ceil(rl.resetIn / 60)} นาที`, }; } const user = await prisma.user.findUnique({ where: { username } }); if (!user || user.status !== 'ACTIVE') { return { error: 'ชื่อผู้ใช้งานหรือรหัสผ่านไม่ถูกต้อง' }; } const ok = await verifyPassword(password, user.passwordHash); if (!ok) { return { error: 'ชื่อผู้ใช้งานหรือรหัสผ่านไม่ถูกต้อง' }; } await prisma.user.update({ where: { id: user.id }, data: { lastLoginAt: new Date() }, }); await resetRateLimit(limitKey); const token = await signSession({ sub: user.id, username: user.username, role: user.role, }); (await cookies()).set(SESSION_COOKIE, token, { httpOnly: true, sameSite: 'lax', secure: process.env.NODE_ENV === 'production', path: '/', maxAge: SESSION_MAX_AGE, }); redirect(next.startsWith('/') ? next : '/dashboard'); } export async function logoutAction(): Promise<void> { (await cookies()).delete(SESSION_COOKIE); redirect('/login'); } |