225 lines
8.3 KiB
PHP
225 lines
8.3 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Admin\Auth;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Rules\RecaptchaV3Rule;
|
|
use App\Services\Admin\AdminAuthService;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
final class AdminAuthController extends Controller
|
|
{
|
|
public function __construct(
|
|
private readonly AdminAuthService $authService
|
|
) {}
|
|
|
|
public function showLogin()
|
|
{
|
|
return view('admin.auth.login');
|
|
}
|
|
|
|
public function storeLogin(Request $request)
|
|
{
|
|
$rules = [
|
|
'login_id' => ['required', 'string', 'max:190'], // admin_users.email(190)
|
|
'password' => ['required', 'string', 'max:255'],
|
|
'remember' => ['nullable'],
|
|
];
|
|
|
|
// 운영에서만 reCAPTCHA 필수
|
|
if (app()->environment('production')) {
|
|
$rules['g-recaptcha-response'] = ['required', new RecaptchaV3Rule('admin_login')];
|
|
}
|
|
|
|
$data = $request->validate($rules);
|
|
|
|
$email = strtolower(trim((string) $data['login_id']));
|
|
$remember = (bool) $request->boolean('remember');
|
|
$ip = (string) $request->ip();
|
|
|
|
$res = $this->authService->startLogin(
|
|
email: $email,
|
|
password: (string) $data['password'],
|
|
remember: $remember,
|
|
ip: $ip
|
|
);
|
|
|
|
if (($res['state'] ?? '') === 'invalid') {
|
|
return back()->withErrors(['login_id' => '이메일 또는 비밀번호를 확인하세요.'])->withInput();
|
|
}
|
|
|
|
if (($res['state'] ?? '') === 'sms_error') {
|
|
return back()->withErrors(['login_id' => '인증 sms 발송에 실패하였습니다.'])->withInput();
|
|
}
|
|
|
|
if (($res['state'] ?? '') === 'blocked') {
|
|
return back()->withErrors(['login_id' => '로그인 할 수 없는 계정입니다.'])->withInput();
|
|
}
|
|
|
|
if (($res['state'] ?? '') === 'must_reset') {
|
|
$request->session()->put('admin_pwreset', [
|
|
'admin_id' => (int) ($res['admin_id'] ?? 0),
|
|
'email' => $email,
|
|
'expires_at' => time() + 600, // 10분
|
|
'ip' => $ip,
|
|
]);
|
|
|
|
return redirect()->route('admin.password.reset.form')
|
|
->with('status', '비밀번호 초기화가 필요합니다. 새 비밀번호를 설정해 주세요.');
|
|
}
|
|
|
|
if (($res['state'] ?? '') !== 'otp_sent') {
|
|
// 방어: 예상치 못한 상태
|
|
return back()->withErrors(['login_id' => '로그인 처리 중 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.'])->withInput();
|
|
}
|
|
|
|
// ✅ OTP 챌린지 ID만 세션에 보관 (OTP 평문/해시 세션 저장 X)
|
|
$request->session()->put('admin_2fa', [
|
|
'challenge_id' => (string) ($res['challenge_id'] ?? ''),
|
|
'masked_phone' => (string) ($res['masked_phone'] ?? ''),
|
|
'expires_at' => time() + (int) config('admin.sms_ttl', 180),
|
|
]);
|
|
|
|
return redirect()->route('admin.otp.form')
|
|
->with('status', '인증번호를 문자로 발송했습니다.');
|
|
}
|
|
|
|
// 비밀번호 초기화 폼
|
|
public function showForceReset(Request $request)
|
|
{
|
|
$pending = (array) $request->session()->get('admin_pwreset', []);
|
|
if (empty($pending['admin_id'])) {
|
|
return redirect()->route('admin.login.form')
|
|
->withErrors(['login_id' => '비밀번호 초기화 세션이 없습니다. 다시 로그인해 주세요.']);
|
|
}
|
|
|
|
if ((int)($pending['expires_at'] ?? 0) < time()) {
|
|
$request->session()->forget('admin_pwreset');
|
|
return redirect()->route('admin.login.form')
|
|
->withErrors(['login_id' => '비밀번호 초기화가 만료되었습니다. 다시 로그인해 주세요.']);
|
|
}
|
|
|
|
return view('admin.auth.password_reset', [
|
|
'email' => (string)($pending['email'] ?? ''),
|
|
]);
|
|
}
|
|
|
|
// 비밀번호 초기화 저장
|
|
public function storeForceReset(Request $request)
|
|
{
|
|
$pending = (array) $request->session()->get('admin_pwreset', []);
|
|
if (empty($pending['admin_id'])) {
|
|
return redirect()->route('admin.login.form')
|
|
->withErrors(['login_id' => '비밀번호 초기화 세션이 없습니다. 다시 로그인해 주세요.']);
|
|
}
|
|
|
|
if ((int)($pending['expires_at'] ?? 0) < time()) {
|
|
$request->session()->forget('admin_pwreset');
|
|
return redirect()->route('admin.login.form')
|
|
->withErrors(['login_id' => '비밀번호 초기화가 만료되었습니다. 다시 로그인해 주세요.']);
|
|
}
|
|
|
|
$data = $request->validate([
|
|
'password' => ['required', 'string', 'min:10', 'max:255', 'confirmed'],
|
|
]);
|
|
|
|
$ip = (string) $request->ip();
|
|
$res = $this->authService->resetPassword(
|
|
adminId: (int) $pending['admin_id'],
|
|
newPassword: (string) $data['password'],
|
|
ip: $ip
|
|
);
|
|
|
|
if (!($res['ok'] ?? false)) {
|
|
$request->session()->forget('admin_pwreset');
|
|
return redirect()->route('admin.login.form')
|
|
->withErrors(['login_id' => '비밀번호 변경에 실패했습니다. 다시 로그인해 주세요.']);
|
|
}
|
|
|
|
$request->session()->forget('admin_pwreset');
|
|
|
|
return redirect()->route('admin.login.form')
|
|
->with('status', '비밀번호가 변경되었습니다. 다시 로그인해 주세요.');
|
|
}
|
|
|
|
public function showOtp(Request $request)
|
|
{
|
|
$pending = (array) $request->session()->get('admin_2fa', []);
|
|
|
|
if (empty($pending['challenge_id'])) {
|
|
return redirect()->route('admin.login.form')
|
|
->withErrors(['login_id' => '인증 정보가 없습니다. 다시 로그인해 주세요.']);
|
|
}
|
|
|
|
if ((int)($pending['expires_at'] ?? 0) < time()) {
|
|
$request->session()->forget('admin_2fa');
|
|
return redirect()->route('admin.login.form')
|
|
->withErrors(['login_id' => '인증이 만료되었습니다. 다시 로그인해 주세요.']);
|
|
}
|
|
|
|
return view('admin.auth.otp', [
|
|
'masked_phone' => (string)($pending['masked_phone'] ?? ''),
|
|
]);
|
|
}
|
|
|
|
public function verifyOtp(Request $request)
|
|
{
|
|
$data = $request->validate([
|
|
'otp' => ['required', 'digits:6'],
|
|
]);
|
|
|
|
$pending = (array) $request->session()->get('admin_2fa', []);
|
|
$challengeId = (string)($pending['challenge_id'] ?? '');
|
|
|
|
if ($challengeId === '') {
|
|
return redirect()->route('admin.login.form')
|
|
->withErrors(['login_id' => '인증 정보가 없습니다. 다시 로그인해 주세요.']);
|
|
}
|
|
|
|
$res = $this->authService->verifyOtp(
|
|
challengeId: $challengeId,
|
|
otp: (string) $data['otp'],
|
|
ip: (string) $request->ip()
|
|
);
|
|
|
|
if (!($res['ok'] ?? false)) {
|
|
$reason = (string)($res['reason'] ?? '');
|
|
$msg = match ($reason) {
|
|
'expired' => '인증이 만료되었습니다. 다시 로그인해 주세요.',
|
|
'attempts' => '시도 횟수를 초과했습니다. 다시 로그인해 주세요.',
|
|
'ip' => '접속 정보가 변경되었습니다. 다시 로그인해 주세요.',
|
|
'blocked' => '로그인 할 수 없는 계정입니다.',
|
|
default => '인증번호가 올바르지 않습니다.',
|
|
};
|
|
|
|
if ($reason !== 'invalid') {
|
|
$request->session()->forget('admin_2fa');
|
|
return redirect()->route('admin.login.form')->withErrors(['login_id' => $msg]);
|
|
}
|
|
|
|
return back()->withErrors(['otp' => $msg]);
|
|
}
|
|
|
|
/** @var \App\Models\AdminUser $admin */
|
|
$admin = $res['admin'];
|
|
|
|
Auth::guard('admin')->login($admin, (bool)($res['remember'] ?? false));
|
|
|
|
$request->session()->forget('admin_2fa');
|
|
|
|
return redirect()->route('admin.home');
|
|
}
|
|
|
|
public function logout(Request $request)
|
|
{
|
|
Auth::guard('admin')->logout();
|
|
|
|
$request->session()->invalidate();
|
|
$request->session()->regenerateToken();
|
|
|
|
return redirect()->route('admin.login.form');
|
|
}
|
|
}
|