238 lines
12 KiB
PHP
238 lines
12 KiB
PHP
@extends('admin.layouts.app')
|
|
|
|
@section('title', '보안 설정 (2차 인증)')
|
|
@section('page_title', '보안 설정')
|
|
@section('page_desc', '2차 인증(SMS / Google OTP) 관리')
|
|
|
|
@push('head')
|
|
<style>
|
|
/* security page only (match sms/logs tone) */
|
|
.sec-wrap{display:flex;flex-direction:column;gap:12px;}
|
|
.sec-card{padding:16px;}
|
|
.sec-head{display:flex;justify-content:space-between;align-items:flex-start;gap:12px;flex-wrap:wrap;}
|
|
.sec-head__t{font-weight:900;font-size:16px;}
|
|
.sec-head__d{margin-top:4px;font-size:12px;line-height:1.55;}
|
|
|
|
.lbtn{padding:8px 12px;font-size:13px;border-radius:12px;line-height:1.1;text-decoration:none;display:inline-flex;align-items:center;justify-content:center;gap:6px;
|
|
border:1px solid rgba(255,255,255,.10);background:rgba(255,255,255,.06);color:inherit;cursor:pointer;}
|
|
.lbtn:hover{background:rgba(255,255,255,.10);text-decoration:none;}
|
|
.lbtn--primary{background:rgba(59,130,246,.88);border-color:rgba(59,130,246,.95);color:#fff;}
|
|
.lbtn--primary:hover{background:rgba(59,130,246,.98);}
|
|
.lbtn--danger{background:rgba(244,63,94,.88);border-color:rgba(244,63,94,.95);color:#fff;}
|
|
.lbtn--danger:hover{background:rgba(244,63,94,.98);}
|
|
.lbtn--ghost{background:transparent;}
|
|
.lbtn--sm{padding:7px 10px;font-size:12px;border-radius:11px;}
|
|
.lbtn--wide{padding:10px 14px;font-weight:800;}
|
|
|
|
.pill{display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border-radius:999px;font-size:12px;
|
|
border:1px solid rgba(255,255,255,.10);background:rgba(255,255,255,.06);}
|
|
.pill--ok{border-color:rgba(34,197,94,.35);background:rgba(34,197,94,.12);}
|
|
.pill--warn{border-color:rgba(245,158,11,.35);background:rgba(245,158,11,.12);}
|
|
.pill--muted{opacity:.9}
|
|
|
|
.mono{padding:6px 10px;border-radius:12px;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.10);
|
|
font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:13px;}
|
|
.muted12{font-size:12px;}
|
|
.divider{margin:14px 0;opacity:.15;}
|
|
|
|
.sec-grid{display:grid;grid-template-columns:1fr;gap:12px;}
|
|
@media (min-width: 980px){ .sec-grid{grid-template-columns:1fr 1fr;} }
|
|
|
|
.help{font-size:13px;line-height:1.65;}
|
|
.help ol{margin:0;padding-left:18px;}
|
|
.help li{margin:4px 0;}
|
|
.help code{padding:2px 6px;border-radius:8px;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.10);}
|
|
|
|
.mode-row{display:flex;gap:10px;flex-wrap:wrap;align-items:center;}
|
|
.mode-row__label{font-size:13px;}
|
|
.mode-row select{width:200px;}
|
|
.hintline{margin-top:8px;font-size:12px;line-height:1.5;}
|
|
.qrbox{background:#fff;border-radius:14px;padding:12px;display:inline-block;}
|
|
.actions{display:flex;gap:8px;flex-wrap:wrap;align-items:center;}
|
|
.otp{max-width:220px;}
|
|
.otp input{font-size:16px;font-weight:900;letter-spacing:.08em;text-align:center;}
|
|
</style>
|
|
@endpush
|
|
|
|
@section('content')
|
|
<section class="sec-wrap">
|
|
|
|
{{-- Header --}}
|
|
<div class="a-card sec-card">
|
|
<div class="sec-head">
|
|
<div>
|
|
<div class="sec-head__t">2차 인증 (SMS / Google OTP)</div>
|
|
<div class="a-muted sec-head__d">
|
|
SMS 또는 Google OTP(TOTP)로 2차 인증을 진행합니다.
|
|
</div>
|
|
</div>
|
|
|
|
<a class="lbtn lbtn--ghost lbtn--sm" href="{{ route('admin.me') }}">← 뒤로가기</a>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- 이용 안내 --}}
|
|
<div class="a-card sec-card">
|
|
<div style="display:flex; justify-content:space-between; align-items:center; gap:10px; flex-wrap:wrap;">
|
|
<div style="font-weight:900;">이용 안내</div>
|
|
<span class="pill pill--muted">도움말</span>
|
|
</div>
|
|
|
|
<div class="a-muted help" style="margin-top:10px;">
|
|
<ol>
|
|
<li>Google Authenticator / Microsoft Authenticator 등 OTP 앱을 설치합니다.</li>
|
|
<li><b>등록 시작</b> 버튼을 눌러 QR을 생성합니다.</li>
|
|
<li>앱에서 QR을 스캔하거나, 시크릿을 수동으로 입력합니다.</li>
|
|
<li>앱에 표시되는 <b>6자리 코드</b>를 입력하면 등록이 완료됩니다.</li>
|
|
<li>등록 완료 후에만 <b>Google OTP 인증</b>으로 전환할 수 있습니다.</li>
|
|
</ol>
|
|
<div class="hintline">
|
|
* OTP는 <b>30초</b>마다 바뀌므로, 시간이 지나면 새 코드로 다시 입력하세요.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- 상태/모드 --}}
|
|
<div class="a-card sec-card">
|
|
<div style="display:flex; justify-content:space-between; gap:12px; flex-wrap:wrap; align-items:flex-start;">
|
|
<div>
|
|
<div style="font-weight:900;">현재 상태</div>
|
|
<div style="margin-top:8px;">
|
|
@if($isRegistered)
|
|
<span class="pill pill--ok">Google OTP 등록됨</span>
|
|
<span class="a-muted muted12" style="margin-left:6px;">({{ $admin->totp_verified_at }})</span>
|
|
@elseif($isPending)
|
|
<span class="pill pill--warn">등록 진행 중</span>
|
|
<span class="a-muted muted12" style="margin-left:6px;">(QR 스캔 후 코드 확인)</span>
|
|
@else
|
|
<span class="pill pill--muted">미등록</span>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
<form method="POST" action="{{ route('admin.totp.mode') }}" class="mode-row">
|
|
@csrf
|
|
<span class="a-muted mode-row__label">2차 인증방법</span>
|
|
|
|
<select class="a-input a-input--sm" name="totp_enabled">
|
|
<option value="0" {{ (int)($admin->totp_enabled ?? 0) === 0 ? 'selected' : '' }}>SMS 인증</option>
|
|
<option value="1"
|
|
{{ (int)($admin->totp_enabled ?? 0) === 1 ? 'selected' : '' }}
|
|
{{ !$isRegistered ? 'disabled' : '' }}
|
|
>Google OTP 인증</option>
|
|
</select>
|
|
|
|
<button class="lbtn lbtn--primary lbtn--sm" type="submit">저장</button>
|
|
|
|
@if(!$isRegistered)
|
|
<div class="a-muted muted12" style="width:100%;">
|
|
※ Google OTP 미등록 상태에서는 OTP 인증으로 전환할 수 없습니다.
|
|
</div>
|
|
@endif
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- 등록/확인 UI --}}
|
|
@if($isPending)
|
|
<div class="sec-grid">
|
|
|
|
{{-- QR --}}
|
|
<div class="a-card sec-card">
|
|
<div style="display:flex;justify-content:space-between;align-items:center;gap:10px;flex-wrap:wrap;">
|
|
<div style="font-weight:900;">QR 코드 스캔</div>
|
|
<span class="pill pill--warn">진행중</span>
|
|
</div>
|
|
|
|
<div style="margin-top:12px;">
|
|
<div class="a-muted muted12" style="margin-bottom:8px;">OTP 앱에서 QR을 스캔하세요</div>
|
|
<div class="qrbox">
|
|
{!! $qrSvg !!}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- 시크릿 + 코드 입력 --}}
|
|
<div class="a-card sec-card">
|
|
<div style="display:flex;justify-content:space-between;align-items:center;gap:10px;flex-wrap:wrap;">
|
|
<div style="font-weight:900;">시크릿 / 등록 완료</div>
|
|
<span class="pill pill--muted">수동 입력 가능</span>
|
|
</div>
|
|
|
|
<div style="margin-top:12px;">
|
|
<div class="a-muted muted12" style="margin-bottom:6px;">시크릿(수동 입력용)</div>
|
|
<div class="mono" style="font-weight:900; font-size:16px; letter-spacing:.08em;">
|
|
{{ $secret }}
|
|
</div>
|
|
<div class="a-muted muted12" style="margin-top:8px;">
|
|
* 앱에서 “수동 입력”을 선택하고 위 시크릿을 등록할 수 있습니다.
|
|
</div>
|
|
</div>
|
|
|
|
<hr class="divider">
|
|
|
|
<form method="POST" action="{{ route('admin.totp.confirm') }}" class="otp">
|
|
@csrf
|
|
<label class="a-label">앱에 표시된 6자리 인증코드</label>
|
|
<input class="a-input a-otp-input" name="code" inputmode="numeric" autocomplete="one-time-code" placeholder="123456">
|
|
@error('code') <div class="a-error">{{ $message }}</div> @enderror
|
|
|
|
<div class="actions" style="margin-top:12px;">
|
|
<button class="lbtn lbtn--primary lbtn--wide" type="submit">등록 완료</button>
|
|
</div>
|
|
</form>
|
|
|
|
<form method="POST" action="{{ route('admin.totp.disable') }}"
|
|
style="margin-top:10px;"
|
|
data-confirm="등록을 취소하고 OTP 정보를 삭제할까요?">
|
|
@csrf
|
|
<button class="lbtn lbtn--danger" type="submit">등록 취소(삭제)</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
@else
|
|
{{-- 미등록 / 등록됨 --}}
|
|
<div class="a-card sec-card">
|
|
<div style="display:flex;justify-content:space-between;align-items:center;gap:10px;flex-wrap:wrap;">
|
|
<div>
|
|
<div style="font-weight:900;">관리</div>
|
|
<div class="a-muted muted12" style="margin-top:4px;">
|
|
등록/재등록/삭제를 통해 OTP 정보를 관리할 수 있습니다.
|
|
</div>
|
|
</div>
|
|
|
|
<div class="actions">
|
|
@if(!$isRegistered)
|
|
<form method="POST" action="{{ route('admin.totp.start') }}"
|
|
data-confirm="Google OTP 등록을 시작할까요?\nQR 스캔 후 6자리 코드를 입력해야 완료됩니다.">
|
|
@csrf
|
|
<button class="lbtn lbtn--primary" type="submit">등록 시작</button>
|
|
</form>
|
|
@else
|
|
<form method="POST" action="{{ route('admin.totp.reset') }}"
|
|
data-confirm="Google OTP를 재등록할까요?\n새 시크릿이 발급되며 기존 등록은 무효화됩니다.">
|
|
@csrf
|
|
<button class="lbtn lbtn--primary" type="submit">재등록(수정)</button>
|
|
</form>
|
|
|
|
<form method="POST" action="{{ route('admin.totp.disable') }}"
|
|
data-confirm="Google OTP를 삭제할까요?\n삭제 후에는 SMS 인증을 사용합니다.">
|
|
@csrf
|
|
<button class="lbtn lbtn--danger" type="submit">삭제</button>
|
|
</form>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
@if($isRegistered)
|
|
<hr class="divider">
|
|
<div class="a-muted help">
|
|
<div>✅ 등록 완료 상태입니다. “2차 인증방법”에서 <b>Google OTP 인증</b>으로 전환할 수 있습니다.</div>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
@endif
|
|
|
|
</section>
|
|
@endsection
|