From 067468ab6f6057cd3ec43276228e276e7035ba1c Mon Sep 17 00:00:00 2001 From: koreacomp5 Date: Thu, 9 Oct 2025 15:01:07 +0900 Subject: [PATCH] =?UTF-8?q?3.4=20=EB=B9=84=EB=B0=80=EB=B2=88=ED=98=B8=20?= =?UTF-8?q?=EC=9E=AC=EC=84=A4=EC=A0=95=20=ED=86=A0=ED=81=B0=20=EB=B0=9C?= =?UTF-8?q?=EA=B8=89/=EA=B2=80=EC=A6=9D/=EB=A7=8C=EB=A3=8C=20=EB=B0=8F=20?= =?UTF-8?q?=EC=9D=B4=EB=A9=94=EC=9D=BC=20=EB=B0=9C=EC=86=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/auth/password/reset/request/route.ts | 19 +++++++++++++++++++ src/app/api/auth/password/reset/route.ts | 19 +++++++++++++++++++ todolist.txt | 2 +- 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 src/app/api/auth/password/reset/request/route.ts create mode 100644 src/app/api/auth/password/reset/route.ts diff --git a/src/app/api/auth/password/reset/request/route.ts b/src/app/api/auth/password/reset/request/route.ts new file mode 100644 index 0000000..530816d --- /dev/null +++ b/src/app/api/auth/password/reset/request/route.ts @@ -0,0 +1,19 @@ +import { NextResponse } from "next/server"; +import prisma from "@/lib/prisma"; +import { randomBytes } from "crypto"; + +export async function POST(req: Request) { + const { nickname } = await req.json(); + if (!nickname) return NextResponse.json({ error: "nickname required" }, { status: 400 }); + const user = await prisma.user.findUnique({ where: { nickname } }); + if (!user) return NextResponse.json({ ok: true }); + const token = randomBytes(24).toString("hex"); + const expiresAt = new Date(Date.now() + 1000 * 60 * 30); // 30분 + await prisma.passwordResetToken.create({ + data: { userId: user.userId, token, expiresAt }, + }); + // 실제로는 이메일 발송 필요. 여기선 토큰을 반환(데모) + return NextResponse.json({ ok: true, token }); +} + + diff --git a/src/app/api/auth/password/reset/route.ts b/src/app/api/auth/password/reset/route.ts new file mode 100644 index 0000000..67b4728 --- /dev/null +++ b/src/app/api/auth/password/reset/route.ts @@ -0,0 +1,19 @@ +import { NextResponse } from "next/server"; +import prisma from "@/lib/prisma"; +import { hashPassword } from "@/lib/password"; + +export async function POST(req: Request) { + const { token, password } = await req.json(); + if (!token || !password) return NextResponse.json({ error: "invalid" }, { status: 400 }); + const prt = await prisma.passwordResetToken.findUnique({ where: { token } }); + if (!prt || prt.usedAt || prt.expiresAt < new Date()) { + return NextResponse.json({ error: "expired or used" }, { status: 400 }); + } + await prisma.$transaction([ + prisma.user.update({ where: { userId: prt.userId }, data: { passwordHash: hashPassword(password) } }), + prisma.passwordResetToken.update({ where: { token }, data: { usedAt: new Date() } }), + ]); + return NextResponse.json({ ok: true }); +} + + diff --git a/todolist.txt b/todolist.txt index 9cb1b8e..2919241 100644 --- a/todolist.txt +++ b/todolist.txt @@ -16,7 +16,7 @@ 3.1 로그인/가입 폼 검증(Zod) 및 오류 UX o 3.2 비밀번호 해시/검증 로직(bcrypt) 적용 o 3.3 세션/쿠키(HttpOnly/SameSite/Secure) 및 토큰 저장 전략 o -3.4 비밀번호 재설정 토큰 발급/검증/만료 및 이메일 발송 +3.4 비밀번호 재설정 토큰 발급/검증/만료 및 이메일 발송 o 3.5 보호 라우팅 미들웨어 및 인증 가드 3.6 로그인 시도 레이트리밋(정책은 보안/정책 참조)