import { eq } from 'drizzle-orm' import { z } from 'zod' import { db } from '../db/client.js' import { users } from '../db/schema.js' import { hashPassword, verifyPassword } from '../lib/password.js' import { createSession, findAuthenticatedUser } from '../lib/authSession.js' const signupSchema = z.object({ email: z.string().trim().email(), password: z.string().min(8).max(72), nickname: z.string().trim().min(2).max(30), }) const loginSchema = z.object({ email: z.string().trim().email(), password: z.string().min(1).max(72), }) function sanitizeUser(user) { return { id: user.id, email: user.email, nickname: user.nickname, createdAt: user.createdAt, updatedAt: user.updatedAt, } } export async function registerAuthRoutes(app) { app.post('/api/auth/signup', async (request, reply) => { const payload = signupSchema.safeParse(request.body) if (!payload.success) { return reply.code(400).send({ message: '회원가입 입력값이 올바르지 않습니다.', issues: payload.error.flatten(), }) } const { email, password, nickname } = payload.data const normalizedEmail = email.toLowerCase() const [existingUser] = await db .select() .from(users) .where(eq(users.email, normalizedEmail)) .limit(1) if (existingUser) { return reply.code(409).send({ message: '이미 사용 중인 이메일입니다.', }) } const now = new Date() const passwordHash = await hashPassword(password) const [user] = await db .insert(users) .values({ email: normalizedEmail, passwordHash, nickname, createdAt: now, updatedAt: now, }) .returning() const { token } = await createSession(user.id) return reply.code(201).send({ message: '회원가입이 완료되었습니다.', token, user: sanitizeUser(user), }) }) app.post('/api/auth/login', async (request, reply) => { const payload = loginSchema.safeParse(request.body) if (!payload.success) { return reply.code(400).send({ message: '로그인 입력값이 올바르지 않습니다.', issues: payload.error.flatten(), }) } const normalizedEmail = payload.data.email.toLowerCase() const [user] = await db .select() .from(users) .where(eq(users.email, normalizedEmail)) .limit(1) if (!user) { return reply.code(401).send({ message: '이메일 또는 비밀번호가 올바르지 않습니다.', }) } const passwordMatches = await verifyPassword(payload.data.password, user.passwordHash) if (!passwordMatches) { return reply.code(401).send({ message: '이메일 또는 비밀번호가 올바르지 않습니다.', }) } const { token } = await createSession(user.id) return { message: '로그인에 성공했습니다.', token, user: sanitizeUser(user), } }) app.get('/api/auth/me', async (request, reply) => { const user = await findAuthenticatedUser(request) if (!user) { return reply.code(401).send({ message: '인증이 필요합니다.', }) } return { user: sanitizeUser(user), } }) }