Files
QRForge-selfhosted/src/users.php
T
dillard feb5380b28 Security hardening: CSRF, rate limiting, session/password policy, audit log
Fixes critical pre-existing issues found during review: bulk_action.php had no
auth check at all (unauthenticated download/delete of any qrcode) and built a
table name from unwhitelisted user input (SQL injection); the QR generator
classes wrote files from unvalidated filename/format, allowing path traversal
and arbitrary file writes. Also pins chillerlan/php-qrcode to 5.0.5 since
master now requires PHP 8.4, breaking the PHP 8.3 build.

- CSRF tokens on all POST forms and the bulk_action.php JSON endpoint
- Login rate limiting (5 attempts / 15 min) via new login_attempts table
- Hardened sessions: httponly/samesite cookies, 30 min idle timeout,
  session regeneration on login
- Forced password change for the default superadmin/superadmin account
- Server-side validation in Users/DynamicQrcode/Qrcode classes
- Audit log table for auth, user, and qrcode actions
- Checked-in db schema (db/init.sql, migrations/) instead of relying on an
  opaque prebuilt db image
- Production docker-compose with Nginx + php-fpm instead of the PHP dev server

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-08 15:00:33 +02:00

85 lines
2.6 KiB
PHP

<?php
require_once 'includes/bootstrap.php';
require_once BASE_PATH . '/includes/auth_validate.php';
require_once BASE_PATH . '/lib/Users/Users.php';
$db = getDbInstance();
$users = new Users();
if ($_SESSION['type'] !== 'super')
$users->failure('Only a "super admin" account can access the admin listing page', 'Location: index.php');
$select = array('id', 'username', 'type');
$search_fields = array('username');
require_once BASE_PATH . '/includes/search_order.php';
$page = filter_input(INPUT_GET, 'page', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? 1;
$db->pageLimit = 15;
$rows = $db->arraybuilder()->paginate('users', $page, $select);
$total_pages = $db->totalPages;
?>
<!DOCTYPE html>
<html lang="en">
<title>Qrcode Generator</title>
<head>
<?php include './includes/head.php'; ?>
</head>
<body class="hold-transition sidebar-mini layout-fixed layout-navbar-fixed layout-footer-fixed">
<div class="wrapper">
<!-- Navbar -->
<?php include './includes/navbar.php'; ?>
<!-- /.navbar -->
<!-- Main Sidebar Container -->
<?php include './includes/sidebar.php'; ?>
<!-- /.Main Sidebar Container -->
<!-- Content Wrapper. Contains page content -->
<div class="content-wrapper">
<!-- Content Header (Page header) -->
<div class="content-header">
<div class="container-fluid">
<div class="row mb-2">
<div class="col-sm-6">
<h1 class="m-0 text-dark">Users</h1>
</div><!-- /.col -->
<div class="col-sm-6">
<ol class="breadcrumb float-sm-right">
<li class="breadcrumb-item">
<a href="user.php" class="btn btn-success"><i class="fa fa-plus"></i> Add new</a>
</li>
</ol>
</div><!-- /.col -->
</div><!-- /.row -->
</div><!-- /.container-fluid -->
</div><!-- /.content-header -->
<!-- Flash message-->
<?php include BASE_PATH . '/includes/flash_messages.php'; ?>
<!-- /.Flash message-->
<!-- Filters -->
<?php $options = $users->setOrderingValues();
include BASE_PATH . '/forms/filters.php'; ?>
<!-- /.Filters -->
<!-- Main content -->
<section class="content">
<div class="container-fluid">
<!-- Table -->
<?php include BASE_PATH . '/forms/table_users.php'; ?>
<!-- /.Table -->
</div><!-- /.container-fluid -->
</section>
</div><!-- /.content-wrapper -->
<!-- Footer and scripts -->
<?php include './includes/footer.php'; ?>
<!-- /.Footer and scripts -->
</body>
</html>