2026-02-04 16:55:00 +09:00

188 lines
7.0 KiB
PHP

@extends('admin.layouts.auth')
@section('title', '로그인')
{{-- reCAPTCHA 스크립트는 페이지에서만 로드 --}}
@push('head')
@php
$siteKey = (string) config('services.recaptcha.site_key');
@endphp
@if($siteKey)
<script>window.__recaptchaSiteKey = @json($siteKey);</script>
<script src="https://www.google.com/recaptcha/api.js?render={{ $siteKey }}"></script>
<script src="{{ asset('assets/js/recaptcha-v3.js') }}"></script>
@endif
@endpush
@section('content')
<form id="loginForm" method="POST" action="{{ route('admin.login.store') }}" class="a-form" novalidate>
@csrf
<input type="hidden" name="g-recaptcha-response" id="g-recaptcha-response" value="">
<div class="a-field">
<label class="a-label" for="login_id">아이디(이메일)</label>
<input
class="a-input"
id="login_id"
name="login_id"
type="text"
autocomplete="username"
autofocus
value="{{ old('login_id') }}"
>
@error('login_id')
<div class="a-error">{{ $message }}</div>
@enderror
</div>
<div class="a-field">
<label class="a-label" for="password">비밀번호</label>
<input
class="a-input"
id="password"
name="password"
type="password"
autocomplete="current-password"
>
@error('password')
<div class="a-error">{{ $message }}</div>
@enderror
</div>
<label class="a-check" style="display:flex; gap:8px; align-items:center; margin:10px 0 0;">
<input type="checkbox" name="remember" value="1" {{ old('remember') ? 'checked' : '' }}>
<span class="a-muted">로그인 유지</span>
</label>
<button class="a-btn a-btn--primary" type="submit" style="margin-top:14px;">
로그인
</button>
<div class="a-help" style="margin-top:10px;">
<small class="a-muted">로그인 성공 SMS 인증번호 입력 단계로 이동합니다.</small>
</div>
</form>
@endsection
@push('scripts')
<script>
(function () {
const form = document.getElementById('loginForm');
if (!form) return;
const emailEl = document.getElementById('login_id');
const pwEl = document.getElementById('password');
const btn = form.querySelector('button[type="submit"]');
// web 공통 showMsg가 있으면 사용, 없으면 alert로 fallback
const showMsgSafe = async (msg, opt) => {
if (typeof window.showMsg === 'function') return window.showMsg(msg, opt);
alert(msg);
};
const isEmail = (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v);
function ensureHiddenRecaptcha(){
let el = document.getElementById('g-recaptcha-response');
if (!el) {
el = document.createElement('input');
el.type = 'hidden';
el.id = 'g-recaptcha-response';
el.name = 'g-recaptcha-response';
form.appendChild(el);
}
return el;
}
async function getRecaptchaToken(action){
// 1) 프로젝트 공통 함수 우선
if (typeof window.recaptchaV3Exec === 'function') {
try {
const t = await window.recaptchaV3Exec(action);
return t || '';
} catch (e) {
return '';
}
}
// 2) fallback: grecaptcha.execute
const siteKey = window.__recaptchaSiteKey || '';
if (!siteKey) return '';
if (typeof window.grecaptcha === 'undefined') return '';
try {
await new Promise(r => window.grecaptcha.ready(r));
const t = await window.grecaptcha.execute(siteKey, { action });
return t || '';
} catch (e) {
return '';
}
}
form.addEventListener('submit', async function (e) {
e.preventDefault();
const email = (emailEl?.value || '').trim();
const pw = (pwEl?.value || '');
if (!email) {
await showMsgSafe('아이디(이메일)를 입력해 주세요.', { type: 'alert', title: '폼체크' });
emailEl?.focus();
return;
}
if (!isEmail(email)) {
await showMsgSafe('아이디는 이메일 형식이어야 합니다.', { type: 'alert', title: '폼체크' });
emailEl?.focus();
return;
}
if (!pw) {
await showMsgSafe('비밀번호를 입력해 주세요.', { type: 'alert', title: '폼체크' });
pwEl?.focus();
return;
}
if (btn) btn.disabled = true;
try {
const isProd = @json(app()->environment('production'));
const hasKey = @json((bool) config('services.recaptcha.site_key'));
// ✅ 운영에서만 토큰 생성 (서버와 동일 정책)
if (isProd && hasKey) {
const hidden = ensureHiddenRecaptcha();
hidden.value = '';
const token = await getRecaptchaToken('admin_login');
hidden.value = token || '';
if (!hidden.value) {
if (btn) btn.disabled = false;
await showMsgSafe(
'보안 검증(reCAPTCHA) 토큰 생성에 실패했습니다. 새로고침 후 다시 시도해 주세요.',
{ type: 'alert', title: '보안검증 실패' }
);
return;
}
}
form.submit();
} catch (err) {
if (btn) btn.disabled = false;
await showMsgSafe('로그인 처리 중 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.', { type: 'alert', title: '오류' });
}
});
// 서버 validation 에러가 있으면 한 번 띄우기(선택)
document.addEventListener('DOMContentLoaded', async () => {
const msg =
@json($errors->first('login_id') ?: $errors->first('password') ?: '');
if (msg) {
await showMsgSafe(msg, { type: 'alert', title: '로그인 실패' });
}
});
})();
</script>
@endpush