Skip to content

Commit 0a2a76e

Browse files
authored
feat: 아이디 회원가입에 이메일 인증번호 확인 추가 (#8)
* feat: 아이디 회원가입에 이메일 인증번호 확인 추가 - 이메일 입력 → [인증번호 받기] → 6자리 인증번호 확인 후 받은 verificationToken으로 가입 - Turnstile은 [인증번호 받기]에서 사용하고, 발송 요청마다 성공·실패와 관계없이 위젯을 리셋 - [가입하기]는 이메일 인증 완료 + 필수 약관 동의 시 활성화 (OAuth 탭은 기존과 동일) - 인증번호 유효 시간·재발송 대기 시간 카운트다운 표시 - 오류 처리: 발송 409(가입된 이메일, 로그인 링크)·429·503·봇 검증 실패, 확인 400(남은 시도 / '다시 요청' 시 발송 단계로 복귀), 가입 409(메시지에 '이메일' 포함 시 이메일 필드, 아니면 아이디), 가입 400 이메일 인증 만료 시 토큰 초기화 후 재인증 - 아이디 중복으로 가입이 실패해도 인증 토큰을 유지해 재인증 없이 다시 가입 가능 * docs: 개인정보 처리방침에 로컬 회원가입 이메일 수집·인증 항목 반영 - 회원가입(로컬) 수집 항목에 이메일 추가, 처리 목적에 중복 가입 방지·계정 안내 추가 - 회원가입 이메일 인증 항목 신설 (인증번호 10분, 인증 완료 정보 30분, 발송 제한용 이메일 해시 24시간) - 보유 기간, Amazon SES 위탁 업무(회원가입 인증번호·계정 안내), 인증번호·토큰 해시 저장 반영 - 2026-10-06 판을 그대로 수정하며 약관 버전(CURRENT_TERMS_VERSION)은 변경하지 않음
1 parent 650a88f commit 0a2a76e

6 files changed

Lines changed: 613 additions & 28 deletions

File tree

‎legal/privacy-policy-261006.md‎

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,17 @@ devport.kr는 다음의 개인정보 항목을 서비스 계약의 체결 및
1818
#### 회원가입 (로컬)
1919

2020
- 법적 근거: 「개인정보 보호법」 제15조제1항제4호 (계약 체결·이행)
21-
- 처리 목적: 회원 가입 의사 확인, 본인 식별·인증, 회원자격 유지·관리
22-
- 수집·이용 항목: 아이디(username), 비밀번호(해시 처리)
21+
- 처리 목적: 회원 가입 의사 확인, 본인 식별·인증, 중복 가입 방지, 비밀번호 재설정 등 계정 관련 안내, 회원자격 유지·관리
22+
- 수집·이용 항목: 아이디(username), 비밀번호(해시 처리), 이메일(email)
2323
- 보유 및 이용 기간: 회원 탈퇴 시까지
2424

25+
#### 회원가입 이메일 인증 (로컬)
26+
27+
- 법적 근거: 「개인정보 보호법」 제15조제1항제4호 (계약 체결·이행)
28+
- 처리 목적: 회원가입 시 입력한 이메일 주소의 소유 확인, 인증번호 발송 횟수 제한 등 부정이용 방지
29+
- 수집·이용 항목: 이메일 주소, 인증번호 및 인증 토큰(해시 처리)
30+
- 보유 및 이용 기간: 인증번호는 발송 후 최대 10분, 인증 완료 정보는 인증 후 최대 30분 보관 후 자동 삭제 (회원가입을 완료하면 즉시 삭제). 발송 횟수 제한을 위한 이메일 주소의 해시값은 최대 24시간 보관 후 자동 삭제
31+
2532
#### 회원가입 (소셜 로그인)
2633

2734
- 법적 근거: 「개인정보 보호법」 제15조제1항제4호 (계약 체결·이행)
@@ -77,6 +84,7 @@ devport.kr는 만 14세 미만 아동의 회원가입을 허용하지 않습니
7784
devport.kr는 법령에 따른 개인정보 보유·이용 기간 또는 정보주체로부터 개인정보를 수집 시에 안내한 보유·이용 기간 내에서 개인정보를 처리·보유합니다. 각각의 개인정보 처리 및 보유 기간은 다음과 같습니다.
7885

7986
- 회원 가입 및 관리 관련 정보: 회원 탈퇴 시까지
87+
- 회원가입 이메일 인증 정보: 인증번호는 발송 후 최대 10분, 인증 완료 정보는 인증 후 최대 30분 (회원가입 완료 시 즉시 삭제), 발송 횟수 제한을 위한 이메일 주소 해시값은 최대 24시간
8088
- 서비스 이용 기록(읽기 이력, 저장한 글, 위키 채팅): 회원 탈퇴 시까지
8189
- 뉴스레터 구독 정보(이메일 주소, 수신 동의 일시, 이메일 인증 일시): 구독 해지 또는 회원 탈퇴 시까지 (해지 시 즉시 파기). 이메일 인증을 완료하지 않은 구독 신청은 인증 링크 만료(24시간) 후 지체 없이 파기
8290
- 접속 로그, IP, User-Agent: 생성일로부터 3개월
@@ -116,7 +124,7 @@ devport.kr는 원활한 개인정보 업무처리를 위하여 다음과 같이
116124
- Google LLC: 소셜 로그인(Google OAuth) 인증 처리
117125
- GitHub, Inc.: 소셜 로그인(GitHub OAuth) 인증 처리
118126
- 네이버 주식회사: 소셜 로그인(Naver OAuth) 인증 처리
119-
- Amazon Web Services, Inc. (Amazon SES, 아시아 태평양(서울) 리전): 이메일 발송 (뉴스레터, 구독 인증 등)
127+
- Amazon Web Services, Inc. (Amazon SES, 아시아 태평양(서울) 리전): 이메일 발송 (회원가입 인증번호, 비밀번호 재설정 등 계정 안내, 뉴스레터, 구독 인증 등)
120128
- OpenAI, Inc.: AI 위키 채팅 응답 생성
121129

122130
devport.kr는 위탁계약 체결 시 「개인정보 보호법」 제26조에 따라 위탁업무 수행목적 외 개인정보 처리금지, 기술적·관리적 보호조치, 재위탁 제한, 수탁자에 대한 관리·감독, 손해배상 등 책임에 관한 사항을 계약서 등 문서에 명시하고, 수탁자가 개인정보를 안전하게 처리하는지를 감독하고 있습니다.
@@ -149,6 +157,7 @@ devport.kr는 서비스 제공을 위하여 아래와 같이 개인정보를 국
149157
devport.kr는 「개인정보 보호법」 제29조에 따라 다음과 같이 안전성 확보에 필요한 기술적·관리적 조치를 하고 있습니다.
150158

151159
- 비밀번호의 암호화: 이용자의 비밀번호는 암호화(해시 처리)되어 저장 및 관리되며, 본인만이 알 수 있습니다.
160+
- 인증 정보의 해시 저장: 회원가입 이메일 인증번호와 인증 토큰은 원문이 아닌 해시값으로만 저장합니다.
152161
- 접근 권한 관리: 개인정보에 대한 접근 권한을 최소한의 인원으로 제한합니다.
153162
- 개인정보의 암호화: 개인정보는 암호화된 통신(HTTPS)을 통해 전송됩니다.
154163
- 보안 토큰 관리: 리프레시 토큰은 HttpOnly 쿠키로 관리하여 클라이언트 스크립트를 통한 접근을 차단합니다.

‎src/lib/__tests__/signupValidation.test.ts‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, expect, it } from 'vitest';
2-
import { validatePassword, validateUsername } from '../signupValidation';
2+
import { validateEmail, validatePassword, validateUsername } from '../signupValidation';
33

44
// The rules the backend enforces (devport-api PR #24).
55
const SERVER_USERNAME = /^[a-zA-Z0-9_-]{3,20}$/;
@@ -44,3 +44,17 @@ describe('validatePassword', () => {
4444
expect(validatePassword('비밀번호입니다!!!')).toBe('비밀번호는 영문, 숫자, 특수문자, 공백만 사용할 수 있습니다.');
4545
});
4646
});
47+
48+
describe('validateEmail', () => {
49+
it('accepts a normal address up to 100 characters', () => {
50+
expect(validateEmail('user@example.com')).toBeNull();
51+
expect(validateEmail(`${'a'.repeat(88)}@example.com`)).toBeNull();
52+
});
53+
54+
it('rejects empty, malformed and too long addresses', () => {
55+
expect(validateEmail('')).toBe('이메일을 입력해주세요.');
56+
expect(validateEmail('user@example')).toBe('올바른 이메일 주소를 입력해주세요.');
57+
expect(validateEmail('us er@example.com')).toBe('올바른 이메일 주소를 입력해주세요.');
58+
expect(validateEmail(`${'a'.repeat(89)}@example.com`)).toBe('올바른 이메일 주소를 입력해주세요.');
59+
});
60+
});

‎src/lib/signupValidation.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
const USERNAME_CHARS = /^[a-zA-Z0-9_-]+$/;
44
const PRINTABLE_ASCII = /^[\x20-\x7E]*$/;
55
const PASSWORD_SPECIAL_CHAR = /[!@#$%^&*(),.?":{}|<>]/;
6+
const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
7+
const EMAIL_MAX_LENGTH = 100;
68

79
export const validateUsername = (username: string): string | null => {
810
if (!username) return '아이디를 입력해주세요.';
@@ -18,3 +20,9 @@ export const validatePassword = (password: string): string | null => {
1820
if (!PASSWORD_SPECIAL_CHAR.test(password)) return '특수문자(!@#$%^&* 등)를 1개 이상 포함해주세요.';
1921
return null;
2022
};
23+
24+
export const validateEmail = (email: string): string | null => {
25+
if (!email) return '이메일을 입력해주세요.';
26+
if (!EMAIL_PATTERN.test(email) || email.length > EMAIL_MAX_LENGTH) return '올바른 이메일 주소를 입력해주세요.';
27+
return null;
28+
};

0 commit comments

Comments
 (0)