# AutoBooking Security — Plan de Implementación
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Implementar seguridad anti-spam, anti-ghost-registration, rate limiting, IP geolocation backup, documentos de conductor, y eliminar la API key de Google Maps del código fuente.
**Architecture:** Plugin nuevo `autobooking-security` centraliza rate limiting e IP blocking. Plugins existentes `autobooking-admin-dashboard` y `autobooking-geo-restrict` se modifican quirúrgicamente. Snippets #14, #15, #18, #19 NO se tocan.
**Tech Stack:** PHP 8.x, WordPress REST API, MariaDB, hCaptcha (hcaptcha.com), ip-api.com, WordPress transients.
**Servidor:** `pi@ssh.famromdon.online:2230` pw `Geronimo6&8` | Container: `autobookingonline-wordpress-autobooking-1` | Plugins: `/var/www/html/wp-content/plugins/` | Local: `D:\Proyectos Software\AutoBooking\`
**Deploy estándar** (usar en cada tarea):
```powershell
$psftp = "C:\Program Files\PuTTY\psftp.exe"
$plink = "C:\Program Files\PuTTY\plink.exe"
$cmds = "$env:TEMP\sftp_cmd.txt"
# 1. subir archivo local → /tmp/archivo.php
[System.IO.File]::WriteAllText($cmds, "put `"D:\Proyectos Software\AutoBooking\ARCHIVO.php`" /tmp/ARCHIVO.php`nbye", (New-Object System.Text.UTF8Encoding $false))
& $psftp -pw "Geronimo6&8" -P 2230 -b $cmds pi@ssh.famromdon.online
# 2. mover al container
echo "y" | & $plink -pw "Geronimo6&8" -P 2230 pi@ssh.famromdon.online 'docker cp /tmp/ARCHIVO.php autobookingonline-wordpress-autobooking-1:/var/www/html/wp-content/plugins/PLUGIN/ARCHIVO.php'
```
---
## Prerequisito manual (ANTES de empezar)
- [ ] Ir a **hcaptcha.com** → crear cuenta gratuita → crear sitio `autobooking.online` → copiar **Site Key** y **Secret Key**
- [ ] **Google Cloud Console** → Credenciales → key `AIzaSyD3VSlYZvDEbbSKUhEFRdUD5rU1JWXX03Q` → agregar restricción HTTP referrer: `*.autobooking.online/*`
---
## Tarea 1: Plugin autobooking-security — Tablas DB + Rate Limiting
**Archivos:** Crear `autobooking-security.php` → subir a `/var/www/html/wp-content/plugins/autobooking-security/autobooking-security.php`
- [ ] **1.1 Crear el archivo del plugin** en `D:\Proyectos Software\AutoBooking\autobooking-security.php`:
```php
get_charset_collate();
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta( "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}ab_rate_limits (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
ip VARCHAR(45) NOT NULL DEFAULT '',
endpoint VARCHAR(80) NOT NULL DEFAULT '',
hits INT UNSIGNED NOT NULL DEFAULT 1,
window_start DATETIME NOT NULL,
PRIMARY KEY (id), KEY ip_ep_win (ip, endpoint, window_start)
) $charset;" );
dbDelta( "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}ab_blocked_ips (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
ip VARCHAR(45) NOT NULL DEFAULT '',
reason VARCHAR(120) NOT NULL DEFAULT '',
blocked_at DATETIME NOT NULL,
expires_at DATETIME NOT NULL,
PRIMARY KEY (id), UNIQUE KEY ip (ip), KEY ip_exp (ip, expires_at)
) $charset;" );
dbDelta( "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}ab_driver_documents (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
driver_id BIGINT UNSIGNED NOT NULL,
doc_type ENUM('license','insurance','vehicle_photo') NOT NULL,
file_url VARCHAR(512) NOT NULL DEFAULT '',
expiry_date DATE NULL,
status ENUM('pending','approved','rejected') NOT NULL DEFAULT 'pending',
reviewed_by BIGINT UNSIGNED NULL,
reviewed_at DATETIME NULL,
created_at DATETIME NOT NULL,
PRIMARY KEY (id), KEY driver_doc (driver_id, doc_type), KEY status (status)
) $charset;" );
}
/* ── HELPERS ──────────────────────────────────────────────── */
function abs_get_ip() {
$ip = '';
if ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
$parts = explode( ',', $_SERVER['HTTP_X_FORWARDED_FOR'] );
$ip = trim( $parts[0] );
}
if ( ! filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) ) {
$ip = $_SERVER['REMOTE_ADDR'] ?? '';
}
return sanitize_text_field( $ip );
}
function abs_is_whitelisted( $ip ) {
$raw = get_option( 'ab_sec_whitelist', '' );
if ( ! $raw ) return false;
return in_array( $ip, array_map( 'trim', explode( "\n", $raw ) ), true );
}
function abs_is_blocked( $ip ) {
global $wpdb;
return (bool) $wpdb->get_var( $wpdb->prepare(
"SELECT id FROM {$wpdb->prefix}ab_blocked_ips WHERE ip = %s AND expires_at > %s",
$ip, current_time( 'mysql' )
) );
}
function abs_block_ip( $ip, $reason, $hours ) {
global $wpdb;
$wpdb->query( $wpdb->prepare(
"INSERT INTO {$wpdb->prefix}ab_blocked_ips (ip, reason, blocked_at, expires_at) VALUES (%s,%s,%s,%s)
ON DUPLICATE KEY UPDATE reason=VALUES(reason), blocked_at=VALUES(blocked_at), expires_at=VALUES(expires_at)",
$ip, $reason, current_time('mysql'), date('Y-m-d H:i:s', time() + $hours * 3600)
) );
}
function abs_check_rate( $ip, $endpoint, $limit, $window_sec, $block_hours = 0 ) {
if ( ! $ip || abs_is_whitelisted( $ip ) ) return true;
if ( abs_is_blocked( $ip ) ) return false;
global $wpdb; $table = $wpdb->prefix . 'ab_rate_limits';
$window = date( 'Y-m-d H:i:s', time() - $window_sec );
$hits = (int) $wpdb->get_var( $wpdb->prepare(
"SELECT SUM(hits) FROM $table WHERE ip=%s AND endpoint=%s AND window_start>=%s",
$ip, $endpoint, $window
) );
if ( $hits >= $limit ) {
if ( $block_hours > 0 ) abs_block_ip( $ip, "Rate limit: $endpoint", $block_hours );
return false;
}
$minute = date( 'Y-m-d H:i:00' );
$exists = $wpdb->get_var( $wpdb->prepare(
"SELECT id FROM $table WHERE ip=%s AND endpoint=%s AND window_start=%s",
$ip, $endpoint, $minute
) );
if ( $exists ) {
$wpdb->query( $wpdb->prepare(
"UPDATE $table SET hits=hits+1 WHERE ip=%s AND endpoint=%s AND window_start=%s",
$ip, $endpoint, $minute
) );
} else {
$wpdb->insert( $table, ['ip'=>$ip,'endpoint'=>$endpoint,'hits'=>1,'window_start'=>$minute] );
}
return true;
}
/* ── RATE LIMITING: login ─────────────────────────────────── */
add_action( 'wp_login_failed', function( $u ) {
abs_check_rate( abs_get_ip(), 'login', 10, 900, 2 );
} );
add_action( 'login_init', function() {
if ( abs_is_blocked( abs_get_ip() ) )
wp_die( 'Tu IP fue bloqueada temporalmente por demasiados intentos fallidos.', 'Bloqueado', ['response'=>429] );
} );
/* ── RATE LIMITING: registro ──────────────────────────────── */
add_filter( 'registration_errors', function( $errors, $login, $email ) {
if ( ! abs_check_rate( abs_get_ip(), 'register', 5, 3600, 24 ) )
$errors->add( 'rate_limit', 'Error: Demasiados intentos desde tu IP. Intenta más tarde.' );
return $errors;
}, 10, 3 );
/* ── RATE LIMITING: REST ──────────────────────────────────── */
add_filter( 'rest_dispatch_request', function( $result, $request, $route, $handler ) {
if ( strpos( $route, '/autobooking/v1/' ) === false ) return $result;
if ( $request->get_method() === 'GET' ) return $result;
if ( ! abs_check_rate( abs_get_ip(), 'rest_post', 120, 60, 0 ) )
return new WP_Error( 'rate_limited', 'Demasiadas solicitudes.', ['status'=>429] );
return $result;
}, 10, 4 );
/* ── LIMPIEZA DIARIA ──────────────────────────────────────── */
add_action( 'abs_daily_cleanup', function() {
global $wpdb;
$wpdb->query("DELETE FROM {$wpdb->prefix}ab_rate_limits WHERE window_start < DATE_SUB(NOW(), INTERVAL 2 DAY)");
$wpdb->query("DELETE FROM {$wpdb->prefix}ab_blocked_ips WHERE expires_at < NOW()");
} );
if ( ! wp_next_scheduled('abs_daily_cleanup') ) wp_schedule_event( time(), 'daily', 'abs_daily_cleanup' );
/* ── HCAPTCHA ─────────────────────────────────────────────── */
add_action( 'register_form', function() {
$key = get_option('ab_hcaptcha_site_key','');
if ( ! $key ) return;
echo '
';
echo '';
} );
add_filter( 'registration_errors', function( $errors, $login, $email ) {
$secret = get_option('ab_hcaptcha_secret','');
if ( ! $secret ) return $errors;
$token = sanitize_text_field( $_POST['h-captcha-response'] ?? '' );
if ( ! $token ) { $errors->add('captcha_missing','Error: Completa el captcha.'); return $errors; }
$res = wp_remote_post('https://api.hcaptcha.com/siteverify', ['body'=>['secret'=>$secret,'response'=>$token,'remoteip'=>abs_get_ip()],'timeout'=>10]);
if ( ! is_wp_error($res) ) {
$body = json_decode(wp_remote_retrieve_body($res), true);
if ( empty($body['success']) ) $errors->add('captcha_invalid','Error: Captcha inválido.');
}
return $errors;
}, 20, 3 );
/* ── EMAIL CONFIRMATION ───────────────────────────────────── */
add_action( 'user_register', function( $uid ) {
$user = get_userdata($uid);
if ( ! $user || ! in_array('driver_pending',(array)$user->roles,true) ) return;
$token = bin2hex(random_bytes(32));
update_user_meta($uid,'ab_email_token',$token);
update_user_meta($uid,'ab_email_token_expires',date('Y-m-d H:i:s',time()+86400));
update_user_meta($uid,'ab_email_confirmed',0);
$url = add_query_arg('token',$token,get_rest_url(null,'autobooking/v1/confirm-email'));
wp_mail($user->user_email,'Confirma tu email — AutoBooking',
"Hola {$user->display_name},\n\nConfirma tu email (válido 24h):\n\n$url\n\nAutoBooking");
} );
add_action( 'template_redirect', function() {
if ( ! is_page('driver-dashboard') && ! is_page('pending-driver') ) return;
if ( ! is_user_logged_in() ) return;
$user = wp_get_current_user(); $roles = (array)$user->roles;
if ( ! in_array('driver_pending',$roles,true) && ! in_array('driver',$roles,true) ) return;
if ( (int)get_user_meta($user->ID,'ab_email_confirmed',true) ) return;
$resend = esc_url(get_rest_url(null,'autobooking/v1/resend-confirmation'));
wp_die(''
.'
Confirma tu email
'
.'
Revisa tu bandeja de entrada y haz clic en el enlace de confirmación.
'
.'
','Confirma tu email',['response'=>403]);
} );
/* ── REST: confirm-email, resend, docs, admin IPs ─────────── */
add_action( 'rest_api_init', function() {
register_rest_route('autobooking/v1','/confirm-email',[
'methods'=>'GET','permission_callback'=>'__return_true',
'callback'=>function(WP_REST_Request $req){
global $wpdb;
$token = sanitize_text_field($req->get_param('token'));
if(!$token){wp_redirect(home_url('/?ab_confirm=invalid'));exit;}
$uid = $wpdb->get_var($wpdb->prepare(
"SELECT user_id FROM {$wpdb->usermeta} WHERE meta_key='ab_email_token' AND meta_value=%s LIMIT 1",$token));
if(!$uid){wp_redirect(home_url('/?ab_confirm=invalid'));exit;}
if(strtotime(get_user_meta((int)$uid,'ab_email_token_expires',true))