Plugin Directory

Changeset 3435758


Ignore:
Timestamp:
01/09/2026 09:30:04 AM (3 months ago)
Author:
nhrrob
Message:

Update to version 1.0.4 from GitHub

Location:
nhrrob-secure
Files:
62 added
6 edited
1 copied

Legend:

Unmodified
Added
Removed
  • nhrrob-secure/tags/1.0.4/nhrrob-secure.php

    r3434007 r3435758  
    11<?php
    22/**
    3  * Plugin Name: NHR Secure | Protect Admin, Debug Logs & Limit Logins
     3 * Plugin Name: NHR Secure – Hide Admin, Limit Login & 2FA
    44 * Plugin URI: http://wordpress.org/plugins/nhrrob-secure/
    55 * Description: Lightweight WordPress security plugin that protects your admin area, hides debug logs, and limits login attempts. Minimal code, maximum protection.
    66 * Author: Nazmul Hasan Robin
    77 * Author URI: https://profiles.wordpress.org/nhrrob/
    8  * Version: 1.0.3
     8 * Version: 1.0.4
    99 * Requires at least: 6.0
    1010 * Requires PHP: 7.4
     
    1616if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
    1717
    18 define( 'NHRROB_SECURE_VERSION', '1.0.3' );
    19 define( 'NHRROB_SECURE_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
     18// Load Composer autoloader
     19require_once __DIR__ . '/vendor/autoload.php';
    2020
    2121/**
    22  * Feature List
    23  * 1. Limit Login Attempts
    24  * 2. Custom Login Page (/hidden-access-52w instead of /wp-login.php)
    25  * 3. Protect Sensitive Files (debug.log)
     22 * The main plugin class
    2623 */
     24final class NHRRob_Secure {
    2725
    28  /**
    29  * ============================
    30  * Helper: Get client IP
    31  * ============================
    32  *
    33  * - Default: uses REMOTE_ADDR
    34  * - Enable proxy detection:
    35  *   add_filter( 'nhrrob_secure_enable_proxy_ip', '__return_true' );
    36  */
    37 function nhrrob_secure_get_ip() {
    38     $ip   = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : 'unknown';
     26    /**
     27     * Plugin version
     28     *
     29     * @var string
     30     */
     31    const version = '1.0.4';
    3932
    40     $enable_proxy = apply_filters( 'nhrrob_secure_enable_proxy_ip', false );
     33    /**
     34     * Class constructor
     35     */
     36    private function __construct() {
     37        $this->define_constants();
    4138
    42     if ( $enable_proxy ) {
    43         if ( ! empty( $_SERVER['HTTP_CF_CONNECTING_IP'] ) ) {
    44             $ip = sanitize_text_field( wp_unslash( $_SERVER['HTTP_CF_CONNECTING_IP'] ) );
    45         } elseif ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
    46             $parts = explode( ',', sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) );
    47             $ip = sanitize_text_field( trim( $parts[0] ) );
     39        add_action( 'plugins_loaded', [ $this, 'init_plugin' ] );
     40    }
     41
     42    /**
     43     * Initialize a singleton instance
     44     *
     45     * @return \NHRRob_Secure
     46     */
     47    public static function init() {
     48        static $instance = false;
     49
     50        if ( ! $instance ) {
     51            $instance = new self();
     52        }
     53
     54        return $instance;
     55    }
     56
     57    /**
     58     * Define the required plugin constants
     59     *
     60     * @return void
     61     */
     62    public function define_constants() {
     63        define( 'NHRROB_SECURE_VERSION', self::version );
     64        define( 'NHRROB_SECURE_FILE', __FILE__ );
     65        define( 'NHRROB_SECURE_PATH', __DIR__ );
     66        define( 'NHRROB_SECURE_PLUGIN_DIR', plugin_dir_path( NHRROB_SECURE_FILE ) );
     67        define( 'NHRROB_SECURE_URL', plugins_url( '', NHRROB_SECURE_FILE ) );
     68        define( 'NHRROB_SECURE_ASSETS', NHRROB_SECURE_URL . '/assets' );
     69    }
     70
     71    /**
     72     * Initialize the plugin
     73     *
     74     * @return void
     75     */
     76    public function init_plugin() {
     77       
     78        // Initialize security features
     79        new \NHRRob\Secure\Security();
     80
     81        // Initialize 2FA features
     82        new \NHRRob\Secure\TwoFactor();
     83
     84        // Initialize assets
     85        new \NHRRob\Secure\Assets();
     86
     87        // Initialize REST API
     88        new \NHRRob\Secure\Admin\Api();
     89
     90        // Initialize admin menu
     91        if ( is_admin() ) {
     92            new \NHRRob\Secure\Admin();
    4893        }
    4994    }
    50 
    51     return apply_filters( 'nhrrob_secure_get_ip', $ip );
    5295}
    5396
     97
    5498/**
    55  * ============================
    56  * Helper: Get limit login failed and block transients
    57  * ============================
     99 * Initializes the main plugin
    58100 *
    59  * @param string $username The username of the user.
    60  * @return array The array contains the failed_key, block_key, failed_value, and block_value.
     101 * @return \NHRRob_Secure
    61102 */
    62 function nhrrob_secure_get_limit_login_transients( $username ) {
    63     $ip = nhrrob_secure_get_ip();
    64     $username_clean = is_string( $username ) ? strtolower( sanitize_user( $username, true ) ) : 'unknown';
    65     $md5 = md5( $ip . '|' . $username_clean );
    66 
    67     $failed_key = 'nhrrob_secure_failed_' . $md5;
    68     $failed_value = (int) get_transient( $failed_key );
    69     $block_key = 'nhrrob_secure_block_' . $md5;
    70     $block_value = get_transient( $block_key );
    71    
    72     return [
    73         'failed_key' => $failed_key,
    74         'block_key' => $block_key,
    75         'failed_value' => $failed_value,
    76         'block_value' => $block_value,
    77     ];
     103function nhrrob_secure() {
     104    return NHRRob_Secure::init();
    78105}
    79106
    80 /**
    81  * ============================
    82  * Helper: Render 404 Page
    83  * ============================
    84  */
    85 function nhrrob_secure_render_404() {
    86     wp_safe_redirect( home_url( '404' ) );
    87     exit;
    88 }
    89 
    90 /**
    91  * ============================================================
    92  * 1. LIMIT LOGIN ATTEMPTS (IP + Username)
    93  * ============================================================
    94  *
    95  * Tracks failed login attempts and blocks IP addresses that exceed the limit.
    96  *
    97  * Usage:
    98  * - Change the maximum allowed login attempts:
    99  *   add_filter( 'nhrrob_secure_login_attempts_limit', fn() => 10 );
    100  * - Turn off the feature:
    101  *   add_filter( 'nhrrob_secure_limit_login_attempts', fn() => false );
    102  */
    103 function nhrrob_secure_limit_login_attempts_init() {
    104     if ( ! apply_filters( 'nhrrob_secure_limit_login_attempts', true ) ) {
    105         return;
    106     }
    107 
    108     add_action( 'wp_login_failed', function( $username ) {
    109         $transients = nhrrob_secure_get_limit_login_transients( $username );
    110         $failed_value = (int) $transients['failed_value'] + 1;
    111 
    112         set_transient( $transients['failed_key'], $failed_value, HOUR_IN_SECONDS );
    113 
    114         $limit = (int) apply_filters( 'nhrrob_secure_login_attempts_limit', 5 );
    115 
    116         if ( $failed_value >= $limit ) {
    117             set_transient( $transients['block_key'], true, 2 * HOUR_IN_SECONDS );
    118         }
    119     });
    120 
    121     add_action( 'wp_login', function( $user_login ) {
    122         $transients = nhrrob_secure_get_limit_login_transients( $user_login );
    123         delete_transient( $transients['failed_key'] );
    124         delete_transient( $transients['block_key'] );
    125     });
    126 
    127     add_filter( 'authenticate', function( $user, $username = null, $password = null ) {
    128         $username_clean = is_string( $username ) ? strtolower( sanitize_user( $username, true ) ) : '';
    129         if ( empty( $username_clean ) ) {
    130             return $user;
    131         }
    132 
    133         $transients = nhrrob_secure_get_limit_login_transients( $username_clean );
    134 
    135         if ( $transients['block_value'] ) {
    136             return new WP_Error(
    137                 'nhrrob_secure_blocked',
    138                 __( 'Too many failed login attempts for this account from your IP. Try again later.', 'nhrrob-secure' )
    139             );
    140         }
    141 
    142         return $user;
    143     }, 30, 3 );
    144 }
    145 
    146 add_action( 'init', 'nhrrob_secure_limit_login_attempts_init' );
    147 
    148 /**
    149  * ============================================================
    150  * 2. CUSTOM LOGIN PAGE
    151  * ============================================================
    152  *
    153  * Changes default login URL from /wp-login.php to /hidden-access-52w
    154  *
    155  * Usage:
    156  * - Change the custom login URL:
    157  *   add_filter( 'nhrrob_secure_custom_login_url', fn() => '/my-custom-login' );
    158  * - Turn off the feature:
    159  *   add_filter( 'nhrrob_secure_custom_login_page', '__return_false' );
    160  */
    161 function nhrrob_secure_custom_login_page_init() {
    162     if ( ! apply_filters( 'nhrrob_secure_custom_login_page', true ) ) {
    163         return;
    164     }
    165 
    166     // Block direct access to wp-login.php
    167     $script_name = isset( $_SERVER['SCRIPT_NAME'] ) ? sanitize_text_field( wp_unslash( $_SERVER['SCRIPT_NAME'] ) ) : '';
    168     if ( strpos( $script_name, '/wp-login.php' ) !== false ) {
    169         nhrrob_secure_render_404();
    170     }
    171    
    172     // Block direct access to wp-admin for guests
    173     add_action( 'init', function() {
    174         $script_name = isset( $_SERVER['SCRIPT_NAME'] ) ? sanitize_text_field( wp_unslash( $_SERVER['SCRIPT_NAME'] ) ) : '';
    175        
    176         if ( is_admin() && ! is_user_logged_in() && ! defined( 'DOING_AJAX' ) && ! defined( 'DOING_CRON' ) ) {
    177              // Allow admin-post.php for frontend form submissions
    178              if ( strpos( $script_name, 'admin-post.php' ) === false ) {
    179                  nhrrob_secure_render_404();
    180              }
    181         }
    182     });
    183 
    184     // Handle custom login URL (use template_redirect for proper WordPress context)
    185     add_action( 'template_redirect', function() {
    186         $custom_login_url = apply_filters( 'nhrrob_secure_custom_login_url', '/hidden-access-52w' );
    187         $custom_login_url = trim( $custom_login_url, '/' );
    188         $custom_login_url = '/' . ltrim( $custom_login_url, '/' );
    189 
    190         $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
    191         $parsed_url = wp_parse_url( $request_uri );
    192         $path = isset( $parsed_url['path'] ) ? $parsed_url['path'] : '';
    193 
    194         // Normalize path (remove trailing slash for comparison)
    195         $path_normalized = rtrim( $path, '/' );
    196         $custom_login_url_normalized = rtrim( $custom_login_url, '/' );
    197 
    198         // Check if request is for custom login URL
    199         if ( $path_normalized === $custom_login_url_normalized || $path === $custom_login_url || $path === $custom_login_url . '/' ) {
    200             // Preserve query string
    201             $query_string = isset( $parsed_url['query'] ) ? '?' . $parsed_url['query'] : '';
    202            
    203             // Temporarily modify REQUEST_URI to load wp-login.php
    204             $_SERVER['REQUEST_URI'] = '/wp-login.php' . $query_string;
    205            
    206             // Bring globals into scope for wp-login.php
    207             global $error, $interim_login, $action, $wp_error, $user_login;
    208            
    209             // Override 404 status (since WP thinks this slug doesn't exist)
    210             if ( function_exists( 'status_header' ) ) {
    211                 status_header( 200 );
    212             }
    213             if ( function_exists( 'nocache_headers' ) ) {
    214                 nocache_headers();
    215             }
    216 
    217 
    218            
    219             // Load WordPress login
    220             require_once( ABSPATH . 'wp-login.php' );
    221            
    222 
    223            
    224             exit;
    225         }
    226     }, 1 );
    227 
    228     // Rewrite wp-login.php URLs to custom login URL
    229     add_filter( 'site_url', function( $url, $path, $scheme ) {
    230         if ( strpos( $url, 'wp-login.php' ) !== false ) {
    231             $custom_login_url = apply_filters( 'nhrrob_secure_custom_login_url', '/hidden-access-52w' );
    232             $custom_login_url = trim( $custom_login_url, '/' );
    233             $url = str_replace( 'wp-login.php', $custom_login_url, $url );
    234             $url = str_replace( '//' . $custom_login_url, '/' . $custom_login_url, $url ); // fix potential double slash if any
    235         }
    236         return $url;
    237     }, 10, 3 );
    238 }
    239 
    240 add_action( 'init', 'nhrrob_secure_custom_login_page_init', 0 );
    241 
    242 /**
    243  * ============================================================
    244  * 3. PROTECT DEBUG LOG FILE
    245  * ============================================================
    246  *
    247  * Blocks direct access to /wp-content/debug.log
    248  * Shows 403 Forbidden for all users
    249  *
    250  * Usage:
    251  * - Turn off the feature:
    252  *   add_filter( 'nhrrob_secure_protect_debug_log', '__return_false' );
    253  */
    254 function nhrrob_secure_protect_debug_log_init() {
    255     if ( ! apply_filters( 'nhrrob_secure_protect_debug_log', true ) ) {
    256         return;
    257     }
    258 
    259     // Check early to catch direct file access
    260     add_action( 'plugins_loaded', function() {
    261         $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
    262         $parsed_url = wp_parse_url( $request_uri );
    263         $path = isset( $parsed_url['path'] ) ? $parsed_url['path'] : '';
    264 
    265         // Check if request is for debug.log in wp-content directory
    266         if ( strpos( $path, '/wp-content/debug.log' ) !== false ||
    267              ( strpos( $path, 'debug.log' ) !== false && strpos( $path, 'wp-content' ) !== false ) ) {
    268             if ( function_exists( 'status_header' ) ) {
    269                 status_header( 403 );
    270             } else {
    271                 http_response_code( 403 );
    272             }
    273             if ( function_exists( 'nocache_headers' ) ) {
    274                 nocache_headers();
    275             }
    276             header( 'Content-Type: text/html; charset=utf-8' );
    277             die( '403 Forbidden' );
    278         }
    279     }, 1 );
    280 
    281     // Also check in template_redirect as backup
    282     add_action( 'template_redirect', function() {
    283         $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
    284         $parsed_url = wp_parse_url( $request_uri );
    285         $path = isset( $parsed_url['path'] ) ? $parsed_url['path'] : '';
    286 
    287         // Check if request is for debug.log in wp-content directory
    288         if ( strpos( $path, '/wp-content/debug.log' ) !== false ||
    289              ( strpos( $path, 'debug.log' ) !== false && strpos( $path, 'wp-content' ) !== false ) ) {
    290             status_header( 403 );
    291             nocache_headers();
    292             header( 'Content-Type: text/html; charset=utf-8' );
    293             die( '403 Forbidden' );
    294         }
    295     }, 1 );
    296 }
    297 
    298 add_action( 'init', 'nhrrob_secure_protect_debug_log_init', 0 );
    299 
    300 /**
    301  * Enable/Disable Features
    302  * Example usages are shown below
    303  */
    304 
    305 // Turn off limit login attempts
    306 // add_filter( 'nhrrob_secure_limit_login_attempts', '__return_false' );
    307 
    308 // Turn off custom login page
    309 // add_filter( 'nhrrob_secure_custom_login_page', '__return_false' );
    310 
    311 // Turn off debug log protection
    312 // add_filter( 'nhrrob_secure_protect_debug_log', '__return_false' );
     107// Call the plugin
     108nhrrob_secure();
  • nhrrob-secure/tags/1.0.4/readme.txt

    r3434007 r3435758  
    1 === NHR Secure | Protect Admin, Debug Logs & Limit Logins ===
     1=== NHR Secure – Hide Admin, Limit Login & 2FA ===
    22Contributors: nhrrob
    3 Tags: security, admin, login, debug, protection
     3Tags: security, hide admin, login protection, debug log, 2fa
    44Requires at least: 6.0
    55Tested up to: 6.9
    66Requires PHP: 7.4
    7 Stable tag: 1.0.3
     7Stable tag: 1.0.4
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    1010
    11 A lightweight WordPress security plugin that protects your admin area, hides debug logs, and limits login attempts.
    12 
     11A lightweight WordPress security plugin to protect your admin area with a custom login URL, hide debug logs, limit login attempts, and add 2FA.
    1312== Description ==
    1413
     
    1817- Limit login attempts to prevent brute-force attacks.
    1918- Hide debug logs to prevent sensitive information disclosure.
     19- Add 2FA to your WordPress site.
    2020
    2121**Features at a glance:**
    2222
    23 ### Limit Login Attempts
     23### 🔒 Limit Login Attempts
    2424Stop brute-force attacks by temporarily blocking IPs after repeated failed login attempts.
     25- Configurable attempt limit (1-20, default: 5)
     26- Blocks based on IP + Username combination
     27- Auto-unblock after 2 hours
    2528
    26 ### 🌟 Lightweight & Minimal
    27 Designed to deliver maximum security with minimal code. No complex settings or configuration needed.
     29### 🔐 Custom Login Page
     30Hide wp-login.php and use a custom login URL.
     31- Default custom URL: `/hidden-access-52w`
     32- Blocks direct access to wp-login.php and wp-admin for guests
    2833
    29 ### 💬 Simple & Effective
    30 Install, activate, and your site is protected instantly.
     34### 🛡️ Protect Debug Log File
     35Blocks direct access to `/wp-content/debug.log`
     36- Returns 403 Forbidden for all users
     37
     38### ⚙️ Modern Settings Page
     39Configure everything from a beautiful React-powered interface.
     40- Located under **Tools → NHR Secure**
     41- Enable/disable each feature
     42
     43### 🔐 Two-Factor Authentication (2FA)
     44Enable two-factor authentication for users.
     45- QR code is generated and displayed on the user profile page
     46- User must enter the code from their 2FA app to login
     47
     48### ⚡ Lightweight & Minimal
     49Designed to deliver maximum security with minimal code. No bloat, no complexity.
     50- Compatible with most WordPress themes and plugins.
    3151
    3252== Installation ==
     
    34541. Upload the `nhrrob-secure` plugin folder to your `/wp-content/plugins/` directory.
    35552. Activate the plugin through the 'Plugins' menu in WordPress.
    36 3. Protection starts automatically — no configuration required.
    37 
     563. Navigate to **Tools → NHR Secure** to configure settings.
    3857
    3958== Frequently Asked Questions ==
    4059
     60= How do I access the settings page? =
     61Navigate to **Tools → NHR Secure** in your WordPress admin dashboard.
     62
    4163= Does it limit login attempts? =
    42 Yes. Repeated failed login attempts from the same IP will be temporarily blocked to prevent brute-force attacks.
     64Yes. Repeated failed login attempts from the same IP will be temporarily blocked to prevent brute-force attacks. You can configure the limit (1-20 attempts) from the settings page.
    4365
    44 = Do I need other plugins? =
    45 No. NHR Secure is standalone and works independently.
     66= What is the default custom login URL? =
     67The default custom login URL is `/hidden-access-52w`. You can change this in the settings page under Tools → NHR Secure.
    4668
    47 = Will it affect my site performance? =
    48 No. NHR Secure is lightweight and designed to have minimal impact on your WordPress performance.
     69= How does 2FA work? =
     702FA (Two-Factor Authentication) adds an extra layer of security to your WordPress site. When enabled, users must enter a code from their 2FA app (e.g., Google Authenticator, Authy) in addition to their username and password to log in.
    4971
     72= Can I disable specific features? =
     73Yes. You can enable or disable each feature from the settings page under Tools → NHR Secure.
    5074
    5175== Screenshots ==
     
    53771. Failed login attempts are blocked.
    54782. Custom login page.
    55 3. /wp-login.php or /wp-admin goes to 404.
    56 4. Debug log is hidden.
     793. Debug log is hidden.
     804. Modern React-powered settings page.
     815. Modern React-powered settings page - part 2.
     826. 2FA setup in user profile.
    5783
    5884
    5985== Changelog ==
     86
     87= 1.0.4 - 09/01/2026 =
     88- Added: Modern React-powered settings page under Tools → NHR Secure
     89- Added: Enable/disable all features from admin interface
     90- Added: Configurable login attempts limit (1-20)
     91- Added: Customizable login page URL from settings
     92- Added: Two-factor authentication (2FA) feature
    6093
    6194= 1.0.3 - 05/01/2026 =
  • nhrrob-secure/trunk/nhrrob-secure.php

    r3434007 r3435758  
    11<?php
    22/**
    3  * Plugin Name: NHR Secure | Protect Admin, Debug Logs & Limit Logins
     3 * Plugin Name: NHR Secure – Hide Admin, Limit Login & 2FA
    44 * Plugin URI: http://wordpress.org/plugins/nhrrob-secure/
    55 * Description: Lightweight WordPress security plugin that protects your admin area, hides debug logs, and limits login attempts. Minimal code, maximum protection.
    66 * Author: Nazmul Hasan Robin
    77 * Author URI: https://profiles.wordpress.org/nhrrob/
    8  * Version: 1.0.3
     8 * Version: 1.0.4
    99 * Requires at least: 6.0
    1010 * Requires PHP: 7.4
     
    1616if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
    1717
    18 define( 'NHRROB_SECURE_VERSION', '1.0.3' );
    19 define( 'NHRROB_SECURE_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
     18// Load Composer autoloader
     19require_once __DIR__ . '/vendor/autoload.php';
    2020
    2121/**
    22  * Feature List
    23  * 1. Limit Login Attempts
    24  * 2. Custom Login Page (/hidden-access-52w instead of /wp-login.php)
    25  * 3. Protect Sensitive Files (debug.log)
     22 * The main plugin class
    2623 */
     24final class NHRRob_Secure {
    2725
    28  /**
    29  * ============================
    30  * Helper: Get client IP
    31  * ============================
    32  *
    33  * - Default: uses REMOTE_ADDR
    34  * - Enable proxy detection:
    35  *   add_filter( 'nhrrob_secure_enable_proxy_ip', '__return_true' );
    36  */
    37 function nhrrob_secure_get_ip() {
    38     $ip   = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : 'unknown';
     26    /**
     27     * Plugin version
     28     *
     29     * @var string
     30     */
     31    const version = '1.0.4';
    3932
    40     $enable_proxy = apply_filters( 'nhrrob_secure_enable_proxy_ip', false );
     33    /**
     34     * Class constructor
     35     */
     36    private function __construct() {
     37        $this->define_constants();
    4138
    42     if ( $enable_proxy ) {
    43         if ( ! empty( $_SERVER['HTTP_CF_CONNECTING_IP'] ) ) {
    44             $ip = sanitize_text_field( wp_unslash( $_SERVER['HTTP_CF_CONNECTING_IP'] ) );
    45         } elseif ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
    46             $parts = explode( ',', sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) );
    47             $ip = sanitize_text_field( trim( $parts[0] ) );
     39        add_action( 'plugins_loaded', [ $this, 'init_plugin' ] );
     40    }
     41
     42    /**
     43     * Initialize a singleton instance
     44     *
     45     * @return \NHRRob_Secure
     46     */
     47    public static function init() {
     48        static $instance = false;
     49
     50        if ( ! $instance ) {
     51            $instance = new self();
     52        }
     53
     54        return $instance;
     55    }
     56
     57    /**
     58     * Define the required plugin constants
     59     *
     60     * @return void
     61     */
     62    public function define_constants() {
     63        define( 'NHRROB_SECURE_VERSION', self::version );
     64        define( 'NHRROB_SECURE_FILE', __FILE__ );
     65        define( 'NHRROB_SECURE_PATH', __DIR__ );
     66        define( 'NHRROB_SECURE_PLUGIN_DIR', plugin_dir_path( NHRROB_SECURE_FILE ) );
     67        define( 'NHRROB_SECURE_URL', plugins_url( '', NHRROB_SECURE_FILE ) );
     68        define( 'NHRROB_SECURE_ASSETS', NHRROB_SECURE_URL . '/assets' );
     69    }
     70
     71    /**
     72     * Initialize the plugin
     73     *
     74     * @return void
     75     */
     76    public function init_plugin() {
     77       
     78        // Initialize security features
     79        new \NHRRob\Secure\Security();
     80
     81        // Initialize 2FA features
     82        new \NHRRob\Secure\TwoFactor();
     83
     84        // Initialize assets
     85        new \NHRRob\Secure\Assets();
     86
     87        // Initialize REST API
     88        new \NHRRob\Secure\Admin\Api();
     89
     90        // Initialize admin menu
     91        if ( is_admin() ) {
     92            new \NHRRob\Secure\Admin();
    4893        }
    4994    }
    50 
    51     return apply_filters( 'nhrrob_secure_get_ip', $ip );
    5295}
    5396
     97
    5498/**
    55  * ============================
    56  * Helper: Get limit login failed and block transients
    57  * ============================
     99 * Initializes the main plugin
    58100 *
    59  * @param string $username The username of the user.
    60  * @return array The array contains the failed_key, block_key, failed_value, and block_value.
     101 * @return \NHRRob_Secure
    61102 */
    62 function nhrrob_secure_get_limit_login_transients( $username ) {
    63     $ip = nhrrob_secure_get_ip();
    64     $username_clean = is_string( $username ) ? strtolower( sanitize_user( $username, true ) ) : 'unknown';
    65     $md5 = md5( $ip . '|' . $username_clean );
    66 
    67     $failed_key = 'nhrrob_secure_failed_' . $md5;
    68     $failed_value = (int) get_transient( $failed_key );
    69     $block_key = 'nhrrob_secure_block_' . $md5;
    70     $block_value = get_transient( $block_key );
    71    
    72     return [
    73         'failed_key' => $failed_key,
    74         'block_key' => $block_key,
    75         'failed_value' => $failed_value,
    76         'block_value' => $block_value,
    77     ];
     103function nhrrob_secure() {
     104    return NHRRob_Secure::init();
    78105}
    79106
    80 /**
    81  * ============================
    82  * Helper: Render 404 Page
    83  * ============================
    84  */
    85 function nhrrob_secure_render_404() {
    86     wp_safe_redirect( home_url( '404' ) );
    87     exit;
    88 }
    89 
    90 /**
    91  * ============================================================
    92  * 1. LIMIT LOGIN ATTEMPTS (IP + Username)
    93  * ============================================================
    94  *
    95  * Tracks failed login attempts and blocks IP addresses that exceed the limit.
    96  *
    97  * Usage:
    98  * - Change the maximum allowed login attempts:
    99  *   add_filter( 'nhrrob_secure_login_attempts_limit', fn() => 10 );
    100  * - Turn off the feature:
    101  *   add_filter( 'nhrrob_secure_limit_login_attempts', fn() => false );
    102  */
    103 function nhrrob_secure_limit_login_attempts_init() {
    104     if ( ! apply_filters( 'nhrrob_secure_limit_login_attempts', true ) ) {
    105         return;
    106     }
    107 
    108     add_action( 'wp_login_failed', function( $username ) {
    109         $transients = nhrrob_secure_get_limit_login_transients( $username );
    110         $failed_value = (int) $transients['failed_value'] + 1;
    111 
    112         set_transient( $transients['failed_key'], $failed_value, HOUR_IN_SECONDS );
    113 
    114         $limit = (int) apply_filters( 'nhrrob_secure_login_attempts_limit', 5 );
    115 
    116         if ( $failed_value >= $limit ) {
    117             set_transient( $transients['block_key'], true, 2 * HOUR_IN_SECONDS );
    118         }
    119     });
    120 
    121     add_action( 'wp_login', function( $user_login ) {
    122         $transients = nhrrob_secure_get_limit_login_transients( $user_login );
    123         delete_transient( $transients['failed_key'] );
    124         delete_transient( $transients['block_key'] );
    125     });
    126 
    127     add_filter( 'authenticate', function( $user, $username = null, $password = null ) {
    128         $username_clean = is_string( $username ) ? strtolower( sanitize_user( $username, true ) ) : '';
    129         if ( empty( $username_clean ) ) {
    130             return $user;
    131         }
    132 
    133         $transients = nhrrob_secure_get_limit_login_transients( $username_clean );
    134 
    135         if ( $transients['block_value'] ) {
    136             return new WP_Error(
    137                 'nhrrob_secure_blocked',
    138                 __( 'Too many failed login attempts for this account from your IP. Try again later.', 'nhrrob-secure' )
    139             );
    140         }
    141 
    142         return $user;
    143     }, 30, 3 );
    144 }
    145 
    146 add_action( 'init', 'nhrrob_secure_limit_login_attempts_init' );
    147 
    148 /**
    149  * ============================================================
    150  * 2. CUSTOM LOGIN PAGE
    151  * ============================================================
    152  *
    153  * Changes default login URL from /wp-login.php to /hidden-access-52w
    154  *
    155  * Usage:
    156  * - Change the custom login URL:
    157  *   add_filter( 'nhrrob_secure_custom_login_url', fn() => '/my-custom-login' );
    158  * - Turn off the feature:
    159  *   add_filter( 'nhrrob_secure_custom_login_page', '__return_false' );
    160  */
    161 function nhrrob_secure_custom_login_page_init() {
    162     if ( ! apply_filters( 'nhrrob_secure_custom_login_page', true ) ) {
    163         return;
    164     }
    165 
    166     // Block direct access to wp-login.php
    167     $script_name = isset( $_SERVER['SCRIPT_NAME'] ) ? sanitize_text_field( wp_unslash( $_SERVER['SCRIPT_NAME'] ) ) : '';
    168     if ( strpos( $script_name, '/wp-login.php' ) !== false ) {
    169         nhrrob_secure_render_404();
    170     }
    171    
    172     // Block direct access to wp-admin for guests
    173     add_action( 'init', function() {
    174         $script_name = isset( $_SERVER['SCRIPT_NAME'] ) ? sanitize_text_field( wp_unslash( $_SERVER['SCRIPT_NAME'] ) ) : '';
    175        
    176         if ( is_admin() && ! is_user_logged_in() && ! defined( 'DOING_AJAX' ) && ! defined( 'DOING_CRON' ) ) {
    177              // Allow admin-post.php for frontend form submissions
    178              if ( strpos( $script_name, 'admin-post.php' ) === false ) {
    179                  nhrrob_secure_render_404();
    180              }
    181         }
    182     });
    183 
    184     // Handle custom login URL (use template_redirect for proper WordPress context)
    185     add_action( 'template_redirect', function() {
    186         $custom_login_url = apply_filters( 'nhrrob_secure_custom_login_url', '/hidden-access-52w' );
    187         $custom_login_url = trim( $custom_login_url, '/' );
    188         $custom_login_url = '/' . ltrim( $custom_login_url, '/' );
    189 
    190         $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
    191         $parsed_url = wp_parse_url( $request_uri );
    192         $path = isset( $parsed_url['path'] ) ? $parsed_url['path'] : '';
    193 
    194         // Normalize path (remove trailing slash for comparison)
    195         $path_normalized = rtrim( $path, '/' );
    196         $custom_login_url_normalized = rtrim( $custom_login_url, '/' );
    197 
    198         // Check if request is for custom login URL
    199         if ( $path_normalized === $custom_login_url_normalized || $path === $custom_login_url || $path === $custom_login_url . '/' ) {
    200             // Preserve query string
    201             $query_string = isset( $parsed_url['query'] ) ? '?' . $parsed_url['query'] : '';
    202            
    203             // Temporarily modify REQUEST_URI to load wp-login.php
    204             $_SERVER['REQUEST_URI'] = '/wp-login.php' . $query_string;
    205            
    206             // Bring globals into scope for wp-login.php
    207             global $error, $interim_login, $action, $wp_error, $user_login;
    208            
    209             // Override 404 status (since WP thinks this slug doesn't exist)
    210             if ( function_exists( 'status_header' ) ) {
    211                 status_header( 200 );
    212             }
    213             if ( function_exists( 'nocache_headers' ) ) {
    214                 nocache_headers();
    215             }
    216 
    217 
    218            
    219             // Load WordPress login
    220             require_once( ABSPATH . 'wp-login.php' );
    221            
    222 
    223            
    224             exit;
    225         }
    226     }, 1 );
    227 
    228     // Rewrite wp-login.php URLs to custom login URL
    229     add_filter( 'site_url', function( $url, $path, $scheme ) {
    230         if ( strpos( $url, 'wp-login.php' ) !== false ) {
    231             $custom_login_url = apply_filters( 'nhrrob_secure_custom_login_url', '/hidden-access-52w' );
    232             $custom_login_url = trim( $custom_login_url, '/' );
    233             $url = str_replace( 'wp-login.php', $custom_login_url, $url );
    234             $url = str_replace( '//' . $custom_login_url, '/' . $custom_login_url, $url ); // fix potential double slash if any
    235         }
    236         return $url;
    237     }, 10, 3 );
    238 }
    239 
    240 add_action( 'init', 'nhrrob_secure_custom_login_page_init', 0 );
    241 
    242 /**
    243  * ============================================================
    244  * 3. PROTECT DEBUG LOG FILE
    245  * ============================================================
    246  *
    247  * Blocks direct access to /wp-content/debug.log
    248  * Shows 403 Forbidden for all users
    249  *
    250  * Usage:
    251  * - Turn off the feature:
    252  *   add_filter( 'nhrrob_secure_protect_debug_log', '__return_false' );
    253  */
    254 function nhrrob_secure_protect_debug_log_init() {
    255     if ( ! apply_filters( 'nhrrob_secure_protect_debug_log', true ) ) {
    256         return;
    257     }
    258 
    259     // Check early to catch direct file access
    260     add_action( 'plugins_loaded', function() {
    261         $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
    262         $parsed_url = wp_parse_url( $request_uri );
    263         $path = isset( $parsed_url['path'] ) ? $parsed_url['path'] : '';
    264 
    265         // Check if request is for debug.log in wp-content directory
    266         if ( strpos( $path, '/wp-content/debug.log' ) !== false ||
    267              ( strpos( $path, 'debug.log' ) !== false && strpos( $path, 'wp-content' ) !== false ) ) {
    268             if ( function_exists( 'status_header' ) ) {
    269                 status_header( 403 );
    270             } else {
    271                 http_response_code( 403 );
    272             }
    273             if ( function_exists( 'nocache_headers' ) ) {
    274                 nocache_headers();
    275             }
    276             header( 'Content-Type: text/html; charset=utf-8' );
    277             die( '403 Forbidden' );
    278         }
    279     }, 1 );
    280 
    281     // Also check in template_redirect as backup
    282     add_action( 'template_redirect', function() {
    283         $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
    284         $parsed_url = wp_parse_url( $request_uri );
    285         $path = isset( $parsed_url['path'] ) ? $parsed_url['path'] : '';
    286 
    287         // Check if request is for debug.log in wp-content directory
    288         if ( strpos( $path, '/wp-content/debug.log' ) !== false ||
    289              ( strpos( $path, 'debug.log' ) !== false && strpos( $path, 'wp-content' ) !== false ) ) {
    290             status_header( 403 );
    291             nocache_headers();
    292             header( 'Content-Type: text/html; charset=utf-8' );
    293             die( '403 Forbidden' );
    294         }
    295     }, 1 );
    296 }
    297 
    298 add_action( 'init', 'nhrrob_secure_protect_debug_log_init', 0 );
    299 
    300 /**
    301  * Enable/Disable Features
    302  * Example usages are shown below
    303  */
    304 
    305 // Turn off limit login attempts
    306 // add_filter( 'nhrrob_secure_limit_login_attempts', '__return_false' );
    307 
    308 // Turn off custom login page
    309 // add_filter( 'nhrrob_secure_custom_login_page', '__return_false' );
    310 
    311 // Turn off debug log protection
    312 // add_filter( 'nhrrob_secure_protect_debug_log', '__return_false' );
     107// Call the plugin
     108nhrrob_secure();
  • nhrrob-secure/trunk/readme.txt

    r3434007 r3435758  
    1 === NHR Secure | Protect Admin, Debug Logs & Limit Logins ===
     1=== NHR Secure – Hide Admin, Limit Login & 2FA ===
    22Contributors: nhrrob
    3 Tags: security, admin, login, debug, protection
     3Tags: security, hide admin, login protection, debug log, 2fa
    44Requires at least: 6.0
    55Tested up to: 6.9
    66Requires PHP: 7.4
    7 Stable tag: 1.0.3
     7Stable tag: 1.0.4
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    1010
    11 A lightweight WordPress security plugin that protects your admin area, hides debug logs, and limits login attempts.
    12 
     11A lightweight WordPress security plugin to protect your admin area with a custom login URL, hide debug logs, limit login attempts, and add 2FA.
    1312== Description ==
    1413
     
    1817- Limit login attempts to prevent brute-force attacks.
    1918- Hide debug logs to prevent sensitive information disclosure.
     19- Add 2FA to your WordPress site.
    2020
    2121**Features at a glance:**
    2222
    23 ### Limit Login Attempts
     23### 🔒 Limit Login Attempts
    2424Stop brute-force attacks by temporarily blocking IPs after repeated failed login attempts.
     25- Configurable attempt limit (1-20, default: 5)
     26- Blocks based on IP + Username combination
     27- Auto-unblock after 2 hours
    2528
    26 ### 🌟 Lightweight & Minimal
    27 Designed to deliver maximum security with minimal code. No complex settings or configuration needed.
     29### 🔐 Custom Login Page
     30Hide wp-login.php and use a custom login URL.
     31- Default custom URL: `/hidden-access-52w`
     32- Blocks direct access to wp-login.php and wp-admin for guests
    2833
    29 ### 💬 Simple & Effective
    30 Install, activate, and your site is protected instantly.
     34### 🛡️ Protect Debug Log File
     35Blocks direct access to `/wp-content/debug.log`
     36- Returns 403 Forbidden for all users
     37
     38### ⚙️ Modern Settings Page
     39Configure everything from a beautiful React-powered interface.
     40- Located under **Tools → NHR Secure**
     41- Enable/disable each feature
     42
     43### 🔐 Two-Factor Authentication (2FA)
     44Enable two-factor authentication for users.
     45- QR code is generated and displayed on the user profile page
     46- User must enter the code from their 2FA app to login
     47
     48### ⚡ Lightweight & Minimal
     49Designed to deliver maximum security with minimal code. No bloat, no complexity.
     50- Compatible with most WordPress themes and plugins.
    3151
    3252== Installation ==
     
    34541. Upload the `nhrrob-secure` plugin folder to your `/wp-content/plugins/` directory.
    35552. Activate the plugin through the 'Plugins' menu in WordPress.
    36 3. Protection starts automatically — no configuration required.
    37 
     563. Navigate to **Tools → NHR Secure** to configure settings.
    3857
    3958== Frequently Asked Questions ==
    4059
     60= How do I access the settings page? =
     61Navigate to **Tools → NHR Secure** in your WordPress admin dashboard.
     62
    4163= Does it limit login attempts? =
    42 Yes. Repeated failed login attempts from the same IP will be temporarily blocked to prevent brute-force attacks.
     64Yes. Repeated failed login attempts from the same IP will be temporarily blocked to prevent brute-force attacks. You can configure the limit (1-20 attempts) from the settings page.
    4365
    44 = Do I need other plugins? =
    45 No. NHR Secure is standalone and works independently.
     66= What is the default custom login URL? =
     67The default custom login URL is `/hidden-access-52w`. You can change this in the settings page under Tools → NHR Secure.
    4668
    47 = Will it affect my site performance? =
    48 No. NHR Secure is lightweight and designed to have minimal impact on your WordPress performance.
     69= How does 2FA work? =
     702FA (Two-Factor Authentication) adds an extra layer of security to your WordPress site. When enabled, users must enter a code from their 2FA app (e.g., Google Authenticator, Authy) in addition to their username and password to log in.
    4971
     72= Can I disable specific features? =
     73Yes. You can enable or disable each feature from the settings page under Tools → NHR Secure.
    5074
    5175== Screenshots ==
     
    53771. Failed login attempts are blocked.
    54782. Custom login page.
    55 3. /wp-login.php or /wp-admin goes to 404.
    56 4. Debug log is hidden.
     793. Debug log is hidden.
     804. Modern React-powered settings page.
     815. Modern React-powered settings page - part 2.
     826. 2FA setup in user profile.
    5783
    5884
    5985== Changelog ==
     86
     87= 1.0.4 - 09/01/2026 =
     88- Added: Modern React-powered settings page under Tools → NHR Secure
     89- Added: Enable/disable all features from admin interface
     90- Added: Configurable login attempts limit (1-20)
     91- Added: Customizable login page URL from settings
     92- Added: Two-factor authentication (2FA) feature
    6093
    6194= 1.0.3 - 05/01/2026 =
Note: See TracChangeset for help on using the changeset viewer.