---
title: "📓 작업일지 — 이메일 회원가입 도입(가입·이메일확인·비밀번호 찾기/변경)"
category: "worklog"
parent: "Claude Code 작업일지"
updated: "2026-07-14"
priority: "High"
purpose: "그동안 소셜(구글·카카오·네이버) 로그인만 지원하던 달록에 이메일/비밀번호 직접 가입·로그인 경로를 새로 도입한 기록. 회원가입, 가입 확인 메일, 비밀번호 찾기(재설정), 설정 내 비밀번호 변경까지 한 세트로 구현되었다(demo 검증분 main 반영)."
read_when: ["이메일 회원가입", "signUpWithEmail", "비밀번호 재설정", "recoveryFlag", "PASSWORD_RECOVERY", "isEmailPasswordUser", "비밀번호 변경", "AuthCallbackPage"]
document_type: "개발일지"
source_status: "generated"
knowledge_group: "03_history"
work_timestamp: "20260714_164626"
source_of_truth: "https://dallog-tools.hansbridge.co.kr/knowledge/"
context: "달록 본레포(ccy-hansbridge/dallog), 커밋 e3545fd(2026-07-14, demo 검증분 main 반영 — deploy 배치 커밋의 일부)."
---

# 작업일지 — 이메일 회원가입 도입(가입·이메일확인·비밀번호 찾기/변경)

> 지금까지 달록은 "소셜 로그인(구글·카카오·네이버)만 지원, 별도 아이디/비밀번호 가입 없음"이 정책이었다(기존 `SocialAuthInfoPage`가 그 안내 화면). 이번 작업으로 이메일 주소와 비밀번호로 직접 가입·로그인할 수 있는 경로가 새로 생겼고, 기존 소셜 로그인은 그대로 유지된다.

## 1. 무엇을 했나

### 이메일 가입/로그인 핵심 로직 — `src/lib/auth.ts`
- `signUpWithEmail(email, password)`: Supabase `auth.signUp` 호출. 이메일 확인(Confirm email) 설정이 켜져 있으면 세션이 즉시 발급되지 않고 확인 메일이 발송되며, 반환값 `needsConfirmation`으로 이를 구분.
- `signInWithEmail(email, password)`: `auth.signInWithPassword` 래퍼.
- `requestPasswordReset(email)` / `updatePassword(newPassword)`: 비밀번호 재설정 메일 발송(`redirectTo: /auth/reset`) 및 복구 세션에서 새 비밀번호 확정.
- `resendSignupEmail(email)`: 확인 메일 재발송.
- `EMAIL_MIN_PASSWORD = 8`: 비밀번호 최소 길이(클라이언트 1차 UX 안내용).
- `isEmailPasswordUser(user)`: 비밀번호 변경 UI 노출 여부 판별. 카카오·네이버는 auth-proxy 워커가 Supabase 이메일 계정(임시 비번)으로 생성해 `app_metadata.provider`가 `'email'`로 잡힐 수 있어, `user_metadata.provider_kakao/naver` 플래그와 합성 이메일 문자열(`kakao`/`naver` 포함 여부)까지 다중 신호로 소셜 계정을 배제(2026.07.14 Codex 치명 반영).
- `friendlyAuthError(message)`: Supabase 인증 오류 메시지를 한국어 안내문으로 변환(잘못된 비밀번호, 미인증 이메일, 중복 가입, rate limit 등).
- `redirectOriginForHost`/`authRedirectOrigin`: 인증 리다이렉트가 `demo.dallog.kr`/`dallog.kr` 등 정규 도메인으로 돌아오도록 호스트 기준 오리진 결정.

### 복구 링크(비밀번호 재설정) 진입 신호 — `src/lib/recoveryFlag.ts`(신규)
- `/auth/reset` 폼을 "복구 링크로 진입(Supabase `PASSWORD_RECOVERY` 이벤트)"했을 때만 열기 위한 상태 플래그.
- `reduceRecoveryFlag(current, event)`: 순수 함수로 상태전이 구현 — `PASSWORD_RECOVERY`→true, `SIGNED_OUT`/`SIGNED_IN`→false(세션 종료·전환 시 반드시 리셋), 그 외 이벤트는 유지.
- 세션 종료·전환 시 리셋하지 않으면 복구 권한이 남아있는 상태로 게스트/다른 계정으로 전환해 비밀번호를 바꾸는 "세션 바꿔치기"가 가능해짐(Codex 재검수 치명 지적 반영).
- `src/lib/supabase.ts`에서 모듈 로드 시점에 `supabase.auth.onAuthStateChange`로 즉시 구독해 컴포넌트 마운트 전에 발생할 수 있는 `PASSWORD_RECOVERY` 이벤트를 레이스 없이 잡음.

### 새 화면 3종
- `src/pages/EmailSignupPage.tsx`(120줄, 신규) — 이메일 가입 폼. 라우트 `/signup`에서 기존 `SocialAuthInfoPage(variant="signup")`을 대체.
- `src/pages/PasswordResetRequestPage.tsx`(67줄, 신규) — 비밀번호 찾기(재설정 메일 요청) 폼. 라우트 `/find-account`에서 기존 `SocialAuthInfoPage(variant="find")`를 대체.
- `src/pages/PasswordResetPage.tsx`(104줄, 신규) — 재설정 메일 링크로 진입해 새 비밀번호를 확정하는 화면. 라우트 `/auth/reset`(스플래시·동의 게이트 이전 단계에서 처리해 복구 세션이 게이트에 걸리지 않게 함).
- 기존 `src/pages/SocialAuthInfoPage.tsx`(71줄)는 삭제됨 — "소셜 로그인만 지원" 안내 화면이 더 이상 맞지 않기 때문.

### 로그인 폼 — `MasterLoginForm.tsx`
- 아이디 입력란이 "이메일" 입력란으로 바뀌고, 입력값이 `@`를 포함하면 `signInWithEmail`로 분기(일반 사용자). 특수계정(마스터 `mster`, 게스트 `qwer1234`)은 기존 분기 유지.
- "비밀번호를 잊으셨나요?" 링크(`/find-account`)가 로그인 폼 하단에 추가됨.

### 콜백 안전성 — `AuthCallbackPage.tsx`
- 콜백 URL에 `error`/`error_code`/`error_description` 파라미터가 실려 오면(만료·거부된 링크) 기존 세션이 남아있어도 성공 화면으로 위장하지 않고 즉시 `/login`으로 이동.
- `getSession()` 결과에 세션이 없거나 오류가 있으면 마찬가지로 `/login`으로 안내(만료·변조된 확인/재설정 링크 대응, Codex 권고 반영).

### 설정 화면 — 비밀번호 변경(`Settings.tsx`)
- 이메일 가입자 전용으로 "비밀번호 변경" 인라인 에디터 추가(`isEmailProvider` 조건, 소셜 계정은 노출 안 함).
- 변경 전 현재 비밀번호로 `signInWithPassword` 재인증을 먼저 통과해야 `updatePassword` 호출 — 세션 탈취 상태에서 비밀번호가 바뀌는 것을 방지.

### 라우팅 — `App.tsx`
- `/signup`, `/find-account` 라우트가 `EmailSignupPage`/`PasswordResetRequestPage`로 교체(게스트·로그인 상태 라우터 양쪽 모두).
- `/auth/reset` 라우트 신설(스플래시/동의 게이트보다 먼저 처리).

### 테스트
- `src/lib/authError.test.ts`(25줄), `src/lib/authHelpers.test.ts`(49줄), `src/lib/recoveryFlag.test.ts`(39줄) 신규 — `friendlyAuthError`, `isEmailPasswordUser` 등 순수 로직 단위 테스트.

## 2. 주요 변경 파일

| 파일 | 내용 |
|---|---|
| `src/lib/auth.ts` | 이메일 가입/로그인/재설정/재발송 함수 + 오류 메시지 변환(+94줄) |
| `src/lib/recoveryFlag.ts` | 신규 — 복구 링크 진입 상태 플래그(순수 상태전이) |
| `src/lib/supabase.ts` | 인증 이벤트 구독으로 recoveryFlag 갱신 |
| `src/pages/EmailSignupPage.tsx` | 신규 — 이메일 가입 폼 |
| `src/pages/PasswordResetRequestPage.tsx` | 신규 — 비밀번호 찾기 폼 |
| `src/pages/PasswordResetPage.tsx` | 신규 — 새 비밀번호 설정 화면 |
| `src/pages/SocialAuthInfoPage.tsx` | 삭제 — 소셜 전용 안내 화면 |
| `src/components/auth/MasterLoginForm.tsx` | 이메일 로그인 분기 + 비밀번호 찾기 링크 |
| `src/pages/AuthCallbackPage.tsx` | 콜백 error 파라미터·세션 실패 시 로그인 화면으로 안내 |
| `src/pages/Settings.tsx` | 이메일 가입자 전용 비밀번호 변경 UI(+재인증) |
| `src/App.tsx` | `/signup`·`/find-account` 라우트 교체, `/auth/reset` 신설 |
| `src/lib/authError.test.ts`, `authHelpers.test.ts`, `recoveryFlag.test.ts` | 신규 단위 테스트 3종 |

### 핵심 발췌 — 세션 바꿔치기 차단(recoveryFlag)
```ts
export function reduceRecoveryFlag(current: boolean, event: string): boolean {
  if (event === 'PASSWORD_RECOVERY') return true
  if (event === 'SIGNED_OUT' || event === 'SIGNED_IN') return false
  return current
}
```

## 3. 관련 커밋

- `e3545fd` deploy(main): demo 검증분 일괄 반영 — 이메일 회원가입(가입·확인·비번찾기/변경) 포함 (2026-07-14)

## 4. 남은 이슈

커밋 기준 별도로 언급된 남은 이슈 없음. (법적문서 개정·재동의 트리거는 별도 작업일지에서 다룸.)

