feat: AutoBooking initial commit — PHP WordPress Plugin (REST API, wpdb, WP_User_Query)
This commit is contained in:
@@ -0,0 +1,583 @@
|
||||
# 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
|
||||
<?php
|
||||
/**
|
||||
* Plugin Name: AutoBooking Security
|
||||
* Description: Rate limiting, IP blocking, hCaptcha, email confirmation, driver documents.
|
||||
* Version: 1.0.0
|
||||
* Author: AutoBooking
|
||||
*/
|
||||
if ( ! defined( 'ABSPATH' ) ) exit;
|
||||
|
||||
/* ── ACTIVACIÓN: crea tablas ──────────────────────────────── */
|
||||
register_activation_hook( __FILE__, 'abs_activate' );
|
||||
function abs_activate() {
|
||||
global $wpdb; $charset = $wpdb->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', '<strong>Error:</strong> 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 '<div class="h-captcha" data-sitekey="'.esc_attr($key).'" style="margin:12px 0;"></div>';
|
||||
echo '<script src="https://js.hcaptcha.com/1/api.js" async defer></script>';
|
||||
} );
|
||||
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','<strong>Error:</strong> 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','<strong>Error:</strong> 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('<div style="font-family:system-ui;max-width:500px;margin:60px auto;text-align:center;padding:32px;background:#111;color:#fff;border-radius:16px;">'
|
||||
.'<p style="font-size:20px;font-weight:700;color:#FF6F00;">Confirma tu email</p>'
|
||||
.'<p style="color:rgba(255,255,255,.7);">Revisa tu bandeja de entrada y haz clic en el enlace de confirmación.</p>'
|
||||
.'<form method="post" action="'.$resend.'">'.wp_nonce_field('wp_rest','_wpnonce',true,false)
|
||||
.'<button type="submit" style="margin-top:16px;background:#FF6F00;color:#fff;border:none;padding:12px 28px;border-radius:10px;font-weight:700;cursor:pointer;">Reenviar email</button>'
|
||||
.'</form></div>','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))<time()){
|
||||
wp_redirect(home_url('/?ab_confirm=expired'));exit;}
|
||||
update_user_meta((int)$uid,'ab_email_confirmed',1);
|
||||
delete_user_meta((int)$uid,'ab_email_token');
|
||||
delete_user_meta((int)$uid,'ab_email_token_expires');
|
||||
wp_redirect(home_url('/pending-driver/?ab_confirm=ok'));exit;
|
||||
},
|
||||
]);
|
||||
|
||||
register_rest_route('autobooking/v1','/resend-confirmation',[
|
||||
'methods'=>'POST','permission_callback'=>'is_user_logged_in',
|
||||
'callback'=>function(){
|
||||
$user=wp_get_current_user(); $token=bin2hex(random_bytes(32));
|
||||
update_user_meta($user->ID,'ab_email_token',$token);
|
||||
update_user_meta($user->ID,'ab_email_token_expires',date('Y-m-d H:i:s',time()+86400));
|
||||
$url=add_query_arg('token',$token,get_rest_url(null,'autobooking/v1/confirm-email'));
|
||||
wp_mail($user->user_email,'Confirma tu email — AutoBooking',"Nuevo enlace (válido 24h):\n\n$url");
|
||||
wp_redirect(home_url('/pending-driver/?ab_confirm=resent'));exit;
|
||||
},
|
||||
]);
|
||||
|
||||
register_rest_route('autobooking/v1','/driver/upload-doc',[
|
||||
'methods'=>'POST','permission_callback'=>'is_user_logged_in',
|
||||
'callback'=>function(WP_REST_Request $req){
|
||||
global $wpdb; $user=wp_get_current_user();
|
||||
$doc_type=sanitize_text_field($req->get_param('doc_type'));
|
||||
if(!in_array($doc_type,['license','insurance','vehicle_photo'],true))
|
||||
return new WP_Error('invalid_type','Tipo inválido.',['status'=>400]);
|
||||
if(empty($_FILES['file'])) return new WP_Error('no_file','Sin archivo.',['status'=>400]);
|
||||
$file=$_FILES['file'];
|
||||
if(!in_array($file['type'],['image/jpeg','image/png','application/pdf'],true))
|
||||
return new WP_Error('invalid_mime','Solo JPG, PNG o PDF.',['status'=>400]);
|
||||
if($file['size']>5*1024*1024) return new WP_Error('file_too_large','Máx 5 MB.',['status'=>400]);
|
||||
require_once ABSPATH.'wp-admin/includes/file.php';
|
||||
$up=wp_upload_dir(); $dir=$up['basedir'].'/ab-driver-docs/'.$user->ID;
|
||||
wp_mkdir_p($dir);
|
||||
$ext=pathinfo($file['name'],PATHINFO_EXTENSION);
|
||||
$fname=$doc_type.'_'.time().'.'.$ext;
|
||||
if(!move_uploaded_file($file['tmp_name'],$dir.'/'.$fname))
|
||||
return new WP_Error('upload_failed','Error al guardar.',['status'=>500]);
|
||||
$url=$up['baseurl'].'/ab-driver-docs/'.$user->ID.'/'.$fname;
|
||||
$data=['driver_id'=>$user->ID,'doc_type'=>$doc_type,'file_url'=>$url,'status'=>'pending','created_at'=>current_time('mysql')];
|
||||
$exists=$wpdb->get_var($wpdb->prepare(
|
||||
"SELECT id FROM {$wpdb->prefix}ab_driver_documents WHERE driver_id=%d AND doc_type=%s",$user->ID,$doc_type));
|
||||
if($exists) $wpdb->update("{$wpdb->prefix}ab_driver_documents",$data,['driver_id'=>$user->ID,'doc_type'=>$doc_type]);
|
||||
else $wpdb->insert("{$wpdb->prefix}ab_driver_documents",$data);
|
||||
return rest_ensure_response(['ok'=>true,'file_url'=>$url]);
|
||||
},
|
||||
]);
|
||||
|
||||
register_rest_route('autobooking/v1','/driver/my-docs',[
|
||||
'methods'=>'GET','permission_callback'=>'is_user_logged_in',
|
||||
'callback'=>function(){
|
||||
global $wpdb; $uid=get_current_user_id();
|
||||
return rest_ensure_response(['docs'=>$wpdb->get_results($wpdb->prepare(
|
||||
"SELECT doc_type,file_url,status,expiry_date FROM {$wpdb->prefix}ab_driver_documents WHERE driver_id=%d",$uid),ARRAY_A)?:[]]);
|
||||
},
|
||||
]);
|
||||
|
||||
register_rest_route('autobooking/v1','/admin/driver/(?P<id>\d+)/docs',[
|
||||
'methods'=>'GET','permission_callback'=>function(){return current_user_can('manage_autobooking');},
|
||||
'callback'=>function(WP_REST_Request $req){
|
||||
global $wpdb; $id=absint($req['id']);
|
||||
return rest_ensure_response(['docs'=>$wpdb->get_results($wpdb->prepare(
|
||||
"SELECT * FROM {$wpdb->prefix}ab_driver_documents WHERE driver_id=%d",$id),ARRAY_A)?:[]]);
|
||||
},
|
||||
]);
|
||||
|
||||
register_rest_route('autobooking/v1','/admin/doc/(?P<id>\d+)/review',[
|
||||
'methods'=>'POST','permission_callback'=>function(){return current_user_can('manage_autobooking');},
|
||||
'callback'=>function(WP_REST_Request $req){
|
||||
global $wpdb; $id=absint($req['id']);
|
||||
$status=in_array($req->get_param('status'),['approved','rejected'],true)?$req->get_param('status'):'pending';
|
||||
$wpdb->update("{$wpdb->prefix}ab_driver_documents",
|
||||
['status'=>$status,'reviewed_by'=>get_current_user_id(),'reviewed_at'=>current_time('mysql')],['id'=>$id]);
|
||||
return rest_ensure_response(['ok'=>true]);
|
||||
},
|
||||
]);
|
||||
|
||||
register_rest_route('autobooking/v1','/admin/blocked-ips',[
|
||||
'methods'=>'GET','permission_callback'=>function(){return current_user_can('manage_autobooking');},
|
||||
'callback'=>function(){
|
||||
global $wpdb;
|
||||
return rest_ensure_response([
|
||||
'blocked'=>$wpdb->get_results("SELECT id,ip,reason,blocked_at,expires_at FROM {$wpdb->prefix}ab_blocked_ips WHERE expires_at>NOW() ORDER BY blocked_at DESC LIMIT 100",ARRAY_A)?:[],
|
||||
'blocked_24h'=>(int)$wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}ab_blocked_ips WHERE blocked_at>=DATE_SUB(NOW(),INTERVAL 24 HOUR)"),
|
||||
]);
|
||||
},
|
||||
]);
|
||||
|
||||
register_rest_route('autobooking/v1','/admin/blocked-ips/(?P<id>\d+)/unblock',[
|
||||
'methods'=>'POST','permission_callback'=>function(){return current_user_can('manage_autobooking');},
|
||||
'callback'=>function(WP_REST_Request $req){
|
||||
global $wpdb; $wpdb->delete("{$wpdb->prefix}ab_blocked_ips",['id'=>absint($req['id'])]);
|
||||
return rest_ensure_response(['ok'=>true]);
|
||||
},
|
||||
]);
|
||||
|
||||
register_rest_route('autobooking/v1','/admin/security-settings/save',[
|
||||
'methods'=>'POST','permission_callback'=>function(){return current_user_can('manage_autobooking');},
|
||||
'callback'=>function(WP_REST_Request $req){
|
||||
$body=$req->get_json_params();
|
||||
if(isset($body['whitelist'])) update_option('ab_sec_whitelist',sanitize_textarea_field($body['whitelist']));
|
||||
if(isset($body['hcaptcha_site_key'])) update_option('ab_hcaptcha_site_key',sanitize_text_field($body['hcaptcha_site_key']));
|
||||
if(isset($body['hcaptcha_secret'])) update_option('ab_hcaptcha_secret',sanitize_text_field($body['hcaptcha_secret']));
|
||||
return rest_ensure_response(['ok'=>true]);
|
||||
},
|
||||
]);
|
||||
} );
|
||||
```
|
||||
|
||||
- [ ] **1.2 Subir y activar el plugin**
|
||||
|
||||
```powershell
|
||||
$psftp = "C:\Program Files\PuTTY\psftp.exe"; $plink = "C:\Program Files\PuTTY\plink.exe"
|
||||
$cmds = "$env:TEMP\sftp1.txt"
|
||||
[System.IO.File]::WriteAllText($cmds, "put `"D:\Proyectos Software\AutoBooking\autobooking-security.php`" /tmp/abs.php`nbye", (New-Object System.Text.UTF8Encoding $false))
|
||||
& $psftp -pw "Geronimo6&8" -P 2230 -b $cmds pi@ssh.famromdon.online
|
||||
echo "y" | & $plink -pw "Geronimo6&8" -P 2230 pi@ssh.famromdon.online 'mkdir -p /tmp/abs_plugin && cp /tmp/abs.php /tmp/abs_plugin/autobooking-security.php && docker exec autobookingonline-wordpress-autobooking-1 mkdir -p /var/www/html/wp-content/plugins/autobooking-security && docker cp /tmp/abs_plugin/autobooking-security.php autobookingonline-wordpress-autobooking-1:/var/www/html/wp-content/plugins/autobooking-security/autobooking-security.php'
|
||||
```
|
||||
|
||||
- [ ] **1.3 Activar desde WP Admin** → `https://autobooking.online/wp-admin/plugins.php` → Activar "AutoBooking Security"
|
||||
|
||||
- [ ] **1.4 Verificar tablas creadas**
|
||||
|
||||
```powershell
|
||||
echo "y" | & $plink -pw "Geronimo6&8" -P 2230 pi@ssh.famromdon.online 'docker exec autobookingonline-wordpress-autobooking-1 php -r "require \"/var/www/html/wp-load.php\"; global \$wpdb; foreach([\"ab_rate_limits\",\"ab_blocked_ips\",\"ab_driver_documents\"] as \$t){ echo \$wpdb->get_var(\"SHOW TABLES LIKE \\\"{$wpdb->prefix}\$t\\\"\") ? \"OK \$t\n\" : \"MISSING \$t\n\"; }"'
|
||||
```
|
||||
Esperado: `OK ab_rate_limits`, `OK ab_blocked_ips`, `OK ab_driver_documents`
|
||||
|
||||
- [ ] **1.5 Commit**
|
||||
```bash
|
||||
git add autobooking-security.php && git commit -m "feat: autobooking-security plugin — rate limiting, hCaptcha, email confirmation, driver docs"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tarea 2: Eliminar GMAPS_KEY del código fuente
|
||||
|
||||
**Archivos:** Modificar `autobooking-admin-dashboard.php` (local)
|
||||
|
||||
- [ ] **2.1 Backup en servidor**
|
||||
```powershell
|
||||
echo "y" | & $plink -pw "Geronimo6&8" -P 2230 pi@ssh.famromdon.online 'docker exec autobookingonline-wordpress-autobooking-1 cp /var/www/html/wp-content/plugins/autobooking-admin-dashboard/autobooking-admin-dashboard.php /var/www/html/wp-content/plugins/autobooking-admin-dashboard/autobooking-admin-dashboard.php.bak'
|
||||
```
|
||||
|
||||
- [ ] **2.2 En el archivo local**, hacer 3 cambios quirúrgicos:
|
||||
|
||||
**Cambio A** — línea 17, reemplazar la constante:
|
||||
```php
|
||||
// ANTES:
|
||||
const GMAPS_KEY = 'AIzaSyD3VSlYZvDEbbSKUhEFRdUD5rU1JWXX03Q';
|
||||
// DESPUÉS (eliminar la línea, la key se lee de wp_options)
|
||||
```
|
||||
|
||||
**Cambio B** — en `enqueue()`, reemplazar el uso de la constante en la URL de Google Maps:
|
||||
```php
|
||||
// ANTES:
|
||||
'https://maps.googleapis.com/maps/api/js?key=' . self::GMAPS_KEY . '&libraries=places,drawing',
|
||||
// DESPUÉS:
|
||||
'https://maps.googleapis.com/maps/api/js?key=' . esc_attr( get_option( 'ab_gmaps_key', '' ) ) . '&libraries=places,drawing',
|
||||
```
|
||||
|
||||
**Cambio C** — en `wp_localize_script`:
|
||||
```php
|
||||
// ANTES:
|
||||
'gmaps_key' => self::GMAPS_KEY,
|
||||
// DESPUÉS:
|
||||
'gmaps_key' => esc_attr( get_option( 'ab_gmaps_key', '' ) ),
|
||||
```
|
||||
|
||||
**Cambio D** — en `rest_settings_get()`:
|
||||
```php
|
||||
// ANTES:
|
||||
'gmaps_key' => get_option( 'ab_gmaps_key', self::GMAPS_KEY ),
|
||||
// DESPUÉS:
|
||||
'gmaps_key' => get_option( 'ab_gmaps_key', '' ),
|
||||
```
|
||||
|
||||
- [ ] **2.3 Desplegar** (mismo comando deploy con `autobooking-admin-dashboard.php`)
|
||||
|
||||
- [ ] **2.4 Verificar** — en WP Admin → AutoBooking Admin → tab CONFIG, asegurarse que el campo "Google Maps Key" tiene el valor `AIzaSyD3VSlYZvDEbbSKUhEFRdUD5rU1JWXX03Q`. Si está vacío, pegarlo y guardar.
|
||||
|
||||
- [ ] **2.5 Verificar en HTML** — Ver fuente de `/admin-dashboard/` (Ctrl+U) → buscar `AIzaSyD3` → NO debe aparecer como texto plano en el source (aparece como valor de option, no hardcoded).
|
||||
|
||||
- [ ] **2.6 Commit**
|
||||
```bash
|
||||
git add autobooking-admin-dashboard.php && git commit -m "fix: Google Maps API key moved from hardcode to wp_options"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tarea 3: IP Geolocation en geo-restrict
|
||||
|
||||
**Archivos:** Modificar `autobooking-geo-restrict.php` (local)
|
||||
|
||||
- [ ] **3.1 Backup en servidor**
|
||||
```powershell
|
||||
echo "y" | & $plink -pw "Geronimo6&8" -P 2230 pi@ssh.famromdon.online 'docker exec autobookingonline-wordpress-autobooking-1 cp /var/www/html/wp-content/plugins/autobooking-geo-restrict/autobooking-geo-restrict.php /var/www/html/wp-content/plugins/autobooking-geo-restrict/autobooking-geo-restrict.php.bak'
|
||||
```
|
||||
|
||||
- [ ] **3.2 Agregar función helper** después de `ab_is_country_allowed()` (~línea 67):
|
||||
|
||||
```php
|
||||
function ab_get_country_from_ip( $ip ) {
|
||||
if ( ! filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) ) return null;
|
||||
$cache_key = 'ab_geo_ip_' . md5( $ip );
|
||||
$cached = get_transient( $cache_key );
|
||||
if ( $cached !== false ) return $cached ?: null;
|
||||
$response = wp_remote_get( 'http://ip-api.com/json/' . rawurlencode( $ip ) . '?fields=countryCode,status', ['timeout'=>5] );
|
||||
if ( is_wp_error( $response ) ) { set_transient( $cache_key, '', 3600 ); return null; }
|
||||
$body = json_decode( wp_remote_retrieve_body( $response ), true );
|
||||
$code = ( ! empty( $body['status'] ) && $body['status'] === 'success' ) ? strtoupper( $body['countryCode'] ?? '' ) : '';
|
||||
set_transient( $cache_key, $code, 86400 );
|
||||
return $code ?: null;
|
||||
}
|
||||
|
||||
function ab_get_request_ip() {
|
||||
if ( function_exists( 'abs_get_ip' ) ) return 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 );
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **3.3 Modificar el callback de `/geo/check`** — reemplazar el return final del callback:
|
||||
|
||||
```php
|
||||
// ANTES (las últimas líneas del callback):
|
||||
return rest_ensure_response( [
|
||||
'allowed' => $allowed,
|
||||
'country' => $country,
|
||||
'message' => $allowed ? '' : get_option( 'ab_geo_blocked_msg', 'AutoBooking is not available in your current location.' ),
|
||||
] );
|
||||
|
||||
// DESPUÉS:
|
||||
if ( $allowed && $country ) {
|
||||
$ip_country = ab_get_country_from_ip( ab_get_request_ip() );
|
||||
if ( $ip_country && $ip_country !== $country ) {
|
||||
return rest_ensure_response(['allowed'=>false,'country'=>$country,'message'=>'Ubicación inconsistente. Verifica tu conexión.']);
|
||||
}
|
||||
}
|
||||
return rest_ensure_response( [
|
||||
'allowed' => $allowed,
|
||||
'country' => $country,
|
||||
'message' => $allowed ? '' : get_option( 'ab_geo_blocked_msg', 'AutoBooking is not available in your current location.' ),
|
||||
] );
|
||||
```
|
||||
|
||||
- [ ] **3.4 Desplegar**
|
||||
|
||||
- [ ] **3.5 Verificar**
|
||||
```
|
||||
GET https://autobooking.online/wp-json/autobooking/v1/geo/check?lat=40.7128&lng=-74.0060
|
||||
```
|
||||
Esperado: `{"allowed":true,"country":"US","message":""}`
|
||||
|
||||
- [ ] **3.6 Commit**
|
||||
```bash
|
||||
git add autobooking-geo-restrict.php && git commit -m "feat: IP geolocation backup in geo/check endpoint"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tarea 4: Sección Seguridad en Admin Dashboard CONFIG
|
||||
|
||||
**Archivos:** Modificar `autobooking-admin-dashboard.php` (HTML del panel CONFIG)
|
||||
|
||||
- [ ] **4.1 Agregar card de Seguridad** en el HTML del panel `panel-settings`, después del card de tarifas (buscar `</div>` que cierra el card de tarifas, ~línea 840):
|
||||
|
||||
```php
|
||||
<div class="abad-card">
|
||||
<div class="abad-card__title">Seguridad</div>
|
||||
<div class="abad-form-group">
|
||||
<label class="abad-label">IPs en whitelist (una por línea)</label>
|
||||
<textarea class="abad-textarea" id="sec-whitelist" rows="3" placeholder="127.0.0.1"></textarea>
|
||||
</div>
|
||||
<div class="abad-form-group">
|
||||
<label class="abad-label">hCaptcha Site Key</label>
|
||||
<input type="text" class="abad-input" id="sec-hcaptcha-site">
|
||||
</div>
|
||||
<div class="abad-form-group">
|
||||
<label class="abad-label">hCaptcha Secret Key</label>
|
||||
<input type="password" class="abad-input" id="sec-hcaptcha-secret">
|
||||
</div>
|
||||
<div class="abad-alert abad-alert--success" id="sec-saved" style="display:none">Guardado.</div>
|
||||
<button class="abad-btn abad-btn--brand" id="btn-save-sec">GUARDAR SEGURIDAD</button>
|
||||
<div class="abad-card__title" style="margin-top:20px">IPs bloqueadas</div>
|
||||
<p class="abad-note" id="sec-blocked-count">—</p>
|
||||
<div id="sec-blocked-table"></div>
|
||||
</div>
|
||||
```
|
||||
|
||||
- [ ] **4.2 Agregar JS** al final de `assets/admin-dashboard.js`:
|
||||
|
||||
```javascript
|
||||
// ── SECURITY ──────────────────────────────────────────────────
|
||||
async function loadSecuritySettings() {
|
||||
try {
|
||||
const r = await apiFetch('/admin/blocked-ips');
|
||||
document.getElementById('sec-blocked-count').textContent = r.blocked_24h + ' IPs bloqueadas en las últimas 24h';
|
||||
const el = document.getElementById('sec-blocked-table');
|
||||
if (!r.blocked.length) { el.innerHTML = '<p style="color:rgba(255,255,255,.4);font-size:13px">Sin IPs bloqueadas.</p>'; return; }
|
||||
el.innerHTML = '<table class="abad-table"><thead><tr><th>IP</th><th>Motivo</th><th>Expira</th><th></th></tr></thead><tbody>'
|
||||
+ r.blocked.map(b => `<tr><td>${b.ip}</td><td style="font-size:12px">${b.reason}</td><td style="font-size:12px">${b.expires_at}</td><td><button class="abad-btn abad-btn--outline abad-btn--sm" onclick="absUnblock(${b.id})">Desbloquear</button></td></tr>`).join('')
|
||||
+ '</tbody></table>';
|
||||
} catch(e) {}
|
||||
}
|
||||
window.absUnblock = async id => { await apiFetch('/admin/blocked-ips/'+id+'/unblock',{method:'POST'}); loadSecuritySettings(); };
|
||||
document.getElementById('btn-save-sec')?.addEventListener('click', async () => {
|
||||
await apiFetch('/admin/security-settings/save', {method:'POST', body: JSON.stringify({
|
||||
whitelist: document.getElementById('sec-whitelist').value,
|
||||
hcaptcha_site_key: document.getElementById('sec-hcaptcha-site').value,
|
||||
hcaptcha_secret: document.getElementById('sec-hcaptcha-secret').value,
|
||||
})});
|
||||
const el = document.getElementById('sec-saved'); el.style.display='block'; setTimeout(()=>el.style.display='none',3000);
|
||||
});
|
||||
document.querySelectorAll('.abad-tab').forEach(btn => {
|
||||
if (btn.dataset.tab === 'settings') btn.addEventListener('click', loadSecuritySettings);
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **4.3 Desplegar** `autobooking-admin-dashboard.php` + `assets/admin-dashboard.js`
|
||||
|
||||
- [ ] **4.4 Verificar** — Admin dashboard → tab CONFIG → ver sección Seguridad → ingresar hCaptcha keys → Guardar → confirmar mensaje "Guardado."
|
||||
|
||||
- [ ] **4.5 Commit**
|
||||
```bash
|
||||
git add autobooking-admin-dashboard.php && git commit -m "feat: security section in admin CONFIG tab"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Verificación final
|
||||
|
||||
- [ ] Bot no puede crear más de 5 cuentas/hora → al intento 6 aparece error de rate limit
|
||||
- [ ] Conductor sin email confirmado → al entrar a `/pending-driver/` ve pantalla "Confirma tu email"
|
||||
- [ ] Clic en enlace del email → redirige a `/pending-driver/?ab_confirm=ok`
|
||||
- [ ] `GET /wp-json/autobooking/v1/geo/check?lat=40.7&lng=-74.0` → `{"allowed":true,...}`
|
||||
- [ ] Ver fuente de página admin → `AIzaSyD3` no aparece hardcoded
|
||||
- [ ] Login con usuario que tenga rol driver + customer → redirige a `/choose-role/` ✓ (no tocado)
|
||||
@@ -0,0 +1,229 @@
|
||||
# AutoBooking Security — Diseño Opción B
|
||||
|
||||
**Fecha:** 2026-05-30
|
||||
**Estado:** Aprobado por usuario
|
||||
**Alcance:** Anti-spam, anti-ghost registrations, rate limiting, IP geolocation backup, documentos de conductor
|
||||
|
||||
---
|
||||
|
||||
## 1. Contexto y problema
|
||||
|
||||
AutoBooking es una plataforma de transporte en WordPress (autobooking.online). Los problemas identificados:
|
||||
|
||||
- **Cuentas fantasma:** cualquier bot puede registrarse como conductor o pasajero sin verificación
|
||||
- **Spam en chat:** `wp_autobooking_chat` sin rate limiting
|
||||
- **API Key de Google Maps expuesta** en código fuente PHP y en HTML del frontend
|
||||
- **GPS spoofeable:** el plugin geo-restrict confía 100% en coordenadas enviadas por el cliente
|
||||
- **Sin rate limiting** en ningún endpoint REST ni en login/registro
|
||||
|
||||
Funcionalidades existentes que se preservan sin cambios:
|
||||
- Selección de rol al login (Snippets #15 + #18: `after-login` → `choose-role`)
|
||||
- Páginas de registro con diseño Forminator
|
||||
- Plugin `autobooking-roles` v1.1.0 (roles driver, driver_pending, corporate_admin)
|
||||
- Todos los plugins de dashboard (driver, passenger, corporate, admin)
|
||||
|
||||
---
|
||||
|
||||
## 2. Arquitectura
|
||||
|
||||
### Componentes nuevos
|
||||
|
||||
| Componente | Tipo | Propósito |
|
||||
|---|---|---|
|
||||
| `autobooking-security.php` | Plugin nuevo | Central de seguridad: rate limiting, nonces, IP geo |
|
||||
| `wp_ab_rate_limits` | Tabla DB | Registro de hits por IP/endpoint |
|
||||
| `wp_ab_blocked_ips` | Tabla DB | IPs bloqueadas con TTL |
|
||||
| `wp_ab_driver_documents` | Tabla DB | Documentos de conductor (licencia, seguro, foto) |
|
||||
| hCaptcha en registro | Hook Forminator | Verificación anti-bot en registro de conductores |
|
||||
|
||||
### Componentes modificados
|
||||
|
||||
| Componente | Cambio |
|
||||
|---|---|
|
||||
| `autobooking-admin-dashboard.php` | Mover `GMAPS_KEY` a `wp_options`; mostrar documentos pendientes en tab Conductores |
|
||||
| `autobooking-geo-restrict.php` | Agregar verificación de IP via ip-api.com con caché transient |
|
||||
|
||||
---
|
||||
|
||||
## 3. Rate Limiting
|
||||
|
||||
### Tabla `wp_ab_rate_limits`
|
||||
```sql
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY
|
||||
ip VARCHAR(45) NOT NULL
|
||||
endpoint VARCHAR(80) NOT NULL
|
||||
hits INT UNSIGNED NOT NULL DEFAULT 1
|
||||
window_start DATETIME NOT NULL
|
||||
KEY (ip, endpoint, window_start)
|
||||
```
|
||||
|
||||
### Tabla `wp_ab_blocked_ips`
|
||||
```sql
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY
|
||||
ip VARCHAR(45) NOT NULL UNIQUE
|
||||
reason VARCHAR(120) NOT NULL DEFAULT ''
|
||||
blocked_at DATETIME NOT NULL
|
||||
expires_at DATETIME NOT NULL
|
||||
KEY (ip, expires_at)
|
||||
```
|
||||
|
||||
### Límites configurados
|
||||
|
||||
| Endpoint / Acción | Límite | Ventana | Bloqueo automático |
|
||||
|---|---|---|---|
|
||||
| Registro de usuario | 5 intentos | 60 min | 24 h |
|
||||
| `wp_login_failed` | 10 fallos | 15 min | 2 h |
|
||||
| `POST /autobooking/v1/*` | 120 requests | 1 min | 30 min |
|
||||
| Chat (write) | 1 mensaje | 2 seg | 10 min |
|
||||
|
||||
### Implementación
|
||||
- Hook `register_post` → verificar IP antes de crear usuario
|
||||
- Hook `wp_login_failed` → contar fallos de login por IP
|
||||
- Filter `rest_dispatch_request` → contar requests REST por IP
|
||||
- Helper `ab_sec_check_rate( $ip, $endpoint, $limit, $window_sec )` → bool
|
||||
- Helper `ab_sec_block_ip( $ip, $reason, $hours )` → void
|
||||
- IPs en whitelist (configurables en tab CONFIG del admin dashboard) nunca se bloquean
|
||||
|
||||
---
|
||||
|
||||
## 4. Anti-Ghost Registration
|
||||
|
||||
### Flujo de registro de conductor
|
||||
|
||||
```
|
||||
1. Usuario llena formulario Forminator en /driver-registration/
|
||||
2. hCaptcha valida (verificación server-side)
|
||||
3. Rate limit: máx 5 registros / hora / IP
|
||||
4. WordPress crea usuario con rol driver_pending
|
||||
5. Email de confirmación enviado → cuenta inactiva hasta confirmar
|
||||
6. Conductor sube 3 documentos desde /pending-driver/
|
||||
7. Admin aprueba en dashboard → rol pasa a driver
|
||||
```
|
||||
|
||||
### hCaptcha
|
||||
- Servicio gratuito (hcaptcha.com) — requiere crear cuenta y obtener site key + secret key
|
||||
- Hook `registration_errors` para verificar `h-captcha-response` via `POST https://api.hcaptcha.com/siteverify`
|
||||
- Si falla → error visible en el formulario, registro rechazado
|
||||
- Site key y secret key guardadas en `wp_options`: `ab_hcaptcha_site_key`, `ab_hcaptcha_secret`
|
||||
|
||||
### Email confirmation
|
||||
- Token de confirmación en `wp_usermeta`: `ab_email_token` (SHA256 random), `ab_email_token_expires` (24h)
|
||||
- Usuario con rol `driver_pending` sin email confirmado → redirigido a `/pending-driver/` con mensaje "Confirma tu email"
|
||||
- Endpoint `GET /autobooking/v1/confirm-email?token=xxx` → valida token, marca `ab_email_confirmed = 1`
|
||||
- Si el token expiró (>24h): mostrar página "Token expirado" con botón para reenviar email de confirmación (endpoint `POST /autobooking/v1/resend-confirmation`)
|
||||
|
||||
### Tabla `wp_ab_driver_documents`
|
||||
```sql
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY
|
||||
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
|
||||
KEY (driver_id, doc_type)
|
||||
KEY (status)
|
||||
```
|
||||
|
||||
### Regla de aprobación
|
||||
- Conductor sube docs desde `/pending-driver/` — tipos permitidos: JPG, PNG, PDF (máx 5 MB por archivo), guardados en `wp-content/uploads/ab-driver-docs/{user_id}/`
|
||||
- Admin ve docs en tab CONDUCTORES → cola de aprobación
|
||||
- Botón "Aprobar conductor" solo habilitado si los 3 documentos existen
|
||||
- Rechazo envía email automático con motivo al conductor
|
||||
|
||||
---
|
||||
|
||||
## 5. IP Geolocation Backup
|
||||
|
||||
### Modificación a `/geo/check`
|
||||
|
||||
```
|
||||
1. Recibir lat/lng del cliente
|
||||
2. Detectar país por bounding boxes (lógica existente — sin cambios)
|
||||
3. Si país por GPS está en allowed list:
|
||||
a. Obtener IP del request (X-Forwarded-For validado, o REMOTE_ADDR)
|
||||
b. Si IP es privada/local (127.x, 10.x, 192.168.x) → confiar en GPS
|
||||
c. Consultar caché: transient "ab_geo_ip_{md5(ip)}" TTL=86400s
|
||||
d. Si no hay caché → llamar ip-api.com/json/{ip}?fields=countryCode
|
||||
e. Si ip-api.com falla (timeout/error) → fail open, permitir acceso
|
||||
f. Si país por IP != país por GPS → retornar allowed=false, mensaje "Ubicación inconsistente"
|
||||
4. Retornar resultado normal
|
||||
```
|
||||
|
||||
- ip-api.com: gratuito, 45 req/min — el caché de 24h por IP lo mantiene muy por debajo del límite
|
||||
- El bloqueo por discrepancia es duro (no solo advertencia)
|
||||
|
||||
---
|
||||
|
||||
## 6. Google Maps API Key
|
||||
|
||||
### Problema
|
||||
`const GMAPS_KEY = 'AIzaSyD3...'` hardcodeada en `autobooking-admin-dashboard.php:17` y expuesta en el HTML de la página de admin.
|
||||
|
||||
### Solución en código
|
||||
- Eliminar la constante `GMAPS_KEY`
|
||||
- Leer siempre de `get_option('ab_gmaps_key', '')` — este campo ya existe en el tab CONFIG del admin dashboard
|
||||
- El campo ya estaba en la UI; solo hay que eliminar el fallback a la constante
|
||||
|
||||
### Acción manual requerida (fuera del código)
|
||||
- Ir a Google Cloud Console → Credenciales → Restricciones de la key
|
||||
- Agregar restricción HTTP: solo `*.autobooking.online/*`
|
||||
|
||||
---
|
||||
|
||||
## 7. Modificaciones al Admin Dashboard
|
||||
|
||||
### Tab CONDUCTORES
|
||||
- Columna "Docs" en cola de aprobación: muestra `X/3` documentos subidos
|
||||
- Botón "Aprobar" deshabilitado si hay documentos faltantes
|
||||
- Modal muestra miniaturas de los 3 documentos con links para ver tamaño completo
|
||||
|
||||
### Tab CONFIG — nueva sección "Seguridad"
|
||||
- Campo: IPs en whitelist (textarea, una por línea)
|
||||
- Tabla: IPs bloqueadas actualmente con columnas IP, Motivo, Expira, botón Desbloquear
|
||||
- Contador: intentos bloqueados en las últimas 24 h
|
||||
|
||||
---
|
||||
|
||||
## 8. Preservación de funcionalidad existente
|
||||
|
||||
| Feature | Estado | Acción |
|
||||
|---|---|---|
|
||||
| Snippets #15 + #18 (choose-role) | Sin cambios | No tocar |
|
||||
| Snippet #14 (login gating) | Sin cambios | No tocar |
|
||||
| Snippet #19 (registro override) | Sin cambios | Solo agregar hCaptcha al hook |
|
||||
| Páginas Forminator | Sin cambios | Solo agregar campo h-captcha-response |
|
||||
| autobooking-roles plugin | Sin cambios | No tocar |
|
||||
| Driver / Passenger / Corporate dashboards | Sin cambios | No tocar |
|
||||
|
||||
---
|
||||
|
||||
## 9. Archivos a crear/modificar
|
||||
|
||||
```
|
||||
CREAR:
|
||||
wp-content/plugins/autobooking-security/autobooking-security.php
|
||||
|
||||
MODIFICAR:
|
||||
wp-content/plugins/autobooking-admin-dashboard/autobooking-admin-dashboard.php
|
||||
wp-content/plugins/autobooking-geo-restrict/autobooking-geo-restrict.php
|
||||
|
||||
ACCIONES MANUALES:
|
||||
1. Google Cloud Console → restringir GMAPS_KEY a *.autobooking.online/*
|
||||
2. hcaptcha.com → crear cuenta gratuita, obtener site_key y secret_key
|
||||
3. Activar plugin autobooking-security desde WP Admin → Plugins
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Criterios de éxito
|
||||
|
||||
1. Un bot no puede crear más de 5 cuentas por hora desde la misma IP
|
||||
2. Un conductor nuevo no puede acceder al dashboard sin confirmar su email
|
||||
3. Un conductor no puede ser aprobado sin los 3 documentos subidos
|
||||
4. GPS de USA + IP de Rusia → bloqueado con mensaje claro
|
||||
5. La API Key de Google Maps no aparece en el HTML de ninguna página pública
|
||||
6. El flujo de selección de rol al login (`choose-role`) sigue funcionando sin cambios
|
||||
7. Las páginas de registro mantienen su diseño visual actual
|
||||
Reference in New Issue
Block a user