feat: 메일 미인증 만료 회원을 기존 탈퇴 절차로 정리

메일 인증 기한이 지난 미인증 회원을 일일 정리에서 탈퇴 처리하고 본인확인 정보와 소셜 연결을 해제한다. 나머지 회원자료는 기존 탈퇴 후 삭제 정책에 따라 정리하며 아이디는 보존한다.

자진 탈퇴와 자동 정리에 공통 함수를 사용하고 인증 완료·재발송과의 동시 변경을 조건부 갱신으로 보호한다. 관리자 상태 표시와 설정 안내, 운영 문서를 함께 반영한다.

검증: 변경 PHP 6개 문법 검사, 임시 DB에서 만료 경계·재발송·동시 변경·자진 탈퇴·유예 후 정리 검사 통과.
This commit is contained in:
whitedot
2026-09-09 07:58:16 +00:00
parent d0f136cca0
commit 672d05f968
7 changed files with 124 additions and 20 deletions
+74 -5
View File
@@ -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)
{