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
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
# Kopieer naar .env en pas de waarden aan. .env wordt niet gecommit (zie .gitignore).
|
||||
|
||||
TYPE=docker
|
||||
QRCODE_GENERATOR=internal-chillerlan.qrcode
|
||||
BASE_URL=http://localhost
|
||||
|
||||
DATABASE_HOST=php-dynamic-qrcode-db
|
||||
DATABASE_PORT=3306
|
||||
DATABASE_NAME=qrcode
|
||||
DATABASE_USER=qrcode
|
||||
DATABASE_PASSWORD=change-me-to-a-strong-password
|
||||
DATABASE_PREFIX=
|
||||
DATABASE_CHARSET=utf8
|
||||
|
||||
MYSQL_ROOT_PASSWORD=change-me-to-a-strong-root-password
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
.settings
|
||||
.project
|
||||
.idea
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
.env
|
||||
+4
-1
@@ -86,7 +86,10 @@ RUN docker-php-ext-install sockets && docker-php-ext-enable sockets
|
||||
|
||||
RUN mkdir -p /opt && chmod 777 /opt
|
||||
WORKDIR /opt
|
||||
RUN git clone https://github.com/chillerlan/php-qrcode.git \
|
||||
# Vastgezet op 5.0.5 (laatste 5.x-release): vanaf 6.0.0 vereist de library PHP >= 8.4,
|
||||
# terwijl deze image op PHP 8.3 draait. Een ongepinde clone van master is bovendien
|
||||
# een reproduceerbaarheids-/supply-chain-risico (build kan zonder waarschuwing breken).
|
||||
RUN git clone --branch 5.0.5 --depth 1 https://github.com/chillerlan/php-qrcode.git \
|
||||
&& chmod -R 777 ./php-qrcode
|
||||
RUN cp ./php-qrcode/composer.json /var/www/html/composer.json
|
||||
RUN mkdir -p /var/www/html/test && chmod 777 /var/www/html/test
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
FROM php:8.3-fpm
|
||||
|
||||
RUN if [ "$(grep '^VERSION_ID=' /etc/os-release | cut -d '=' -f 2 | tr -d '"')" -eq "9" ]; then \
|
||||
sed -i -e 's/deb.debian.org/archive.debian.org/g' \
|
||||
-e 's/security.debian.org/archive.debian.org/g' \
|
||||
-e '/stretch-updates/d' /etc/apt/sources.list; \
|
||||
fi
|
||||
|
||||
ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/
|
||||
|
||||
RUN chmod +x /usr/local/bin/install-php-extensions
|
||||
|
||||
RUN DEBIAN_FRONTEND=noninteractive apt-get update -q \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get install -qq -y \
|
||||
curl \
|
||||
libzip-dev \
|
||||
libjpeg62-turbo-dev \
|
||||
libpng-dev \
|
||||
libfreetype6-dev \
|
||||
zip unzip \
|
||||
&& install-php-extensions \
|
||||
gd \
|
||||
gettext \
|
||||
intl \
|
||||
mysqli \
|
||||
opcache \
|
||||
pdo_mysql \
|
||||
sockets \
|
||||
zip
|
||||
|
||||
# Install Composer.
|
||||
ENV PATH=$PATH:/root/composer/vendor/bin \
|
||||
COMPOSER_ALLOW_SUPERUSER=1 \
|
||||
COMPOSER_HOME=/root/composer
|
||||
RUN cd /opt \
|
||||
&& curl -sSL https://getcomposer.org/installer > composer-setup.php \
|
||||
&& curl -sSL https://composer.github.io/installer.sha384sum > composer-setup.sha384sum \
|
||||
&& sha384sum --check composer-setup.sha384sum \
|
||||
&& php composer-setup.php --install-dir=/usr/local/bin --filename=composer --2 \
|
||||
&& rm /opt/composer-setup.php /opt/composer-setup.sha384sum
|
||||
|
||||
RUN mkdir -p /opt && chmod 777 /opt
|
||||
WORKDIR /opt
|
||||
# Zie Dockerfile: vastgezet op 5.0.5, want 6.0.0+ vereist PHP >= 8.4.
|
||||
RUN git clone --branch 5.0.5 --depth 1 https://github.com/chillerlan/php-qrcode.git \
|
||||
&& chmod -R 777 ./php-qrcode
|
||||
RUN cp ./php-qrcode/composer.json /var/www/html/composer.json
|
||||
RUN cp -R ./php-qrcode/src /var/www/html/
|
||||
|
||||
WORKDIR /var/www/html
|
||||
RUN composer update
|
||||
COPY ./src ./
|
||||
RUN chown -R www-data:www-data /var/www/html \
|
||||
&& find /var/www/html -type f -exec chmod 644 {} \; \
|
||||
&& find /var/www/html -type d -exec chmod 755 {} \; \
|
||||
&& chmod -R 775 /var/www/html/saved_qrcode
|
||||
|
||||
EXPOSE 9000
|
||||
CMD ["php-fpm"]
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
|
||||
SET time_zone = "+00:00";
|
||||
|
||||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
||||
/*!40101 SET NAMES utf8 */;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `users` (
|
||||
`id` int(25) NOT NULL AUTO_INCREMENT,
|
||||
`username` varchar(50) NOT NULL,
|
||||
`password` varchar(255) NOT NULL,
|
||||
`series_id` varchar(60) DEFAULT NULL,
|
||||
`remember_token` varchar(255) DEFAULT NULL,
|
||||
`expires` datetime DEFAULT NULL,
|
||||
`type` varchar(10) NOT NULL,
|
||||
`must_change_password` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`password_changed_at` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `username` (`username`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=0 ;
|
||||
|
||||
-- Default super admin account. Credentials: superadmin / superadmin
|
||||
-- must_change_password=1 forces a password change on first login (see Fase 1 hardening).
|
||||
INSERT INTO `users` (`id`, `username`, `password`, `series_id`, `remember_token`, `expires`, `type`, `must_change_password`, `password_changed_at`) VALUES
|
||||
(1, 'superadmin', '$2y$10$xpZc5KC.aU2XHkcqhuZGFuAnqmtL4Unt8MysOyylceq.19XIyoZpG', NULL, NULL, NULL, 'super', 1, NULL);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `dynamic_qrcodes` (
|
||||
`id` int(10) NOT NULL AUTO_INCREMENT,
|
||||
`id_owner` int(25) NULL DEFAULT NULL,
|
||||
`filename` varchar(45) NOT NULL,
|
||||
`format` varchar(45) DEFAULT NULL,
|
||||
`identifier` longtext,
|
||||
`link` varchar(500) DEFAULT NULL,
|
||||
`qrcode` varchar(60) DEFAULT NULL,
|
||||
`scan` int(11) NOT NULL DEFAULT '0',
|
||||
`state` varchar(20) NOT NULL DEFAULT 'enable',
|
||||
`created_by` int(10) unsigned NOT NULL DEFAULT '0',
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_by` int(10) unsigned NOT NULL DEFAULT '0',
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=0 ;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `static_qrcodes` (
|
||||
`id` int(10) NOT NULL AUTO_INCREMENT,
|
||||
`id_owner` int(25) NULL DEFAULT NULL,
|
||||
`filename` varchar(45) CHARACTER SET utf8 NOT NULL,
|
||||
`format` varchar(45) DEFAULT NULL,
|
||||
`type` varchar(45) CHARACTER SET utf8 DEFAULT NULL,
|
||||
`content` mediumtext CHARACTER SET utf8,
|
||||
`qrcode` varchar(60) CHARACTER SET utf8 DEFAULT NULL,
|
||||
`state` varchar(20) CHARACTER SET utf8 NOT NULL DEFAULT 'enable',
|
||||
`created_by` int(10) unsigned NOT NULL DEFAULT '0',
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_by` int(10) unsigned NOT NULL DEFAULT '0',
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=0 ;
|
||||
|
||||
-- Security hardening (Fase 1): rate limiting op login pogingen
|
||||
CREATE TABLE IF NOT EXISTS `login_attempts` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`username` varchar(50) NOT NULL,
|
||||
`ip_address` varchar(45) NOT NULL,
|
||||
`success` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`attempted_at` datetime NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `username_attempted_at` (`username`, `attempted_at`),
|
||||
KEY `ip_attempted_at` (`ip_address`, `attempted_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
-- Security hardening (Fase 1): audit log van gevoelige acties
|
||||
CREATE TABLE IF NOT EXISTS `audit_log` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int(25) DEFAULT NULL,
|
||||
`username` varchar(50) DEFAULT NULL,
|
||||
`action` varchar(50) NOT NULL,
|
||||
`target_type` varchar(30) DEFAULT NULL,
|
||||
`target_id` varchar(50) DEFAULT NULL,
|
||||
`ip_address` varchar(45) DEFAULT NULL,
|
||||
`user_agent` varchar(255) DEFAULT NULL,
|
||||
`created_at` datetime NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `created_at` (`created_at`),
|
||||
KEY `user_id` (`user_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
||||
@@ -0,0 +1,57 @@
|
||||
-- Fase 1 security hardening migratie.
|
||||
-- Voer uit tegen een bestaande database (gebruikt de originele
|
||||
-- giandonatoinverso/php-dynamic-qr-code-db image of een oudere init.sql).
|
||||
-- Kolommen/tabellen worden alleen toegevoegd als ze nog niet bestaan.
|
||||
|
||||
SET @db := DATABASE();
|
||||
|
||||
SET @col_exists := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'users' AND COLUMN_NAME = 'must_change_password'
|
||||
);
|
||||
SET @sql := IF(@col_exists = 0,
|
||||
'ALTER TABLE `users` ADD COLUMN `must_change_password` TINYINT(1) NOT NULL DEFAULT 0',
|
||||
'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @col_exists := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'users' AND COLUMN_NAME = 'password_changed_at'
|
||||
);
|
||||
SET @sql := IF(@col_exists = 0,
|
||||
'ALTER TABLE `users` ADD COLUMN `password_changed_at` DATETIME DEFAULT NULL',
|
||||
'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- Bestaand superadmin account met het fabriekswachtwoord (superadmin/superadmin)
|
||||
-- moet bij eerstvolgende login het wachtwoord wijzigen.
|
||||
UPDATE `users`
|
||||
SET `must_change_password` = 1
|
||||
WHERE `username` = 'superadmin'
|
||||
AND `password` = '$2y$10$xpZc5KC.aU2XHkcqhuZGFuAnqmtL4Unt8MysOyylceq.19XIyoZpG';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `login_attempts` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`username` varchar(50) NOT NULL,
|
||||
`ip_address` varchar(45) NOT NULL,
|
||||
`success` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`attempted_at` datetime NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `username_attempted_at` (`username`, `attempted_at`),
|
||||
KEY `ip_attempted_at` (`ip_address`, `attempted_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `audit_log` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int(25) DEFAULT NULL,
|
||||
`username` varchar(50) DEFAULT NULL,
|
||||
`action` varchar(50) NOT NULL,
|
||||
`target_type` varchar(30) DEFAULT NULL,
|
||||
`target_id` varchar(50) DEFAULT NULL,
|
||||
`ip_address` varchar(45) DEFAULT NULL,
|
||||
`user_agent` varchar(255) DEFAULT NULL,
|
||||
`created_at` datetime NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `created_at` (`created_at`),
|
||||
KEY `user_id` (`user_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
@@ -0,0 +1,66 @@
|
||||
services:
|
||||
nginx:
|
||||
image: "nginx:1.27-alpine"
|
||||
restart: "unless-stopped"
|
||||
ports:
|
||||
- "80:80"
|
||||
# 443 pas openzetten zodra SSL-certificaten zijn gemount (bv. via certbot-volume
|
||||
# of een losse reverse proxy zoals Caddy/Traefik ervoor). Zie infra-fase van het plan.
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
- php_dynamic_qrcode_saved_qrcode_data:/var/www/html/saved_qrcode:ro
|
||||
depends_on:
|
||||
- php-dynamic-qrcode
|
||||
networks:
|
||||
- php-dynamic-qrcode-network
|
||||
|
||||
php-dynamic-qrcode:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.fpm
|
||||
restart: "unless-stopped"
|
||||
environment:
|
||||
TYPE: "docker"
|
||||
QRCODE_GENERATOR: "${QRCODE_GENERATOR:-internal-chillerlan.qrcode}"
|
||||
BASE_URL: "${BASE_URL:?zet BASE_URL in .env, bv. https://qr.ensembia.com}"
|
||||
DATABASE_HOST: "php-dynamic-qrcode-db"
|
||||
DATABASE_PORT: "3306"
|
||||
DATABASE_NAME: "${DATABASE_NAME:-qrcode}"
|
||||
DATABASE_USER: "${DATABASE_USER:-qrcode}"
|
||||
DATABASE_PASSWORD: "${DATABASE_PASSWORD:?zet DATABASE_PASSWORD in .env}"
|
||||
DATABASE_PREFIX: "${DATABASE_PREFIX:-}"
|
||||
DATABASE_CHARSET: "${DATABASE_CHARSET:-utf8}"
|
||||
depends_on:
|
||||
php-dynamic-qrcode-db:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- php_dynamic_qrcode_saved_qrcode_data:/var/www/html/saved_qrcode
|
||||
networks:
|
||||
- php-dynamic-qrcode-network
|
||||
|
||||
php-dynamic-qrcode-db:
|
||||
image: "mysql:8.0"
|
||||
restart: "unless-stopped"
|
||||
volumes:
|
||||
- php_dynamic_qrcode_db_data:/var/lib/mysql
|
||||
- ./db/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD:?zet MYSQL_ROOT_PASSWORD in .env}"
|
||||
MYSQL_DATABASE: "${DATABASE_NAME:-qrcode}"
|
||||
MYSQL_USER: "${DATABASE_USER:-qrcode}"
|
||||
MYSQL_PASSWORD: "${DATABASE_PASSWORD:?zet DATABASE_PASSWORD in .env}"
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
networks:
|
||||
- php-dynamic-qrcode-network
|
||||
|
||||
volumes:
|
||||
php_dynamic_qrcode_db_data:
|
||||
php_dynamic_qrcode_saved_qrcode_data:
|
||||
|
||||
networks:
|
||||
php-dynamic-qrcode-network:
|
||||
driver: bridge
|
||||
+26
-21
@@ -1,48 +1,53 @@
|
||||
version: "3.2"
|
||||
services:
|
||||
php-dynamic-qrcode:
|
||||
image: "giandonatoinverso/php-dynamic-qr-code:latest"
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
restart: "unless-stopped"
|
||||
environment:
|
||||
TYPE: "docker"
|
||||
QRCODE_GENERATOR: "internal-chillerlan.qrcode"
|
||||
BASE_URL: "https://mydomain.com"
|
||||
TYPE: "${TYPE:-docker}"
|
||||
QRCODE_GENERATOR: "${QRCODE_GENERATOR:-internal-chillerlan.qrcode}"
|
||||
BASE_URL: "${BASE_URL:-http://localhost}"
|
||||
DATABASE_HOST: "php-dynamic-qrcode-db"
|
||||
DATABASE_PORT: "3306"
|
||||
DATABASE_NAME: "qrcode"
|
||||
DATABASE_USER: "qrcode"
|
||||
DATABASE_PASSWORD: "changeme"
|
||||
DATABASE_PREFIX: ""
|
||||
DATABASE_CHARSET: "utf8"
|
||||
DATABASE_NAME: "${DATABASE_NAME:-qrcode}"
|
||||
DATABASE_USER: "${DATABASE_USER:-qrcode}"
|
||||
DATABASE_PASSWORD: "${DATABASE_PASSWORD:?zet DATABASE_PASSWORD in .env}"
|
||||
DATABASE_PREFIX: "${DATABASE_PREFIX:-}"
|
||||
DATABASE_CHARSET: "${DATABASE_CHARSET:-utf8}"
|
||||
ports:
|
||||
- 80:80
|
||||
- "80:80"
|
||||
depends_on:
|
||||
- php-dynamic-qrcode-db
|
||||
php-dynamic-qrcode-db:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- php_dynamic_qrcode_saved_qrcode_data:/var/www/html/saved_qrcode
|
||||
networks:
|
||||
- php-dynamic-qrcode-network
|
||||
|
||||
php-dynamic-qrcode-db:
|
||||
image: "giandonatoinverso/php-dynamic-qr-code-db:latest"
|
||||
image: "mysql:8.0"
|
||||
restart: "unless-stopped"
|
||||
volumes:
|
||||
- php_dynamic_qrcode_db_data:/var/lib/mysql
|
||||
ports:
|
||||
- '13306:3306'
|
||||
- ./db/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: "changeme"
|
||||
MYSQL_DATABASE: "qrcode"
|
||||
MYSQL_USER: "qrcode"
|
||||
MYSQL_PASSWORD: "changeme"
|
||||
MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD:?zet MYSQL_ROOT_PASSWORD in .env}"
|
||||
MYSQL_DATABASE: "${DATABASE_NAME:-qrcode}"
|
||||
MYSQL_USER: "${DATABASE_USER:-qrcode}"
|
||||
MYSQL_PASSWORD: "${DATABASE_PASSWORD:?zet DATABASE_PASSWORD in .env}"
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
networks:
|
||||
- php-dynamic-qrcode-network
|
||||
|
||||
volumes:
|
||||
php_dynamic_qrcode_db_data:
|
||||
php_dynamic_qrcode_config_data:
|
||||
php_dynamic_qrcode_saved_qrcode_data:
|
||||
|
||||
networks:
|
||||
php-dynamic-qrcode-network:
|
||||
driver: bridge
|
||||
driver: bridge
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
root /var/www/html;
|
||||
index index.php;
|
||||
|
||||
client_max_body_size 20m;
|
||||
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header Referrer-Policy "same-origin" always;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php$is_args$args;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
fastcgi_pass php-dynamic-qrcode:9000;
|
||||
fastcgi_index index.php;
|
||||
include fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
}
|
||||
|
||||
# Statisch gegenereerde qrcodes mogen gedownload worden, maar niet als PHP uitgevoerd.
|
||||
location /saved_qrcode/ {
|
||||
location ~ \.php$ {
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
|
||||
location ~ /\. {
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
+57
-34
@@ -1,66 +1,89 @@
|
||||
<?php
|
||||
|
||||
|
||||
require_once 'config/config.php';
|
||||
session_start();
|
||||
require_once 'includes/bootstrap.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST')
|
||||
{
|
||||
csrf_verify_or_die();
|
||||
|
||||
$username = filter_input(INPUT_POST, 'username');
|
||||
$password = filter_input(INPUT_POST, 'password');
|
||||
$remember = filter_input(INPUT_POST, 'remember');
|
||||
|
||||
if (!$username || !$password) {
|
||||
$_SESSION['login_failure'] = 'Invalid username or password';
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
if (qr_is_login_locked_out($username)) {
|
||||
$_SESSION['login_failure'] = 'Too many failed login attempts. Try again in 15 minutes.';
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Get DB instance.
|
||||
$db = getDbInstance();
|
||||
|
||||
$db->where('username', $username);
|
||||
$row = $db->getOne('users');
|
||||
|
||||
if ($db->count >= 1)
|
||||
if ($db->count >= 1 && password_verify($password, $row['password']))
|
||||
{
|
||||
$db_password = $row['password'];
|
||||
qr_record_login_attempt($username, true);
|
||||
|
||||
// Voorkom session fixation: nieuwe sessie-id na een geslaagde login.
|
||||
session_regenerate_id(true);
|
||||
|
||||
$_SESSION['user_logged_in'] = TRUE;
|
||||
$_SESSION['type'] = $row['type'];
|
||||
$_SESSION['user_id'] = $row['id'];
|
||||
$_SESSION['username'] = $row['username'];
|
||||
$_SESSION['must_change_password'] = !empty($row['must_change_password']);
|
||||
$_SESSION['last_activity'] = time();
|
||||
|
||||
audit_log('login_success');
|
||||
|
||||
$user_id = $row['id'];
|
||||
|
||||
if (password_verify($password, $db_password))
|
||||
if ($remember)
|
||||
{
|
||||
$_SESSION['user_logged_in'] = TRUE;
|
||||
$_SESSION['type'] = $row['type'];
|
||||
$_SESSION['user_id'] = $row['id'];
|
||||
$series_id = randomString(16);
|
||||
$remember_token = getSecureRandomToken(20);
|
||||
$encryted_remember_token = password_hash($remember_token,PASSWORD_DEFAULT);
|
||||
|
||||
if ($remember)
|
||||
{
|
||||
$series_id = randomString(16);
|
||||
$remember_token = getSecureRandomToken(20);
|
||||
$encryted_remember_token = password_hash($remember_token,PASSWORD_DEFAULT);
|
||||
$expiry_time = date('Y-m-d H:i:s', strtotime(' + 30 days'));
|
||||
$expires = strtotime($expiry_time);
|
||||
$is_https = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off';
|
||||
|
||||
$expiry_time = date('Y-m-d H:i:s', strtotime(' + 30 days'));
|
||||
$expires = strtotime($expiry_time);
|
||||
$cookie_options = [
|
||||
'expires' => $expires,
|
||||
'path' => '/',
|
||||
'secure' => $is_https,
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
];
|
||||
|
||||
setcookie('series_id', $series_id, $expires, '/');
|
||||
setcookie('remember_token', $remember_token, $expires, '/');
|
||||
setcookie('series_id', $series_id, $cookie_options);
|
||||
setcookie('remember_token', $remember_token, $cookie_options);
|
||||
|
||||
$db = getDbInstance();
|
||||
$db->where ('id',$user_id);
|
||||
$db = getDbInstance();
|
||||
$db->where ('id',$user_id);
|
||||
|
||||
$update_remember = array(
|
||||
'series_id'=> $series_id,
|
||||
'remember_token' => $encryted_remember_token,
|
||||
'expires' =>$expiry_time
|
||||
);
|
||||
$db->update('users', $update_remember);
|
||||
}
|
||||
// Authentication successfull redirect user
|
||||
header('Location: index.php');
|
||||
}
|
||||
else
|
||||
{
|
||||
$_SESSION['login_failure'] = 'Invalid username or password';
|
||||
header('Location: login.php');
|
||||
$update_remember = array(
|
||||
'series_id'=> $series_id,
|
||||
'remember_token' => $encryted_remember_token,
|
||||
'expires' =>$expiry_time
|
||||
);
|
||||
$db->update('users', $update_remember);
|
||||
}
|
||||
// Authentication successfull redirect user
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
}
|
||||
else
|
||||
{
|
||||
qr_record_login_attempt($username, false);
|
||||
$_SESSION['login_failure'] = 'Invalid username or password';
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
|
||||
+31
-17
@@ -1,9 +1,14 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once 'config/config.php';
|
||||
require_once 'includes/bootstrap.php';
|
||||
require_once BASE_PATH . '/includes/auth_validate.php';
|
||||
require_once BASE_PATH . '/lib/DynamicQrcode/DynamicQrcode.php';
|
||||
require_once BASE_PATH . '/lib/StaticQrcode/StaticQrcode.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
csrf_verify_header_or_die();
|
||||
|
||||
$allowed_types = ['dynamic', 'static'];
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$db = getDbInstance();
|
||||
$json = json_decode(file_get_contents('php://input'), true);
|
||||
@@ -12,8 +17,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$params = $json['params'];
|
||||
$files = [];
|
||||
|
||||
if (isset($json['type'])) {
|
||||
$type = filter_var($json['type'], FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
if (isset($json['type']) && in_array($json['type'], $allowed_types, true)) {
|
||||
$type = $json['type'];
|
||||
} else {
|
||||
echo json_encode([
|
||||
'data' => 'Type action field in the request.',
|
||||
@@ -31,9 +36,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
}
|
||||
|
||||
foreach ($params as $param) {
|
||||
$row = $db->where('id', $param);
|
||||
$db->where('id', $param);
|
||||
if ($_SESSION['type'] !== 'super') {
|
||||
$db->where('id_owner', $_SESSION['user_id']);
|
||||
$db->orWhere('id_owner', NULL, 'IS');
|
||||
}
|
||||
$row = $db->getOne("{$type}_qrcodes");
|
||||
@$files[] = SAVED_QRCODE_FOLDER . $row['qrcode'];
|
||||
if ($row !== NULL) {
|
||||
$files[] = SAVED_QRCODE_FOLDER . $row['qrcode'];
|
||||
}
|
||||
}
|
||||
|
||||
$zip = new ZipArchive();
|
||||
@@ -50,6 +61,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
|
||||
$zip->close();
|
||||
|
||||
audit_log('bulk_download', $type, implode(',', $params));
|
||||
|
||||
echo json_encode([
|
||||
'data' => $url_path,
|
||||
'status' => 200
|
||||
@@ -57,10 +70,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
exit();
|
||||
} else if($json["action"] == "delete") {
|
||||
$params = $json['params'];
|
||||
$files = [];
|
||||
|
||||
if (isset($json['type'])) {
|
||||
$type = filter_var($json['type'], FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
if (isset($json['type']) && in_array($json['type'], $allowed_types, true)) {
|
||||
$type = $json['type'];
|
||||
} else {
|
||||
echo json_encode([
|
||||
'data' => 'Type action field in the request.',
|
||||
@@ -79,16 +91,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
|
||||
if($type == "dynamic")
|
||||
$instance = new DynamicQrcode();
|
||||
else if($type == "static")
|
||||
$instance = new StaticQrcode();
|
||||
else
|
||||
die("Type not allowed");
|
||||
$instance = new StaticQrcode();
|
||||
|
||||
foreach ($params as $param) {
|
||||
$a = 0;
|
||||
$instance->deleteQrcode($param, true);
|
||||
}
|
||||
|
||||
audit_log('bulk_delete', $type, implode(',', $params));
|
||||
|
||||
echo json_encode([
|
||||
'action' => "delete",
|
||||
'data' => "Qrcode deleted",
|
||||
@@ -96,9 +107,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
]);
|
||||
exit();
|
||||
|
||||
} else
|
||||
exit("Action not allowed");
|
||||
} else {
|
||||
echo json_encode(['data' => 'Action not allowed', 'status' => 400]);
|
||||
exit();
|
||||
}
|
||||
} else {
|
||||
exit('Direct access to this script not allowed.');
|
||||
http_response_code(405);
|
||||
echo json_encode(['data' => 'Direct access to this script not allowed.', 'status' => 405]);
|
||||
exit();
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
require_once 'includes/bootstrap.php';
|
||||
|
||||
if (empty($_SESSION['user_logged_in'])) {
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$forced = !empty($_SESSION['must_change_password']);
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
csrf_verify_or_die();
|
||||
|
||||
$current_password = $_POST['current_password'] ?? '';
|
||||
$new_password = $_POST['new_password'] ?? '';
|
||||
$confirm_password = $_POST['confirm_password'] ?? '';
|
||||
|
||||
$db = getDbInstance();
|
||||
$db->where('id', $_SESSION['user_id']);
|
||||
$user = $db->getOne('users');
|
||||
|
||||
if ($user === NULL || !password_verify($current_password, $user['password'])) {
|
||||
$_SESSION['failure'] = 'Current password is incorrect.';
|
||||
} elseif (strlen($new_password) < 10) {
|
||||
$_SESSION['failure'] = 'New password must be at least 10 characters long.';
|
||||
} elseif ($new_password !== $confirm_password) {
|
||||
$_SESSION['failure'] = 'New password and confirmation do not match.';
|
||||
} elseif ($new_password === $current_password) {
|
||||
$_SESSION['failure'] = 'New password must be different from the current password.';
|
||||
} else {
|
||||
$db = getDbInstance();
|
||||
$db->where('id', $_SESSION['user_id']);
|
||||
$db->update('users', [
|
||||
'password' => password_hash($new_password, PASSWORD_DEFAULT),
|
||||
'must_change_password' => 0,
|
||||
'password_changed_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
|
||||
$_SESSION['must_change_password'] = false;
|
||||
audit_log('password_changed');
|
||||
|
||||
$_SESSION['success'] = 'Password updated successfully.';
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<title>Change password - Qrcode Generator</title>
|
||||
<?php include './includes/head.php'; ?>
|
||||
|
||||
<body class="login-page" style="min-height: 512.391px;">
|
||||
<div class="login-box">
|
||||
<div class="login-logo">
|
||||
<img src="dist/img/DynamicQRCode_Original.png" style="width: 95%; height: 95%">
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body login-card-body">
|
||||
<p class="login-box-msg">
|
||||
<?php echo $forced
|
||||
? 'You must change your password before continuing.'
|
||||
: 'Change your password'; ?>
|
||||
</p>
|
||||
|
||||
<?php include './includes/flash_messages.php'; ?>
|
||||
|
||||
<form method="POST" action="change_password.php">
|
||||
<?php echo csrf_field(); ?>
|
||||
<div class="input-group mb-3">
|
||||
<input type="password" name="current_password" class="form-control" placeholder="Current password" required="required" autocomplete="current-password">
|
||||
</div>
|
||||
<div class="input-group mb-3">
|
||||
<input type="password" name="new_password" class="form-control" placeholder="New password (min. 10 characters)" required="required" minlength="10" autocomplete="new-password">
|
||||
</div>
|
||||
<div class="input-group mb-3">
|
||||
<input type="password" name="confirm_password" class="form-control" placeholder="Confirm new password" required="required" minlength="10" autocomplete="new-password">
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<button type="submit" class="btn btn-primary btn-block">Update password</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<?php if (!$forced): ?>
|
||||
<p class="mt-3 text-center"><a href="index.php">Back to dashboard</a></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="../../plugins/jquery/jquery.min.js"></script>
|
||||
<script src="../../plugins/bootstrap/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="../../dist/js/adminlte.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Vendored
+3
@@ -470,6 +470,9 @@
|
||||
data: JSON.stringify(data),
|
||||
dataType: "json",
|
||||
contentType: 'application/json',
|
||||
headers: {
|
||||
'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
|
||||
},
|
||||
success: (res) => {
|
||||
if (res.status == 200) {
|
||||
if(data["action"] === "download") {
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once 'config/config.php';
|
||||
require_once 'includes/bootstrap.php';
|
||||
require_once BASE_PATH.'/includes/auth_validate.php';
|
||||
require_once BASE_PATH . '/lib/DynamicQrcode/DynamicQrcode.php';
|
||||
|
||||
$dynamic_qrcode_instance = new DynamicQrcode();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
csrf_verify_or_die();
|
||||
}
|
||||
|
||||
$edit = false;
|
||||
if($_SERVER["REQUEST_METHOD"] === "GET" && isset($_GET["edit"]) && $_GET["edit"] == "true" && isset($_GET["id"])) {
|
||||
$edit = true;
|
||||
@@ -83,6 +86,7 @@ if($_SERVER["REQUEST_METHOD"] === "POST" && !isset($_POST["edit"])) {
|
||||
<h3 class="card-title">Enter the requested data</h3>
|
||||
</div>
|
||||
<form class="form" action="" method="post" id="dynamic_form" enctype="multipart/form-data">
|
||||
<?php echo csrf_field(); ?>
|
||||
<div class="card-body">
|
||||
<?php
|
||||
if($edit)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once 'config/config.php';
|
||||
require_once 'includes/bootstrap.php';
|
||||
require_once BASE_PATH . '/includes/auth_validate.php';
|
||||
require_once BASE_PATH . '/lib/DynamicQrcode/DynamicQrcode.php';
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<span class="input-group-text"><i class="fa fa-lock"></i></span>
|
||||
</div>
|
||||
|
||||
<input type="password" name="password" placeholder="Password" class="form-control" required="required" autocomplete="off">
|
||||
<input type="password" name="password" placeholder="<?php echo ($edit) ? 'Leave blank to keep current password' : 'Password'; ?>" class="form-control" <?php echo ($edit) ? '' : 'required="required"'; ?> minlength="10" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<form class="form" action="static_qrcode.php?type=2fa" method="post" id="static_form" enctype="multipart/form-data">
|
||||
<?php echo csrf_field(); ?>
|
||||
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
||||
<!-- Input forms -->
|
||||
<div class="col-sm-12 mb-2">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<form class="form" action="static_qrcode.php?type=bitcoin" method="post" id="static_form" enctype="multipart/form-data">
|
||||
<?php echo csrf_field(); ?>
|
||||
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
||||
<!-- Input forms -->
|
||||
<div class="col-sm-12 mb-2">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<form class="form" action="static_qrcode.php?type=bookmark" method="post" id="static_form" enctype="multipart/form-data">
|
||||
<?php echo csrf_field(); ?>
|
||||
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
||||
<!-- Input forms -->
|
||||
<div class="col-sm-12 mb-2">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<form class="form" action="static_qrcode.php?type=email" method="post" id="static_form" enctype="multipart/form-data">
|
||||
<?php echo csrf_field(); ?>
|
||||
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
||||
<!-- Input forms -->
|
||||
<div class="col-sm-12 mb-2">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<form class="form" action="static_qrcode.php?type=event" method="post" id="static_form" enctype="multipart/form-data">
|
||||
<?php echo csrf_field(); ?>
|
||||
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
||||
<!-- Input forms -->
|
||||
<div class="col-sm-4">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<form class="form" action="static_qrcode.php?type=location" method="post" id="static_form" enctype="multipart/form-data">
|
||||
<?php echo csrf_field(); ?>
|
||||
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
||||
<!-- Input forms -->
|
||||
<div class="col-sm-4">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<form class="form" action="static_qrcode.php?type=paypal" method="post" id="static_form" enctype="multipart/form-data">
|
||||
<?php echo csrf_field(); ?>
|
||||
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
||||
<!-- Input forms -->
|
||||
<div class="col-sm-12 mb-2">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<form class="form" action="static_qrcode.php?type=phone" method="post" id="static_form" enctype="multipart/form-data">
|
||||
<?php echo csrf_field(); ?>
|
||||
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
||||
<!-- Input forms -->
|
||||
<div class="col-sm-12 mb-2">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<form class="form" action="static_qrcode.php?type=skype" method="post" id="static_form" enctype="multipart/form-data">
|
||||
<?php echo csrf_field(); ?>
|
||||
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
||||
<!-- Input forms -->
|
||||
<div class="col-sm-4">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<form class="form" action="static_qrcode.php?type=sms" method="post" id="static_form" enctype="multipart/form-data">
|
||||
<?php echo csrf_field(); ?>
|
||||
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
||||
<!-- Input forms -->
|
||||
<div class="col-sm-12 mb-2">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<form class="form" action="static_qrcode.php?type=text" method="post" id="static_form" enctype="multipart/form-data">
|
||||
<?php echo csrf_field(); ?>
|
||||
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
||||
<!-- Input forms -->
|
||||
<div class="col-sm-6">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<form class="form" action="static_qrcode.php?type=vcard" method="post" id="static_form" enctype="multipart/form-data">
|
||||
<?php echo csrf_field(); ?>
|
||||
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
||||
<!-- Input forms -->
|
||||
<!-- First row -->
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<form class="form" action="static_qrcode.php?type=whatsapp" method="post" id="static_form" enctype="multipart/form-data">
|
||||
<?php echo csrf_field(); ?>
|
||||
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
||||
<!-- Input forms -->
|
||||
<div class="col-sm-12 mb-2">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<form class="form" action="static_qrcode.php?type=wifi" method="post" id="static_form" enctype="multipart/form-data">
|
||||
<?php echo csrf_field(); ?>
|
||||
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
||||
<!-- Input forms -->
|
||||
<div class="col-sm-12 mb-2">
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<div id="err-msg"></div>
|
||||
<div class="bulk-action-wrapper">
|
||||
<form id="bulk-action" action="bulk_action.php" method="POST">
|
||||
<?php echo csrf_field(); ?>
|
||||
<div class="col-sm-12 mb-2" style="margin-left: 10px">
|
||||
<div class="row">
|
||||
<div class="col-5 col-md-2">
|
||||
@@ -100,6 +101,7 @@
|
||||
<div class="modal fade" id="delete-modal" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<form action="dynamic_qrcode.php" method="POST">
|
||||
<?php echo csrf_field(); ?>
|
||||
<!-- Modal content -->
|
||||
|
||||
<div class="modal-content">
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<div id="err-msg"></div>
|
||||
<div class="bulk-action-wrapper">
|
||||
<form id="bulk-action" action="bulk_action.php" method="POST">
|
||||
<?php echo csrf_field(); ?>
|
||||
<div class="col-sm-12 mb-2" style="margin-left: 10px">
|
||||
<div class="row">
|
||||
<div class="col-5 col-md-2">
|
||||
@@ -96,6 +97,7 @@
|
||||
<div class="modal fade" id="delete-modal" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<form action="static_qrcode.php" method="POST">
|
||||
<?php echo csrf_field(); ?>
|
||||
<!-- Modal content -->
|
||||
|
||||
<div class="modal-content">
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
<div class="modal fade" id="delete-modal" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<form action="user.php" method="POST">
|
||||
<?php echo csrf_field(); ?>
|
||||
<!-- Modal content -->
|
||||
|
||||
<div class="modal-content">
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
/**
|
||||
* Centrale bootstrap voor elke entrypoint: config laden, sessie starten met
|
||||
* verharde instellingen, sessie-timeout en verplichte wachtwoordwijziging afdwingen.
|
||||
*
|
||||
* Vervangt de losse "session_start(); require_once 'config/config.php';" aanroepen.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../config/config.php';
|
||||
require_once __DIR__ . '/security.php';
|
||||
|
||||
qr_session_start();
|
||||
qr_enforce_session_timeout();
|
||||
qr_enforce_password_change();
|
||||
@@ -1,6 +1,7 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||
<meta name="csrf-token" content="<?php echo csrf_token(); ?>">
|
||||
|
||||
<!-- Font Awesome Icons -->
|
||||
<link rel="stylesheet" href="plugins/fontawesome-free/css/all.min.css">
|
||||
|
||||
@@ -16,14 +16,10 @@
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-lg dropdown-menu-right">
|
||||
<div class="dropdown-divider"></div>
|
||||
<!--<a href="#" class="dropdown-item">
|
||||
<i class="fas fa-user"></i> Profile
|
||||
<a href="./change_password.php" class="dropdown-item">
|
||||
<i class="fas fa-key"></i> Change password
|
||||
</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a href="#" class="dropdown-item">
|
||||
<i class="fa fa-cog"></i> Settings
|
||||
</a>-->
|
||||
<div class="dropdown-divider"></div>
|
||||
<a href="./logout.php" class="dropdown-item">
|
||||
<i class="fas fa-sign-out-alt"></i> Logout
|
||||
</a>
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
/**
|
||||
* Fase 1 security hardening: sessiebeheer, CSRF, rate limiting, audit log.
|
||||
* Wordt geladen via includes/bootstrap.php, dat als eerste in elke entrypoint hoort te staan.
|
||||
*/
|
||||
|
||||
define('SESSION_IDLE_TIMEOUT', 30 * 60); // 30 minuten inactiviteit -> uitloggen
|
||||
define('LOGIN_MAX_ATTEMPTS', 5);
|
||||
define('LOGIN_LOCKOUT_WINDOW', 15 * 60); // 15 minuten
|
||||
|
||||
/**
|
||||
* Start de sessie met verharde cookie-instellingen. Moet vóór elke output aangeroepen worden.
|
||||
*/
|
||||
function qr_session_start() {
|
||||
if (session_status() === PHP_SESSION_ACTIVE) {
|
||||
return;
|
||||
}
|
||||
|
||||
$is_https = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off';
|
||||
|
||||
ini_set('session.gc_maxlifetime', (string) SESSION_IDLE_TIMEOUT);
|
||||
ini_set('session.use_strict_mode', '1');
|
||||
|
||||
session_set_cookie_params([
|
||||
'lifetime' => 0,
|
||||
'path' => '/',
|
||||
'domain' => '',
|
||||
'secure' => $is_https,
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
]);
|
||||
|
||||
session_start();
|
||||
}
|
||||
|
||||
function qr_client_ip() {
|
||||
return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Logt de gebruiker uit als de sessie te lang inactief is geweest.
|
||||
*/
|
||||
function qr_enforce_session_timeout() {
|
||||
if (empty($_SESSION['user_logged_in'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$now = time();
|
||||
|
||||
if (isset($_SESSION['last_activity']) && ($now - $_SESSION['last_activity']) > SESSION_IDLE_TIMEOUT) {
|
||||
$_SESSION = [];
|
||||
$_SESSION['login_failure'] = 'Je sessie is verlopen wegens inactiviteit. Log opnieuw in.';
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$_SESSION['last_activity'] = $now;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stuurt ingelogde gebruikers met een verplichte wachtwoordwijziging naar change_password.php,
|
||||
* behalve op de wijzigingspagina en logout zelf.
|
||||
*/
|
||||
function qr_enforce_password_change() {
|
||||
if (empty($_SESSION['user_logged_in']) || empty($_SESSION['must_change_password'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$current_script = basename(parse_url($_SERVER['SCRIPT_NAME'], PHP_URL_PATH));
|
||||
$exempt = ['change_password.php', 'logout.php'];
|
||||
|
||||
if (in_array($current_script, $exempt, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
header('Location: change_password.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* CSRF-bescherming
|
||||
*/
|
||||
function csrf_token() {
|
||||
if (empty($_SESSION['csrf_token'])) {
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
return $_SESSION['csrf_token'];
|
||||
}
|
||||
|
||||
function csrf_field() {
|
||||
return '<input type="hidden" name="csrf_token" value="' . htmlspecialchars(csrf_token(), ENT_QUOTES, 'UTF-8') . '">';
|
||||
}
|
||||
|
||||
function csrf_is_valid($token) {
|
||||
return isset($_SESSION['csrf_token']) && is_string($token) && hash_equals($_SESSION['csrf_token'], $token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Voor klassieke form-POSTs: verwacht een verborgen veld "csrf_token".
|
||||
*/
|
||||
function csrf_verify_or_die() {
|
||||
if (!csrf_is_valid($_POST['csrf_token'] ?? '')) {
|
||||
http_response_code(403);
|
||||
exit('403 Forbidden: invalid or missing CSRF token.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Voor JSON/AJAX-endpoints (bv. bulk_action.php): verwacht header X-CSRF-Token.
|
||||
*/
|
||||
function csrf_verify_header_or_die() {
|
||||
$token = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
|
||||
if (!csrf_is_valid($token)) {
|
||||
http_response_code(403);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['data' => 'Invalid or missing CSRF token', 'status' => 403]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rate limiting op login
|
||||
*/
|
||||
function qr_record_login_attempt($username, $success) {
|
||||
$db = getDbInstance();
|
||||
$db->insert('login_attempts', [
|
||||
'username' => $username,
|
||||
'ip_address' => qr_client_ip(),
|
||||
'success' => $success ? 1 : 0,
|
||||
'attempted_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
}
|
||||
|
||||
function qr_is_login_locked_out($username) {
|
||||
$db = getDbInstance();
|
||||
$window_start = date('Y-m-d H:i:s', time() - LOGIN_LOCKOUT_WINDOW);
|
||||
|
||||
$db->where('username', $username);
|
||||
$db->where('success', 0);
|
||||
$db->where('attempted_at', $window_start, '>=');
|
||||
$count = $db->getValue('login_attempts', 'count(*)');
|
||||
|
||||
return $count !== null && $count >= LOGIN_MAX_ATTEMPTS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Audit log
|
||||
*/
|
||||
function audit_log($action, $target_type = null, $target_id = null) {
|
||||
$db = getDbInstance();
|
||||
$db->insert('audit_log', [
|
||||
'user_id' => $_SESSION['user_id'] ?? null,
|
||||
'username' => $_SESSION['username'] ?? null,
|
||||
'action' => $action,
|
||||
'target_type' => $target_type,
|
||||
'target_id' => $target_id !== null ? (string) $target_id : null,
|
||||
'ip_address' => qr_client_ip(),
|
||||
'user_agent' => substr($_SERVER['HTTP_USER_AGENT'] ?? '', 0, 255),
|
||||
'created_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
}
|
||||
+1
-11
@@ -1,15 +1,5 @@
|
||||
<?php
|
||||
//Use httponly flag
|
||||
ini_set('session.cookie_httponly', 1);
|
||||
|
||||
//Use only cookies
|
||||
ini_set('session.use_only_cookies', 1);
|
||||
|
||||
//Use secure flag
|
||||
ini_set('session.cookie_secure', 1);
|
||||
|
||||
session_start();
|
||||
require_once './config/config.php';
|
||||
require_once 'includes/bootstrap.php';
|
||||
require_once 'includes/auth_validate.php';
|
||||
|
||||
$db = getDbInstance();
|
||||
|
||||
@@ -56,6 +56,8 @@ class DynamicQrcode {
|
||||
* We save into db the url of qrcode image
|
||||
*/
|
||||
public function addQrcode($input_data) {
|
||||
$this->validateLink($input_data['link'] ?? '');
|
||||
|
||||
if($input_data['id_owner'] != "")
|
||||
$data_to_db['id_owner'] = $input_data['id_owner'];
|
||||
else
|
||||
@@ -79,6 +81,8 @@ class DynamicQrcode {
|
||||
*
|
||||
*/
|
||||
public function editQrcode($input_data) {
|
||||
$this->validateLink($input_data['link'] ?? '');
|
||||
|
||||
if($input_data['id_owner'] != "")
|
||||
$data_to_db['id_owner'] = $input_data['id_owner'];
|
||||
else
|
||||
@@ -117,6 +121,17 @@ class DynamicQrcode {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Server-side validatie van de redirect-link (verplicht, max. 500 tekens per kolomdefinitie).
|
||||
*/
|
||||
private function validateLink($link) {
|
||||
$link = trim((string) $link);
|
||||
|
||||
if ($link === '' || strlen($link) > 500) {
|
||||
$this->failure('Link is required and must be at most 500 characters.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flash message Failure process
|
||||
*/
|
||||
|
||||
@@ -20,6 +20,8 @@ class Qrcode {
|
||||
private string $table;
|
||||
private string $redirect_url;
|
||||
|
||||
const ALLOWED_FORMATS = ['png', 'gif', 'jpeg', 'jpg', 'svg', 'svgbw', 'eps'];
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -43,6 +45,33 @@ class Qrcode {
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Voorkomt path traversal / arbitrary file write via een gemanipuleerde bestandsnaam.
|
||||
*/
|
||||
private function sanitizeFilename($filename) {
|
||||
$filename = trim((string) $filename);
|
||||
|
||||
if ($filename === '' || strlen($filename) > 45) {
|
||||
$this->failure('Filename must be between 1 and 45 characters.');
|
||||
}
|
||||
|
||||
if (preg_match('#[\\/\\\\]#', $filename) || strpos($filename, '..') !== false || strpos($filename, "\0") !== false) {
|
||||
$this->failure('Filename cannot contain path separators.');
|
||||
}
|
||||
|
||||
return $filename;
|
||||
}
|
||||
|
||||
private function validateFormat($format) {
|
||||
$format = strtolower((string) $format);
|
||||
|
||||
if (!in_array($format, self::ALLOWED_FORMATS, true)) {
|
||||
$this->failure('Invalid qr code format.');
|
||||
}
|
||||
|
||||
return $format;
|
||||
}
|
||||
|
||||
public function getQrcode($id) {
|
||||
$db = getDbInstance();
|
||||
|
||||
@@ -94,6 +123,9 @@ class Qrcode {
|
||||
public function addQrcode($input_data, $data_to_db, $data_to_qrcode) {
|
||||
$options = $this->setOptions($input_data);
|
||||
|
||||
$data_to_db['filename'] = $this->sanitizeFilename($data_to_db['filename']);
|
||||
$data_to_db['format'] = $this->validateFormat($data_to_db['format']);
|
||||
|
||||
$outputInterface = QRGdImagePNG::class;
|
||||
$imageFormat = strtolower($data_to_db['format']);
|
||||
$fileExt = $imageFormat;
|
||||
@@ -305,21 +337,23 @@ class Qrcode {
|
||||
$this->failure('You cannot create a new qr code with an existing name on the server!');
|
||||
|
||||
if ($last_id){
|
||||
audit_log('qrcode_created', $this->table, $last_id);
|
||||
$this->success('Qr code added successfully!');
|
||||
}
|
||||
else {
|
||||
$this->failure('Insert failed: ' . $db->getLastError());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Edit qr code
|
||||
*
|
||||
*
|
||||
*/
|
||||
public function editQrcode($input_data, $data_to_db) {
|
||||
$db = getDbInstance();
|
||||
$old_qrcode = $this->getQrcode($input_data["id"]);
|
||||
|
||||
$data_to_db['filename'] = $this->sanitizeFilename($data_to_db['filename']);
|
||||
$data_to_db['qrcode'] = $data_to_db['filename'].'.'.$old_qrcode["format"];
|
||||
|
||||
if(!file_exists(SAVED_QRCODE_DIRECTORY.$data_to_db['filename'].'.'.$old_qrcode["format"]) || $data_to_db['filename'] == $input_data["old_filename"]){
|
||||
@@ -337,6 +371,7 @@ class Qrcode {
|
||||
$this->failure('You cannot edit a qr code with an existing name on the server!');
|
||||
|
||||
if ($stat){
|
||||
audit_log('qrcode_updated', $this->table, $input_data['id']);
|
||||
$this->success('Qr code updated successfully!');
|
||||
}
|
||||
else {
|
||||
@@ -344,10 +379,10 @@ class Qrcode {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Delete qr code
|
||||
*
|
||||
*
|
||||
*/
|
||||
public function deleteQrcode($id, $async = false) {
|
||||
$db = getDbInstance();
|
||||
@@ -356,7 +391,11 @@ class Qrcode {
|
||||
|
||||
$db->where('id', $id);
|
||||
$status = $db->delete($this->table);
|
||||
|
||||
|
||||
if ($status) {
|
||||
audit_log('qrcode_deleted', $this->table, $id);
|
||||
}
|
||||
|
||||
try{
|
||||
unlink(SAVED_QRCODE_DIRECTORY.$qrcode["filename"].'.'.$qrcode["format"]);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ class Qrcode {
|
||||
private string $table;
|
||||
private string $redirect_url;
|
||||
|
||||
const ALLOWED_FORMATS = ['png', 'gif', 'jpeg', 'jpg', 'svg', 'eps'];
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -28,6 +30,33 @@ class Qrcode {
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Voorkomt path traversal / arbitrary file write via een gemanipuleerde bestandsnaam.
|
||||
*/
|
||||
private function sanitizeFilename($filename) {
|
||||
$filename = trim((string) $filename);
|
||||
|
||||
if ($filename === '' || strlen($filename) > 45) {
|
||||
$this->failure('Filename must be between 1 and 45 characters.');
|
||||
}
|
||||
|
||||
if (preg_match('#[\\/\\\\]#', $filename) || strpos($filename, '..') !== false || strpos($filename, "\0") !== false) {
|
||||
$this->failure('Filename cannot contain path separators.');
|
||||
}
|
||||
|
||||
return $filename;
|
||||
}
|
||||
|
||||
private function validateFormat($format) {
|
||||
$format = strtolower((string) $format);
|
||||
|
||||
if (!in_array($format, self::ALLOWED_FORMATS, true)) {
|
||||
$this->failure('Invalid qr code format.');
|
||||
}
|
||||
|
||||
return $format;
|
||||
}
|
||||
|
||||
public function getQrcode($id) {
|
||||
$db = getDbInstance();
|
||||
|
||||
@@ -79,6 +108,9 @@ class Qrcode {
|
||||
public function addQrcode($input_data, $data_to_db, $data_to_qrcode) {
|
||||
$options = $this->setOptions($input_data);
|
||||
|
||||
$data_to_db['filename'] = $this->sanitizeFilename($data_to_db['filename']);
|
||||
$data_to_db['format'] = $this->validateFormat($data_to_db['format']);
|
||||
|
||||
if(!file_exists(SAVED_QRCODE_DIRECTORY.$data_to_db['filename'].'.'.$data_to_db['format'])){
|
||||
$url =
|
||||
'https://api.qrserver.com/v1/create-qr-code/?data='.
|
||||
@@ -111,6 +143,7 @@ class Qrcode {
|
||||
$this->failure('You cannot create a new qr code with an existing name on the server!');
|
||||
|
||||
if ($last_id){
|
||||
audit_log('qrcode_created', $this->table, $last_id);
|
||||
$this->success('Qr code added successfully!');
|
||||
}
|
||||
else {
|
||||
@@ -126,6 +159,7 @@ class Qrcode {
|
||||
$db = getDbInstance();
|
||||
$old_qrcode = $this->getQrcode($input_data["id"]);
|
||||
|
||||
$data_to_db['filename'] = $this->sanitizeFilename($data_to_db['filename']);
|
||||
$data_to_db['qrcode'] = $data_to_db['filename'].'.'.$old_qrcode["format"];
|
||||
|
||||
if(!file_exists(SAVED_QRCODE_DIRECTORY.$data_to_db['filename'].'.'.$old_qrcode["format"]) || $data_to_db['filename'] == $input_data["old_filename"]){
|
||||
@@ -143,6 +177,7 @@ class Qrcode {
|
||||
$this->failure('You cannot edit a qr code with an existing name on the server!');
|
||||
|
||||
if ($stat){
|
||||
audit_log('qrcode_updated', $this->table, $input_data['id']);
|
||||
$this->success('Qr code updated successfully!');
|
||||
}
|
||||
else {
|
||||
@@ -162,7 +197,11 @@ class Qrcode {
|
||||
|
||||
$db->where('id', $id);
|
||||
$status = $db->delete($this->table);
|
||||
|
||||
|
||||
if ($status) {
|
||||
audit_log('qrcode_deleted', $this->table, $id);
|
||||
}
|
||||
|
||||
try{
|
||||
unlink(SAVED_QRCODE_DIRECTORY.$qrcode["filename"].'.'.$qrcode["format"]);
|
||||
}
|
||||
|
||||
+59
-11
@@ -3,6 +3,8 @@ require_once 'config/config.php';
|
||||
|
||||
class Users
|
||||
{
|
||||
const ALLOWED_TYPES = ['super', 'admin'];
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -10,6 +12,25 @@ class Users
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Server-side validatie van username/type. Geeft een foutmelding terug (string) of null als geldig.
|
||||
*/
|
||||
private function validateUsernameAndType($username, $type) {
|
||||
if (!is_string($username) || strlen($username) < 3 || strlen($username) > 50) {
|
||||
return 'Username must be between 3 and 50 characters.';
|
||||
}
|
||||
|
||||
if (!preg_match('/^[a-zA-Z0-9._-]+$/', $username)) {
|
||||
return 'Username may only contain letters, numbers, dots, underscores and hyphens.';
|
||||
}
|
||||
|
||||
if (!in_array($type, self::ALLOWED_TYPES, true)) {
|
||||
return 'Invalid user type.';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -54,6 +75,15 @@ class Users
|
||||
public function addUser($input_data) {
|
||||
$db = getDbInstance();
|
||||
|
||||
$validation_error = $this->validateUsernameAndType($input_data['username'] ?? '', $input_data['type'] ?? '');
|
||||
if ($validation_error !== null) {
|
||||
$this->failure($validation_error, 'Location: user.php');
|
||||
}
|
||||
|
||||
if (!isset($input_data['password']) || strlen($input_data['password']) < 10) {
|
||||
$this->failure('Password must be at least 10 characters long.', 'Location: user.php');
|
||||
}
|
||||
|
||||
$data_to_db["username"] = $input_data["username"];
|
||||
$data_to_db['password'] = password_hash($input_data['password'], PASSWORD_DEFAULT);
|
||||
$data_to_db["type"] = $input_data["type"];
|
||||
@@ -66,8 +96,10 @@ class Users
|
||||
|
||||
$last_id = $db->insert('users', $data_to_db);
|
||||
|
||||
if ($last_id)
|
||||
if ($last_id) {
|
||||
audit_log('user_created', 'user', $last_id);
|
||||
$this->success('User added successfully');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,28 +109,43 @@ class Users
|
||||
public function editUser($input_data) {
|
||||
$db = getDbInstance();
|
||||
|
||||
$query_string = http_build_query(array(
|
||||
'id' => $input_data["id"],
|
||||
'edit' => "true",
|
||||
));
|
||||
|
||||
$validation_error = $this->validateUsernameAndType($input_data['username'] ?? '', $input_data['type'] ?? '');
|
||||
if ($validation_error !== null) {
|
||||
$this->failure($validation_error, 'Location: user.php?'.$query_string);
|
||||
}
|
||||
|
||||
if (isset($input_data['password']) && strlen($input_data['password']) > 0 && strlen($input_data['password']) < 10) {
|
||||
$this->failure('Password must be at least 10 characters long.', 'Location: user.php?'.$query_string);
|
||||
}
|
||||
|
||||
$db->where('username', $input_data['username']);
|
||||
$db->where('id', $input_data["id"], '!=');
|
||||
$row = $db->getOne('users');
|
||||
|
||||
if (!empty($row['username'])) {
|
||||
$query_string = http_build_query(array(
|
||||
'id' => $input_data["id"],
|
||||
'edit' => "true",
|
||||
));
|
||||
$this->failure('Username already exists', 'Location: user.php?'.$query_string);
|
||||
}
|
||||
|
||||
$data_to_db["username"] = $input_data["username"];
|
||||
$data_to_db['password'] = password_hash($input_data['password'], PASSWORD_DEFAULT);
|
||||
$data_to_db["type"] = $input_data["type"];
|
||||
|
||||
// Alleen wachtwoord overschrijven als er een nieuwe waarde is opgegeven.
|
||||
if (!empty($input_data['password'])) {
|
||||
$data_to_db['password'] = password_hash($input_data['password'], PASSWORD_DEFAULT);
|
||||
}
|
||||
|
||||
$db->where('id', $input_data["id"]);
|
||||
$stat = $db->update('users', $data_to_db);
|
||||
|
||||
if ($stat)
|
||||
|
||||
if ($stat) {
|
||||
audit_log('user_updated', 'user', $input_data['id']);
|
||||
$this->success('User updated successfully!');
|
||||
else
|
||||
} else
|
||||
$this->failure('Failed to update User: ' . $db->getLastError());
|
||||
}
|
||||
|
||||
@@ -116,9 +163,10 @@ class Users
|
||||
$db->where('id', $id);
|
||||
$stat = $db->delete('users');
|
||||
|
||||
if ($stat)
|
||||
if ($stat) {
|
||||
audit_log('user_deleted', 'user', $id);
|
||||
$this->info('User deleted successfully!');
|
||||
else
|
||||
} else
|
||||
$this->failure('Unable to delete user');
|
||||
}
|
||||
|
||||
|
||||
+11
-2
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once 'config/config.php';
|
||||
require_once 'includes/bootstrap.php';
|
||||
$token = bin2hex(openssl_random_pseudo_bytes(16));
|
||||
|
||||
// If User has already logged in, redirect to dashboard page.
|
||||
if (isset($_SESSION['user_logged_in']) && $_SESSION['user_logged_in'] === TRUE)
|
||||
{
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
// If user has previously selected "remember me option":
|
||||
@@ -33,9 +33,17 @@ if (isset($_COOKIE['series_id']) && isset($_COOKIE['remember_token']))
|
||||
exit;
|
||||
}
|
||||
|
||||
session_regenerate_id(true);
|
||||
|
||||
$_SESSION['user_logged_in'] = TRUE;
|
||||
$_SESSION['user_id'] = $row['id'];
|
||||
$_SESSION['type'] = $row['type'];
|
||||
$_SESSION['username'] = $row['username'];
|
||||
$_SESSION['must_change_password'] = !empty($row['must_change_password']);
|
||||
$_SESSION['last_activity'] = time();
|
||||
|
||||
audit_log('login_success_remember');
|
||||
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
}
|
||||
@@ -71,6 +79,7 @@ if (isset($_COOKIE['series_id']) && isset($_COOKIE['remember_token']))
|
||||
<p class="login-box-msg">Sign in to start your session</p>
|
||||
|
||||
<form method="POST" action="authenticate.php">
|
||||
<?php echo csrf_field(); ?>
|
||||
<div class="input-group mb-3">
|
||||
<input type="text" name="username" class="form-control" placeholder="Username" required="required">
|
||||
<div class="input-group-append">
|
||||
|
||||
+14
-4
@@ -1,10 +1,20 @@
|
||||
<?php
|
||||
require_once './config/config.php';
|
||||
session_start();
|
||||
require_once 'includes/bootstrap.php';
|
||||
|
||||
if (!empty($_SESSION['user_logged_in'])) {
|
||||
audit_log('logout');
|
||||
}
|
||||
|
||||
$_SESSION = [];
|
||||
|
||||
if (ini_get('session.use_cookies')) {
|
||||
$params = session_get_cookie_params();
|
||||
setcookie(session_name(), '', time() - 42000, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
|
||||
}
|
||||
|
||||
session_destroy();
|
||||
|
||||
|
||||
if(isset($_COOKIE['series_id']) && isset($_COOKIE['remember_token'])){
|
||||
if (isset($_COOKIE['series_id']) && isset($_COOKIE['remember_token'])) {
|
||||
clearAuthCookie();
|
||||
}
|
||||
header('Location:index.php');
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once 'config/config.php';
|
||||
require_once 'includes/bootstrap.php';
|
||||
require_once BASE_PATH.'/includes/auth_validate.php';
|
||||
require_once BASE_PATH . '/lib/StaticQrcode/StaticQrcode.php';
|
||||
|
||||
$static_qrcode_instance = new StaticQrcode();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
csrf_verify_or_die();
|
||||
}
|
||||
|
||||
$edit = false;
|
||||
if($_SERVER["REQUEST_METHOD"] === "GET" && isset($_GET["edit"]) && $_GET["edit"] == "true" && isset($_GET["id"])) {
|
||||
$edit = true;
|
||||
@@ -115,6 +118,7 @@ if($_SERVER["REQUEST_METHOD"] === "POST" && !isset($_POST["edit"])) {
|
||||
</div>
|
||||
<?php if($edit) {?>
|
||||
<form class="form" action="" method="post" id="static_form" enctype="multipart/form-data">
|
||||
<?php echo csrf_field(); ?>
|
||||
<div class="card-body">
|
||||
<?php include BASE_PATH . '/forms/form_static_edit.php';?>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once 'config/config.php';
|
||||
require_once 'includes/bootstrap.php';
|
||||
require_once BASE_PATH . '/includes/auth_validate.php';
|
||||
require_once BASE_PATH . '/lib/StaticQrcode/StaticQrcode.php';
|
||||
|
||||
|
||||
+5
-2
@@ -1,6 +1,5 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once 'config/config.php';
|
||||
require_once 'includes/bootstrap.php';
|
||||
require_once BASE_PATH . '/includes/auth_validate.php';
|
||||
require_once BASE_PATH . '/lib/Users/Users.php';
|
||||
|
||||
@@ -9,6 +8,9 @@ $user_instance = new Users();
|
||||
if ($_SESSION['type'] !== 'super')
|
||||
$user_instance->failure('Only a "super admin" account can access the admin listing page', 'Location: index.php');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
csrf_verify_or_die();
|
||||
}
|
||||
|
||||
$edit = false;
|
||||
if($_SERVER["REQUEST_METHOD"] === "GET" && isset($_GET["edit"]) && $_GET["edit"] == "true" && isset($_GET["id"])) {
|
||||
@@ -83,6 +85,7 @@ if($_SERVER["REQUEST_METHOD"] === "POST" && !isset($_POST["edit"])) {
|
||||
<h3 class="card-title">Enter the requested data</h3>
|
||||
</div>
|
||||
<form class="well form-horizontal" action="" method="post" id="contact_form" enctype="multipart/form-data">
|
||||
<?php echo csrf_field(); ?>
|
||||
<div class="card-body">
|
||||
<?php include BASE_PATH . '/forms/form_users.php'; ?>
|
||||
</div>
|
||||
|
||||
+1
-2
@@ -1,6 +1,5 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once 'config/config.php';
|
||||
require_once 'includes/bootstrap.php';
|
||||
require_once BASE_PATH . '/includes/auth_validate.php';
|
||||
require_once BASE_PATH . '/lib/Users/Users.php';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user