From 402826b7ef63991e759cf312d2aa77df0520b140 Mon Sep 17 00:00:00 2001 From: Francesco Picone Date: Tue, 9 Dec 2025 17:50:01 +0100 Subject: [PATCH] add user confirmation --- .gitignore | 1 + admin/users.php | 59 +++++++- database/add_email_verification.sql | 23 ++++ database/schema.sql | 5 + includes/functions.php | 17 ++- login.php | 68 ++++----- register.php | 58 +++++--- resend_verification.php | 207 ++++++++++++++++++++++++++++ send_email.py | 38 ++++- verify_email.php | 196 ++++++++++++++++++++++++++ 10 files changed, 611 insertions(+), 61 deletions(-) create mode 100644 database/add_email_verification.sql create mode 100644 resend_verification.php create mode 100644 verify_email.php diff --git a/.gitignore b/.gitignore index 4d9d5de..d65c959 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ test_email.php # Log *.log +logs/ # OS .DS_Store diff --git a/admin/users.php b/admin/users.php index aa7a465..1615bcd 100644 --- a/admin/users.php +++ b/admin/users.php @@ -29,6 +29,45 @@ if (isset($_GET['toggle_active']) && is_numeric($_GET['toggle_active'])) { exit; } +// Gestione re-invio email verifica +if (isset($_GET['resend_verification']) && is_numeric($_GET['resend_verification'])) { + $user_id = (int)$_GET['resend_verification']; + + try { + $pdo = get_db_connection(); + $stmt = $pdo->prepare("SELECT id, email, first_name, email_verified FROM users WHERE id = ?"); + $stmt->execute([$user_id]); + $user = $stmt->fetch(); + + if ($user && !$user['email_verified']) { + // Genera nuovo token + $email_token = bin2hex(random_bytes(32)); + $token_expires = date('Y-m-d H:i:s', strtotime('+24 hours')); + + $stmt = $pdo->prepare("UPDATE users SET email_token = ?, email_token_expires = ? WHERE id = ?"); + $stmt->execute([$email_token, $token_expires, $user_id]); + + // Invia email + $verify_url = SITE_URL . "/verify_email.php?token=" . $email_token; + $subject = "Conferma il tuo account su " . SITE_NAME; + $body = "

Ciao " . htmlspecialchars($user['first_name']) . ",

+

Un amministratore ha inviato nuovamente il link di verifica per il tuo account.

+

Conferma Email

+

Questo link è valido per 24 ore.

"; + + send_email($user['email'], $subject, $body); + set_flash_message('success', 'Email di verifica inviata a ' . htmlspecialchars($user['email'])); + } else { + set_flash_message('error', 'Utente già verificato o non trovato'); + } + } catch (Exception $e) { + set_flash_message('error', 'Errore durante l\'invio dell\'email'); + } + + header('Location: users.php'); + exit; +} + // Ottieni tutti gli utenti non admin $pdo = get_db_connection(); $stmt = $pdo->query(" @@ -102,6 +141,7 @@ $users = $stmt->fetchAll(); Nome Email + Stato Email Registrato il Ultimo Accesso Acquisti @@ -116,7 +156,16 @@ $users = $stmt->fetchAll(); - + + + + + + ✓ Verificata + + ⚠️ Non verificata + + fetchAll(); class="btn btn-small "> + + + 📧 Re-Invia + + diff --git a/database/add_email_verification.sql b/database/add_email_verification.sql new file mode 100644 index 0000000..cdc966e --- /dev/null +++ b/database/add_email_verification.sql @@ -0,0 +1,23 @@ +-- Aggiunge campi per verifica email agli utenti esistenti +-- Esegui questo script per aggiornare il database senza perdere dati + +USE pilatesplatform; + +-- Aggiungi campi per verifica email +ALTER TABLE users +ADD COLUMN email_verified BOOLEAN DEFAULT FALSE COMMENT 'True se email verificata' AFTER is_active, +ADD COLUMN email_token VARCHAR(64) DEFAULT NULL COMMENT 'Token per verifica email' AFTER email_verified, +ADD COLUMN email_token_expires DATETIME DEFAULT NULL COMMENT 'Scadenza token verifica email' AFTER email_token; + +-- Aggiungi indici per performance +ALTER TABLE users +ADD INDEX idx_email_verified (email_verified), +ADD INDEX idx_email_token (email_token); + +-- Imposta tutti gli utenti esistenti come verificati (per retrocompatibilità) +UPDATE users SET email_verified = TRUE WHERE email_verified = FALSE; + +-- Imposta admin come verificato +UPDATE users SET email_verified = TRUE WHERE is_admin = TRUE; + +SELECT 'Migrazione completata! Campi email_verified, email_token, email_token_expires aggiunti.' AS status; diff --git a/database/schema.sql b/database/schema.sql index 7cb180a..cf5bcf8 100644 --- a/database/schema.sql +++ b/database/schema.sql @@ -22,6 +22,9 @@ CREATE TABLE IF NOT EXISTS users ( last_name VARCHAR(100) NOT NULL COMMENT 'Cognome', is_admin BOOLEAN DEFAULT FALSE COMMENT 'True se amministratore', is_active BOOLEAN DEFAULT TRUE COMMENT 'Permette di disabilitare account', + email_verified BOOLEAN DEFAULT FALSE COMMENT 'True se email verificata', + email_token VARCHAR(64) DEFAULT NULL COMMENT 'Token per verifica email', + email_token_expires DATETIME DEFAULT NULL COMMENT 'Scadenza token verifica email', profile_image VARCHAR(255) DEFAULT NULL COMMENT 'URL immagine profilo', phone VARCHAR(20) DEFAULT NULL COMMENT 'Numero telefono opzionale', created_at DATETIME NOT NULL COMMENT 'Data registrazione', @@ -31,6 +34,8 @@ CREATE TABLE IF NOT EXISTS users ( INDEX idx_email (email), INDEX idx_is_admin (is_admin), + INDEX idx_email_verified (email_verified), + INDEX idx_email_token (email_token), INDEX idx_deleted_at (deleted_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Utenti della piattaforma'; diff --git a/includes/functions.php b/includes/functions.php index ff6d760..bdfe2b7 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -208,17 +208,22 @@ function get_user_by_id($user_id) { * @param string $first_name Nome * @param string $last_name Cognome * @param bool $is_admin Se è amministratore + * @param string|null $email_token Token verifica email + * @param string|null $token_expires Scadenza token * @return int|false ID del nuovo utente o false in caso di errore */ -function create_user($email, $password, $first_name, $last_name, $is_admin = false) { +function create_user($email, $password, $first_name, $last_name, $is_admin = false, $email_token = null, $token_expires = null) { $pdo = get_db_connection(); $hashed_password = hash_password($password); try { $stmt = $pdo->prepare(" - INSERT INTO users (email, password, first_name, last_name, is_admin, created_at) - VALUES (?, ?, ?, ?, ?, NOW()) + INSERT INTO users ( + email, password, first_name, last_name, is_admin, + email_verified, email_token, email_token_expires, created_at + ) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW()) "); $stmt->execute([ @@ -226,12 +231,16 @@ function create_user($email, $password, $first_name, $last_name, $is_admin = fal $hashed_password, $first_name, $last_name, - $is_admin ? 1 : 0 + $is_admin ? 1 : 0, + $is_admin ? 1 : 0, // Admin sempre verificati + $email_token, + $token_expires ]); return $pdo->lastInsertId(); } catch (PDOException $e) { + error_log("Create user error: " . $e->getMessage()); return false; } } diff --git a/login.php b/login.php index e6e3e30..77685d2 100644 --- a/login.php +++ b/login.php @@ -39,39 +39,45 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $user = get_user_by_email($email); if ($user && verify_password($password, $user['password'])) { - // Password corretta - effettua il login + // Password corretta - verifica se email è confermata - // Aggiorna data ultimo accesso - $pdo = get_db_connection(); - $stmt = $pdo->prepare("UPDATE users SET last_login = NOW() WHERE id = ?"); - $stmt->execute([$user['id']]); - - // Log attività - $stmt = $pdo->prepare(" - INSERT INTO activity_log (user_id, action, description, ip_address, user_agent, created_at) - VALUES (?, 'login', 'Utente ha effettuato il login', ?, ?, NOW()) - "); - $stmt->execute([ - $user['id'], - $_SERVER['REMOTE_ADDR'] ?? null, - $_SERVER['HTTP_USER_AGENT'] ?? null - ]); - - // Imposta la sessione - login_user($user['id'], $user['is_admin']); - - // Reindirizza alla pagina appropriata - if (isset($_SESSION['redirect_after_login'])) { - $redirect = $_SESSION['redirect_after_login']; - unset($_SESSION['redirect_after_login']); - header("Location: $redirect"); - } elseif ($user['is_admin']) { - header('Location: admin/dashboard.php'); + // Admin possono sempre accedere + if (!$user['is_admin'] && !$user['email_verified']) { + $error = 'Devi verificare la tua email prima di accedere. Invia nuovo link'; + } elseif (!$user['is_active']) { + $error = 'Il tuo account è stato disattivato. Contatta l\'amministratore.'; } else { - header('Location: user/dashboard.php'); + // Aggiorna data ultimo accesso + $pdo = get_db_connection(); + $stmt = $pdo->prepare("UPDATE users SET last_login = NOW() WHERE id = ?"); + $stmt->execute([$user['id']]); + + // Log attività + $stmt = $pdo->prepare(" + INSERT INTO activity_log (user_id, action, description, ip_address, user_agent, created_at) + VALUES (?, 'login', 'Utente ha effettuato il login', ?, ?, NOW()) + "); + $stmt->execute([ + $user['id'], + $_SERVER['REMOTE_ADDR'] ?? null, + $_SERVER['HTTP_USER_AGENT'] ?? null + ]); + + // Imposta la sessione + login_user($user['id'], $user['is_admin']); + + // Reindirizza alla pagina appropriata + if (isset($_SESSION['redirect_after_login'])) { + $redirect = $_SESSION['redirect_after_login']; + unset($_SESSION['redirect_after_login']); + header("Location: $redirect"); + } elseif ($user['is_admin']) { + header('Location: admin/dashboard.php'); + } else { + header('Location: user/dashboard.php'); + } + exit; } - exit; - } else { $error = 'Email o password non corretti'; } @@ -103,7 +109,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
- +
diff --git a/register.php b/register.php index 40e395a..356b7cb 100644 --- a/register.php +++ b/register.php @@ -45,14 +45,19 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (get_user_by_email($email)) { $error = 'Questa email è già registrata'; } else { - // Crea il nuovo utente - $user_id = create_user($email, $password, $first_name, $last_name); + // Genera token di verifica email + $email_token = bin2hex(random_bytes(32)); + $token_expires = date('Y-m-d H:i:s', strtotime('+24 hours')); + + // Crea il nuovo utente (non verificato) + $user_id = create_user($email, $password, $first_name, $last_name, false, $email_token, $token_expires); if ($user_id) { $success = true; - // Invia email di benvenuto - $subject = "Benvenuto su " . SITE_NAME . "!"; + // Invia email di verifica + $verify_url = SITE_URL . "/verify_email.php?token=" . $email_token; + $subject = "Conferma il tuo account su " . SITE_NAME; $body = " @@ -63,27 +68,34 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { .content { background: #f9f9f9; padding: 30px; border-radius: 0 0 8px 8px; } .button { display: inline-block; padding: 12px 30px; background: #667eea; color: white; text-decoration: none; border-radius: 5px; margin: 20px 0; } .footer { text-align: center; color: #999; font-size: 12px; margin-top: 20px; } + .warning { background: #fff3cd; border-left: 4px solid #ffc107; padding: 15px; margin: 20px 0; }
-

Benvenuto su " . SITE_NAME . "!

+

Conferma il tuo account

Ciao " . htmlspecialchars($first_name) . ",

-

Grazie per esserti registrato sulla nostra piattaforma! Il tuo account è stato creato con successo.

+

Grazie per esserti registrato su " . SITE_NAME . "!

+

Per completare la registrazione e attivare il tuo account, devi confermare il tuo indirizzo email cliccando sul pulsante qui sotto:

+
+ ✅ Conferma Email +
+

Oppure copia e incolla questo link nel tuo browser:
+ " . $verify_url . "

+
+ ⏰ Importante: Questo link è valido per 24 ore. Dopo questo periodo dovrai richiedere un nuovo link di verifica. +

I tuoi dati di accesso:

  • Email: " . htmlspecialchars($email) . "
  • Password: quella che hai scelto durante la registrazione
-

Ora puoi accedere al catalogo delle nostre lezioni e iniziare il tuo percorso di Pilates!

-
- Vai alla Dashboard -
-

Se hai domande o hai bisogno di assistenza, non esitare a contattarci.

-

Buon allenamento!
Il team di " . SITE_NAME . "

+

Una volta verificata la tua email, potrai accedere al catalogo delle nostre lezioni e iniziare il tuo percorso di Pilates!

+

Se non hai richiesto questa registrazione, ignora questa email.

+

A presto!
Il team di " . SITE_NAME . "

- + +
+

✅ Registrazione completata!

+

Ti abbiamo inviato un'email di verifica a

+

Per attivare il tuo account, clicca sul link che troverai nell'email.

+

+ ⚠️ Il link è valido per 24 ore. Se non lo trovi, controlla la cartella spam. +

+

Sarai reindirizzato alla pagina di login tra pochi secondi...

+
+
diff --git a/resend_verification.php b/resend_verification.php new file mode 100644 index 0000000..930ee07 --- /dev/null +++ b/resend_verification.php @@ -0,0 +1,207 @@ +prepare(" + SELECT id, first_name, email_verified + FROM users + WHERE email = ? + AND deleted_at IS NULL + "); + $stmt->execute([$email]); + $user = $stmt->fetch(); + + if (!$user) { + // Per sicurezza, non rivelare se l'email esiste + $success = true; + } elseif ($user['email_verified']) { + $error = 'Questo account è già stato verificato. Puoi effettuare il login.'; + } else { + // Genera nuovo token + $email_token = bin2hex(random_bytes(32)); + $token_expires = date('Y-m-d H:i:s', strtotime('+24 hours')); + + // Aggiorna token + $stmt = $pdo->prepare(" + UPDATE users + SET email_token = ?, + email_token_expires = ?, + updated_at = NOW() + WHERE id = ? + "); + $stmt->execute([$email_token, $token_expires, $user['id']]); + + // Log attività + $stmt = $pdo->prepare(" + INSERT INTO activity_log (user_id, action, description, ip_address, user_agent, created_at) + VALUES (?, 'resend_verification', 'Richiesto nuovo link verifica email', ?, ?, NOW()) + "); + $stmt->execute([ + $user['id'], + $_SERVER['REMOTE_ADDR'] ?? null, + $_SERVER['HTTP_USER_AGENT'] ?? null + ]); + + // Invia email + $verify_url = SITE_URL . "/verify_email.php?token=" . $email_token; + $subject = "Conferma il tuo account su " . SITE_NAME; + $body = " + + + + + +
+
+

Nuovo Link di Verifica

+
+
+

Ciao " . htmlspecialchars($user['first_name']) . ",

+

Hai richiesto un nuovo link per verificare il tuo account su " . SITE_NAME . ".

+

Per completare la registrazione e attivare il tuo account, clicca sul pulsante qui sotto:

+ +

Oppure copia e incolla questo link nel tuo browser:
+ " . $verify_url . "

+
+ ⏰ Importante: Questo link è valido per 24 ore. Questo nuovo link sostituisce quello precedente. +
+

Se non hai richiesto questo link, ignora questa email e il tuo account rimarrà non verificato.

+

A presto!
Il team di " . SITE_NAME . "

+
+ +
+ + + "; + + send_email($email, $subject, $body); + $success = true; + } + + } catch (PDOException $e) { + $error = 'Errore durante l\'invio. Riprova più tardi.'; + error_log("Resend verification error: " . $e->getMessage()); + } + } +} +?> + + + + + + Re-Invio Verifica Email - <?php echo SITE_NAME; ?> + + + +
+
+
+

+ +

+
+
+
+ +
+
+

📧 Re-Invio Email di Verifica

+ + +
+ +
+ + + +
+

✅ Email Inviata!

+

Se l'email è registrata nel nostro sistema, riceverai un nuovo link di verifica.

+

+ Controlla la tua casella email (e la cartella spam). Il link è valido per 24 ore. +

+ +
+ +

+ Inserisci la tua email per ricevere un nuovo link di verifica +

+ + +
+ + +
+ + + + + + +
+
+ +
+
+

© . Tutti i diritti riservati.

+
+
+ + diff --git a/send_email.py b/send_email.py index 25e04c9..d0490b6 100644 --- a/send_email.py +++ b/send_email.py @@ -2,15 +2,29 @@ """ Script Python per invio email SMTP Legge le credenziali da config.php e invia email tramite Gmail +Log solo in caso di errore """ import sys import json import re import smtplib +import logging from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from pathlib import Path +from datetime import datetime + +# Configura logging solo per errori +log_file = Path(__file__).parent / 'logs' / 'email_errors.log' +log_file.parent.mkdir(exist_ok=True) + +logging.basicConfig( + filename=str(log_file), + level=logging.ERROR, + format='%(asctime)s - %(levelname)s - %(message)s', + datefmt='%Y-%m-%d %H:%M:%S' +) def parse_php_config(config_path): """Estrae le configurazioni SMTP dal file config.php""" @@ -86,19 +100,25 @@ def send_email(to_email, subject, html_body, config): } except smtplib.SMTPAuthenticationError as e: + error_msg = f'Autenticazione SMTP fallita per {to_email}: {str(e)}' + logging.error(error_msg) return { 'success': False, - 'error': f'Autenticazione SMTP fallita: {str(e)}' + 'error': error_msg } except smtplib.SMTPException as e: + error_msg = f'Errore SMTP invio a {to_email}: {str(e)}' + logging.error(error_msg) return { 'success': False, - 'error': f'Errore SMTP: {str(e)}' + 'error': error_msg } except Exception as e: + error_msg = f'Errore generico invio a {to_email}: {str(e)}' + logging.error(error_msg) return { 'success': False, - 'error': f'Errore generico: {str(e)}' + 'error': error_msg } def main(): @@ -115,9 +135,11 @@ def main(): data = json.loads(sys.argv[1]) if 'to' not in data or 'subject' not in data or 'html' not in data: + error = 'Parametri mancanti: to, subject, html sono obbligatori' + logging.error(error) print(json.dumps({ 'success': False, - 'error': 'Parametri mancanti: to, subject, html sono obbligatori' + 'error': error })) sys.exit(1) @@ -133,15 +155,19 @@ def main(): sys.exit(0 if result['success'] else 1) except json.JSONDecodeError as e: + error = f'JSON non valido: {str(e)}' + logging.error(error) print(json.dumps({ 'success': False, - 'error': f'JSON non valido: {str(e)}' + 'error': error })) sys.exit(1) except Exception as e: + error = f'Errore: {str(e)}' + logging.error(error) print(json.dumps({ 'success': False, - 'error': f'Errore: {str(e)}' + 'error': error })) sys.exit(1) diff --git a/verify_email.php b/verify_email.php new file mode 100644 index 0000000..6eea5c2 --- /dev/null +++ b/verify_email.php @@ -0,0 +1,196 @@ +prepare(" + SELECT id, email, first_name, email_token_expires, email_verified + FROM users + WHERE email_token = ? + AND deleted_at IS NULL + "); + $stmt->execute([$token]); + $user = $stmt->fetch(); + + if (!$user) { + $error = 'Token non valido o già utilizzato.'; + } elseif ($user['email_verified']) { + $error = 'Questo account è già stato verificato. Puoi effettuare il login.'; + $email = $user['email']; + } elseif (strtotime($user['email_token_expires']) < time()) { + $error = 'Il token è scaduto. Richiedi un nuovo link di verifica dalla pagina di login.'; + $email = $user['email']; + } else { + // Token valido - verifica email + $stmt = $pdo->prepare(" + UPDATE users + SET email_verified = TRUE, + email_token = NULL, + email_token_expires = NULL, + updated_at = NOW() + WHERE id = ? + "); + $stmt->execute([$user['id']]); + + // Log attività + $stmt = $pdo->prepare(" + INSERT INTO activity_log (user_id, action, description, ip_address, user_agent, created_at) + VALUES (?, 'email_verified', 'Email verificata con successo', ?, ?, NOW()) + "); + $stmt->execute([ + $user['id'], + $_SERVER['REMOTE_ADDR'] ?? null, + $_SERVER['HTTP_USER_AGENT'] ?? null + ]); + + // Invia email di benvenuto + $subject = "Benvenuto su " . SITE_NAME . "!"; + $body = " + + + + + +
+
+

✅ Account Attivato!

+
+
+

Ciao " . htmlspecialchars($user['first_name']) . ",

+

🎉 Il tuo account è stato verificato con successo!

+

Ora puoi accedere alla piattaforma e iniziare a esplorare il nostro catalogo di lezioni di Pilates.

+ +

Cosa puoi fare ora:

+
    +
  • 🎥 Guarda le lezioni demo gratuite
  • +
  • 📚 Esplora il catalogo completo
  • +
  • 🛒 Acquista le lezioni che ti interessano
  • +
  • 👤 Personalizza il tuo profilo
  • +
+

Se hai domande o hai bisogno di assistenza, non esitare a contattarci.

+

Buon allenamento!
Il team di " . SITE_NAME . "

+
+ +
+ + + "; + + send_email($user['email'], $subject, $body); + + $success = true; + $email = $user['email']; + } + + } catch (PDOException $e) { + $error = 'Errore durante la verifica. Riprova più tardi.'; + error_log("Email verification error: " . $e->getMessage()); + } +} else { + $error = 'Token mancante. Controlla il link nell\'email di verifica.'; +} +?> + + + + + + Verifica Email - <?php echo SITE_NAME; ?> + + + +
+
+
+

+ +

+
+
+
+ +
+
+

+ +

+ + +
+

+ Il tuo account è stato attivato con successo! +

+

+ Email verificata: +

+

+ Ora puoi accedere alla piattaforma e iniziare il tuo percorso di Pilates! +

+ +
+ +
+

+ +

+ +

+ Puoi richiedere un nuovo link di verifica dalla pagina di login. +

+ + +
+ +
+
+ + + +