345 lines
11 KiB
PHP
345 lines
11 KiB
PHP
<?php
|
|
|
|
namespace App\Repositories\Member;
|
|
|
|
use App\Models\Member\MemAuth;
|
|
use App\Models\Member\MemAuthInfo;
|
|
use App\Models\Member\MemAuthLog;
|
|
use App\Models\Member\MemInfo;
|
|
use App\Models\Member\MemJoinFilter;
|
|
use App\Models\Member\MemJoinLog;
|
|
use App\Support\LegacyCrypto\CiSeedCrypto;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Carbon;
|
|
|
|
class MemberAuthRepository
|
|
{
|
|
/* =========================================================
|
|
* mem_auth (기존)
|
|
* ========================================================= */
|
|
|
|
public function upsertState(
|
|
int $memNo,
|
|
string $authType,
|
|
string $authState,
|
|
?string $authDate = null
|
|
): void {
|
|
$authDate = $authDate ?: Carbon::now()->toDateString();
|
|
|
|
DB::table('mem_auth')->updateOrInsert(
|
|
['mem_no' => $memNo, 'auth_type' => $authType],
|
|
['auth_state' => $authState, 'auth_date' => $authDate]
|
|
);
|
|
}
|
|
|
|
public function markRequested(int $memNo, string $authType, array $logInfo = []): void
|
|
{
|
|
$this->setStateWithLog($memNo, $authType, MemAuth::STATE_R, MemAuthLog::STATE_P, $logInfo);
|
|
}
|
|
|
|
public function markProcessing(int $memNo, string $authType, array $logInfo = []): void
|
|
{
|
|
$this->setStateWithLog($memNo, $authType, MemAuth::STATE_P, MemAuthLog::STATE_P, $logInfo);
|
|
}
|
|
|
|
public function markSuccess(int $memNo, string $authType, array $logInfo = []): void
|
|
{
|
|
$this->setStateWithLog($memNo, $authType, MemAuth::STATE_Y, MemAuthLog::STATE_S, $logInfo);
|
|
}
|
|
|
|
public function markFail(int $memNo, string $authType, array $logInfo = []): void
|
|
{
|
|
$this->setStateWithLog($memNo, $authType, MemAuth::STATE_N, MemAuthLog::STATE_F, $logInfo);
|
|
}
|
|
|
|
public function mergeAuthInfo(int $memNo, string $authType, array $payload): void
|
|
{
|
|
DB::transaction(function () use ($memNo, $authType, $payload) {
|
|
$row = MemAuthInfo::query()->find($memNo);
|
|
|
|
if (!$row) {
|
|
$row = new MemAuthInfo();
|
|
$row->mem_no = $memNo;
|
|
$row->auth_info = [];
|
|
}
|
|
|
|
$data = $row->auth_info ?: [];
|
|
$data[$authType] = array_merge($data[$authType] ?? [], $payload);
|
|
|
|
$row->auth_info = $data;
|
|
$row->save();
|
|
});
|
|
}
|
|
|
|
private function setStateWithLog(
|
|
int $memNo,
|
|
string $authType,
|
|
string $authState,
|
|
string $logState,
|
|
array $logInfo
|
|
): void {
|
|
DB::transaction(function () use ($memNo, $authType, $authState, $logState, $logInfo) {
|
|
$this->upsertState($memNo, $authType, $authState);
|
|
|
|
MemAuthLog::query()->create([
|
|
'mem_no' => $memNo,
|
|
'type' => $authType,
|
|
'state' => $logState,
|
|
'info' => $logInfo,
|
|
'rgdate' => Carbon::now()->toDateTimeString(),
|
|
]);
|
|
});
|
|
}
|
|
|
|
public function getState(int $memNo, string $authType): ?string
|
|
{
|
|
return DB::table('mem_auth')
|
|
->where('mem_no', $memNo)
|
|
->where('auth_type', $authType)
|
|
->value('auth_state');
|
|
}
|
|
|
|
public function isVerified(int $memNo, string $authType): bool
|
|
{
|
|
return $this->getState($memNo, $authType) === MemAuth::STATE_Y;
|
|
}
|
|
|
|
/* =========================================================
|
|
* Step0: phone check + join_filter + join_log
|
|
* ========================================================= */
|
|
|
|
private function normalizeKoreanPhone(string $raw): ?string
|
|
{
|
|
$digits = preg_replace('/\D+/', '', $raw ?? '');
|
|
|
|
if (strlen($digits) !== 11) {
|
|
return null;
|
|
}
|
|
if (substr($digits, 0, 3) !== '010') {
|
|
return null;
|
|
}
|
|
|
|
return $digits; // ✅ 무조건 11자리 숫자만 리턴
|
|
}
|
|
|
|
/**
|
|
* filter 컬럼에 phone/ip/ip_c가 들어있다는 전제의 기본 구현.
|
|
* - join_block: A 차단 / S 주의(알림) / N 비활성
|
|
*/
|
|
public function checkJoinFilter(string $phone, string $ip4 = '', string $ip4c = ''): ?array
|
|
{
|
|
$targets = array_values(array_filter([$phone, $ip4, $ip4c]));
|
|
if (!$targets) return null;
|
|
|
|
$rows = MemJoinFilter::query()
|
|
->whereIn('filter', $targets)
|
|
->where(function ($q) {
|
|
$q->whereNull('join_block')->orWhere('join_block', '!=', 'N');
|
|
})
|
|
->orderByDesc('seq')
|
|
->get();
|
|
|
|
if ($rows->isEmpty()) return null;
|
|
|
|
foreach ($rows as $r) {
|
|
if ((string)$r->join_block === 'A') {
|
|
return ['hit' => true, 'block' => true, 'gubun' => $r->gubun ?? 'filter_block', 'row' => $r];
|
|
}
|
|
}
|
|
foreach ($rows as $r) {
|
|
if ((string)$r->join_block === 'S') {
|
|
return ['hit' => true, 'block' => false, 'gubun' => $r->gubun ?? 'filter_notice', 'row' => $r];
|
|
}
|
|
}
|
|
|
|
$r = $rows->first();
|
|
return ['hit' => true, 'block' => false, 'gubun' => $r->gubun ?? 'filter_hit', 'row' => $r];
|
|
}
|
|
|
|
public function writeJoinLog(array $data): void
|
|
{
|
|
MemJoinLog::query()->create([
|
|
'gubun' => $data['gubun'] ?? null,
|
|
'mem_no' => (int)($data['mem_no'] ?? 0),
|
|
'cell_corp' => $data['cell_corp'] ?? 'n',
|
|
'cell_phone' => $data['cell_phone'] ?? '',
|
|
'email' => $data['email'] ?? null,
|
|
'ip4' => $data['ip4'] ?? '',
|
|
'ip4_c' => $data['ip4_c'] ?? '',
|
|
'error_code' => $data['error_code'] ?? '',
|
|
'dt_reg' => Carbon::now()->toDateTimeString(),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Step0 통합 처리
|
|
*/
|
|
public function step0PhoneCheck(string $rawPhone, string $ip4 = ''): array
|
|
{
|
|
$base = [
|
|
'ok' => false,
|
|
'reason' => '',
|
|
'message' => '',
|
|
'redirect' => null,
|
|
'phone' => null,
|
|
'filter' => null,
|
|
'admin_phones' => [],
|
|
];
|
|
|
|
// 0) IP 필터 먼저
|
|
$ip4c = $this->ipToCClass($ip4);
|
|
$ipHit = $this->checkJoinFilterByIp($ip4, $ip4c);
|
|
|
|
if ($ipHit) {
|
|
$base['filter'] = $ipHit;
|
|
$base['admin_phones'] = $ipHit['admin_phones'] ?? [];
|
|
}
|
|
|
|
if ($ipHit && ($ipHit['join_block'] ?? '') === 'A') {
|
|
return array_merge($base, [
|
|
'ok' => false,
|
|
'reason' => 'blocked_ip',
|
|
'message' => '현재 가입이 제한된 정보입니다. 고객센터로 문의해 주세요.',
|
|
]);
|
|
}
|
|
|
|
// 1) 전화번호 검증
|
|
$phone = $this->normalizeKoreanPhone($rawPhone);
|
|
if (!$phone) {
|
|
return array_merge($base, [
|
|
'ok' => false,
|
|
'reason' => 'invalid_phone',
|
|
'message' => '휴대폰 번호 형식이 올바르지 않습니다.',
|
|
]);
|
|
}
|
|
$base['phone'] = $phone;
|
|
|
|
// 2) 이미 회원인지 체크
|
|
$member = $this->findMemberByPhone($phone);
|
|
if ($member && !empty($member->mem_no)) {
|
|
return array_merge($base, [
|
|
'ok' => false,
|
|
'reason' => 'already_member',
|
|
'message' => "이미 가입된 전화번호 입니다.\n\n아이디 찾기로 이동할까요?",
|
|
'redirect' => route('web.auth.find_id'), // ✅ 이미가입이면 아이디 찾기
|
|
'matched_mem_no' => (int)$member->mem_no, // 필요하면 유지
|
|
]);
|
|
}
|
|
|
|
// 3) 기존 phone+ip 필터
|
|
$filter = $this->checkJoinFilter($phone, $ip4, $ip4c);
|
|
if ($filter && ($filter['block'] ?? false) === true) {
|
|
return array_merge($base, [
|
|
'ok' => false,
|
|
'reason' => 'blocked',
|
|
'filter' => $filter,
|
|
'message' => '현재 가입이 제한된 정보입니다. 고객센터로 문의해 주세요.',
|
|
]);
|
|
}
|
|
|
|
// 4) 성공(가입 가능)
|
|
return array_merge($base, [
|
|
'ok' => true,
|
|
'reason' => 'ok',
|
|
'filter' => $filter ?: $ipHit,
|
|
'message' => "회원가입 가능한 전화번호 입니다.\n\n약관동의 페이지로 이동합니다.",
|
|
'redirect' => route('web.auth.register.terms'),
|
|
]);
|
|
}
|
|
|
|
|
|
|
|
private function findMemberByPhone(string $phone): ?object
|
|
{
|
|
/** @var CiSeedCrypto $seed */
|
|
$seed = app(CiSeedCrypto::class);
|
|
$phoneEnc = $seed->encrypt($phone);
|
|
|
|
// 실제로 매칭되는 회원 1건을 가져옴
|
|
return DB::table('mem_info')
|
|
->select('mem_no', 'cell_phone')
|
|
->where('cell_phone', $phoneEnc)
|
|
->limit(1)
|
|
->first();
|
|
}
|
|
|
|
|
|
public function ipToCClass(string $ip): string
|
|
{
|
|
if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
|
return '';
|
|
}
|
|
$p = explode('.', $ip);
|
|
return count($p) === 4 ? ($p[0] . '.' . $p[1] . '.' . $p[2] . '.0') : '';
|
|
}
|
|
|
|
private function checkJoinFilterByIp(string $ip4, string $ip4c): ?array
|
|
{$ip4 = "19dd.0";
|
|
// IPv4 아니면 필터 적용 안 함
|
|
if (!filter_var($ip4, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
|
return null;
|
|
}
|
|
|
|
|
|
// 우선순위: A(차단) > S(관리자알림) > N(무시)
|
|
$row = DB::table('mem_join_filter')
|
|
->whereIn('join_block', ['A', 'S'])
|
|
->where(function ($q) use ($ip4, $ip4c) {
|
|
// ✅ C class (보통 gubun_code='01', filter='162.168.0.0')
|
|
$q->where(function ($q2) use ($ip4c) {
|
|
$q2->where('gubun_code', '01')
|
|
->where('filter', $ip4c);
|
|
});
|
|
|
|
// ✅ D class (보통 gubun_code='02', filter='162.168.0.2')
|
|
$q->orWhere(function ($q2) use ($ip4) {
|
|
$q2->where('gubun_code', '02')
|
|
->where('filter', $ip4);
|
|
});
|
|
|
|
// ✅ (레거시) gubun_code가 01인데도 filter에 단일 IP가 들어있는 케이스 방어
|
|
$q->orWhere(function ($q2) use ($ip4) {
|
|
$q2->where('gubun_code', '01')
|
|
->where('filter', $ip4);
|
|
});
|
|
})
|
|
->orderByRaw("CASE join_block WHEN 'A' THEN 0 WHEN 'S' THEN 1 ELSE 9 END")
|
|
->limit(1)
|
|
->first();
|
|
|
|
if (!$row) return null;
|
|
|
|
$adminPhones = $this->parseAdminPhones($row->admin_phone ?? '');
|
|
|
|
return [
|
|
'gubun_code' => $row->gubun_code ?? null,
|
|
'join_block' => $row->join_block ?? null,
|
|
'filter' => $row->filter ?? null,
|
|
'admin_phones' => $adminPhones,
|
|
'ip' => $ip4,
|
|
'ip4c' => $ip4c,
|
|
];
|
|
}
|
|
|
|
private function parseAdminPhones($value): array
|
|
{
|
|
// admin_phone: ["010-3682-8958", ...] 형태(=JSON 배열)라고 했으니 JSON 우선
|
|
if (is_array($value)) return $value;
|
|
|
|
$s = trim((string)$value);
|
|
if ($s === '') return [];
|
|
|
|
$json = json_decode($s, true);
|
|
if (json_last_error() === JSON_ERROR_NONE && is_array($json)) {
|
|
return array_values(array_filter(array_map('trim', $json)));
|
|
}
|
|
|
|
// 혹시 "010...,010..." 같은 문자열이면 콤마 분리 fallback
|
|
$parts = array_map('trim', explode(',', $s));
|
|
return array_values(array_filter($parts));
|
|
}
|
|
}
|
|
|
|
|
|
|