FeaturesAuthAccount & Auth Emails

Account & Auth Emails (계정 설정 · 비밀번호 재설정 · 이메일 변경)

SPEC #145 정합. BE v0.109.0 도입. 링크(토큰 URL) 방식 · Azure Communication Services Email.

Overview

이메일 인프라 + 3 시나리오를 토큰 링크로 처리한다. 토큰 원문은 메일의 URL(?token=)에만 담기고 DB 엔 해시만 저장한다(일회용·만료·같은 type 재발급 시 이전 미사용 토큰 무효화).

시나리오토큰 type만료미인증 공개 페이지
계정 발급(setup link)ACCOUNT_SETUP72h/setup?token=
비밀번호 재설정(reset link)PASSWORD_RESET1h/forgot-password · /reset-password?token=
이메일 변경 인증(verify link, 새 주소)EMAIL_CHANGE1h/email-change/verify?token=

계정 role 별 링크 도메인이 다르다 — 운영자 = apps/admin(admin.space.linkmusic.io) · 본사·점장 = apps/space(space.linkmusic.io). 따라서 두 앱 모두 동일한 공개 페이지 4종(/setup· /forgot-password·/reset-password·/email-change/verify)을 가진다.

FE 공개 페이지 (미인증 · 양 앱)

(auth) 그룹의 standalone AuthShell + AuthCard(로그인 류 중앙 카드, 셸·사이드바 없음). 토큰은 URL query 에서 읽는다. middleware isPublic 에 4 경로를 등록하고, BFF catch-all proxy (/api/backend/[...path])는 공개 backend endpoint(auth/setup/*·auth/password-reset/*· auth/email-change/confirm·terms/active·privacy-policy/active)를 세션·Authorization 없이 그대로 프록시한다(토큰이 곧 인증). terms/active·privacy-policy/active 는 SPEC #147 의 setup 약관 “보기” 링크용으로 추가됐다.

  • /setup?token= — useVerifyAccountSetup 로 토큰 검증(이메일·role·만료 요약 + 활성 약관 버전) → 비밀번호 설정 폼(8~100자·확인 일치) + (SPEC #147) 약관 동의 → useCompleteAccountSetup → 완료 후 [로그인하기]. 무효/만료(ACCOUNT_TOKEN_INVALID) 시 공용 ErrorState ([로그인으로] actionHref="/login" + [비밀번호 재발급 요청] /forgot-password, setup-token-error) — dead-end 해소(SPEC #154).

    약관 동의 (SPEC #147) — 약관 동의 시점이 발급에서 계정 주인 본인의 이 setup 단계로 이전됐다. verify 응답의 activeTermsVersion·activePrivacyPolicyVersion(nullable)로 약관을 제시한다:

    • 활성 버전이 있으면 항목별 동의 체크박스(이용약관·개인정보) + 전체 동의 + 각 “보기” 링크 (public /api/v1/terms/active·/api/v1/privacy-policy/active 를 새 탭) + (선택) 동의자 이름. 필수 약관 전체 동의 + 비번 정책 통과 시에만 CTA 활성. atom-grounded(전용 시안 없음 — AuthCard· Banner·Button·raw checkbox).
    • 둘 다 null(OPERATOR·미게시)이면 동의 단계를 생략하고 비밀번호만 받는다.
    • complete 제출 body 에 termsVersion·privacyPolicyVersion·signerName(생략 시 계정명) 동봉.
    • 에러 INVALID_LEGAL_VERSION(동의 중 약관 개정 — 새로고침 후 재동의 안내). admin·space 두 앱의 setup-client.tsx 가 동일.
  • /forgot-password — 이메일 입력 → useRequestPasswordReset → 존재 은닉 안내(“가입된 계정이 있으면 재설정 메일을 보냈습니다”). 로그인 페이지 “비밀번호를 잊었나요?” 링크가 여기로 연결(기존 “준비 중” placeholder 대체).

  • /reset-password?token= — 새 비밀번호 폼 → useConfirmPasswordReset. 무효 링크(token 없음)는 공용 ErrorState([로그인으로]+[비밀번호 재발급 요청], reset-password-token-error, SPEC #154). 제출 시 만료는 인라인 처리.

  • /email-change/verify?token= — 마운트 시 useConfirmEmailChange(토큰만) → 전환 완료/실패 안내. 실패(만료·무효·EMAIL_ALREADY_IN_USE 재충돌)는 공용 ErrorState([로그인으로], email-change-verify-error, SPEC #154).

FE 이메일 변경 UI (인증 · 3 프로필)

공용 컴포넌트 EmailChangeSection(admin·space 미러)에 role 별 요청 훅을 wiring. 새 이메일 입력 → 요청 → “새 주소로 인증 메일을 보냈습니다” 안내. 중복(EMAIL_ALREADY_IN_USE) 에러 표시. 기존 비밀번호 변경 섹션 근처에 배치.

  • 운영자 — apps/admin /settings/profile · useRequestOperatorEmailChange
  • 본사 — apps/space /admin/settings · useRequestHqEmailChange
  • 점장 — apps/space /store/profile · useRequestStoreEmailChange

운영사 점장 이메일 정정 (V74)

본인 변경과 별도로, 운영사가 점장 계정의 로그인 이메일을 바로잡는 경로가 있다 (POST /api/v1/admin/stores/{storeId}/managers/{managerId}/email, apps/admin 매장 상세 점장 탭 [이메일 정정]).

  • 즉시 교체 — credentialSetAt·lastLoginAt 이 모두 null(아직 아무도 비밀번호를 정하지도 로그인하지도 않음). 옛 주소로 나간 ACCOUNT_SETUP·PASSWORD_RESET·EMAIL_CHANGE 토큰을 먼저 무효화한 뒤 교체하고, 새 주소로 계정 설정 메일을 다시 보낸다.
  • 새 주소 인증(2단계) — 그 밖. 위 EMAIL_CHANGE 흐름 그대로 새 주소로 인증 링크(1h), 기존 주소로 변경 알림을 보낸다. 단 알림의 새 주소는 가린다(r***@x.com) — 옛 주소의 주인이 잘못 발급된·잃어버린 주소의 주인(제3자)일 수 있다.
  • credentialSetAt 은 계정 설정 완료·비밀번호 재설정 확정·비밀번호 변경에서만 기록된다. 관리자 임시 비밀번호 재설정·setup 재발송은 바꾸지 않는다(판정자 오염 방지).

이메일 변경 확정 공통 — 확정(/email-change/verify) 시 옛 주소로 나간 대기 중 ACCOUNT_SETUP·PASSWORD_RESET 토큰을 무효화한다. 재설정 확정은 토큰의 발급 주소를 보지 않으므로, 남겨 두면 옛 주소 수신자가 확정 뒤에도 비밀번호를 새로 정할 수 있었다. 세션은 끊지 않는다(주소를 잃은 점장의 무인 매장 기기가 로그아웃되면 재생이 멈춘다).

FE 계정 발급 UI

발급 성공 화면(AccountShareSection)에 [설정 이메일 보내기](useResendAccountSetup(accountId))를 추가했다 — 운영자가 계정 설정 안내 메일을 바로 보낼 수 있다(권장 경로). 발급 응답의 계정 id (OperatorIssueResponse.id · StoreManagerIssueResponse.managerAccountId)를 보관해 활성화한다.

⚠️ 현황 메모 — BE v0.109.0 의 계정 발급 endpoint(issueOperator·issueStoreManager)는 여전히 요청 body 에 tempPassword 를 받고 임시 비밀번호 안내문을 생성한다. 따라서 안내문 복사(temp-password)는 메일 미수신 대비 오프라인 폴백으로 유지하고, 그 위에 [설정 이메일 보내기]를 권장 경로로 얹었다. 발급 흐름을 setup-link 전용으로 완전 전환하는 것은 후속 BE 변경 과제다.

에러 코드

  • ACCOUNT_TOKEN_INVALID — 토큰 무효·만료·이미 사용됨.
  • EMAIL_ALREADY_IN_USE — 이메일 변경 요청/전환 시 새 이메일 중복.