feat: 비밀번호 재설정 플로우 (이메일 링크 기반)

백엔드:
- User 엔티티에 passwordResetTokenHash/passwordResetExpiresAt(select:false) 추가
- UserService: setPasswordResetToken/findByPasswordResetTokenHash/updatePassword
- MailService: sendPasswordResetEmail (dev=로그 출력, prod=수신자만)
- AuthService: requestPasswordReset(존재 비노출·로컬계정만·1시간 토큰),
  resetPassword(토큰 해시 검증→새 비번 저장→전 세션 폐기)
- POST /auth/forgot-password(3/분)·/auth/reset-password(5/분), 가입과 동일 비번 규칙
- OAuth 콜백의 잔여 /repos 리다이렉트를 /projects 로 교정(리팩터 누락분)

프론트:
- useAuth: forgotPassword/resetPassword
- ForgotPasswordPage(/forgot-password), ResetPasswordPage(/reset-password, 토큰=쿼리)
- LoginPage '비밀번호 찾기' 링크 연결 + 재설정 완료(?reset=1) 배너

런타임 검증: 재설정→새 비번 로그인 200·구 비번 거부·토큰 재사용 거부·약한 비번 VAL_001·
없는 이메일 동일응답(비노출). 백엔드 build/lint 0·14 tests, 프론트 type-check/lint/build 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-20 20:50:34 +09:00
parent cb6aa07ebc
commit 52b70363d9
12 changed files with 745 additions and 7 deletions
+12
View File
@@ -19,6 +19,18 @@ const routes: RouteRecordRaw[] = [
component: () => import('@/pages/relay/SignupPage.vue'),
meta: { requiresAuth: false },
},
{
path: '/forgot-password',
name: 'forgot-password',
component: () => import('@/pages/relay/ForgotPasswordPage.vue'),
meta: { requiresAuth: false },
},
{
path: '/reset-password',
name: 'reset-password',
component: () => import('@/pages/relay/ResetPasswordPage.vue'),
meta: { requiresAuth: false },
},
{
path: '/projects',
name: 'project-list',