PHP WebShell

Текущая директория: /var/www/bitcardoApp/models/auth

Просмотр файла: login_process.php.bak

<?php
/**
 * models/auth/login_process.php
 *
 * What this does (carefully, without breaking existing behavior):
 *  - Loads bootstrap (headers + secure session + serv_config + db_config + settings)
 *  - Validates reCAPTCHA (server-side)
 *  - Looks up user by email OR phone (prepared statement)
 *  - Verifies password (password_hash / password_verify)
 *  - Sets required session keys (email is needed for /chat/sso)
 *  - OPTIONAL (only if present):
 *      - lib/session.php -> create session record + remember-me cookie
 *      - lib/device.php  -> if OTP-on-new-device is enabled & device is new, redirect to /auth/challenge.php
 *      - lib/rate_limit.php -> throttle attempts (per IP + per login)
 *  - Redirects to dashboard on success; back to login on failure, with a safe error
 *
 * Notes:
 *  - We rely on config/bootstrap.php to send headers & start a hardened session.
 *  - We never double-include serv_config/db_config/settings. Bootstrap already did that.
 *  - Every exit path uses a small safe_redirect() helper to avoid partial output issues.
 */

////////////////////////////////////////////
// 0) Bootstrap (required)
////////////////////////////////////////////
$__bootstrap = __DIR__ . '/../../config/bootstrap.php';
if (!file_exists($__bootstrap)) {
    // Absolute minimum fallback to avoid fatal if bootstrap isn't deployed yet.
    if (session_status() === PHP_SESSION_NONE) { session_start(); }
    require_once __DIR__ . '/../../config/serv_config.php';
    require_once __DIR__ . '/../../config/db_config.php';
    // settings are optional; ignore if not present
    $__settings = __DIR__ . '/../../config/settings.php';
    if (file_exists($__settings)) { require_once $__settings; }
    unset($__settings);
} else {
    require_once $__bootstrap;
}
unset($__bootstrap);

////////////////////////////////////////////
// 0.1) Small helpers
////////////////////////////////////////////
/** Safe redirect that tolerates accidental output. */
function safe_redirect(string $location) {
    if (!headers_sent()) {
        header('Location: ' . $location);
    } else {
        echo '<script>location.href=' . json_encode($location) . ';</script>';
    }
    exit;
}

/** Uniform error exit back to login with a message. */
function fail_with(string $msg) {
    $_SESSION['error'] = $msg;
    safe_redirect('../../auth/login.php');
}


// CSRF check (optional but recommended)
if (!isset($_POST['csrf'], $_SESSION['csrf']) || !hash_equals($_SESSION['csrf'], $_POST['csrf'])) {
    $_SESSION['error'] = 'Session expired. Please try again.';
    header('Location: ../../auth/login.php'); exit;
}


////////////////////////////////////////////
// 1) Collect + normalize input
////////////////////////////////////////////
$loginRaw = $_POST['login']    ?? '';
$passRaw  = $_POST['password'] ?? '';

$login    = trim(preg_replace('/\s+/', ' ', (string)$loginRaw)); // collapse internal whitespace
$password = (string)$passRaw;

// Keep the typed login for user convenience on failure
$_SESSION['form_data'] = ['login' => $login];

////////////////////////////////////////////
// 2) Validate reCAPTCHA (server-side)
////////////////////////////////////////////
$recaptchaResponse = $_POST['g-recaptcha-response'] ?? '';
$secret = defined('RECAPTCHA_SECRET') ? RECAPTCHA_SECRET : '';

if ($secret === '') {
    // Not configured: fail closed (or you can flip to a warning in dev)
    fail_with('Captcha is not configured. Please try again later.');
}

// Build a tiny HTTP context with timeouts to avoid hanging
$ctx = stream_context_create([
    'http' => [
        'method'  => 'POST',
        'header'  => "Content-type: application/x-www-form-urlencoded\r\n",
        'content' => http_build_query([
            'secret'   => $secret,
            'response' => $recaptchaResponse,
            // Optionally: 'remoteip' => $_SERVER['REMOTE_ADDR'] ?? null,
        ]),
        'timeout' => 5,
    ]
]);

$verify = @file_get_contents('https://www.google.com/recaptcha/api/siteverify', false, $ctx);
$captcha = $verify ? json_decode($verify, true) : ['success' => false];

if (empty($captcha['success'])) {
    fail_with('Captcha verification failed.');
}

////////////////////////////////////////////
// 3) Quick sanity checks
////////////////////////////////////////////
if (!($conn instanceof mysqli)) {
    // DB not ready; do not leak details
    fail_with('Service temporarily unavailable. Please try again.');
}
if ($login === '' || $password === '') {
    fail_with('Please enter your login and password.');
}

////////////////////////////////////////////
// 3.5) ADDED: rate-limit (feature-flagged & optional)
////////////////////////////////////////////
$rateLib = __DIR__ . '/../../lib/rate_limit.php';
$rlActive = false;
if (file_exists($rateLib)) {
    require_once $rateLib;
    // Feature toggle via settings.php (lowercase keys); default ON
    $rlActive = function_exists('is_enabled') ? is_enabled('rate_limit_enabled', true) : true;

    if ($rlActive) {
        // Policy (conservative defaults)
        $IP_LIMIT      = 10;   // attempts
        $IP_WINDOW_SEC = 900;  // 15m
        $IP_LOCK_SEC   = 900;  // 15m

        $ID_LIMIT      = 7;    // attempts per email/phone
        $ID_WINDOW_SEC = 900;  // 15m
        $ID_LOCK_SEC   = 900;  // 15m

        // Tokens
        $ipToken  = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
        $loginTok = function_exists('rl_norm_login') ? rl_norm_login($login) : mb_strtolower(trim($login));

        // Check IP bucket
        if (function_exists('rl_check_and_inc')) {
            $r = rl_check_and_inc($conn, 'ip', $ipToken, $IP_LIMIT, $IP_WINDOW_SEC, $IP_LOCK_SEC);
            if (!$r['ok']) {
                fail_with("Too many attempts from your network. Try again in {$r['locked_for']}s.");
            }
        }

        // Check identifier bucket
        if ($loginTok !== '' && function_exists('rl_check_and_inc')) {
            $r = rl_check_and_inc($conn, 'login', $loginTok, $ID_LIMIT, $ID_WINDOW_SEC, $ID_LOCK_SEC);
            if (!$r['ok']) {
                fail_with("Too many attempts on this account. Try again in {$r['locked_for']}s.");
            }
        }
    }
}
unset($rateLib);

////////////////////////////////////////////
// 4) Find user (by email OR phone) with prepared statement
////////////////////////////////////////////
$sql = "SELECT user_id, email, first_name, last_name, password_hash
        FROM users
        WHERE email = ? OR phone = ?
        LIMIT 1";
$stmt = $conn->prepare($sql);
if (!$stmt) {
    // Do not expose SQL errors to the user
    fail_with('Service temporarily unavailable. Please try again.');
}
$stmt->bind_param('ss', $login, $login);
$stmt->execute();
$stmt->store_result();

if ($stmt->num_rows !== 1) {
    $stmt->close();
    fail_with('Invalid login credentials.');
}

$stmt->bind_result($userId, $email, $firstName, $lastName, $hash);
$stmt->fetch();

////////////////////////////////////////////
// 5) Verify password (timing-safe)
////////////////////////////////////////////
if (!is_string($hash) || $hash === '' || !password_verify($password, $hash)) {
    $stmt->close();
    fail_with('Invalid login credentials.');
}

// Good path → close stmt
$stmt->close();

////////////////////////////////////////////
// 6) Establish session identity
////////////////////////////////////////////
if (session_status() !== PHP_SESSION_ACTIVE) { session_start(); }
// Refresh session ID after successful auth
session_regenerate_id(true);

// Set keys (email is used by /chat/sso to create/auto-login on Grupo)
$_SESSION['user_id']    = (int)$userId;
$_SESSION['email']      = $email;
$_SESSION['first_name'] = $firstName ?? '';
$_SESSION['last_name']  = $lastName ?? '';
$_SESSION['loggedIn']   = true;

// Clear prefill
unset($_SESSION['form_data']);

////////////////////////////////////////////
// 6.5) ADDED: rate-limit reset on success (if active)
////////////////////////////////////////////
if (!empty($rlActive) && function_exists('rl_reset')) {
    $ipToken  = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
    $loginTok = function_exists('rl_norm_login') ? rl_norm_login($login) : mb_strtolower(trim($login));
    rl_reset($conn, 'ip', $ipToken);
    if ($loginTok !== '') rl_reset($conn, 'login', $loginTok);
}

////////////////////////////////////////////
// 7) Optional Phase 1: session record + remember-me (only if present)
////////////////////////////////////////////
$rememberRequested = !empty($_POST['remember']); // checkbox name="remember"
$libDevice  = __DIR__ . '/../../lib/device.php';
$libSession = __DIR__ . '/../../lib/session.php';
if (file_exists($libSession)) {
    require_once $libSession;

    // Respect DB flag if settings helper exists; default allow
    $rememberAllowed = true;
    if (function_exists('is_enabled')) {
        $rememberAllowed = is_enabled('remember_me', true);
    }

    if (function_exists('session_record_create')) {
        // Writes user_sessions row + sets remember cookie if requested/allowed
        session_record_create($conn, (int)$userId, ($rememberAllowed && $rememberRequested));
    }
}
unset($libSession);


$hasGrace = !empty($_SESSION['first_session_grace']);
if ($otpEnabled && $requireOnNew && !$trusted && !$hasGrace) {
    safe_redirect('/auth/challenge.php');
}
// Consume grace once per session
unset($_SESSION['first_session_grace']);

////////////////////////////////////////////
// 8) Optional Phase 2: device trust + TOTP challenge gate (only if present)
////////////////////////////////////////////
if (file_exists($libDevice)) {
    require_once $libDevice;

    // Defaults: challenge OFF unless explicitly enabled in DB
    $otpEnabled   = function_exists('is_enabled') ? is_enabled('otp_enabled', true) : true;
    $requireOnNew = function_exists('is_enabled') ? is_enabled('otp_require_on_new_dev', true) : false;

    if ($otpEnabled && $requireOnNew && function_exists('device_is_trusted')) {
        $trusted = device_is_trusted($conn, (int)$userId);
        if (!$trusted) {
            // Challenge exists? Redirect there. If you haven't created it yet, this line is never reached unless you enabled the flags.
            safe_redirect('/auth/challenge.php');
        }
    }
}
unset($libDevice);

////////////////////////////////////////////
// 9) Success → Dashboard
////////////////////////////////////////////
safe_redirect('../../user/dashboard/index.php');

Выполнить команду


Для локальной разработки. Не используйте в интернете!