Sign-up + 이메일 인증
회원가입 → 이메일 코드 발송 → 코드 확인 → 그 다음에야 다른 API 사용 가능.
흐름
1. ClientAuth.signUp(body)
→ 200 + accessToken (sign-in 과 동일)
→ user.emailVerifiedAt 은 null
→ 다른 endpoint 호출 시 403 (이메일 인증 강제)
2. ClientEmailVerification.sendVerification({ email })
→ user.email 로 6자리 OTP 발송
→ 응답에 expiresAt + nextResendAvailableAt
3. ClientEmailVerification.verifyEmail({ code })
→ user.email / user.emailVerifiedAt 갱신
→ 이후 모든 endpoint 호출 가능
1. 회원가입
const res = await ClientAuth.signUp({
body: {
tenantKey: 'classum',
userId: 'newbie',
password: 'pw-12345678',
displayName: 'Newbie',
email: 'newbie@classum.com',
},
})
hooks.setAccessToken(res.data!.accessToken, res.data!.accessTokenExpiresAt)
에러 코드
| status | code | 의미 |
|---|---|---|
| 400 | signUp/EmailDomainNotAllowed |
tenant 의 허용 이메일 도메인 정책에 없는 도메인 |
| 400 | (zod) | password min(8) / email format / userId regex |
| 404 | tenant/NotFound |
tenant 없음 / soft-deleted |
| 409 | user/UserIdDuplicated |
같은 tenant 안 userId 중복 |
2. 이메일 인증 코드 발송
const res = await ClientEmailVerification.sendVerification({
body: { email: 'newbie@classum.com' },
})
// 200 — { expiresAt, nextResendAvailableAt }
- OTP 만료: 10분
- 시도 횟수: 5회 (틀리면 코드 무효화)
- 재발송 cooldown: 60초
에러 코드
| status | code | 의미 |
|---|---|---|
| 400 | (zod) | email format |
| 429 | emailVerification/CooldownActive |
60초 cooldown 중 |
3. 코드 검증
const res = await ClientEmailVerification.verifyEmail({ body: { code: '123456' } })
// 200 — { user: { ..., email, emailVerifiedAt } }
이 시점부터 다른 모든 client_auth endpoint 호출 가능.
에러 코드
| status | code | 의미 |
|---|---|---|
| 400 | (zod) | code 가 6자리 숫자 아님 |
| 401 | emailVerification/InvalidCode |
만료 / 시도초과 / 불일치 — 모두 같은 응답 |
4. React + TanStack Query 예시
function SignUpFlow() {
const [stage, setStage] = useState<'form' | 'verify'>('form')
const signUpMutation = useMutation({
mutationFn: async (input) => {
const res = await ClientAuth.signUp({ body: input })
if (res.error) throw res.error
return res.data!
},
onSuccess: (data) => {
hooks.setAccessToken(data.accessToken, data.accessTokenExpiresAt)
ClientEmailVerification.sendVerification({ body: { email: data.user.email! } })
setStage('verify')
},
})
const verifyMutation = useMutation({
mutationFn: async (code: string) => {
const res = await ClientEmailVerification.verifyEmail({ body: { code } })
if (res.error) throw res.error
return res.data!
},
onSuccess: () => navigate('/'),
})
return stage === 'form' ? <SignUpForm onSubmit={signUpMutation.mutate} /> : <VerifyCode onSubmit={verifyMutation.mutate} />
}
주의 — admin_issued user 는 별도
BackOffice 가 발급한 매니저 (source=admin_issued) 는 이메일 인증 강제 안 됨. sign-in 후 바로 모든 endpoint 사용 가능. 임시 비밀번호는 별도 변경 흐름 권장.