yazrkisvs3vvvsv33svrhetrjsvyvsvvyjsvsvs
tavsuysvssvvjvvyjyjkvsvqd
qfogsvvsvsegjdgdfgdskhgdksvqsvshdqsd
<?php
header('Content-Type: application/json');
require_once dirname(__DIR__) . '/auth.php';
require_once dirname(__DIR__) . '/helpers/json.php';
dm_require_login();
dm_csrf_verify();

$user = dm_user();
$input = dm_json_decode_or(file_get_contents('php://input'), [], ['context'=>'update-profile']);

if (!$pdo) { echo json_encode(['ok'=>false,'error'=>'Sin BD']); exit; }

$name             = trim((string)($input['name'] ?? ''));
$current_password = (string)($input['current_password'] ?? '');
$pass             = (string)($input['password']  ?? '');
$pass2            = (string)($input['password2'] ?? '');
$new_email        = trim((string)($input['new_email'] ?? ''));

if (!$name) { echo json_encode(['ok'=>false,'error'=>'El nombre es obligatorio']); exit; }
if (mb_strlen($name) > 120) { echo json_encode(['ok'=>false,'error'=>'Nombre máximo 120 caracteres']); exit; }

// FASE 8.6: solicitud de cambio de email — genera token de verificación y envía link al nuevo email
if ($new_email !== '') {
    if (!filter_var($new_email, FILTER_VALIDATE_EMAIL)) {
        echo json_encode(['ok'=>false,'error'=>'Email inválido']); exit;
    }
    if (strcasecmp($new_email, $user['email']) === 0) {
        echo json_encode(['ok'=>false,'error'=>'El email nuevo es igual al actual']); exit;
    }
    if ($current_password === '') {
        echo json_encode(['ok'=>false,'error'=>'Para cambiar el email debes ingresar tu contraseña actual']); exit;
    }
    // Verificar password actual
    try {
        $cur = $pdo->prepare('SELECT password FROM dm_users WHERE id=? LIMIT 1');
        $cur->execute([$user['id']]);
        $cur_hash = (string)$cur->fetchColumn();
        if (!$cur_hash || !password_verify($current_password, $cur_hash)) {
            echo json_encode(['ok'=>false,'error'=>'La contraseña actual no es correcta']); exit;
        }
    } catch (\Throwable $e) { echo json_encode(['ok'=>false,'error'=>'Error verificando credenciales']); exit; }
    // Verificar que el email nuevo no esté en uso
    try {
        $du = $pdo->prepare('SELECT id FROM dm_users WHERE email=? AND id<>? LIMIT 1');
        $du->execute([$new_email, $user['id']]);
        if ($du->fetchColumn()) {
            echo json_encode(['ok'=>false,'error'=>'Ese email ya está registrado por otro usuario']); exit;
        }
    } catch (\Throwable $e) {}
    // Crear tabla + insertar token de verificación (TTL 1 hora)
    try {
        $pdo->exec("CREATE TABLE IF NOT EXISTS dm_email_change_tokens (
            token       VARCHAR(64) PRIMARY KEY,
            user_id     INT UNSIGNED NOT NULL,
            new_email   VARCHAR(190) NOT NULL,
            expires_at  DATETIME    NOT NULL,
            created_at  DATETIME    NOT NULL DEFAULT CURRENT_TIMESTAMP,
            INDEX idx_user (user_id),
            INDEX idx_exp  (expires_at)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        $token   = bin2hex(random_bytes(32));
        $expires = date('Y-m-d H:i:s', time() + 3600);
        // Invalidar tokens previos del mismo user (solo uno activo a la vez)
        $pdo->prepare('DELETE FROM dm_email_change_tokens WHERE user_id=?')->execute([$user['id']]);
        $pdo->prepare('INSERT INTO dm_email_change_tokens (token, user_id, new_email, expires_at) VALUES (?,?,?,?)')
            ->execute([$token, $user['id'], $new_email, $expires]);
        // Construir link de verificación
        $proto = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
        $host  = $_SERVER['HTTP_HOST'] ?? 'dimark.co';
        $base  = $proto . '://' . $host . dirname($_SERVER['SCRIPT_NAME'] ?? '/');
        $link  = rtrim($base, '/') . '/../verify-email.php?t=' . $token;
        // Enviar email (mail() simple — en producción usar SMTP/SendGrid)
        $subject = '[Brain] Confirma tu nuevo email';
        $msg = "Para confirmar el cambio a {$new_email} haz clic en el siguiente link (válido 1 hora):\n\n{$link}\n\nSi no solicitaste este cambio, ignora este mensaje — tu email actual sigue siendo válido.";
        $headers = "From: no-reply@dimark.co\r\nContent-Type: text/plain; charset=utf-8\r\n";
        @mail($new_email, $subject, $msg, $headers);
        dm_log($user['id'], null, 'email_change_requested', "Solicitud cambio email a {$new_email}");
        echo json_encode(['ok'=>true, 'email_change_pending'=>true, 'message'=>'Te enviamos un link de confirmación al nuevo email. Tiene validez de 1 hora.']);
        exit;
    } catch (\Throwable $e) {
        echo json_encode(['ok'=>false,'error'=>'No se pudo generar el token de verificación']); exit;
    }
}

// FASE 6 FIX: rate limit — máx 3 cambios/hora por usuario (anti-bruteforce + anti-abuse)
try {
    $rl = $pdo->prepare("SELECT COUNT(*) FROM dm_activity_log WHERE user_id=? AND action='update_profile' AND created_at >= (NOW() - INTERVAL 1 HOUR)");
    $rl->execute([$user['id']]);
    if ((int)$rl->fetchColumn() >= 3) {
        echo json_encode(['ok'=>false,'error'=>'Demasiados cambios de perfil esta hora. Espera 60 minutos.']); exit;
    }
} catch (\Throwable $e) { /* dm_activity_log puede no existir, no bloquea operación */ }

if ($pass !== '') {
    // FASE 6 FIX CRÍTICO: requerir password actual + verificarlo
    if ($current_password === '') {
        echo json_encode(['ok'=>false,'error'=>'Para cambiar la contraseña debes ingresar la actual']); exit;
    }
    try {
        $cur = $pdo->prepare('SELECT password FROM dm_users WHERE id=? LIMIT 1');
        $cur->execute([$user['id']]);
        $cur_hash = (string)$cur->fetchColumn();
        if (!$cur_hash || !password_verify($current_password, $cur_hash)) {
            echo json_encode(['ok'=>false,'error'=>'La contraseña actual no es correcta']); exit;
        }
    } catch (\Throwable $e) {
        echo json_encode(['ok'=>false,'error'=>'Error verificando credenciales']); exit;
    }
    // FASE 6 FIX: mínimo 8 chars (antes 6) — sigue siendo común sin ser frustrante para usuarios
    if (strlen($pass) < 8) { echo json_encode(['ok'=>false,'error'=>'Contraseña mínimo 8 caracteres']); exit; }
    if (strlen($pass) > 200) { echo json_encode(['ok'=>false,'error'=>'Contraseña demasiado larga']); exit; }
    if ($pass !== $pass2) { echo json_encode(['ok'=>false,'error'=>'Las contraseñas no coinciden']); exit; }
    if ($pass === $current_password) { echo json_encode(['ok'=>false,'error'=>'La nueva contraseña debe ser distinta de la actual']); exit; }
    $hash = password_hash($pass, PASSWORD_BCRYPT);
    $pdo->prepare('UPDATE dm_users SET name=?, password=? WHERE id=?')->execute([$name, $hash, $user['id']]);
    // FASE 6 FIX: regenerar session ID tras cambio de password (mitigar session fixation)
    if (session_status() !== PHP_SESSION_ACTIVE) session_start();
    session_regenerate_id(true);
} else {
    $pdo->prepare('UPDATE dm_users SET name=? WHERE id=?')->execute([$name, $user['id']]);
}

// Reabrir sesión para actualizar el nombre en memoria (session_write_close fue llamado por dm_require_login)
if (session_status() === PHP_SESSION_NONE) session_start();
$_SESSION['dm_name'] = $name;
dm_log($user['id'], null, 'update_profile', $pass !== '' ? 'Perfil + password actualizados' : 'Perfil actualizado');
echo json_encode(['ok'=>true]);
