feat: 메일 미인증 만료 회원을 기존 탈퇴 절차로 정리
메일 인증 기한이 지난 미인증 회원을 일일 정리에서 탈퇴 처리하고 본인확인 정보와 소셜 연결을 해제한다. 나머지 회원자료는 기존 탈퇴 후 삭제 정책에 따라 정리하며 아이디는 보존한다. 자진 탈퇴와 자동 정리에 공통 함수를 사용하고 인증 완료·재발송과의 동시 변경을 조건부 갱신으로 보호한다. 관리자 상태 표시와 설정 안내, 운영 문서를 함께 반영한다. 검증: 변경 PHP 6개 문법 검사, 임시 DB에서 만료 경계·재발송·동시 변경·자진 탈퇴·유예 후 정리 검사 통과.
This commit is contained in:
+1
-1
@@ -761,7 +761,7 @@ if ($config['cf_sms_use'] && $config['cf_icode_id'] && $config['cf_icode_pw']) {
|
||||
<tr>
|
||||
<th scope="row"><label for="cf_email_certify_minutes">메일인증 유효시간</label></th>
|
||||
<td>
|
||||
<?php echo help('인증메일 발송 후 링크를 사용할 수 있는 시간을 분 단위로 설정합니다. 0은 만료시간을 적용하지 않습니다.') ?>
|
||||
<?php echo help('인증메일 발송 후 링크를 사용할 수 있는 시간을 분 단위로 설정합니다. 0은 만료시간과 미인증 만료 정리를 적용하지 않습니다.<br>만료된 미인증 회원은 최고관리자 접속 시 하루 한 번 실행되는 정리에서 탈퇴 처리하며, 본인확인 정보와 소셜 연결을 해제합니다. 나머지 회원자료는 회원탈퇴후 삭제일 설정에 따라 정리합니다.') ?>
|
||||
<input type="number" name="cf_email_certify_minutes" value="<?php echo isset($config['cf_email_certify_minutes']) ? (int) $config['cf_email_certify_minutes'] : 60; ?>" id="cf_email_certify_minutes" class="frm_input" min="0"> 분
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -275,6 +275,8 @@ $colspan = 16;
|
||||
<?php
|
||||
if ($leave_msg || $intercept_msg) {
|
||||
echo $leave_msg . ' ' . $intercept_msg;
|
||||
} else if ($config['cf_use_email_certify'] && !preg_match('/[1-9]/', $row['mb_email_certify'])) {
|
||||
echo '<span class="txt_false">메일 미인증</span>';
|
||||
} else {
|
||||
echo "정상";
|
||||
}
|
||||
|
||||
@@ -49,6 +49,23 @@ if($config['cf_memo_del'] > 0) {
|
||||
}
|
||||
}
|
||||
|
||||
// 메일 인증 기한이 지난 회원도 본인확인 정보를 해제하고 탈퇴 처리한다.
|
||||
// 재발송된 토큰의 발급 시각을 사용하며 실제 변경 직전에 회원 상태를 다시 확인한다.
|
||||
$email_certify_minutes = isset($config['cf_email_certify_minutes']) ? (int) $config['cf_email_certify_minutes'] : 60;
|
||||
if ($config['cf_use_email_certify'] && $email_certify_minutes > 0) {
|
||||
$sql = " select mb_id, mb_email_certify, mb_email_certify2, mb_datetime, mb_leave_date, mb_memo
|
||||
from {$g5['member_table']}
|
||||
where mb_leave_date = ''
|
||||
and mb_email_certify not regexp '[1-9]'
|
||||
and mb_memo not regexp '^[0-9]{8}.*삭제함' ";
|
||||
$result = sql_query($sql);
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
if (is_expired_email_certify_member($row)) {
|
||||
g5_leave_member($row['mb_id'], true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 탈퇴회원 자동 삭제
|
||||
if($config['cf_leave_day'] > 0) {
|
||||
$sql = " select mb_id from {$g5['member_table']}
|
||||
@@ -79,4 +96,4 @@ if(isset($config['cf_optimize_date'])) {
|
||||
run_event('cf_optimize_date_update', $config);
|
||||
}
|
||||
|
||||
run_event('db_table_optimize_end', $config);
|
||||
run_event('db_table_optimize_end', $config);
|
||||
|
||||
@@ -36,7 +36,8 @@ if ($mb_md5)
|
||||
else
|
||||
{
|
||||
if ($mb_md5 === $row['mb_email_certify2']) {
|
||||
sql_query(" update {$g5['member_table']} set mb_email_certify2 = '' where mb_id = '{$esc_mb_id}' ");
|
||||
$esc_mb_md5 = sql_real_escape_string($mb_md5);
|
||||
sql_query(" update {$g5['member_table']} set mb_email_certify2 = '' where mb_id = '{$esc_mb_id}' and mb_email_certify2 = '{$esc_mb_md5}' ");
|
||||
alert('메일인증 유효시간이 만료되었습니다. 인증메일을 다시 요청해 주십시오.', G5_URL);
|
||||
}
|
||||
|
||||
|
||||
+3
-12
@@ -12,12 +12,8 @@ $post_mb_password = isset($_POST['mb_password']) ? trim($_POST['mb_password']) :
|
||||
if (!($post_mb_password && check_password($post_mb_password, $member['mb_password'])))
|
||||
alert('비밀번호가 틀립니다.');
|
||||
|
||||
// 회원탈퇴일을 저장
|
||||
$date = date("Ymd");
|
||||
$sql = " update {$g5['member_table']} set mb_leave_date = '{$date}', mb_memo = '".date('Ymd', G5_SERVER_TIME)." 탈퇴함\n".sql_real_escape_string($member['mb_memo'])."', mb_certify = '', mb_adult = 0, mb_dupinfo = '' where mb_id = '{$member['mb_id']}' ";
|
||||
sql_query($sql);
|
||||
|
||||
run_event('member_leave', $member);
|
||||
if (!g5_leave_member($member['mb_id']))
|
||||
alert('회원탈퇴를 처리하지 못했습니다. 회원 상태를 확인해 주십시오.');
|
||||
|
||||
// 3.09 수정 (로그아웃)
|
||||
unset($_SESSION['ss_mb_id']);
|
||||
@@ -25,9 +21,4 @@ unset($_SESSION['ss_mb_id']);
|
||||
if (!$url)
|
||||
$url = G5_URL;
|
||||
|
||||
//소셜로그인 해제
|
||||
if(function_exists('social_member_link_delete')){
|
||||
social_member_link_delete($member['mb_id']);
|
||||
}
|
||||
|
||||
alert(''.$member['mb_nick'].'님께서는 '. date("Y년 m월 d일") .'에 회원에서 탈퇴 하셨습니다.', $url);
|
||||
alert(''.$member['mb_nick'].'님께서는 '. date("Y년 m월 d일") .'에 회원에서 탈퇴 하셨습니다.', $url);
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# 메일 미인증 회원의 만료 및 탈퇴 처리
|
||||
|
||||
메일인증을 사용하는 사이트에서 인증 링크의 유효시간이 지난 미인증 회원은 기존 회원탈퇴 절차로 정리한다. 기본 유효시간은 60분이며 기본환경설정에서 분 단위로 변경할 수 있다. `0`은 인증 링크 만료와 미인증 만료 정리를 모두 사용하지 않는 설정이다.
|
||||
|
||||
## 실행 시점과 대상
|
||||
|
||||
- 기존 데이터 정리와 동일하게 최고관리자 접속 시 하루 한 번 실행한다. 만료 시각에 즉시 실행되는 예약 작업은 아니며 최고관리자가 접속하지 않으면 정리가 지연된다.
|
||||
- 인증을 완료했거나 이미 탈퇴·삭제된 회원과 최고관리자는 제외한다.
|
||||
- 재발송한 토큰은 새 발급 시각부터 유효시간을 계산한다. 기존 형식의 토큰이나 이미 폐기된 토큰은 회원가입 시각을 기준으로 한다. 유효한 기준 시각을 확인할 수 없는 회원은 자동 처리하지 않는다.
|
||||
- 유효시간의 마지막 초까지는 인증할 수 있다. 실제 탈퇴 직전에 회원 상태를 다시 읽고, 그 이후 인증 완료·토큰 재발송·이메일 변경이 발생하면 해당 탈퇴 처리를 중단한다.
|
||||
- 메일인증을 사용하지 않는 사이트는 미인증 만료 정리를 실행하지 않는다. 소셜 가입 시 이미 인증 처리된 회원도 대상이 아니다.
|
||||
|
||||
## 두 단계의 처리
|
||||
|
||||
1. **만료 회원 탈퇴 처리**: 탈퇴일과 만료 사유를 기록하고 `mb_certify`, `mb_adult`, `mb_dupinfo`를 초기화한다. 이메일 인증·비밀번호 재설정 토큰을 폐기하고 소셜 연결을 해제한다. 일반 탈퇴와 같은 `member_leave` 이벤트를 실행한다. 이후 로그인 및 기존 인증 링크 사용은 불가능하다.
|
||||
2. **탈퇴 후 회원자료 정리**: 기존 `회원탈퇴후 삭제일` 설정과 `member_delete()`를 사용한다. 연락처·주소·비밀번호·포인트 및 기존 삭제 함수가 처리하는 연관 데이터를 정리한다. 이 설정이 `0`이면 두 번째 자동 정리는 하지 않지만 첫 단계의 본인확인 해제는 실행한다.
|
||||
|
||||
기존 정책대로 회원 아이디는 보존하여 재사용을 막는다. 첫 단계에서 회원 레코드를 삭제하거나 연락처·포인트를 즉시 제거하지 않는다. 개인정보 전체의 즉시 삭제 기능은 아니다.
|
||||
|
||||
## 기존 사이트 적용
|
||||
|
||||
적용 시 이미 만료된 미인증 회원도 다음 정리 대상이 된다. 기본환경설정의 메일인증 유효시간과 회원탈퇴후 삭제일을 확인하고, 운영 중인 확장의 `member_leave` 이벤트가 자동 정리에도 적합한지 점검한다. 자동 정리는 만료 회원 본인의 요청이 아니라 최고관리자 요청에서 실행되므로 이벤트 인수의 회원정보를 기준으로 처리해야 한다.
|
||||
|
||||
관리자 회원 목록은 정리 전에는 `메일 미인증`, 탈퇴 처리 후에는 `탈퇴함`으로 표시한다. 만료된 회원을 다시 정상 계정으로 복구하거나 동일 아이디로 재가입시키는 기능은 포함하지 않는다.
|
||||
+74
-5
@@ -2706,6 +2706,16 @@ function get_email_certify_token()
|
||||
return get_random_token_string(16) . '.' . G5_SERVER_TIME;
|
||||
}
|
||||
|
||||
// 기존 형식 또는 폐기된 토큰은 회원가입 시각을 기준으로 한다.
|
||||
function get_email_certify_issued_at($stored_token, $mb_datetime)
|
||||
{
|
||||
if (preg_match('/\.([0-9]{10})$/', $stored_token, $matches)) {
|
||||
return (int) $matches[1];
|
||||
}
|
||||
|
||||
return strtotime($mb_datetime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 메일 인증 토큰의 일치 여부와 유효시간을 확인한다.
|
||||
*
|
||||
@@ -2729,11 +2739,7 @@ function is_valid_email_certify_token($token, $stored_token, $mb_datetime, $vali
|
||||
return true;
|
||||
}
|
||||
|
||||
if (preg_match('/\.([0-9]{10})$/', $stored_token, $matches)) {
|
||||
$issued_at = (int) $matches[1];
|
||||
} else {
|
||||
$issued_at = strtotime($mb_datetime);
|
||||
}
|
||||
$issued_at = get_email_certify_issued_at($stored_token, $mb_datetime);
|
||||
|
||||
if (!$issued_at) {
|
||||
return false;
|
||||
@@ -2744,6 +2750,25 @@ function is_valid_email_certify_token($token, $stored_token, $mb_datetime, $vali
|
||||
return $issued_at + ($valid_minutes * 60) >= $now;
|
||||
}
|
||||
|
||||
// 인증 완료·탈퇴·삭제 회원과 만료시간을 사용하지 않는 사이트는 제외한다.
|
||||
function is_expired_email_certify_member($mb, $now = null)
|
||||
{
|
||||
global $config;
|
||||
|
||||
$valid_minutes = isset($config['cf_email_certify_minutes']) ? (int) $config['cf_email_certify_minutes'] : 60;
|
||||
if (!$config['cf_use_email_certify'] || $valid_minutes < 1 || empty($mb['mb_id'])
|
||||
|| $mb['mb_id'] === $config['cf_admin'] || !empty($mb['mb_leave_date'])
|
||||
|| preg_match('/[1-9]/', $mb['mb_email_certify'])
|
||||
|| preg_match('#^[0-9]{8}.*삭제함#', $mb['mb_memo'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$issued_at = get_email_certify_issued_at($mb['mb_email_certify2'], $mb['mb_datetime']);
|
||||
$now = $now === null ? G5_SERVER_TIME : (int) $now;
|
||||
|
||||
return $issued_at > 0 && $issued_at + ($valid_minutes * 60) < $now;
|
||||
}
|
||||
|
||||
/**
|
||||
* CSRF 방지용 Origin/Referer 검증 (OWASP 권장 패턴).
|
||||
*
|
||||
@@ -4049,6 +4074,50 @@ function conv_unescape_nl($str)
|
||||
return str_replace($search, $replace, $str);
|
||||
}
|
||||
|
||||
// 자진 탈퇴와 메일 미인증 만료 정리에서 동일한 탈퇴 처리를 사용한다.
|
||||
function g5_leave_member($mb_id, $expired_email_only = false)
|
||||
{
|
||||
global $config, $g5;
|
||||
|
||||
$mb = get_member($mb_id);
|
||||
if (empty($mb['mb_id']) || $mb['mb_id'] === $config['cf_admin'] || !empty($mb['mb_leave_date'])
|
||||
|| preg_match('#^[0-9]{8}.*삭제함#', $mb['mb_memo'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$condition = '';
|
||||
if ($expired_email_only) {
|
||||
if (!is_expired_email_certify_member($mb)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 조회 이후 인증 완료·재발송·회원정보 변경이 있었다면 탈퇴시키지 않는다.
|
||||
foreach (array('mb_email_certify', 'mb_email_certify2', 'mb_datetime', 'mb_email') as $field) {
|
||||
$condition .= " and {$field} = '".sql_real_escape_string($mb[$field])."'";
|
||||
}
|
||||
}
|
||||
|
||||
$date = date('Ymd', G5_SERVER_TIME);
|
||||
$memo = $date . ($expired_email_only ? ' 메일 미인증 만료로 탈퇴함' : ' 탈퇴함') . "\n";
|
||||
$esc_mb_id = sql_real_escape_string($mb['mb_id']);
|
||||
$sql = " update {$g5['member_table']}
|
||||
set mb_leave_date = '{$date}',
|
||||
mb_memo = CONCAT('".sql_real_escape_string($memo)."', IFNULL(mb_memo, '')),
|
||||
mb_certify = '', mb_adult = 0, mb_dupinfo = '',
|
||||
mb_email_certify2 = '', mb_lost_certify = ''
|
||||
where mb_id = '{$esc_mb_id}' and mb_leave_date = '' {$condition} ";
|
||||
if (!sql_query($sql) || get_sql_affected_rows() < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
run_event('member_leave', $mb);
|
||||
if (function_exists('social_member_link_delete')) {
|
||||
social_member_link_delete($mb['mb_id']);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 회원 삭제
|
||||
function member_delete($mb_id)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user