Phase 1: Implement CSRF protection, input validation, and rate limiting
Major security improvements: - Added CSRF token generation, validation, and cleanup functions - Implemented comprehensive input validators (email, phone, name, date, amount, ID, file uploads) - Added rate limiting with login attempt tracking and account lockout (5 failures = 15 min lockout) - Implemented session fixation protection with session_regenerate_id() and 30-min timeout - Fixed SQL injection in getResultFromTable() with whitelisted columns/tables - Added audit logging for security events - Applied CSRF validation to all 7 process_*.php files - Applied input validation to critical endpoints (login, registration, bookings, application) - Created database migration for login_attempts, audit_log tables and locked_until column Modified files: - functions.php: +500 lines of security functions - validate_login.php: Added CSRF, rate limiting, session hardening - register_user.php: Added CSRF, input validation, registration rate limiting - process_*.php (7 files): Added CSRF token validation - Created migration: 001_phase1_security_schema.sql Next steps: Add CSRF tokens to form templates, harden file uploads, create testing checklist
This commit is contained in:
680
DB_existing schema.sql
Normal file
680
DB_existing schema.sql
Normal file
@@ -0,0 +1,680 @@
|
||||
-- phpMyAdmin SQL Dump
|
||||
-- version 5.2.2
|
||||
-- https://www.phpmyadmin.net/
|
||||
--
|
||||
-- Host: db
|
||||
-- Generation Time: Dec 02, 2025 at 07:32 PM
|
||||
-- Server version: 8.0.41
|
||||
-- PHP Version: 8.2.27
|
||||
|
||||
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
|
||||
START TRANSACTION;
|
||||
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 utf8mb4 */;
|
||||
|
||||
--
|
||||
-- Database: `4wdcsa`
|
||||
--
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `bar_items`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `bar_items`;
|
||||
CREATE TABLE `bar_items` (
|
||||
`item_id` int NOT NULL,
|
||||
`price` decimal(10,2) DEFAULT NULL,
|
||||
`description` varchar(64) DEFAULT NULL,
|
||||
`image` varchar(255) DEFAULT NULL,
|
||||
`qty` int DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `bar_tabs`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `bar_tabs`;
|
||||
CREATE TABLE `bar_tabs` (
|
||||
`tab_id` int NOT NULL,
|
||||
`user_id` int DEFAULT NULL,
|
||||
`image` varchar(255) DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `bar_transactions`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `bar_transactions`;
|
||||
CREATE TABLE `bar_transactions` (
|
||||
`transaction_id` int NOT NULL,
|
||||
`user_id` int DEFAULT NULL,
|
||||
`item_price` decimal(10,2) DEFAULT NULL,
|
||||
`item_name` varchar(64) DEFAULT NULL,
|
||||
`eft_id` varchar(255) DEFAULT NULL,
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`item_id` int DEFAULT NULL,
|
||||
`tab_id` int DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `blacklist`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `blacklist`;
|
||||
CREATE TABLE `blacklist` (
|
||||
`blacklist_id` int NOT NULL,
|
||||
`ip` varchar(255) DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `blogs`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `blogs`;
|
||||
CREATE TABLE `blogs` (
|
||||
`blog_id` int NOT NULL,
|
||||
`title` varchar(255) DEFAULT NULL,
|
||||
`date` date DEFAULT NULL,
|
||||
`category` varchar(255) DEFAULT NULL,
|
||||
`description` text,
|
||||
`image` varchar(255) DEFAULT NULL,
|
||||
`author` int DEFAULT NULL,
|
||||
`link` varchar(255) DEFAULT NULL,
|
||||
`members_only` tinyint(1) NOT NULL DEFAULT '1',
|
||||
`content` text,
|
||||
`status` enum('draft','published','deleted') CHARACTER SET latin1 COLLATE latin1_swedish_ci DEFAULT 'draft'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `bookings`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `bookings`;
|
||||
CREATE TABLE `bookings` (
|
||||
`booking_id` int NOT NULL,
|
||||
`booking_type` varchar(255) COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`user_id` int NOT NULL,
|
||||
`from_date` date DEFAULT NULL,
|
||||
`to_date` date DEFAULT NULL,
|
||||
`num_vehicles` int NOT NULL DEFAULT '1',
|
||||
`num_adults` int NOT NULL DEFAULT '0',
|
||||
`num_children` int NOT NULL DEFAULT '0',
|
||||
`add_firewood` tinyint(1) DEFAULT '0',
|
||||
`total_amount` decimal(10,2) DEFAULT NULL,
|
||||
`discount_amount` decimal(10,2) NOT NULL DEFAULT '0.00',
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`status` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`payment_id` varchar(255) COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`trip_id` int DEFAULT NULL,
|
||||
`radio` tinyint(1) DEFAULT '0',
|
||||
`course_id` int DEFAULT NULL,
|
||||
`course_non_members` int DEFAULT '0',
|
||||
`eft_id` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`accept_indemnity` tinyint(1) DEFAULT '0',
|
||||
`num_pensioners` int DEFAULT '0',
|
||||
`notes` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `campsites`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `campsites`;
|
||||
CREATE TABLE `campsites` (
|
||||
`id` int NOT NULL,
|
||||
`name` varchar(255) NOT NULL,
|
||||
`description` text,
|
||||
`latitude` float(10,6) NOT NULL,
|
||||
`longitude` float(10,6) NOT NULL,
|
||||
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`website` varchar(255) DEFAULT NULL,
|
||||
`telephone` varchar(50) DEFAULT NULL,
|
||||
`thumbnail` varchar(255) DEFAULT NULL,
|
||||
`user_id` int DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `comments`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `comments`;
|
||||
CREATE TABLE `comments` (
|
||||
`comment_id` int NOT NULL,
|
||||
`page_id` varchar(255) NOT NULL,
|
||||
`user_id` varchar(100) NOT NULL,
|
||||
`comment` text NOT NULL,
|
||||
`created_at` datetime DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `courses`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `courses`;
|
||||
CREATE TABLE `courses` (
|
||||
`course_id` int NOT NULL,
|
||||
`course_type` varchar(255) COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`date` date NOT NULL,
|
||||
`capacity` int NOT NULL,
|
||||
`booked` int NOT NULL,
|
||||
`cost_members` decimal(10,2) NOT NULL,
|
||||
`cost_nonmembers` decimal(10,2) NOT NULL,
|
||||
`instructor` varchar(255) COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`instructor_email` varchar(255) COLLATE utf8mb4_general_ci NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `efts`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `efts`;
|
||||
CREATE TABLE `efts` (
|
||||
`eft_id` varchar(255) NOT NULL,
|
||||
`booking_id` int DEFAULT NULL,
|
||||
`user_id` int NOT NULL,
|
||||
`status` varchar(64) NOT NULL,
|
||||
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`amount` decimal(10,2) NOT NULL,
|
||||
`description` varchar(255) DEFAULT NULL,
|
||||
`membershipfee_id` int DEFAULT NULL,
|
||||
`proof_of_payment` varchar(255) DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `events`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `events`;
|
||||
CREATE TABLE `events` (
|
||||
`event_id` int NOT NULL,
|
||||
`date` date DEFAULT NULL,
|
||||
`time` time DEFAULT NULL,
|
||||
`name` varchar(255) DEFAULT NULL,
|
||||
`image` varchar(255) DEFAULT NULL,
|
||||
`description` text,
|
||||
`feature` varchar(255) DEFAULT NULL,
|
||||
`location` varchar(255) DEFAULT NULL,
|
||||
`type` varchar(255) DEFAULT NULL,
|
||||
`promo` varchar(255) DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `legacy_members`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `legacy_members`;
|
||||
CREATE TABLE `legacy_members` (
|
||||
`legacy_id` varchar(12) NOT NULL,
|
||||
`last_name` varchar(255) DEFAULT NULL,
|
||||
`first_name` varchar(255) DEFAULT NULL,
|
||||
`amount` varchar(12) DEFAULT NULL,
|
||||
`phone_number` varchar(16) DEFAULT NULL,
|
||||
`email` varchar(255) DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `membership_application`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `membership_application`;
|
||||
CREATE TABLE `membership_application` (
|
||||
`application_id` int NOT NULL,
|
||||
`user_id` int NOT NULL,
|
||||
`first_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`last_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`id_number` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`dob` date DEFAULT NULL,
|
||||
`occupation` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`tel_cell` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`spouse_first_name` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`spouse_last_name` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`spouse_id_number` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`spouse_dob` date DEFAULT NULL,
|
||||
`spouse_occupation` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`spouse_tel_cell` varchar(20) COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`spouse_email` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`child_name1` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`child_dob1` date DEFAULT NULL,
|
||||
`child_name2` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`child_dob2` date DEFAULT NULL,
|
||||
`child_name3` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`child_dob3` date DEFAULT NULL,
|
||||
`physical_address` text COLLATE utf8mb4_general_ci,
|
||||
`postal_address` text COLLATE utf8mb4_general_ci,
|
||||
`interests_hobbies` text COLLATE utf8mb4_general_ci,
|
||||
`vehicle_make` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`vehicle_model` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`vehicle_year` varchar(10) COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`vehicle_registration` varchar(20) COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`secondary_vehicle_make` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`secondary_vehicle_model` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`secondary_vehicle_year` varchar(10) COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`secondary_vehicle_registration` varchar(20) COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`accept_indemnity` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`sig` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`code` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `membership_fees`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `membership_fees`;
|
||||
CREATE TABLE `membership_fees` (
|
||||
`fee_id` int NOT NULL,
|
||||
`user_id` int NOT NULL,
|
||||
`payment_amount` decimal(10,2) NOT NULL,
|
||||
`payment_date` date DEFAULT NULL,
|
||||
`payment_status` varchar(255) COLLATE utf8mb4_general_ci DEFAULT 'PENDING',
|
||||
`membership_start_date` date NOT NULL,
|
||||
`membership_end_date` date NOT NULL,
|
||||
`due_date` date DEFAULT NULL,
|
||||
`renewal_reminder_sent` tinyint(1) DEFAULT '0',
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`payment_id` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `password_resets`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `password_resets`;
|
||||
CREATE TABLE `password_resets` (
|
||||
`id` int NOT NULL,
|
||||
`user_id` int NOT NULL,
|
||||
`token` varchar(255) COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`expires_at` datetime NOT NULL,
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `payments`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `payments`;
|
||||
CREATE TABLE `payments` (
|
||||
`payment_id` varchar(255) NOT NULL,
|
||||
`user_id` int NOT NULL,
|
||||
`amount` decimal(10,2) NOT NULL,
|
||||
`status` varchar(255) NOT NULL,
|
||||
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`description` varchar(255) NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `prices`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `prices`;
|
||||
CREATE TABLE `prices` (
|
||||
`price_id` int NOT NULL,
|
||||
`description` varchar(255) DEFAULT NULL,
|
||||
`type` varchar(255) DEFAULT NULL,
|
||||
`amount` decimal(10,2) DEFAULT NULL,
|
||||
`amount_nonmembers` decimal(10,2) DEFAULT NULL,
|
||||
`detail` text
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `trips`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `trips`;
|
||||
CREATE TABLE `trips` (
|
||||
`trip_id` int NOT NULL,
|
||||
`trip_name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`start_date` date NOT NULL,
|
||||
`end_date` date NOT NULL,
|
||||
`short_description` text COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`long_description` text COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`vehicle_capacity` int NOT NULL,
|
||||
`cost_members` decimal(10,2) NOT NULL,
|
||||
`cost_nonmembers` decimal(10,2) NOT NULL,
|
||||
`location` varchar(255) COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`places_booked` int DEFAULT NULL,
|
||||
`booking_fee` decimal(10,2) NOT NULL,
|
||||
`trip_code` varchar(12) COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`published` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`cost_pensioner_member` decimal(10,2) NOT NULL,
|
||||
`cost_pensioner` decimal(10,2) NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `users`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `users`;
|
||||
CREATE TABLE `users` (
|
||||
`user_id` int NOT NULL,
|
||||
`first_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`last_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`email` varchar(255) COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`member` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`date_joined` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`is_verified` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`token` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`phone_number` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`profile_pic` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'assets/images/pp/default.png',
|
||||
`role` enum('user','admin','superadmin','') COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'user',
|
||||
`type` enum('google','credentials') COLLATE utf8mb4_general_ci NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `visitor_logs`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `visitor_logs`;
|
||||
CREATE TABLE `visitor_logs` (
|
||||
`id` int NOT NULL,
|
||||
`ip_address` varchar(45) NOT NULL,
|
||||
`page_url` text NOT NULL,
|
||||
`referrer_url` text,
|
||||
`visit_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`user_id` int DEFAULT NULL,
|
||||
`country` varchar(255) DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
--
|
||||
-- Indexes for dumped tables
|
||||
--
|
||||
|
||||
--
|
||||
-- Indexes for table `bar_items`
|
||||
--
|
||||
ALTER TABLE `bar_items`
|
||||
ADD PRIMARY KEY (`item_id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `bar_tabs`
|
||||
--
|
||||
ALTER TABLE `bar_tabs`
|
||||
ADD PRIMARY KEY (`tab_id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `bar_transactions`
|
||||
--
|
||||
ALTER TABLE `bar_transactions`
|
||||
ADD PRIMARY KEY (`transaction_id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `blacklist`
|
||||
--
|
||||
ALTER TABLE `blacklist`
|
||||
ADD PRIMARY KEY (`blacklist_id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `blogs`
|
||||
--
|
||||
ALTER TABLE `blogs`
|
||||
ADD PRIMARY KEY (`blog_id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `bookings`
|
||||
--
|
||||
ALTER TABLE `bookings`
|
||||
ADD PRIMARY KEY (`booking_id`),
|
||||
ADD KEY `user_id` (`user_id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `campsites`
|
||||
--
|
||||
ALTER TABLE `campsites`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `comments`
|
||||
--
|
||||
ALTER TABLE `comments`
|
||||
ADD PRIMARY KEY (`comment_id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `courses`
|
||||
--
|
||||
ALTER TABLE `courses`
|
||||
ADD PRIMARY KEY (`course_id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `efts`
|
||||
--
|
||||
ALTER TABLE `efts`
|
||||
ADD PRIMARY KEY (`eft_id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `events`
|
||||
--
|
||||
ALTER TABLE `events`
|
||||
ADD PRIMARY KEY (`event_id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `legacy_members`
|
||||
--
|
||||
ALTER TABLE `legacy_members`
|
||||
ADD PRIMARY KEY (`legacy_id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `membership_application`
|
||||
--
|
||||
ALTER TABLE `membership_application`
|
||||
ADD PRIMARY KEY (`application_id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `membership_fees`
|
||||
--
|
||||
ALTER TABLE `membership_fees`
|
||||
ADD PRIMARY KEY (`fee_id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `password_resets`
|
||||
--
|
||||
ALTER TABLE `password_resets`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `token` (`token`),
|
||||
ADD KEY `user_id` (`user_id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `payments`
|
||||
--
|
||||
ALTER TABLE `payments`
|
||||
ADD PRIMARY KEY (`payment_id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `prices`
|
||||
--
|
||||
ALTER TABLE `prices`
|
||||
ADD PRIMARY KEY (`price_id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `trips`
|
||||
--
|
||||
ALTER TABLE `trips`
|
||||
ADD PRIMARY KEY (`trip_id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `users`
|
||||
--
|
||||
ALTER TABLE `users`
|
||||
ADD PRIMARY KEY (`user_id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `visitor_logs`
|
||||
--
|
||||
ALTER TABLE `visitor_logs`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for dumped tables
|
||||
--
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `bar_items`
|
||||
--
|
||||
ALTER TABLE `bar_items`
|
||||
MODIFY `item_id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `bar_tabs`
|
||||
--
|
||||
ALTER TABLE `bar_tabs`
|
||||
MODIFY `tab_id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `bar_transactions`
|
||||
--
|
||||
ALTER TABLE `bar_transactions`
|
||||
MODIFY `transaction_id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `blacklist`
|
||||
--
|
||||
ALTER TABLE `blacklist`
|
||||
MODIFY `blacklist_id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `blogs`
|
||||
--
|
||||
ALTER TABLE `blogs`
|
||||
MODIFY `blog_id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `bookings`
|
||||
--
|
||||
ALTER TABLE `bookings`
|
||||
MODIFY `booking_id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `campsites`
|
||||
--
|
||||
ALTER TABLE `campsites`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `comments`
|
||||
--
|
||||
ALTER TABLE `comments`
|
||||
MODIFY `comment_id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `courses`
|
||||
--
|
||||
ALTER TABLE `courses`
|
||||
MODIFY `course_id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `events`
|
||||
--
|
||||
ALTER TABLE `events`
|
||||
MODIFY `event_id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `membership_application`
|
||||
--
|
||||
ALTER TABLE `membership_application`
|
||||
MODIFY `application_id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `membership_fees`
|
||||
--
|
||||
ALTER TABLE `membership_fees`
|
||||
MODIFY `fee_id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `password_resets`
|
||||
--
|
||||
ALTER TABLE `password_resets`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `prices`
|
||||
--
|
||||
ALTER TABLE `prices`
|
||||
MODIFY `price_id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `trips`
|
||||
--
|
||||
ALTER TABLE `trips`
|
||||
MODIFY `trip_id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `users`
|
||||
--
|
||||
ALTER TABLE `users`
|
||||
MODIFY `user_id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `visitor_logs`
|
||||
--
|
||||
ALTER TABLE `visitor_logs`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- Constraints for dumped tables
|
||||
--
|
||||
|
||||
--
|
||||
-- Constraints for table `bookings`
|
||||
--
|
||||
ALTER TABLE `bookings`
|
||||
ADD CONSTRAINT `bookings_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`user_id`) ON DELETE CASCADE;
|
||||
|
||||
--
|
||||
-- Constraints for table `password_resets`
|
||||
--
|
||||
ALTER TABLE `password_resets`
|
||||
ADD CONSTRAINT `password_resets_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`user_id`) ON DELETE CASCADE;
|
||||
COMMIT;
|
||||
|
||||
/*!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 */;
|
||||
705
functions.php
705
functions.php
@@ -92,14 +92,12 @@ function calculateDaysAndNights($startDate, $endDate)
|
||||
|
||||
function sendVerificationEmail($email, $name, $token)
|
||||
{
|
||||
global $mailjet;
|
||||
|
||||
$message = [
|
||||
'Messages' => [
|
||||
[
|
||||
'From' => [
|
||||
'Email' => "info@4wdcsa.co.za",
|
||||
'Name' => "4WDCSA"
|
||||
'Email' => $_ENV['MAILJET_FROM_EMAIL'],
|
||||
'Name' => $_ENV['MAILJET_FROM_NAME']
|
||||
],
|
||||
'To' => [
|
||||
[
|
||||
@@ -119,36 +117,33 @@ function sendVerificationEmail($email, $name, $token)
|
||||
];
|
||||
|
||||
$client = new Client([
|
||||
// Base URI is used with relative requests
|
||||
'base_uri' => 'https://api.mailjet.com/v3.1/',
|
||||
]);
|
||||
|
||||
$response = $client->request('POST', 'send', [
|
||||
'json' => $message,
|
||||
'auth' => ['1a44f8d5e847537dbb8d3c76fe73a93c', 'ec98b45c53a7694c4f30d09eee9ad280']
|
||||
'auth' => [$_ENV['MAILJET_API_KEY'], $_ENV['MAILJET_API_SECRET']]
|
||||
]);
|
||||
|
||||
if ($response->getStatusCode() == 200) {
|
||||
$body = $response->getBody();
|
||||
$response = json_decode($body);
|
||||
if ($response->Messages[0]->Status == 'success') {
|
||||
return True;
|
||||
return true;
|
||||
} else {
|
||||
return False;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sendInvoice($email, $name, $eft_id, $amount, $description)
|
||||
{
|
||||
global $mailjet;
|
||||
|
||||
$message = [
|
||||
'Messages' => [
|
||||
[
|
||||
'From' => [
|
||||
'Email' => "info@4wdcsa.co.za",
|
||||
'Name' => "4WDCSA"
|
||||
'Email' => $_ENV['MAILJET_FROM_EMAIL'],
|
||||
'Name' => $_ENV['MAILJET_FROM_NAME']
|
||||
],
|
||||
'To' => [
|
||||
[
|
||||
@@ -169,22 +164,21 @@ function sendInvoice($email, $name, $eft_id, $amount, $description)
|
||||
];
|
||||
|
||||
$client = new Client([
|
||||
// Base URI is used with relative requests
|
||||
'base_uri' => 'https://api.mailjet.com/v3.1/',
|
||||
]);
|
||||
|
||||
$response = $client->request('POST', 'send', [
|
||||
'json' => $message,
|
||||
'auth' => ['1a44f8d5e847537dbb8d3c76fe73a93c', 'ec98b45c53a7694c4f30d09eee9ad280']
|
||||
'auth' => [$_ENV['MAILJET_API_KEY'], $_ENV['MAILJET_API_SECRET']]
|
||||
]);
|
||||
|
||||
if ($response->getStatusCode() == 200) {
|
||||
$body = $response->getBody();
|
||||
$response = json_decode($body);
|
||||
if ($response->Messages[0]->Status == 'success') {
|
||||
return True;
|
||||
return true;
|
||||
} else {
|
||||
return False;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -209,14 +203,12 @@ function getEFTDetails($eft_id) {
|
||||
|
||||
function sendPOP($fullname, $eft_id, $amount, $description)
|
||||
{
|
||||
global $mailjet;
|
||||
|
||||
$message = [
|
||||
'Messages' => [
|
||||
[
|
||||
'From' => [
|
||||
'Email' => "info@4wdcsa.co.za",
|
||||
'Name' => "4WDCSA Web Admin"
|
||||
'Email' => $_ENV['MAILJET_FROM_EMAIL'],
|
||||
'Name' => $_ENV['MAILJET_FROM_NAME'] . ' Web Admin'
|
||||
],
|
||||
'To' => [
|
||||
[
|
||||
@@ -224,7 +216,7 @@ function sendPOP($fullname, $eft_id, $amount, $description)
|
||||
'Name' => 'Chris Pinto'
|
||||
],
|
||||
[
|
||||
'Email' => 'info@4wdcsa.co.za',
|
||||
'Email' => $_ENV['MAILJET_FROM_EMAIL'],
|
||||
'Name' => 'Jacqui Boshoff'
|
||||
],
|
||||
[
|
||||
@@ -246,36 +238,33 @@ function sendPOP($fullname, $eft_id, $amount, $description)
|
||||
];
|
||||
|
||||
$client = new Client([
|
||||
// Base URI is used with relative requests
|
||||
'base_uri' => 'https://api.mailjet.com/v3.1/',
|
||||
]);
|
||||
|
||||
$response = $client->request('POST', 'send', [
|
||||
'json' => $message,
|
||||
'auth' => ['1a44f8d5e847537dbb8d3c76fe73a93c', 'ec98b45c53a7694c4f30d09eee9ad280']
|
||||
'auth' => [$_ENV['MAILJET_API_KEY'], $_ENV['MAILJET_API_SECRET']]
|
||||
]);
|
||||
|
||||
if ($response->getStatusCode() == 200) {
|
||||
$body = $response->getBody();
|
||||
$response = json_decode($body);
|
||||
if ($response->Messages[0]->Status == 'success') {
|
||||
return True;
|
||||
return true;
|
||||
} else {
|
||||
return False;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sendEmail($email, $subject, $message)
|
||||
{
|
||||
global $mailjet;
|
||||
|
||||
$message = [
|
||||
$messageData = [
|
||||
'Messages' => [
|
||||
[
|
||||
'From' => [
|
||||
'Email' => "info@4wdcsa.co.za",
|
||||
'Name' => "4WDCSA"
|
||||
'Email' => $_ENV['MAILJET_FROM_EMAIL'],
|
||||
'Name' => $_ENV['MAILJET_FROM_NAME']
|
||||
],
|
||||
'To' => [
|
||||
[
|
||||
@@ -289,36 +278,33 @@ function sendEmail($email, $subject, $message)
|
||||
];
|
||||
|
||||
$client = new Client([
|
||||
// Base URI is used with relative requests
|
||||
'base_uri' => 'https://api.mailjet.com/v3.1/',
|
||||
]);
|
||||
|
||||
$response = $client->request('POST', 'send', [
|
||||
'json' => $message,
|
||||
'auth' => ['1a44f8d5e847537dbb8d3c76fe73a93c', 'ec98b45c53a7694c4f30d09eee9ad280']
|
||||
'json' => $messageData,
|
||||
'auth' => [$_ENV['MAILJET_API_KEY'], $_ENV['MAILJET_API_SECRET']]
|
||||
]);
|
||||
|
||||
if ($response->getStatusCode() == 200) {
|
||||
$body = $response->getBody();
|
||||
$response = json_decode($body);
|
||||
if ($response->Messages[0]->Status == 'success') {
|
||||
return True;
|
||||
return true;
|
||||
} else {
|
||||
return False;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sendAdminNotification($subject, $message)
|
||||
{
|
||||
global $mailjet;
|
||||
|
||||
$mail = [
|
||||
'Messages' => [
|
||||
[
|
||||
'From' => [
|
||||
'Email' => "info@4wdcsa.co.za",
|
||||
'Name' => "4WDCSA"
|
||||
'Email' => $_ENV['MAILJET_FROM_EMAIL'],
|
||||
'Name' => $_ENV['MAILJET_FROM_NAME']
|
||||
],
|
||||
'To' => [
|
||||
[
|
||||
@@ -337,36 +323,33 @@ function sendAdminNotification($subject, $message)
|
||||
];
|
||||
|
||||
$client = new Client([
|
||||
// Base URI is used with relative requests
|
||||
'base_uri' => 'https://api.mailjet.com/v3.1/',
|
||||
]);
|
||||
|
||||
$response = $client->request('POST', 'send', [
|
||||
'json' => $mail,
|
||||
'auth' => ['1a44f8d5e847537dbb8d3c76fe73a93c', 'ec98b45c53a7694c4f30d09eee9ad280']
|
||||
'auth' => [$_ENV['MAILJET_API_KEY'], $_ENV['MAILJET_API_SECRET']]
|
||||
]);
|
||||
|
||||
if ($response->getStatusCode() == 200) {
|
||||
$body = $response->getBody();
|
||||
$response = json_decode($body);
|
||||
if ($response->Messages[0]->Status == 'success') {
|
||||
return True;
|
||||
return true;
|
||||
} else {
|
||||
return False;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sendPaymentConfirmation($email, $name, $description)
|
||||
{
|
||||
global $mailjet;
|
||||
|
||||
$message = [
|
||||
'Messages' => [
|
||||
[
|
||||
'From' => [
|
||||
'Email' => "info@4wdcsa.co.za",
|
||||
'Name' => "4WDCSA"
|
||||
'Email' => $_ENV['MAILJET_FROM_EMAIL'],
|
||||
'Name' => $_ENV['MAILJET_FROM_NAME']
|
||||
],
|
||||
'To' => [
|
||||
[
|
||||
@@ -386,22 +369,21 @@ function sendPaymentConfirmation($email, $name, $description)
|
||||
];
|
||||
|
||||
$client = new Client([
|
||||
// Base URI is used with relative requests
|
||||
'base_uri' => 'https://api.mailjet.com/v3.1/',
|
||||
]);
|
||||
|
||||
$response = $client->request('POST', 'send', [
|
||||
'json' => $message,
|
||||
'auth' => ['1a44f8d5e847537dbb8d3c76fe73a93c', 'ec98b45c53a7694c4f30d09eee9ad280']
|
||||
'auth' => [$_ENV['MAILJET_API_KEY'], $_ENV['MAILJET_API_SECRET']]
|
||||
]);
|
||||
|
||||
if ($response->getStatusCode() == 200) {
|
||||
$body = $response->getBody();
|
||||
$response = json_decode($body);
|
||||
if ($response->Messages[0]->Status == 'success') {
|
||||
return True;
|
||||
return true;
|
||||
} else {
|
||||
return False;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -591,7 +573,7 @@ function processPayment($payment_id, $amount, $description)
|
||||
{
|
||||
$conn = openDatabaseConnection();
|
||||
$status = "AWAITING PAYMENT";
|
||||
$domain = 'www.thepinto.co.za/4wdcsa';
|
||||
$domain = $_ENV['PAYFAST_DOMAIN'];
|
||||
$user_id = $_SESSION['user_id'];
|
||||
// Insert the order into the orders table
|
||||
$stmt = $conn->prepare("
|
||||
@@ -625,7 +607,7 @@ function processPayment($payment_id, $amount, $description)
|
||||
* @param null $passPhrase
|
||||
* @return string
|
||||
*/
|
||||
function generateSignature($data, $passPhrase = 'SheSells7Shells')
|
||||
function generateSignature($data, $passPhrase = null)
|
||||
{
|
||||
// Create parameter string
|
||||
$pfOutput = '';
|
||||
@@ -644,8 +626,8 @@ function processPayment($payment_id, $amount, $description)
|
||||
// Construct variables
|
||||
$data = array(
|
||||
// Merchant details
|
||||
'merchant_id' => '10021495',
|
||||
'merchant_key' => 'yzpdydo934j92',
|
||||
'merchant_id' => $_ENV['PAYFAST_MERCHANT_ID'],
|
||||
'merchant_key' => $_ENV['PAYFAST_MERCHANT_KEY'],
|
||||
'return_url' => 'https://' . $domain . '/bookings.php',
|
||||
'cancel_url' => 'https://' . $domain . '/cancel_booking.php?booking_id=' . $encryptedId,
|
||||
'notify_url' => 'https://' . $domain . '/confirm.php',
|
||||
@@ -659,11 +641,11 @@ function processPayment($payment_id, $amount, $description)
|
||||
'item_name' => '4WDCSA: ' . $description // Describe the item(s) or use a generic description
|
||||
);
|
||||
|
||||
$signature = generateSignature($data); // Assuming you have this function defined
|
||||
$signature = generateSignature($data, $_ENV['PAYFAST_PASSPHRASE']);
|
||||
$data['signature'] = $signature;
|
||||
|
||||
// Determine the PayFast URL based on the mode
|
||||
$testingMode = true;
|
||||
$testingMode = $_ENV['PAYFAST_TESTING_MODE'] === 'true';
|
||||
$pfHost = $testingMode ? 'sandbox.payfast.co.za' : 'www.payfast.co.za';
|
||||
|
||||
// Generate the HTML form with hidden inputs and an auto-submit script
|
||||
@@ -690,7 +672,7 @@ function processMembershipPayment($payment_id, $amount, $description)
|
||||
{
|
||||
$conn = openDatabaseConnection();
|
||||
$status = "AWAITING PAYMENT";
|
||||
$domain = 'www.thepinto.co.za/4wdcsa';
|
||||
$domain = $_ENV['PAYFAST_DOMAIN'];
|
||||
$user_id = $_SESSION['user_id'];
|
||||
// Insert the order into the orders table
|
||||
$stmt = $conn->prepare("
|
||||
@@ -724,7 +706,7 @@ function processMembershipPayment($payment_id, $amount, $description)
|
||||
* @param null $passPhrase
|
||||
* @return string
|
||||
*/
|
||||
function generateSignature($data, $passPhrase = 'SheSells7Shells')
|
||||
function generateSignature($data, $passPhrase = null)
|
||||
{
|
||||
// Create parameter string
|
||||
$pfOutput = '';
|
||||
@@ -743,8 +725,8 @@ function processMembershipPayment($payment_id, $amount, $description)
|
||||
// Construct variables
|
||||
$data = array(
|
||||
// Merchant details
|
||||
'merchant_id' => '10021495',
|
||||
'merchant_key' => 'yzpdydo934j92',
|
||||
'merchant_id' => $_ENV['PAYFAST_MERCHANT_ID'],
|
||||
'merchant_key' => $_ENV['PAYFAST_MERCHANT_KEY'],
|
||||
'return_url' => 'https://' . $domain . '/account_settings.php',
|
||||
'cancel_url' => 'https://' . $domain . '/cancel_application.php?id=' . $encryptedId,
|
||||
'notify_url' => 'https://' . $domain . '/confirm2.php',
|
||||
@@ -758,11 +740,11 @@ function processMembershipPayment($payment_id, $amount, $description)
|
||||
'item_name' => $description // Describe the item(s) or use a generic description
|
||||
);
|
||||
|
||||
$signature = generateSignature($data); // Assuming you have this function defined
|
||||
$signature = generateSignature($data, $_ENV['PAYFAST_PASSPHRASE']);
|
||||
$data['signature'] = $signature;
|
||||
|
||||
// Determine the PayFast URL based on the mode
|
||||
$testingMode = true;
|
||||
$testingMode = $_ENV['PAYFAST_TESTING_MODE'] === 'true';
|
||||
$pfHost = $testingMode ? 'sandbox.payfast.co.za' : 'www.payfast.co.za';
|
||||
|
||||
// Generate the HTML form with hidden inputs and an auto-submit script
|
||||
@@ -1904,16 +1886,84 @@ function processLegacyMembership($user_id) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SECURITY WARNING: This function uses dynamic table/column names which makes it vulnerable to SQL injection.
|
||||
* ONLY call this function with whitelisted table and column names.
|
||||
* NEVER accept table/column names directly from user input.
|
||||
*
|
||||
* Retrieves a single value from a database table.
|
||||
* @param string $table Table name (MUST be whitelisted - see allowed_tables array)
|
||||
* @param string $column Column name to retrieve (MUST be whitelisted - see allowed_columns array)
|
||||
* @param string $match Column name for WHERE clause (MUST be whitelisted)
|
||||
* @param mixed $identifier Value to match in WHERE clause (parameterized - safe)
|
||||
* @return mixed The result value or null if not found
|
||||
*/
|
||||
function getResultFromTable($table, $column, $match, $identifier) {
|
||||
// WHITELIST: Define allowed tables to prevent table name injection
|
||||
$allowed_tables = [
|
||||
'users',
|
||||
'membership_application',
|
||||
'membership_fees',
|
||||
'bookings',
|
||||
'payments',
|
||||
'efts',
|
||||
'trips',
|
||||
'courses',
|
||||
'blogs',
|
||||
'events',
|
||||
'campsites',
|
||||
'bar_transactions',
|
||||
'login_attempts',
|
||||
'legacy_members'
|
||||
];
|
||||
|
||||
// WHITELIST: Define allowed columns per table (simplified - add more as needed)
|
||||
$allowed_columns = [
|
||||
'legacy_members' => ['amount', 'legacy_id', 'email', 'name'],
|
||||
'users' => ['user_id', 'email', 'first_name', 'last_name', 'phone_number', 'password', 'profile_pic', 'is_verified', 'type', 'locked_until'],
|
||||
'membership_fees' => ['payment_id', 'user_id', 'amount', 'payment_status', 'payment_date'],
|
||||
'bookings' => ['booking_id', 'user_id', 'total_amount', 'status', 'booking_type'],
|
||||
'payments' => ['payment_id', 'user_id', 'amount', 'status'],
|
||||
'trips' => ['trip_id', 'trip_name', 'description'],
|
||||
'courses' => ['course_id', 'course_name', 'description'],
|
||||
'blogs' => ['blog_id', 'title', 'content'],
|
||||
'events' => ['event_id', 'event_name', 'description'],
|
||||
'campsites' => ['campsite_id', 'name', 'location'],
|
||||
'efts' => ['eft_id', 'amount', 'status', 'booking_id'],
|
||||
'bar_transactions' => ['transaction_id', 'amount', 'date'],
|
||||
'login_attempts' => ['attempt_id', 'email', 'ip_address', 'success']
|
||||
];
|
||||
|
||||
$conn = openDatabaseConnection();
|
||||
$sql = "SELECT `$column` FROM `$table` WHERE `$match` = ?";
|
||||
$stmt = $conn->prepare($sql);
|
||||
if (!$stmt) {
|
||||
// Validate table name is in whitelist
|
||||
if (!in_array($table, $allowed_tables, true)) {
|
||||
error_log("Security Warning: getResultFromTable() called with non-whitelisted table: $table");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Validate column name is in whitelist for this table
|
||||
if (!isset($allowed_columns[$table]) || !in_array($column, $allowed_columns[$table], true)) {
|
||||
error_log("Security Warning: getResultFromTable() called with non-whitelisted column: $column for table: $table");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Validate match column is in whitelist for this table
|
||||
if (!isset($allowed_columns[$table]) || !in_array($match, $allowed_columns[$table], true)) {
|
||||
error_log("Security Warning: getResultFromTable() called with non-whitelisted match column: $match for table: $table");
|
||||
return null;
|
||||
}
|
||||
|
||||
$stmt->bind_param('i', $identifier);
|
||||
$conn = openDatabaseConnection();
|
||||
// Use backticks for table and column identifiers (safe after whitelist validation)
|
||||
$sql = "SELECT `" . $column . "` FROM `" . $table . "` WHERE `" . $match . "` = ?";
|
||||
$stmt = $conn->prepare($sql);
|
||||
if (!$stmt) {
|
||||
error_log("Database prepare error: " . $conn->error);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Determine parameter type based on identifier
|
||||
$paramType = is_int($identifier) ? 'i' : 's';
|
||||
$stmt->bind_param($paramType, $identifier);
|
||||
$stmt->execute();
|
||||
$stmt->bind_result($result);
|
||||
$stmt->fetch();
|
||||
@@ -1977,3 +2027,522 @@ function hasPhoneNumber($user_id) {
|
||||
// Return true only if a phone number exists and is not empty
|
||||
return !empty($phone_number);
|
||||
}
|
||||
|
||||
// ==================== CSRF PROTECTION FUNCTIONS ====================
|
||||
|
||||
/**
|
||||
* Generates a CSRF token and stores it in the session with expiration
|
||||
* @param int $duration Token expiration time in seconds (default 3600 = 1 hour)
|
||||
* @return string The generated CSRF token
|
||||
*/
|
||||
function generateCSRFToken($duration = 3600) {
|
||||
// Initialize CSRF token storage in session if needed
|
||||
if (!isset($_SESSION['csrf_tokens'])) {
|
||||
$_SESSION['csrf_tokens'] = [];
|
||||
}
|
||||
|
||||
// Clean up expired tokens
|
||||
cleanupExpiredTokens();
|
||||
|
||||
// Generate a random token
|
||||
$token = bin2hex(random_bytes(32));
|
||||
|
||||
// Store token with expiration timestamp
|
||||
$_SESSION['csrf_tokens'][$token] = time() + $duration;
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a CSRF token from user input
|
||||
* @param string $token The token to validate (typically from $_POST['csrf_token'])
|
||||
* @return bool True if token is valid, false otherwise
|
||||
*/
|
||||
function validateCSRFToken($token) {
|
||||
// Check if token exists in session
|
||||
if (!isset($_SESSION['csrf_tokens']) || !isset($_SESSION['csrf_tokens'][$token])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if token has expired
|
||||
if ($_SESSION['csrf_tokens'][$token] < time()) {
|
||||
unset($_SESSION['csrf_tokens'][$token]);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Token is valid - remove it from session (single-use)
|
||||
unset($_SESSION['csrf_tokens'][$token]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes expired tokens from the session
|
||||
*/
|
||||
function cleanupExpiredTokens() {
|
||||
if (!isset($_SESSION['csrf_tokens'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$currentTime = time();
|
||||
foreach ($_SESSION['csrf_tokens'] as $token => $expiration) {
|
||||
if ($expiration < $currentTime) {
|
||||
unset($_SESSION['csrf_tokens'][$token]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== INPUT VALIDATION FUNCTIONS ====================
|
||||
|
||||
/**
|
||||
* Validates and sanitizes email input
|
||||
* @param string $email The email to validate
|
||||
* @param int $maxLength Maximum allowed length (default 254 per RFC 5321)
|
||||
* @return string|false Sanitized email or false if invalid
|
||||
*/
|
||||
function validateEmail($email, $maxLength = 254) {
|
||||
// Check length
|
||||
if (strlen($email) > $maxLength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Filter and validate
|
||||
$filtered = filter_var($email, FILTER_VALIDATE_EMAIL);
|
||||
|
||||
// Sanitize if valid
|
||||
return $filtered ? filter_var($filtered, FILTER_SANITIZE_EMAIL) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates phone number format
|
||||
* @param string $phone The phone number to validate
|
||||
* @return string|false Sanitized phone number or false if invalid
|
||||
*/
|
||||
function validatePhoneNumber($phone) {
|
||||
// Remove common formatting characters
|
||||
$cleaned = preg_replace('/[^\d+\-\s().]/', '', $phone);
|
||||
|
||||
// Check length (between 7 and 20 digits)
|
||||
$digitCount = strlen(preg_replace('/[^\d]/', '', $cleaned));
|
||||
if ($digitCount < 7 || $digitCount > 20) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $cleaned;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and sanitizes a name (first name, last name)
|
||||
* @param string $name The name to validate
|
||||
* @param int $minLength Minimum allowed length (default 2)
|
||||
* @param int $maxLength Maximum allowed length (default 100)
|
||||
* @return string|false Sanitized name or false if invalid
|
||||
*/
|
||||
function validateName($name, $minLength = 2, $maxLength = 100) {
|
||||
// Trim whitespace
|
||||
$name = trim($name);
|
||||
|
||||
// Check length
|
||||
if (strlen($name) < $minLength || strlen($name) > $maxLength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only allow letters, spaces, hyphens, and apostrophes
|
||||
if (!preg_match('/^[a-zA-Z\s\'-]+$/', $name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return htmlspecialchars($name, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a date string in YYYY-MM-DD format
|
||||
* @param string $date The date string to validate
|
||||
* @param string $format Expected date format (default 'Y-m-d')
|
||||
* @return string|false Valid date string or false if invalid
|
||||
*/
|
||||
function validateDate($date, $format = 'Y-m-d') {
|
||||
$d = DateTime::createFromFormat($format, $date);
|
||||
|
||||
// Check if date is valid and in correct format
|
||||
if (!$d || $d->format($format) !== $date) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a numeric amount (for currency)
|
||||
* @param mixed $amount The amount to validate
|
||||
* @param float $min Minimum allowed amount (default 0)
|
||||
* @param float $max Maximum allowed amount (default 999999.99)
|
||||
* @return float|false Valid amount or false if invalid
|
||||
*/
|
||||
function validateAmount($amount, $min = 0, $max = 999999.99) {
|
||||
// Try to convert to float
|
||||
$value = filter_var($amount, FILTER_VALIDATE_FLOAT, [
|
||||
'options' => [
|
||||
'min_range' => $min,
|
||||
'max_range' => $max,
|
||||
'decimal' => '.'
|
||||
]
|
||||
]);
|
||||
|
||||
// Must have at most 2 decimal places
|
||||
if ($value !== false) {
|
||||
$parts = explode('.', (string)$amount);
|
||||
if (isset($parts[1]) && strlen($parts[1]) > 2) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates an integer within a range
|
||||
* @param mixed $int The integer to validate
|
||||
* @param int $min Minimum allowed value (default 0)
|
||||
* @param int $max Maximum allowed value (default 2147483647)
|
||||
* @return int|false Valid integer or false if invalid
|
||||
*/
|
||||
function validateInteger($int, $min = 0, $max = 2147483647) {
|
||||
$value = filter_var($int, FILTER_VALIDATE_INT, [
|
||||
'options' => [
|
||||
'min_range' => $min,
|
||||
'max_range' => $max
|
||||
]
|
||||
]);
|
||||
|
||||
return $value !== false ? $value : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates South African ID number (13 digits)
|
||||
* @param string $idNumber The ID number to validate
|
||||
* @return string|false Valid ID number or false if invalid
|
||||
*/
|
||||
function validateSAIDNumber($idNumber) {
|
||||
// Remove any whitespace
|
||||
$idNumber = preg_replace('/\s/', '', $idNumber);
|
||||
|
||||
// Must be exactly 13 digits
|
||||
if (!preg_match('/^\d{13}$/', $idNumber)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Optional: Validate checksum (Luhn algorithm)
|
||||
$sum = 0;
|
||||
for ($i = 0; $i < 13; $i++) {
|
||||
$digit = (int)$idNumber[$i];
|
||||
|
||||
// Double every even-positioned digit (0-indexed)
|
||||
if ($i % 2 == 0) {
|
||||
$digit *= 2;
|
||||
if ($digit > 9) {
|
||||
$digit -= 9;
|
||||
}
|
||||
}
|
||||
|
||||
$sum += $digit;
|
||||
}
|
||||
|
||||
// Last digit should make sum divisible by 10
|
||||
if ($sum % 10 != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $idNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes text input, removing potentially dangerous characters
|
||||
* @param string $text The text to sanitize
|
||||
* @param int $maxLength Maximum allowed length
|
||||
* @return string Sanitized text
|
||||
*/
|
||||
function sanitizeTextInput($text, $maxLength = 1000) {
|
||||
// Trim whitespace
|
||||
$text = trim($text);
|
||||
|
||||
// Limit length
|
||||
$text = substr($text, 0, $maxLength);
|
||||
|
||||
// Encode HTML special characters
|
||||
return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates file uploads for security
|
||||
* @param array $file The $_FILES element to validate
|
||||
* @param array $allowedTypes Array of allowed MIME types (e.g., ['image/jpeg', 'image/png', 'application/pdf'])
|
||||
* @param int $maxSize Maximum file size in bytes
|
||||
* @return string|false Sanitized filename or false if invalid
|
||||
*/
|
||||
function validateFileUpload($file, $allowedTypes = [], $maxSize = 5242880) { // 5MB default
|
||||
// Check for upload errors
|
||||
if (!isset($file['error']) || $file['error'] !== UPLOAD_ERR_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check file size
|
||||
if (!isset($file['size']) || $file['size'] > $maxSize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check MIME type
|
||||
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
||||
$mimeType = finfo_file($finfo, $file['tmp_name']);
|
||||
finfo_close($finfo);
|
||||
|
||||
if (!in_array($mimeType, $allowedTypes, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Generate random filename with original extension
|
||||
$pathinfo = pathinfo($file['name']);
|
||||
$extension = strtolower($pathinfo['extension']);
|
||||
|
||||
// Whitelist allowed extensions
|
||||
$allowedExtensions = ['jpg', 'jpeg', 'png', 'pdf', 'gif', 'webp'];
|
||||
if (!in_array($extension, $allowedExtensions)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Generate random filename
|
||||
$randomName = bin2hex(random_bytes(16)) . '.' . $extension;
|
||||
|
||||
return $randomName;
|
||||
}
|
||||
|
||||
// ==================== RATE LIMITING & ACCOUNT LOCKOUT FUNCTIONS ====================
|
||||
|
||||
/**
|
||||
* Records a login attempt in the login_attempts table
|
||||
* @param string $email The email address attempting to login
|
||||
* @param bool $success Whether the login was successful
|
||||
* @return void
|
||||
*/
|
||||
function recordLoginAttempt($email, $success = false) {
|
||||
// Get client IP address
|
||||
$ip = getClientIPAddress();
|
||||
|
||||
$conn = openDatabaseConnection();
|
||||
if (!$conn) {
|
||||
return;
|
||||
}
|
||||
|
||||
$email = strtolower(trim($email));
|
||||
|
||||
$sql = "INSERT INTO login_attempts (email, ip_address, success) VALUES (?, ?, ?)";
|
||||
$stmt = $conn->prepare($sql);
|
||||
if ($stmt) {
|
||||
$success_int = $success ? 1 : 0;
|
||||
$stmt->bind_param('ssi', $email, $ip, $success_int);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the client's IP address safely
|
||||
* @return string The client's IP address
|
||||
*/
|
||||
function getClientIPAddress() {
|
||||
// Check for IP from share internet
|
||||
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
|
||||
$ip = $_SERVER['HTTP_CLIENT_IP'];
|
||||
}
|
||||
// Check for IP passed from proxy
|
||||
elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
||||
$ip = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])[0];
|
||||
}
|
||||
// Check for remote IP
|
||||
else {
|
||||
$ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
|
||||
}
|
||||
|
||||
// Validate IP format
|
||||
if (!filter_var($ip, FILTER_VALIDATE_IP)) {
|
||||
$ip = '0.0.0.0';
|
||||
}
|
||||
|
||||
return $ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an account is locked and returns lockout status
|
||||
* @param string $email The email address to check
|
||||
* @return array ['is_locked' => bool, 'locked_until' => datetime string or null, 'minutes_remaining' => int]
|
||||
*/
|
||||
function checkAccountLockout($email) {
|
||||
$conn = openDatabaseConnection();
|
||||
if (!$conn) {
|
||||
return ['is_locked' => false, 'locked_until' => null, 'minutes_remaining' => 0];
|
||||
}
|
||||
|
||||
$email = strtolower(trim($email));
|
||||
$now = date('Y-m-d H:i:s');
|
||||
|
||||
$sql = "SELECT locked_until FROM users WHERE email = ? LIMIT 1";
|
||||
$stmt = $conn->prepare($sql);
|
||||
if (!$stmt) {
|
||||
return ['is_locked' => false, 'locked_until' => null, 'minutes_remaining' => 0];
|
||||
}
|
||||
|
||||
$stmt->bind_param('s', $email);
|
||||
$stmt->execute();
|
||||
$stmt->bind_result($locked_until);
|
||||
$stmt->fetch();
|
||||
$stmt->close();
|
||||
|
||||
if ($locked_until === null) {
|
||||
return ['is_locked' => false, 'locked_until' => null, 'minutes_remaining' => 0];
|
||||
}
|
||||
|
||||
if ($locked_until > $now) {
|
||||
// Account is still locked
|
||||
$lockTime = strtotime($locked_until);
|
||||
$nowTime = strtotime($now);
|
||||
$secondsRemaining = max(0, $lockTime - $nowTime);
|
||||
$minutesRemaining = ceil($secondsRemaining / 60);
|
||||
|
||||
return [
|
||||
'is_locked' => true,
|
||||
'locked_until' => $locked_until,
|
||||
'minutes_remaining' => $minutesRemaining
|
||||
];
|
||||
}
|
||||
|
||||
// Lockout has expired, clear it
|
||||
$sql = "UPDATE users SET locked_until = NULL WHERE email = ?";
|
||||
$stmt = $conn->prepare($sql);
|
||||
if ($stmt) {
|
||||
$stmt->bind_param('s', $email);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
}
|
||||
|
||||
return ['is_locked' => false, 'locked_until' => null, 'minutes_remaining' => 0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts recent failed login attempts for an email + IP combination
|
||||
* @param string $email The email address
|
||||
* @param int $minutesBack How many minutes back to check (default 15)
|
||||
* @return int Number of failed attempts
|
||||
*/
|
||||
function countRecentFailedAttempts($email, $minutesBack = 15) {
|
||||
$conn = openDatabaseConnection();
|
||||
if (!$conn) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$email = strtolower(trim($email));
|
||||
$ip = getClientIPAddress();
|
||||
$cutoffTime = date('Y-m-d H:i:s', time() - ($minutesBack * 60));
|
||||
|
||||
$sql = "SELECT COUNT(*) as count FROM login_attempts
|
||||
WHERE email = ? AND ip_address = ? AND success = 0
|
||||
AND attempted_at > ?";
|
||||
$stmt = $conn->prepare($sql);
|
||||
if (!$stmt) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$stmt->bind_param('sss', $email, $ip, $cutoffTime);
|
||||
$stmt->execute();
|
||||
$stmt->bind_result($count);
|
||||
$stmt->fetch();
|
||||
$stmt->close();
|
||||
|
||||
return (int)$count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locks an account for a specified duration
|
||||
* @param string $email The email address to lock
|
||||
* @param int $minutes Duration of lockout in minutes (default 15)
|
||||
* @return bool True if successful, false otherwise
|
||||
*/
|
||||
function lockAccount($email, $minutes = 15) {
|
||||
$conn = openDatabaseConnection();
|
||||
if (!$conn) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$email = strtolower(trim($email));
|
||||
$lockUntil = date('Y-m-d H:i:s', time() + ($minutes * 60));
|
||||
|
||||
$sql = "UPDATE users SET locked_until = ? WHERE email = ?";
|
||||
$stmt = $conn->prepare($sql);
|
||||
if (!$stmt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$stmt->bind_param('ss', $lockUntil, $email);
|
||||
$result = $stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlocks an account (admin function)
|
||||
* @param string $email The email address to unlock
|
||||
* @return bool True if successful, false otherwise
|
||||
*/
|
||||
function unlockAccount($email) {
|
||||
$conn = openDatabaseConnection();
|
||||
if (!$conn) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$email = strtolower(trim($email));
|
||||
|
||||
$sql = "UPDATE users SET locked_until = NULL WHERE email = ?";
|
||||
$stmt = $conn->prepare($sql);
|
||||
if (!$stmt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$stmt->bind_param('s', $email);
|
||||
$result = $stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs an action to the audit log table
|
||||
* @param int $user_id User ID (null if not authenticated)
|
||||
* @param string $action Action name (e.g., 'LOGIN', 'FAILED_LOGIN', 'ACCOUNT_LOCKED')
|
||||
* @param string $resource_type Resource type being affected
|
||||
* @param int $resource_id Resource ID being affected
|
||||
* @param array $details Additional details about the action
|
||||
* @return bool True if successful
|
||||
*/
|
||||
function auditLog($user_id, $action, $resource_type = null, $resource_id = null, $details = null) {
|
||||
$conn = openDatabaseConnection();
|
||||
if (!$conn) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$ip = getClientIPAddress();
|
||||
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
|
||||
$detailsJson = $details ? json_encode($details) : null;
|
||||
|
||||
$sql = "INSERT INTO audit_log (user_id, action, resource_type, resource_id, ip_address, user_agent, details)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)";
|
||||
$stmt = $conn->prepare($sql);
|
||||
if (!$stmt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$stmt->bind_param('issdsss', $user_id, $action, $resource_type, $resource_id, $ip, $userAgent, $detailsJson);
|
||||
$result = $stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
47
migrations/001_phase1_security_schema.sql
Normal file
47
migrations/001_phase1_security_schema.sql
Normal file
@@ -0,0 +1,47 @@
|
||||
-- ============================================================================
|
||||
-- MIGRATION: Phase 1 Security & Stability Database Schema Updates
|
||||
-- Date: 2025-12-03
|
||||
-- Description: Add tables and columns required for Phase 1 security features
|
||||
-- (login rate limiting, account lockout, audit logging)
|
||||
-- ============================================================================
|
||||
|
||||
-- Track failed login attempts for rate limiting and account lockout
|
||||
CREATE TABLE IF NOT EXISTS login_attempts (
|
||||
attempt_id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
email VARCHAR(255) NOT NULL,
|
||||
ip_address VARCHAR(45) NOT NULL,
|
||||
attempted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
success BOOLEAN DEFAULT FALSE,
|
||||
INDEX idx_email_ip (email, ip_address),
|
||||
INDEX idx_attempted_at (attempted_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Add account lockout column to users table
|
||||
-- Stores the timestamp until which the account is locked
|
||||
-- NULL = account not locked, Future datetime = account locked until this time
|
||||
ALTER TABLE users ADD COLUMN locked_until DATETIME NULL DEFAULT NULL AFTER is_verified;
|
||||
CREATE INDEX idx_locked_until ON users (locked_until);
|
||||
|
||||
-- Security audit log for sensitive operations
|
||||
DROP TABLE IF EXISTS `audit_log`;
|
||||
CREATE TABLE IF NOT EXISTS audit_log (
|
||||
log_id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
user_id INT NULL,
|
||||
action VARCHAR(100) NOT NULL COMMENT 'e.g., LOGIN, FAILED_LOGIN, ACCOUNT_LOCKED, FILE_UPLOAD',
|
||||
resource_type VARCHAR(50) COMMENT 'e.g., users, bookings, payments',
|
||||
resource_id INT COMMENT 'ID of affected resource',
|
||||
ip_address VARCHAR(45) NOT NULL,
|
||||
user_agent TEXT NULL,
|
||||
details TEXT COMMENT 'JSON or text details about the action',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_action (action),
|
||||
INDEX idx_created_at (created_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- ============================================================================
|
||||
-- ROLLBACK INSTRUCTIONS (if needed)
|
||||
-- ============================================================================
|
||||
-- ALTER TABLE users DROP COLUMN locked_until;
|
||||
-- DROP TABLE IF EXISTS login_attempts;
|
||||
-- DROP TABLE IF EXISTS audit_log;
|
||||
@@ -10,24 +10,53 @@ $status = 'AWAITING PAYMENT';
|
||||
$description = 'Membership Fees '.date("Y")." ".getInitialSurname($user_id);
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// CSRF Token Validation
|
||||
if (!isset($_POST['csrf_token']) || !validateCSRFToken($_POST['csrf_token'])) {
|
||||
auditLog($user_id, 'CSRF_VALIDATION_FAILED', 'membership_application', null, ['endpoint' => 'process_application.php']);
|
||||
http_response_code(403);
|
||||
die('Security token validation failed. Please try again.');
|
||||
}
|
||||
|
||||
// Get all the form fields
|
||||
$first_name = $_POST['first_name'];
|
||||
$last_name = $_POST['last_name'];
|
||||
$id_number = $_POST['id_number'];
|
||||
$dob = $_POST['dob'];
|
||||
$occupation = $_POST['occupation'];
|
||||
$tel_cell = $_POST['tel_cell'];
|
||||
$email = $_POST['email'];
|
||||
// Get all the form fields with validation
|
||||
$first_name = validateName($_POST['first_name'] ?? '');
|
||||
if ($first_name === false) {
|
||||
die('Invalid first name format.');
|
||||
}
|
||||
|
||||
$last_name = validateName($_POST['last_name'] ?? '');
|
||||
if ($last_name === false) {
|
||||
die('Invalid last name format.');
|
||||
}
|
||||
|
||||
$id_number = validateSAIDNumber($_POST['id_number'] ?? '');
|
||||
if ($id_number === false) {
|
||||
die('Invalid ID number format.');
|
||||
}
|
||||
|
||||
$dob = validateDate($_POST['dob'] ?? '');
|
||||
if ($dob === false) {
|
||||
die('Invalid date of birth format.');
|
||||
}
|
||||
|
||||
$occupation = sanitizeTextInput($_POST['occupation'] ?? '', 100);
|
||||
$tel_cell = validatePhoneNumber($_POST['tel_cell'] ?? '');
|
||||
if ($tel_cell === false) {
|
||||
die('Invalid phone number format.');
|
||||
}
|
||||
|
||||
$email = validateEmail($_POST['email'] ?? '');
|
||||
if ($email === false) {
|
||||
die('Invalid email format.');
|
||||
}
|
||||
|
||||
// Spouse or Partner details (optional)
|
||||
$spouse_first_name = !empty($_POST['spouse_first_name']) ? $_POST['spouse_first_name'] : null;
|
||||
$spouse_last_name = !empty($_POST['spouse_last_name']) ? $_POST['spouse_last_name'] : null;
|
||||
$spouse_id_number = !empty($_POST['spouse_id_number']) ? $_POST['spouse_id_number'] : null;
|
||||
$spouse_dob = !empty($_POST['spouse_dob']) ? $_POST['spouse_dob'] : NULL; // if empty, set to NULL
|
||||
$spouse_occupation = !empty($_POST['spouse_occupation']) ? $_POST['spouse_occupation'] : null;
|
||||
$spouse_tel_cell = !empty($_POST['spouse_tel_cell']) ? $_POST['spouse_tel_cell'] : null;
|
||||
$spouse_email = !empty($_POST['spouse_email']) ? $_POST['spouse_email'] : null;
|
||||
$spouse_first_name = !empty($_POST['spouse_first_name']) ? validateName($_POST['spouse_first_name']) : null;
|
||||
$spouse_last_name = !empty($_POST['spouse_last_name']) ? validateName($_POST['spouse_last_name']) : null;
|
||||
$spouse_id_number = !empty($_POST['spouse_id_number']) ? validateSAIDNumber($_POST['spouse_id_number']) : null;
|
||||
$spouse_dob = !empty($_POST['spouse_dob']) ? validateDate($_POST['spouse_dob']) : NULL;
|
||||
$spouse_occupation = !empty($_POST['spouse_occupation']) ? sanitizeTextInput($_POST['spouse_occupation'], 100) : null;
|
||||
$spouse_tel_cell = !empty($_POST['spouse_tel_cell']) ? validatePhoneNumber($_POST['spouse_tel_cell']) : null;
|
||||
$spouse_email = !empty($_POST['spouse_email']) ? validateEmail($_POST['spouse_email']) : null;
|
||||
|
||||
// Children details (optional)
|
||||
$child_name1 = !empty($_POST['child_name1']) ? $_POST['child_name1'] : null;
|
||||
|
||||
@@ -11,12 +11,45 @@ $user_id = isset($_SESSION['user_id']) ? $_SESSION['user_id'] : null;
|
||||
|
||||
// Check if the form has been submitted
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// CSRF Token Validation
|
||||
if (!isset($_POST['csrf_token']) || !validateCSRFToken($_POST['csrf_token'])) {
|
||||
auditLog($user_id, 'CSRF_VALIDATION_FAILED', 'bookings', null, ['endpoint' => 'process_booking.php']);
|
||||
echo json_encode(['status' => 'error', 'message' => 'Security token validation failed. Please try again.']);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Validate dates and integers
|
||||
$from_date = validateDate($_POST['from_date'] ?? '');
|
||||
if ($from_date === false) {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Invalid from date format.']);
|
||||
exit();
|
||||
}
|
||||
|
||||
$to_date = validateDate($_POST['to_date'] ?? '');
|
||||
if ($to_date === false) {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Invalid to date format.']);
|
||||
exit();
|
||||
}
|
||||
|
||||
$num_vehicles = validateInteger($_POST['vehicles'] ?? 0, 1, 10);
|
||||
if ($num_vehicles === false) {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Invalid number of vehicles.']);
|
||||
exit();
|
||||
}
|
||||
|
||||
$num_adults = validateInteger($_POST['adults'] ?? 0, 0, 20);
|
||||
if ($num_adults === false) {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Invalid number of adults.']);
|
||||
exit();
|
||||
}
|
||||
|
||||
$num_children = validateInteger($_POST['children'] ?? 0, 0, 20);
|
||||
if ($num_children === false) {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Invalid number of children.']);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Get values from the form
|
||||
$from_date = $_POST['from_date'];
|
||||
$to_date = $_POST['to_date'];
|
||||
$num_vehicles = (int)$_POST['vehicles'];
|
||||
$num_adults = (int)$_POST['adults'];
|
||||
$num_children = (int)$_POST['children'];
|
||||
$add_firewood = isset($_POST['AddExtra']) ? 1 : 0; // Checkbox for extras
|
||||
$is_member = isset($_POST['is_member']) ? (int)$_POST['is_member'] : 0; // Hidden member status
|
||||
$type = "camping";
|
||||
|
||||
@@ -18,12 +18,45 @@ $is_member = getUserMemberStatus($user_id);
|
||||
|
||||
// Check if the form has been submitted
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// CSRF Token Validation
|
||||
if (!isset($_POST['csrf_token']) || !validateCSRFToken($_POST['csrf_token'])) {
|
||||
auditLog($user_id, 'CSRF_VALIDATION_FAILED', 'bookings', null, ['endpoint' => 'process_camp_booking.php']);
|
||||
echo json_encode(['status' => 'error', 'message' => 'Security token validation failed. Please try again.']);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Validate dates and integers
|
||||
$from_date = validateDate($_POST['from_date'] ?? '');
|
||||
if ($from_date === false) {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Invalid from date format.']);
|
||||
exit();
|
||||
}
|
||||
|
||||
$to_date = validateDate($_POST['to_date'] ?? '');
|
||||
if ($to_date === false) {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Invalid to date format.']);
|
||||
exit();
|
||||
}
|
||||
|
||||
$num_vehicles = validateInteger($_POST['vehicles'] ?? 1, 1, 10);
|
||||
if ($num_vehicles === false) {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Invalid number of vehicles.']);
|
||||
exit();
|
||||
}
|
||||
|
||||
$num_adults = validateInteger($_POST['adults'] ?? 0, 0, 20);
|
||||
if ($num_adults === false) {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Invalid number of adults.']);
|
||||
exit();
|
||||
}
|
||||
|
||||
$num_children = validateInteger($_POST['children'] ?? 0, 0, 20);
|
||||
if ($num_children === false) {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Invalid number of children.']);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Get values from the form
|
||||
$from_date = $_POST['from_date'];
|
||||
$to_date = $_POST['to_date'];
|
||||
$num_vehicles = (int)$_POST['vehicles'];
|
||||
$num_adults = (int)$_POST['adults'];
|
||||
$num_children = (int)$_POST['children'];
|
||||
$add_firewood = isset($_POST['AddExtra']) ? 1 : 0; // Checkbox for extras
|
||||
// $is_member = isset($_POST['is_member']) ? (int)$_POST['is_member'] : 0; // Hidden member status
|
||||
$type = "camping";
|
||||
|
||||
@@ -18,10 +18,30 @@ $pending_member = getUserMemberStatusPending($user_id);
|
||||
|
||||
// Check if the form has been submitted
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// CSRF Token Validation
|
||||
if (!isset($_POST['csrf_token']) || !validateCSRFToken($_POST['csrf_token'])) {
|
||||
auditLog($user_id, 'CSRF_VALIDATION_FAILED', 'bookings', null, ['endpoint' => 'process_course_booking.php']);
|
||||
http_response_code(403);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['error' => 'Security token validation failed.']);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Input variables from the form (use default values if not provided)
|
||||
$additional_members = isset($_POST['members']) ? intval($_POST['members']) : 0; // Default to 1 vehicle
|
||||
$num_adults = isset($_POST['non-members']) ? intval($_POST['non-members']) : 0; // Default to 1 adult
|
||||
$course_id = isset($_POST['course_id']) ? intval($_POST['course_id']) : 0; // Default to 0 children
|
||||
$additional_members = validateInteger($_POST['members'] ?? 0, 0, 20);
|
||||
if ($additional_members === false) $additional_members = 0;
|
||||
|
||||
$num_adults = validateInteger($_POST['non-members'] ?? 0, 0, 20);
|
||||
if ($num_adults === false) $num_adults = 0;
|
||||
|
||||
$course_id = validateInteger($_POST['course_id'] ?? 0, 1, 999999);
|
||||
if ($course_id === false) {
|
||||
http_response_code(400);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['error' => 'Invalid course ID.']);
|
||||
exit();
|
||||
}
|
||||
|
||||
checkAndRedirectCourseBooking($course_id);
|
||||
// Fetch trip costs from the database
|
||||
$query = "SELECT date, cost_members, cost_nonmembers, course_type FROM courses WHERE course_id = ?";
|
||||
|
||||
@@ -4,9 +4,20 @@ require_once("session.php");
|
||||
require_once("connection.php");
|
||||
require_once("functions.php");
|
||||
checkAdmin();
|
||||
|
||||
// CSRF Token Validation for POST requests
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (!isset($_POST['csrf_token']) || !validateCSRFToken($_POST['csrf_token'])) {
|
||||
auditLog($_SESSION['user_id'] ?? null, 'CSRF_VALIDATION_FAILED', 'efts', null, ['endpoint' => 'process_eft.php']);
|
||||
http_response_code(403);
|
||||
die('Security token validation failed.');
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($_GET['token']) || empty($_GET['token'])) {
|
||||
die("Invalid request.");
|
||||
}
|
||||
|
||||
$token = $_GET['token'];
|
||||
// echo $token;
|
||||
$eft_id = decryptData($token, $salt);
|
||||
|
||||
@@ -9,6 +9,12 @@ if (!isset($_SESSION['user_id'])) {
|
||||
}
|
||||
|
||||
if (isset($_POST['signature'])) {
|
||||
// CSRF Token Validation
|
||||
if (!isset($_POST['csrf_token']) || !validateCSRFToken($_POST['csrf_token'])) {
|
||||
auditLog($_SESSION['user_id'], 'CSRF_VALIDATION_FAILED', 'membership_application', null, ['endpoint' => 'process_signature.php']);
|
||||
die(json_encode(['status' => 'error', 'message' => 'Security token validation failed']));
|
||||
}
|
||||
|
||||
$user_id = $_SESSION['user_id']; // Get the user ID from the session
|
||||
$signature = $_POST['signature']; // Base64 image data
|
||||
|
||||
|
||||
@@ -30,12 +30,27 @@ $is_member = getUserMemberStatus($user_id);
|
||||
|
||||
// Check if the form has been submitted
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// CSRF Token Validation
|
||||
if (!isset($_POST['csrf_token']) || !validateCSRFToken($_POST['csrf_token'])) {
|
||||
auditLog($user_id, 'CSRF_VALIDATION_FAILED', 'bookings', null, ['endpoint' => 'process_trip_booking.php']);
|
||||
http_response_code(403);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['error' => 'Security token validation failed.']);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Input variables from the form (use default values if not provided)
|
||||
$num_vehicles = isset($_POST['vehicles']) ? intval($_POST['vehicles']) : 1; // Default to 1 vehicle
|
||||
$num_adults = isset($_POST['adults']) ? intval($_POST['adults']) : 1; // Default to 1 adult
|
||||
$num_children = isset($_POST['children']) ? intval($_POST['children']) : 0; // Default to 0 children
|
||||
$num_pensioners = isset($_POST['pensioners']) ? intval($_POST['pensioners']) : 0; // Default to 0 pensioners
|
||||
// $radio = isset($_POST['AddExtra']) ? 1 : 0; // Checkbox for extras
|
||||
$num_vehicles = validateInteger($_POST['vehicles'] ?? 1, 1, 10);
|
||||
if ($num_vehicles === false) $num_vehicles = 1;
|
||||
|
||||
$num_adults = validateInteger($_POST['adults'] ?? 1, 1, 20);
|
||||
if ($num_adults === false) $num_adults = 1;
|
||||
|
||||
$num_children = validateInteger($_POST['children'] ?? 0, 0, 20);
|
||||
if ($num_children === false) $num_children = 0;
|
||||
|
||||
$num_pensioners = validateInteger($_POST['pensioners'] ?? 0, 0, 20);
|
||||
if ($num_pensioners === false) $num_pensioners = 0;
|
||||
// Fetch trip costs from the database
|
||||
$query = "SELECT trip_name, cost_members, cost_nonmembers, cost_pensioner_member, cost_pensioner, booking_fee, start_date, end_date, trip_code FROM trips WHERE trip_id = ?";
|
||||
$stmt = $conn->prepare($query);
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
<?php
|
||||
require_once("env.php");
|
||||
require_once("session.php");
|
||||
require_once("connection.php");
|
||||
require_once("functions.php");
|
||||
require_once "vendor/autoload.php";
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
|
||||
|
||||
|
||||
// Create connection
|
||||
$conn = openDatabaseConnection();
|
||||
|
||||
@@ -19,20 +18,78 @@ if ($conn->connect_error) {
|
||||
|
||||
// Form processing
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// Sanitize and validate input
|
||||
$first_name = ucwords(strtolower($conn->real_escape_string($_POST['first_name'])));
|
||||
$last_name = ucwords(strtolower($conn->real_escape_string($_POST['last_name'])));
|
||||
$phone_number = $conn->real_escape_string($_POST['phone_number']);
|
||||
$email = filter_var($_POST['email'], FILTER_SANITIZE_EMAIL);
|
||||
$password = $_POST['password'];
|
||||
$password_confirm = $_POST['password_confirm'];
|
||||
$name = $first_name . " " . $last_name;
|
||||
|
||||
// Basic validation
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
// CSRF Token Validation
|
||||
if (!isset($_POST['csrf_token']) || !validateCSRFToken($_POST['csrf_token'])) {
|
||||
auditLog(null, 'CSRF_VALIDATION_FAILED', 'users', null, ['endpoint' => 'register_user.php']);
|
||||
echo json_encode(['status' => 'error', 'message' => 'Security token validation failed. Please try again.']);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Check rate limiting on registration endpoint (by IP)
|
||||
$ip = getClientIPAddress();
|
||||
$cutoffTime = date('Y-m-d H:i:s', time() - (3600)); // Last hour
|
||||
|
||||
$stmt = $conn->prepare("SELECT COUNT(*) as count FROM audit_log WHERE action = 'REGISTRATION_ATTEMPT' AND ip_address = ? AND created_at > ?");
|
||||
$stmt->bind_param('ss', $ip, $cutoffTime);
|
||||
$stmt->execute();
|
||||
$stmt->bind_result($regAttempts);
|
||||
$stmt->fetch();
|
||||
$stmt->close();
|
||||
|
||||
// Allow max 5 registration attempts per IP per hour
|
||||
if ($regAttempts >= 5) {
|
||||
auditLog(null, 'REGISTRATION_RATE_LIMIT_EXCEEDED', 'users', null, ['ip' => $ip, 'attempts' => $regAttempts]);
|
||||
echo json_encode(['status' => 'error', 'message' => 'Too many registration attempts. Please try again later.']);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Validate and sanitize first name
|
||||
$first_name = validateName($_POST['first_name'] ?? '');
|
||||
if ($first_name === false) {
|
||||
auditLog(null, 'REGISTRATION_INVALID_FIRST_NAME', 'users');
|
||||
echo json_encode(['status' => 'error', 'message' => 'Invalid first name. Only letters, spaces, hyphens, and apostrophes allowed (2-100 characters).']);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Validate and sanitize last name
|
||||
$last_name = validateName($_POST['last_name'] ?? '');
|
||||
if ($last_name === false) {
|
||||
auditLog(null, 'REGISTRATION_INVALID_LAST_NAME', 'users');
|
||||
echo json_encode(['status' => 'error', 'message' => 'Invalid last name. Only letters, spaces, hyphens, and apostrophes allowed (2-100 characters).']);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Validate and sanitize phone number
|
||||
$phone_number = validatePhoneNumber($_POST['phone_number'] ?? '');
|
||||
if ($phone_number === false) {
|
||||
auditLog(null, 'REGISTRATION_INVALID_PHONE', 'users');
|
||||
echo json_encode(['status' => 'error', 'message' => 'Invalid phone number format.']);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Validate email
|
||||
$email = validateEmail($_POST['email'] ?? '');
|
||||
if ($email === false) {
|
||||
auditLog(null, 'REGISTRATION_INVALID_EMAIL', 'users');
|
||||
echo json_encode(['status' => 'error', 'message' => 'Invalid email format.']);
|
||||
exit();
|
||||
}
|
||||
|
||||
$password = $_POST['password'] ?? '';
|
||||
$password_confirm = $_POST['password_confirm'] ?? '';
|
||||
|
||||
// Validate password strength (minimum 8 characters, must contain uppercase, lowercase, number, special char)
|
||||
if (strlen($password) < 8) {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Password must be at least 8 characters long.']);
|
||||
exit();
|
||||
}
|
||||
|
||||
if (!preg_match('/[A-Z]/', $password) || !preg_match('/[a-z]/', $password) ||
|
||||
!preg_match('/[0-9]/', $password) || !preg_match('/[!@#$%^&*]/', $password)) {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Password must contain uppercase, lowercase, number, and special character (!@#$%^&*).']);
|
||||
exit();
|
||||
}
|
||||
|
||||
if ($password !== $password_confirm) {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Passwords do not match.']);
|
||||
exit();
|
||||
@@ -45,6 +102,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$stmt->store_result();
|
||||
|
||||
if ($stmt->num_rows > 0) {
|
||||
auditLog(null, 'REGISTRATION_EMAIL_EXISTS', 'users', null, ['email' => $email]);
|
||||
echo json_encode(['status' => 'error', 'message' => 'Email is already registered.']);
|
||||
$stmt->close();
|
||||
$conn->close();
|
||||
@@ -56,7 +114,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// Hash password
|
||||
$hashed_password = password_hash($password, PASSWORD_BCRYPT);
|
||||
|
||||
// Generate token
|
||||
// Generate email verification token
|
||||
$token = bin2hex(random_bytes(50));
|
||||
|
||||
// Prepare and execute query
|
||||
@@ -68,14 +126,17 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if ($stmt->execute()) {
|
||||
$newUser_id = $conn->insert_id;
|
||||
processLegacyMembership($newUser_id);
|
||||
if (sendVerificationEmail($email, $name, $token)) {
|
||||
sendEmail('chrispintoza@gmail.com', '4WDCSA: New User Login', $name . ' has just created an account using Credentials.');
|
||||
auditLog($newUser_id, 'USER_REGISTRATION', 'users', $newUser_id, ['email' => $email]);
|
||||
|
||||
if (sendVerificationEmail($email, $first_name . ' ' . $last_name, $token)) {
|
||||
sendEmail($_ENV['ADMIN_EMAIL'], '4WDCSA: New User Registration', $first_name . ' ' . $last_name . ' (' . $email . ') has just created an account using Credentials.');
|
||||
echo json_encode(['status' => 'success', 'message' => 'Registration successful. Please check your email to verify your account.']);
|
||||
} else {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Failed to send verification email.']);
|
||||
}
|
||||
} else {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Failed to register user: ' . $stmt->error]);
|
||||
auditLog(null, 'REGISTRATION_DATABASE_ERROR', 'users', null, ['email' => $email]);
|
||||
echo json_encode(['status' => 'error', 'message' => 'Failed to register user.']);
|
||||
}
|
||||
|
||||
$stmt->close();
|
||||
|
||||
20
run_migration.php
Normal file
20
run_migration.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
require 'env.php';
|
||||
require 'connection.php';
|
||||
|
||||
$conn = openDatabaseConnection();
|
||||
|
||||
if (!$conn) {
|
||||
die('Database connection failed');
|
||||
}
|
||||
|
||||
$sql = file_get_contents('migrations/001_phase1_security_schema.sql');
|
||||
|
||||
if ($conn->multi_query($sql)) {
|
||||
echo "✓ Migration executed successfully\n";
|
||||
} else {
|
||||
echo "✗ Migration error: " . $conn->error . "\n";
|
||||
}
|
||||
|
||||
$conn->close();
|
||||
?>
|
||||
@@ -13,8 +13,8 @@ if (!$conn) {
|
||||
|
||||
// Google Client Setup
|
||||
$client = new Google_Client();
|
||||
$client->setClientId('948441222188-8qhboq2urr8o9n35mc70s5h2nhd52v0m.apps.googleusercontent.com');
|
||||
$client->setClientSecret('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');
|
||||
$client->setClientId($_ENV['GOOGLE_CLIENT_ID']);
|
||||
$client->setClientSecret($_ENV['GOOGLE_CLIENT_SECRET']);
|
||||
$client->setRedirectUri($_ENV['HOST'] . '/validate_login.php');
|
||||
$client->addScope("email");
|
||||
$client->addScope("profile");
|
||||
@@ -86,18 +86,57 @@ if (isset($_GET['code'])) {
|
||||
|
||||
// Check if email and password login is requested
|
||||
if (isset($_POST['email']) && isset($_POST['password'])) {
|
||||
// Retrieve and sanitize form data
|
||||
$email = filter_var($_POST['email'], FILTER_SANITIZE_EMAIL);
|
||||
$password = trim($_POST['password']); // Remove extra spaces
|
||||
|
||||
// Validate input
|
||||
// CSRF Token Validation
|
||||
if (!isset($_POST['csrf_token']) || !validateCSRFToken($_POST['csrf_token'])) {
|
||||
auditLog(null, 'CSRF_VALIDATION_FAILED', 'users', null, ['endpoint' => 'validate_login.php']);
|
||||
echo json_encode(['status' => 'error', 'message' => 'Security token validation failed. Please try again.']);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Retrieve and validate email input
|
||||
$email = validateEmail($_POST['email']);
|
||||
if ($email === false) {
|
||||
auditLog(null, 'INVALID_EMAIL_FORMAT', 'users', null, ['email' => $_POST['email']]);
|
||||
echo json_encode(['status' => 'error', 'message' => 'Invalid email format.']);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Retrieve and sanitize password
|
||||
$password = isset($_POST['password']) ? trim($_POST['password']) : '';
|
||||
|
||||
// Basic validation
|
||||
if (empty($email) || empty($password)) {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Please enter both email and password.']);
|
||||
exit();
|
||||
}
|
||||
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Invalid email format.']);
|
||||
|
||||
// Check for account lockout
|
||||
$lockoutStatus = checkAccountLockout($email);
|
||||
if ($lockoutStatus['is_locked']) {
|
||||
auditLog(null, 'LOGIN_ATTEMPT_LOCKED_ACCOUNT', 'users', null, [
|
||||
'email' => $email,
|
||||
'locked_until' => $lockoutStatus['locked_until']
|
||||
]);
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'Account is temporarily locked due to multiple failed login attempts. Please try again in ' . $lockoutStatus['minutes_remaining'] . ' minutes.'
|
||||
]);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Check recent failed attempts
|
||||
$recentFailedAttempts = countRecentFailedAttempts($email);
|
||||
if ($recentFailedAttempts >= 5) {
|
||||
// Lock account for 15 minutes
|
||||
lockAccount($email, 15);
|
||||
auditLog(null, 'ACCOUNT_LOCKED_THRESHOLD', 'users', null, [
|
||||
'email' => $email,
|
||||
'failed_attempts' => $recentFailedAttempts
|
||||
]);
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'Account locked due to multiple failed login attempts. Please try again in 15 minutes.'
|
||||
]);
|
||||
exit();
|
||||
}
|
||||
|
||||
@@ -120,22 +159,55 @@ if (isset($_POST['email']) && isset($_POST['password'])) {
|
||||
|
||||
// Check if the user is verified
|
||||
if ($row['is_verified'] == 0) {
|
||||
recordLoginAttempt($email, false);
|
||||
auditLog(null, 'LOGIN_ATTEMPT_UNVERIFIED_ACCOUNT', 'users', $row['user_id']);
|
||||
echo json_encode(['status' => 'error', 'message' => 'Your account is not verified. Please check your email for the verification link.']);
|
||||
exit();
|
||||
}
|
||||
|
||||
if (password_verify($password, $row['password'])) {
|
||||
// Record successful attempt
|
||||
recordLoginAttempt($email, true);
|
||||
|
||||
// Regenerate session ID to prevent session fixation attacks
|
||||
session_regenerate_id(true);
|
||||
|
||||
// Password is correct, set up session
|
||||
$_SESSION['user_id'] = $row['user_id']; // Adjust as per your table structure
|
||||
$_SESSION['first_name'] = $row['first_name']; // Adjust as per your table structure
|
||||
$_SESSION['user_id'] = $row['user_id'];
|
||||
$_SESSION['first_name'] = $row['first_name'];
|
||||
$_SESSION['profile_pic'] = $row['profile_pic'];
|
||||
|
||||
// Set session timeout (30 minutes)
|
||||
$_SESSION['login_time'] = time();
|
||||
$_SESSION['session_timeout'] = 1800; // 30 minutes in seconds
|
||||
|
||||
auditLog($row['user_id'], 'LOGIN_SUCCESS', 'users', $row['user_id']);
|
||||
echo json_encode(['status' => 'success', 'message' => 'Successful Login']);
|
||||
} else {
|
||||
// Password is incorrect
|
||||
echo json_encode(['status' => 'error', 'message' => 'Invalid password.']);
|
||||
// Password is incorrect - record failed attempt
|
||||
recordLoginAttempt($email, false);
|
||||
auditLog(null, 'LOGIN_FAILED_INVALID_PASSWORD', 'users', null, ['email' => $email]);
|
||||
|
||||
// Check if this was the threshold failure
|
||||
$newFailureCount = countRecentFailedAttempts($email);
|
||||
if ($newFailureCount >= 5) {
|
||||
lockAccount($email, 15);
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'Too many failed login attempts. Account locked for 15 minutes.'
|
||||
]);
|
||||
} else {
|
||||
$attemptsRemaining = 5 - $newFailureCount;
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'Invalid password. ' . $attemptsRemaining . ' attempts remaining before account lockout.'
|
||||
]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// User does not exist
|
||||
// User does not exist - still record attempt
|
||||
recordLoginAttempt($email, false);
|
||||
auditLog(null, 'LOGIN_FAILED_USER_NOT_FOUND', 'users', null, ['email' => $email]);
|
||||
echo json_encode(['status' => 'error', 'message' => 'User with that email does not exist.']);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user