🚀 مُصادَق: تحديث برمجي جديد 2026-05-03 15:16
This commit is contained in:
@@ -64,7 +64,7 @@ final class Application
|
|||||||
header('Strict-Transport-Security: max-age=31536000; includeSubDomains; preload');
|
header('Strict-Transport-Security: max-age=31536000; includeSubDomains; preload');
|
||||||
header('Referrer-Policy: strict-origin-when-cross-origin');
|
header('Referrer-Policy: strict-origin-when-cross-origin');
|
||||||
header('Permissions-Policy: camera=(), microphone=(), geolocation=()');
|
header('Permissions-Policy: camera=(), microphone=(), geolocation=()');
|
||||||
header('Content-Security-Policy: default-src \'self\'; script-src \'self\' cdn.tailwindcss.com unpkg.com; style-src \'self\' \'unsafe-inline\' fonts.googleapis.com; font-src fonts.gstatic.com');
|
header('Content-Security-Policy: default-src \'self\'; script-src \'self\' \'unsafe-inline\' cdn.tailwindcss.com unpkg.com cdn.jsdelivr.net; style-src \'self\' \'unsafe-inline\' fonts.googleapis.com; font-src fonts.gstatic.com; img-src \'self\' data: blob:;');
|
||||||
header_remove('X-Powered-By');
|
header_remove('X-Powered-By');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -178,6 +178,37 @@ final class AuthController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function login2FAVerify(Request $request): void
|
||||||
|
{
|
||||||
|
$data = $request->getBody();
|
||||||
|
$code = $data['code'] ?? '';
|
||||||
|
$userId = $request->user->user_id;
|
||||||
|
|
||||||
|
$db = \App\Core\Database::getInstance();
|
||||||
|
$stmt = $db->prepare("SELECT totp_secret FROM users WHERE id = ?");
|
||||||
|
$stmt->execute([$userId]);
|
||||||
|
$secret = $stmt->fetchColumn();
|
||||||
|
|
||||||
|
$totpService = new \App\Services\TotpService();
|
||||||
|
if ($secret && $totpService->verify($secret, $code)) {
|
||||||
|
// Re-fetch user for full data
|
||||||
|
$stmt = $db->prepare("SELECT * FROM users WHERE id = ?");
|
||||||
|
$stmt->execute([$userId]);
|
||||||
|
$user = $stmt->fetch();
|
||||||
|
|
||||||
|
$authService = new AuthService();
|
||||||
|
$tokens = $authService->generateTokens($user);
|
||||||
|
|
||||||
|
Response::json([
|
||||||
|
'success' => true,
|
||||||
|
'data' => $tokens,
|
||||||
|
'message' => 'تم التحقق بنجاح'
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
Response::error('رمز التحقق غير صحيح', 'INVALID_CODE', 401);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function disable2FA(Request $request): void
|
public function disable2FA(Request $request): void
|
||||||
{
|
{
|
||||||
$db = \App\Core\Database::getInstance();
|
$db = \App\Core\Database::getInstance();
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ $router->addRoute('POST', '/api/v1/auth/2fa/verify', [
|
|||||||
'middleware' => [\App\Middleware\AuthMiddleware::class],
|
'middleware' => [\App\Middleware\AuthMiddleware::class],
|
||||||
'handler' => [AuthController::class, 'verify2FA']
|
'handler' => [AuthController::class, 'verify2FA']
|
||||||
]);
|
]);
|
||||||
|
$router->addRoute('POST', '/api/v1/auth/2fa/verify_login', [
|
||||||
|
'middleware' => [\App\Middleware\AuthMiddleware::class],
|
||||||
|
'handler' => [AuthController::class, 'login2FAVerify']
|
||||||
|
]);
|
||||||
$router->addRoute('POST', '/api/v1/auth/2fa/disable', [
|
$router->addRoute('POST', '/api/v1/auth/2fa/disable', [
|
||||||
'middleware' => [\App\Middleware\AuthMiddleware::class],
|
'middleware' => [\App\Middleware\AuthMiddleware::class],
|
||||||
'handler' => [AuthController::class, 'disable2FA']
|
'handler' => [AuthController::class, 'disable2FA']
|
||||||
@@ -139,9 +143,10 @@ $router->addRoute('GET', '/api/v1/health', function($request) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// ══ Determine if this is an API request ═════════════════════════════
|
// ══ Determine if this is an API request ═════════════════════════════
|
||||||
$apiRoute = $_GET['route'] ?? null;
|
$requestPath = $_GET['route'] ?? parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
|
||||||
|
$isApi = str_starts_with($requestPath, '/api/v1');
|
||||||
|
|
||||||
if (!$apiRoute) {
|
if (!$isApi) {
|
||||||
// Not an API call — serve the SPA shell
|
// Not an API call — serve the SPA shell
|
||||||
include __DIR__ . '/shell.php';
|
include __DIR__ . '/shell.php';
|
||||||
exit;
|
exit;
|
||||||
|
|||||||
@@ -147,7 +147,7 @@
|
|||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
<script>
|
<script>
|
||||||
const API = {
|
const API = {
|
||||||
baseUrl: '/api/v1',
|
baseUrl: 'index.php?route=/api/v1',
|
||||||
get token() { return localStorage.getItem('access_token'); },
|
get token() { return localStorage.getItem('access_token'); },
|
||||||
async req(method, path, body = null, files = false) {
|
async req(method, path, body = null, files = false) {
|
||||||
const headers = { 'Accept': 'application/json' };
|
const headers = { 'Accept': 'application/json' };
|
||||||
@@ -667,6 +667,60 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function render2FAChallenge(tempToken) {
|
||||||
|
document.getElementById('auth-content').innerHTML = `
|
||||||
|
<div class="text-center mb-10">
|
||||||
|
<h1 class="text-3xl font-black tracking-tighter mb-2">2FA_CHALLENGE</h1>
|
||||||
|
<p class="text-bb-dim text-[10px] bb-mono uppercase">Enter terminal access code</p>
|
||||||
|
</div>
|
||||||
|
<form id="2fa-form" class="space-y-6">
|
||||||
|
<input type="text" name="code" placeholder="000000" class="w-full bb-panel bg-black p-3 bb-mono text-center text-xl tracking-[1em]" required autofocus>
|
||||||
|
<button type="submit" class="w-full bb-btn bb-btn-primary py-4 font-black">VERIFY_IDENTITY</button>
|
||||||
|
</form>
|
||||||
|
`;
|
||||||
|
document.getElementById('2fa-form').onsubmit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const code = new FormData(e.target).get('code');
|
||||||
|
try {
|
||||||
|
// Note: We use the temp token in the header
|
||||||
|
const res = await fetch('index.php?route=/api/v1/auth/2fa/verify_login', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${tempToken}` },
|
||||||
|
body: JSON.stringify({ code })
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
if (res.ok) saveAuth(data.data);
|
||||||
|
else showToast(data.error?.message_ar || 'INVALID_CODE', 'error');
|
||||||
|
} catch(err) {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderRegister() {
|
||||||
|
document.getElementById('auth-content').innerHTML = `
|
||||||
|
<div class="text-center mb-10">
|
||||||
|
<h1 class="text-3xl font-black tracking-tighter mb-2">NEW_TERMINAL</h1>
|
||||||
|
<p class="text-bb-dim text-[10px] bb-mono uppercase">Register Platform Account</p>
|
||||||
|
</div>
|
||||||
|
<form id="register-form" class="space-y-4">
|
||||||
|
<input type="text" name="name" placeholder="FULL_NAME" class="w-full bb-panel bg-black p-3 bb-mono text-sm" required>
|
||||||
|
<input type="email" name="email" placeholder="EMAIL_IDENTITY" class="w-full bb-panel bg-black p-3 bb-mono text-sm" required>
|
||||||
|
<input type="password" name="password" placeholder="PASSWORD" class="w-full bb-panel bg-black p-3 bb-mono text-sm" required>
|
||||||
|
<button type="submit" class="w-full bb-btn bb-btn-primary py-4 font-black">INITIALIZE_ACCOUNT</button>
|
||||||
|
</form>
|
||||||
|
<div class="mt-8 text-center text-bb-dim text-[10px] bb-mono">
|
||||||
|
ALREADY_HAVE_ACCESS? <a href="#" onclick="renderLogin()" class="text-white hover:underline">LOGIN_TERMINAL</a>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
document.getElementById('register-form').onsubmit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const fd = new FormData(e.target);
|
||||||
|
try {
|
||||||
|
const res = await API.post('/auth/register', Object.fromEntries(fd));
|
||||||
|
saveAuth(res.data);
|
||||||
|
} catch(err) {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function saveAuth(data) {
|
function saveAuth(data) {
|
||||||
localStorage.setItem('access_token', data.access_token);
|
localStorage.setItem('access_token', data.access_token);
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
|
|||||||
Reference in New Issue
Block a user