feat(auth): integrate Captcha validation in PasswordAuthenticator
- Added CaptchaGuard dependency to PasswordAuthenticator. - Implemented Captcha validation in the authenticate method to enhance security. - Updated the login modal in home.html.twig to redirect to the admin panel instead of opening a modal. - Enhanced the Altcha widget with localized strings for better user experience. - Removed the login modal implementation from home.html.twig to streamline the login process. - Updated manifest.json and AST cache files to reflect changes in the codebase.
This commit is contained in:
@@ -89,7 +89,7 @@
|
||||
<svg viewbox="0 0 24 24" fill="none"><path d="M4 7h16M4 12h16M4 17h16" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/></svg>
|
||||
</button>
|
||||
<button class="btn btn-ghost" id="openRegModal" style="font-size:13px;padding:8px 20px">ثبت نام</button>
|
||||
<button class="btn btn-blue" id="openLoginModal" style="font-size:13px;padding:8px 20px">ورود به پنل</button>
|
||||
<a href="/admin" class="btn btn-blue" style="font-size:13px;padding:8px 20px;text-decoration:none">ورود به پنل</a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
@@ -558,7 +558,7 @@
|
||||
</div>
|
||||
|
||||
<!-- ALTCHA captcha — proof-of-work در پسزمینه (بدون تعامل کاربر) -->
|
||||
<altcha-widget id="regAltcha" challengeurl="/api/v1/altcha/challenge" auto="onload" style="display:block;margin-top:16px"></altcha-widget>
|
||||
<altcha-widget id="regAltcha" challengeurl="/api/v1/altcha/challenge" auto="onload" style="display:block;margin-top:16px" strings='{"label":"من ربات نیستم","verifying":"در حال بررسی...","verified":"تأیید شد","waitAlert":"در حال بررسی... لطفاً منتظر بمانید.","error":"احراز هویت ناموفق بود. کمی بعد دوباره تلاش کنید.","expired":"احراز هویت منقضی شد. دوباره تلاش کنید."}'></altcha-widget>
|
||||
|
||||
<!-- Error / Success -->
|
||||
<div id="regMsg" style="display:none;margin-top:14px;padding:12px 16px;border-radius:10px;font-size:13px"></div>
|
||||
@@ -731,132 +731,6 @@ msg.style.background = type === 'success' ? '#dcfce7' : '#fef2f2';
|
||||
msg.style.color = type === 'success' ? '#16a34a' : '#ef4444';
|
||||
msg.style.border = type === 'success' ? '1px solid #bbf7d0' : '1px solid #fecaca';
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!-- ===================== Panel Login Modal ===================== -->
|
||||
<div id="loginOverlay" style="display:none;position:fixed;inset:0;background:oklch(0.2 0.05 285 / 0.55);z-index:900;backdrop-filter:blur(4px);overflow-y:auto;padding:24px 16px" onclick="if(event.target===this)closeLoginModal()">
|
||||
<div style="background:#fff;border-radius:20px;max-width:420px;margin:auto;padding:32px 28px;position:relative;box-shadow:0 24px 60px oklch(0.3 0.1 285 / 0.22)">
|
||||
<button onclick="closeLoginModal()" aria-label="بستن" style="position:absolute;top:16px;left:20px;background:none;border:none;cursor:pointer;font-size:22px;color:var(--text-2);line-height:1">×</button>
|
||||
|
||||
<h2 style="margin:0 0 6px;font-size:20px;font-weight:800;color:var(--ink)">ورود به پنل</h2>
|
||||
<p style="margin:0 0 24px;font-size:13px;color:var(--text-2)">با شماره موبایل و رمز عبور وارد شوید</p>
|
||||
|
||||
<div style="display:flex;flex-direction:column;gap:14px">
|
||||
<div>
|
||||
<label style="display:block;font-size:13px;font-weight:600;margin-bottom:5px;color:var(--ink)">شماره موبایل</label>
|
||||
<input id="loginMobile" type="tel" placeholder="09xxxxxxxxx" dir="ltr" autocomplete="username" style="width:100%;border:1.5px solid var(--border);border-radius:10px;padding:10px 14px;font-size:14px;font-family:inherit;outline:none;box-sizing:border-box;color:var(--ink);text-align:right"/>
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;font-size:13px;font-weight:600;margin-bottom:5px;color:var(--ink)">رمز عبور</label>
|
||||
<input id="loginPass" type="password" placeholder="••••••••" autocomplete="current-password" style="width:100%;border:1.5px solid var(--border);border-radius:10px;padding:10px 14px;font-size:14px;font-family:inherit;outline:none;box-sizing:border-box;color:var(--ink)"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="loginMsg" style="display:none;margin-top:14px;padding:12px 16px;border-radius:10px;font-size:13px"></div>
|
||||
|
||||
<button id="loginSubmitBtn" onclick="submitLogin()" style="margin-top:20px;width:100%;background:var(--blue);color:#fff;border:none;border-radius:12px;padding:13px;font-size:15px;font-weight:700;cursor:pointer;font-family:inherit;transition:opacity .2s">
|
||||
ورود به سیستم
|
||||
</button>
|
||||
<p style="text-align:center;font-size:12px;color:var(--text-2);margin:12px 0 0">حساب ندارید؟ از دکمهی «ثبت نام» استفاده کنید</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var loginOverlay = document.getElementById('loginOverlay');
|
||||
var loginBtn = document.getElementById('loginSubmitBtn');
|
||||
|
||||
var openBtn = document.getElementById('openLoginModal');
|
||||
if (openBtn)
|
||||
openBtn.addEventListener('click', function () {
|
||||
loginOverlay.style.display = 'block';
|
||||
document.body.style.overflow = 'hidden';
|
||||
});
|
||||
|
||||
window.closeLoginModal = function () {
|
||||
loginOverlay.style.display = 'none';
|
||||
document.body.style.overflow = '';
|
||||
};
|
||||
|
||||
function loginMsg(text, type) {
|
||||
var m = document.getElementById('loginMsg');
|
||||
m.textContent = text;
|
||||
m.style.display = 'block';
|
||||
m.style.background = type === 'success' ? '#dcfce7' : '#fef2f2';
|
||||
m.style.color = type === 'success' ? '#16a34a' : '#ef4444';
|
||||
m.style.border = type === 'success' ? '1px solid #bbf7d0' : '1px solid #fecaca';
|
||||
}
|
||||
|
||||
// state را دقیقاً به شکل store ادمین (کلید clinicpro-auth) مینویسیم تا SPA پس از ریدایرکت لاگین بماند.
|
||||
function persistAuth(tokens, info) {
|
||||
var d = (info && info.data) ? info.data : {};
|
||||
var state = {
|
||||
token: tokens.access_token,
|
||||
refreshToken: tokens.refresh_token || null,
|
||||
isAuthenticated: true,
|
||||
userUuid: d.uuid || null,
|
||||
userName: d.realName || null,
|
||||
primaryRole: d.primary_role || null,
|
||||
dbUuid: d.db_uuid || null,
|
||||
dbKey: d.db_key || null,
|
||||
doctorUuid: d.doctor_uuid || null,
|
||||
context: d.context || null,
|
||||
availableContexts: d.available_contexts || []
|
||||
};
|
||||
localStorage.setItem('clinicpro-auth', JSON.stringify({ state: state, version: 0 }));
|
||||
}
|
||||
|
||||
window.submitLogin = function () {
|
||||
var mobile = document.getElementById('loginMobile').value.trim();
|
||||
var pass = document.getElementById('loginPass').value;
|
||||
|
||||
if (! /^09[0-9]{9}$/.test(mobile)) {
|
||||
loginMsg('شماره موبایل معتبر نیست', 'error');
|
||||
return;
|
||||
}
|
||||
if (! pass) {
|
||||
loginMsg('رمز عبور را وارد کنید', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
loginBtn.disabled = true;
|
||||
loginBtn.textContent = 'در حال ورود...';
|
||||
|
||||
fetch('/api/v1/user/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ mobile_number: mobile, password: pass })
|
||||
}).then(function (r) {
|
||||
return r.json().then(function (j) { return { ok: r.ok, body: j }; });
|
||||
}).then(function (res) {
|
||||
if (! res.ok || ! res.body.access_token) {
|
||||
var err = (res.body.errors && res.body.errors[0]) ? res.body.errors[0].message : 'نام کاربری یا رمز عبور اشتباه است';
|
||||
loginMsg(err, 'error');
|
||||
loginBtn.disabled = false;
|
||||
loginBtn.textContent = 'ورود به سیستم';
|
||||
return;
|
||||
}
|
||||
var tokens = res.body;
|
||||
// اطلاعات کاربر را میگیریم تا state کامل ذخیره شود؛ اگر نشد، با state حداقلی ادامه بده.
|
||||
fetch('/oauth/userinfo', { headers: { 'Authorization': 'Bearer ' + tokens.access_token } })
|
||||
.then(function (r) { return r.json(); })
|
||||
.catch(function () { return null; })
|
||||
.then(function (info) {
|
||||
persistAuth(tokens, info);
|
||||
loginMsg('خوش آمدید، در حال انتقال به پنل...', 'success');
|
||||
window.location.href = '/admin';
|
||||
});
|
||||
}).catch(function () {
|
||||
loginMsg('خطا در اتصال به سرور', 'error');
|
||||
loginBtn.disabled = false;
|
||||
loginBtn.textContent = 'ورود به سیستم';
|
||||
});
|
||||
};
|
||||
|
||||
document.getElementById('loginPass').addEventListener('keydown', function (e) {
|
||||
if (e.key === 'Enter') submitLogin();
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user