PHP WebShell

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

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

<?php
// lib/session.php — resilient remember-me with duration support.
// Works with different column namings (selector vs remember_selector).

/* ---------- helpers ---------- */
if (!function_exists('base64url')) {
  function base64url(string $bin): string {
    return rtrim(strtr(base64_encode($bin), '+/', '-_'), '=');
  }
}

if (!function_exists('rm_cookie_name')) {
  function rm_cookie_name(): string {
    return 'bc_rm';
  }
}

if (!function_exists('rm_set_cookie')) {
  function rm_set_cookie(string $value, int $days = 30): void {
    setcookie(
      rm_cookie_name(),
      $value,
      [
        'expires'  => time() + 60 * 60 * 24 * $days,
        'path'     => '/',
        'domain'   => 'wallet.bitcardo.com', // adjust if needed
        'secure'   => !empty($_SERVER['HTTPS']),
        'httponly' => true,
        'samesite' => 'Lax',
      ]
    );
    $_COOKIE[rm_cookie_name()] = $value;
  }
}

if (!function_exists('rm_clear_cookie')) {
  function rm_clear_cookie(): void {
    setcookie(
      rm_cookie_name(),
      '',
      [
        'expires'  => time() - 3600,
        'path'     => '/',
        'domain'   => 'wallet.bitcardo.com',
        'secure'   => !empty($_SERVER['HTTPS']),
        'httponly' => true,
        'samesite' => 'Lax',
      ]
    );
    unset($_COOKIE[rm_cookie_name()]);
  }
}

/* ---------- schema detection (cached) ---------- */
if (!function_exists('usess_schema')) {
  function usess_schema(mysqli $conn): array {
    static $cache;
    if ($cache !== null) return $cache;

    $have = [
      'selector'                => false,
      'validator_hash'          => false,
      'remember_selector'       => false,
      'remember_validator_hash' => false,
      'is_remembered'           => false,
      'expires_at'              => false,
      'revoked_at'              => false,
      'last_seen_at'            => false,
      'ip_address'              => false,
      'user_agent'              => false,
    ];

    $res = $conn->query("SHOW COLUMNS FROM user_sessions");
    if ($res) {
      while ($col = $res->fetch_assoc()) {
        $name = strtolower($col['Field']);
        if (array_key_exists($name, $have)) {
          $have[$name] = true;
        }
      }
      $res->close();
    }

    return $cache = $have;
  }
}

/**
 * Resolve which columns to use for selector/validator based on schema.
 * Returns:
 *  ['selector' => 'remember_selector' or 'selector',
 *   'validator' => 'remember_validator_hash' or 'validator_hash']
 */
if (!function_exists('usess_resolve_columns')) {
  function usess_resolve_columns(array $sch): array {
    $selectorCol  = null;
    $validatorCol = null;

    if (!empty($sch['remember_selector'])) {
      $selectorCol = 'remember_selector';
    } elseif (!empty($sch['selector'])) {
      $selectorCol = 'selector';
    }

    if (!empty($sch['remember_validator_hash'])) {
      $validatorCol = 'remember_validator_hash';
    } elseif (!empty($sch['validator_hash'])) {
      $validatorCol = 'validator_hash';
    }

    return [
      'selector'  => $selectorCol,
      'validator' => $validatorCol,
    ];
  }
}

/* ---------- main API ---------- */
if (!function_exists('session_record_create')) {
  function session_record_create(
    mysqli $conn,
    int $userId,
    bool $remember = false,
    int $durationSeconds = 2592000 // default 30 days for old callers
  ): void {

    $phpSid = session_id();
    $ip     = $_SERVER['REMOTE_ADDR'] ?? '';
    $ua     = substr($_SERVER['HTTP_USER_AGENT'] ?? '', 0, 255);
    $now    = new DateTimeImmutable();
    $nowSql = $now->format('Y-m-d H:i:s');

    // Bound duration
    if ($durationSeconds < 0) {
      $durationSeconds = 0;
    }
    $MAX = 30 * 24 * 60 * 60;
    if ($durationSeconds > $MAX) {
      $durationSeconds = $MAX;
    }

    $sch  = usess_schema($conn);
    $cols = usess_resolve_columns($sch);

    $selectorCol  = $cols['selector'];   // 'remember_selector' or 'selector'
    $validatorCol = $cols['validator'];  // 'remember_validator_hash' or 'validator_hash'

    // Can this schema support remember-me?
    $rememberCapable = (
      !empty($selectorCol) &&
      !empty($validatorCol) &&
      !empty($sch['is_remembered']) &&
      !empty($sch['expires_at'])
    );

    if (!$rememberCapable) {
      $remember = false;
    }

    if ($remember && $durationSeconds > 0) {
      // Generate selector + validator
      $selector  = base64url(random_bytes(16));   // public part
      $validator = base64url(random_bytes(32));   // secret part
      $valHash   = hash('sha256', $validator);

      // Exact DB expiry in seconds
      $expiresAtObj = $now->modify('+' . $durationSeconds . ' seconds');
      $expiresAt    = $expiresAtObj->format('Y-m-d H:i:s');

      $cookieVal  = $selector . ':' . $validator;
      $cookieDays = max(1, (int)ceil($durationSeconds / 86400));
      rm_set_cookie($cookieVal, $cookieDays);

      // Insert full remember row with dynamic columns
      $sql = "INSERT INTO user_sessions
              (user_id, php_session_id, {$selectorCol}, {$validatorCol}, is_remembered,
               ip_address, user_agent, last_seen_at, expires_at)
              VALUES (?, ?, ?, ?, 1, ?, ?, ?, ?)";

      $stmt = $conn->prepare($sql);
      if ($stmt) {
        $stmt->bind_param(
          'isssssss',
          $userId,
          $phpSid,
          $selector,
          $valHash,
          $ip,
          $ua,
          $nowSql,
          $expiresAt
        );
        $stmt->execute();
        $stmt->close();
      }
      return;
    }

    // No remember-me → minimal insert
    if ($sch['ip_address'] && $sch['user_agent'] && $sch['last_seen_at']) {
      $sql = "INSERT INTO user_sessions
              (user_id, php_session_id, ip_address, user_agent, last_seen_at)
              VALUES (?, ?, ?, ?, ?)";
      $stmt = $conn->prepare($sql);
      if ($stmt) {
        $stmt->bind_param('issss', $userId, $phpSid, $ip, $ua, $nowSql);
        $stmt->execute();
        $stmt->close();
      }
    } else {
      $sql = "INSERT INTO user_sessions (user_id, php_session_id) VALUES (?, ?)";
      $stmt = $conn->prepare($sql);
      if ($stmt) {
        $stmt->bind_param('is', $userId, $phpSid);
        $stmt->execute();
        $stmt->close();
      }
    }
  }
}

/**
 * Try to auto-login from bc_rm cookie.
 */
if (!function_exists('session_try_autologin')) {
  function session_try_autologin(mysqli $conn): bool {
    if (!empty($_SESSION['user_id'])) return true;

    $sch    = usess_schema($conn);
    $cols   = usess_resolve_columns($sch);
    $selectorCol  = $cols['selector'];
    $validatorCol = $cols['validator'];

    $cookie = $_COOKIE[rm_cookie_name()] ?? '';
    if (!$cookie || strpos($cookie, ':') === false) return false;

    // Table cannot support remember-me lookup
    if (empty($selectorCol) || empty($validatorCol) || !$sch['is_remembered'] || !$sch['expires_at']) {
      rm_clear_cookie();
      return false;
    }

    list($selector, $validator) = explode(':', $cookie, 2);
    $selector = substr($selector, 0, 255);
    $valHash  = hash('sha256', $validator);

    // Look up by selector + validator hash
    $sql = "SELECT user_id, expires_at, revoked_at
            FROM user_sessions
            WHERE {$selectorCol} = ? AND {$validatorCol} = ? AND is_remembered = 1
            ORDER BY expires_at DESC
            LIMIT 1";

    $stmt = $conn->prepare($sql);
    if (!$stmt) {
      rm_clear_cookie();
      return false;
    }

    $stmt->bind_param('ss', $selector, $valHash);
    $stmt->execute();
    $stmt->bind_result($userId, $expiresAt, $revokedAt);
    $found = $stmt->fetch();
    $stmt->close();

    if (!$found || $revokedAt) {
      rm_clear_cookie();
      return false;
    }

    // Expired?
    if ($expiresAt && new DateTimeImmutable($expiresAt) < new DateTimeImmutable()) {
      rm_clear_cookie();
      // best-effort mark revoked by selector
      $uSql = "UPDATE user_sessions
               SET revoked_at = NOW()
               WHERE {$selectorCol} = ? AND {$validatorCol} = ? AND is_remembered = 1";
      $u = $conn->prepare($uSql);
      if ($u) {
        $u->bind_param('ss', $selector, $valHash);
        $u->execute();
        $u->close();
      }
      return false;
    }

    // Load user basics
    $u = $conn->prepare("SELECT email, first_name, last_name FROM users WHERE user_id = ? LIMIT 1");
    if (!$u) {
      rm_clear_cookie();
      return false;
    }

    $u->bind_param('i', $userId);
    $u->execute();
    $u->bind_result($email, $first, $last);
    if (!$u->fetch()) {
      $u->close();
      rm_clear_cookie();
      return false;
    }
    $u->close();

    if (session_status() !== PHP_SESSION_ACTIVE) {
      session_start();
    }
    session_regenerate_id(true);

    $_SESSION['user_id']    = (int)$userId;
    $_SESSION['email']      = $email;
    $_SESSION['first_name'] = $first ?? '';
    $_SESSION['last_name']  = $last ?? '';
    $_SESSION['loggedIn']   = true;

    // Update last_seen_at if present
    if ($sch['last_seen_at']) {
      $updSql = "UPDATE user_sessions
                 SET last_seen_at = NOW()
                 WHERE {$selectorCol} = ? AND {$validatorCol} = ? AND is_remembered = 1";
      $t = $conn->prepare($updSql);
      if ($t) {
        $t->bind_param('ss', $selector, $valHash);
        $t->execute();
        $t->close();
      }
    }

    return true;
  }
}

/**
 * Revoke remember-me cookie (and mark DB rows revoked where possible).
 */
if (!function_exists('session_revoke_cookie')) {
  function session_revoke_cookie(mysqli $conn): void {
    $cookie = $_COOKIE[rm_cookie_name()] ?? '';
    if (!$cookie || strpos($cookie, ':') === false) {
      rm_clear_cookie();
      return;
    }

    $sch  = usess_schema($conn);
    $cols = usess_resolve_columns($sch);
    $selectorCol = $cols['selector'];

    if (!empty($selectorCol)) {
      list($selector,) = explode(':', $cookie, 2);
      $selector = substr($selector, 0, 255);

      $sql = "UPDATE user_sessions
              SET revoked_at = NOW()
              WHERE {$selectorCol} = ? AND is_remembered = 1";
      $u = $conn->prepare($sql);
      if ($u) {
        $u->bind_param('s', $selector);
        $u->execute();
        $u->close();
      }
    }

    rm_clear_cookie();
  }
}

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


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