fix: 미인증 계정 재가입 허용 및 로그인 화면 인증 메일 재발송

인증 미완료 상태에서 재가입/로그인이 모두 막혀 데드락되던 문제 해결.
- signup: 인증 완료 계정만 409로 거부하고, 미인증 로컬 계정은 입력값으로 갱신 후 인증 메일 재발송(중복 계정 생성 안 함)
- 로그인 화면: 미인증(403) 응답 시 '인증 메일 다시 보내기' 인라인 액션 노출

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-22 13:42:34 +09:00
parent afed0d087a
commit 5e742e5c8b
3 changed files with 114 additions and 5 deletions
+82 -1
View File
@@ -16,6 +16,12 @@ const showPassword = ref(false)
const keepLogin = ref(true)
const submitting = ref(false)
// 미인증 계정 로그인 시도 시(서버 403) 인라인 재발송 안내 노출
const needsVerify = ref(false)
const pendingEmail = ref('')
const resending = ref(false)
const resendDone = ref(false)
// 상단 안내 배너 — 이메일 인증(/login?verified=1|0) 또는 비밀번호 재설정 완료(/login?reset=1)
const verifyBanner = computed<{ ok: boolean; text: string } | null>(() => {
if (route.query.reset === '1')
@@ -34,6 +40,7 @@ const verifyBanner = computed<{ ok: boolean; text: string } | null>(() => {
async function onLogin() {
if (submitting.value) return
submitting.value = true
needsVerify.value = false
try {
await authStore.login({
email: email.value.trim(),
@@ -41,10 +48,30 @@ async function onLogin() {
keepLogin: keepLogin.value,
})
await router.push('/projects')
} catch (err) {
// 미인증 계정은 서버가 403 으로 응답 → 인라인 재발송 안내 노출(메시지 토스트는 인터셉터가 처리)
const status = (err as { response?: { status?: number } })?.response?.status
if (status === 403) {
pendingEmail.value = email.value.trim()
resendDone.value = false
needsVerify.value = true
}
} finally {
submitting.value = false
}
}
// 인증 메일 재발송 — 미인증 로그인 안내에서 호출(존재 여부는 서버가 노출하지 않음)
async function resendVerify() {
if (resending.value || !pendingEmail.value) return
resending.value = true
try {
await authStore.resendVerification(pendingEmail.value)
resendDone.value = true
} catch {
// 에러 메시지는 API 인터셉터에서 전역 처리됨
} finally {
submitting.value = false
resending.value = false
}
}
@@ -156,6 +183,32 @@ const points = [
{{ verifyBanner.text }}
</div>
<!-- 미인증 계정 로그인 시도 인증 메일 재발송 안내 -->
<div
v-if="needsVerify"
class="verify-banner err resend-banner"
role="status"
>
<div class="rb-msg">
이메일 인증이 완료되지 않았습니다. 받은 인증 메일의 링크로 가입을 완료해 주세요.
</div>
<button
v-if="!resendDone"
class="rb-btn"
type="button"
:disabled="resending"
@click="resendVerify"
>
{{ resending ? '보내는 중…' : '인증 메일 다시 보내기' }}
</button>
<div
v-else
class="rb-done"
>
인증 메일을 다시 보냈습니다. 메일함을 확인해 주세요.
</div>
</div>
<!-- 소셜 로그인 -->
<div class="socials">
<button
@@ -518,6 +571,34 @@ const points = [
height: 1rem;
flex-shrink: 0;
}
/* 미인증 재발송 안내 — 메시지 + 액션 버튼(세로 배치) */
.resend-banner {
flex-direction: column;
align-items: flex-start;
gap: 0.5rem;
}
.resend-banner .rb-btn {
border: 1px solid var(--red-border);
background: #fff;
color: var(--red);
font-family: inherit;
font-size: 0.781rem;
font-weight: 700;
padding: 0.4375rem 0.75rem;
border-radius: var(--radius-sm);
cursor: pointer;
}
.resend-banner .rb-btn:hover:not(:disabled) {
background: #fff5f5;
}
.resend-banner .rb-btn:disabled {
opacity: 0.6;
cursor: default;
}
.resend-banner .rb-done {
font-weight: 600;
color: #1b7a3d;
}
.socials {
display: flex;
flex-direction: column;