feat: 소셜 로그인(Google/Kakao) 및 회원가입 이메일 인증 추가

- User 엔티티 확장: passwordHash nullable, provider, emailVerified, 인증 토큰 해시/만료(select:false)
- 회원가입: 자동 로그인 폐지 → 미인증 계정 생성 + 인증 메일 발송(MailService 플레이스홀더, 백엔드 로그로 링크 출력)
- 이메일 인증: GET /auth/verify-email(해시 매칭+만료 검사) → 프론트로 리다이렉트, 재발송 엔드포인트
- 로그인 게이트: 미인증 로컬 403 차단, 소셜 전용 계정은 소셜 로그인 안내
- 소셜 OAuth: passport google/kakao 전략(키 미설정 시 자동 비활성), 이메일로 계정 매칭/생성
- 기존 로컬 계정 그랜드페더링(부팅 시 인증 완료 보정)
- 프론트: 가입 후 인증 메일 안내 화면, 로그인 소셜 버튼 실연동 + 인증 결과 배너
- env(APP_API_URL/FRONTEND_URL/GOOGLE_*/KAKAO_*) 문서화, 문서·계약 갱신

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-19 17:04:30 +09:00
parent 7a2c70a260
commit 8766a4f5b6
21 changed files with 961 additions and 84 deletions
+15 -5
View File
@@ -1,13 +1,23 @@
import { useApi } from './useApi'
import type { AuthUser, LoginPayload, SignupPayload } from '@/types/auth'
import type {
AuthUser,
LoginPayload,
SignupPayload,
SignupResult,
} from '@/types/auth'
// 인증 도메인 API Composable — 인터셉터가 success/data 를 언래핑하므로 data 타입으로 캐스팅
export function useAuth() {
const api = useApi()
// 회원가입 (성공 시 백엔드가 인증 쿠키를 설정해 자동 로그인됨)
async function signup(payload: SignupPayload): Promise<AuthUser> {
return (await api.post('/auth/signup', payload)) as unknown as AuthUser
// 회원가입 — 인증 메일 발송 후 대기(자동 로그인 X). 인증 완료 전까지 로그인 불가.
async function signup(payload: SignupPayload): Promise<SignupResult> {
return (await api.post('/auth/signup', payload)) as unknown as SignupResult
}
// 인증 메일 재발송
async function resendVerification(email: string): Promise<void> {
await api.post('/auth/resend-verification', { email })
}
// 로그인
@@ -26,5 +36,5 @@ export function useAuth() {
return (await api.get('/auth/me', { silent: true })) as unknown as AuthUser
}
return { signup, login, logout, fetchMe }
return { signup, resendVerification, login, logout, fetchMe }
}