WebSite X5Help Center

10 ANSWERS - 1 USEFUL
Domenico B.
Domenico B.
User
Author

Con un pò di santa pazienza sono riuscito a svilupparlo per le pagine protette di wx5.

A breve lo condivido per farlo testare. 

Read more
Posted on the from Domenico B.
Domenico B.
Domenico B.
User
Author

In questa pagina lo trovate potete testarlo eccolo

Read more
Posted on the from Domenico B.
Roberto M.
Roberto M.
User

Ciao Domenico, sempre in smanettamento a 360°! laughing

Analizzando il codice nel dettaglio, noto che la struttura fa un bel po' di cose:Bravo!

  • Genera e decodifica il Base32: Crea la stringa segreta per l'utente.
  • Calcola l'algoritmo TOTP: Prende l'orario del server, applica l'hash HMAC-SHA1 e genera le 6 cifre.
  • Supporta i codici di recupero e il Rate Limiting: Gestisce le tabelle per contare i tentativi falliti in caso di brute-force.

Guardando però le righe da 120 a 170, vedo che il codice richiede una connessione PDO attiva (PDO $pdo):

► public static function ensureAttemptsTable(PDO $pdo): void

Questo, secondo me, comporta diverse problematiche pratiche per l'ecosistema WX5:

  1. Scollegamento totale da WX5: WebSite X5 gestisce i suoi utenti tramite sessioni e file interni (o tabelle DB strutturate a modo suo). Questa classe presuppone che ci si sia scritti da soli l'intero sistema di login in PHP nativo, con tabelle MySQL personalizzate e chiamate PDO dedicate.

  2. Zero automazione: Chi scarica questo pacchetto deve:

  • Configurare una connessione PDO MySQL a parte.
  • Crearsi gli script di ponte che chiamano TOTP::generateSecret() e TOTP::verify().
  • Iniettare questo codice nei file di login di WX5 senza che il programma lo sovrascriva alla prima esportazione.

Secondo me è codice PER SVILUPPATORI undecided, non da utenti WX5: Chi usa WebSite X5 lo fa proprio per evitare di dover scrivere classi PHP a mano, gestire connessioni PDO o manipolare tabelle DB.

In sintesi, per come la vedo io: Hai scritto un'ottima classe PHP per la 2FA con rate limiting e PDO, l'hai chiamata TOTP.php, ma così com'è risulta troppo complessa e poco orientata all'utente medio di WebSite X5.

L'architettura generale e la logica TOTP sono strutturate benissimo, ma secondo me prima del rilascio ci sono 3 correzioni fondamentali da fare nell'integrazione.

Cosa c'è di fatto bene? ▼

  • Logica TOTP e Sicurezza Algoritmica: L'uso della classe TOTP.php con calcolo HMAC, segreti Base32 e sincronizzazione temporale è impeccabile.

  • Protezione Brute Force: La gestione dei tentativi errati (tentativi e blocco temporaneo progressivo) è solida e protegge bene dagli attacchi automatizzati.

  • User Experience (UX): Il modulo JavaScript con auto-focus sui 6 box, gestione del Backspace e incolla automatico offre un'ottima esperienza d'uso.

Le 3 cose su cui intervenire: (se vuoi)

  1. Bypass di Sicurezza (Critico): Nel codice originale, se un utente non è loggato ($utente è false), il blocco di controllo viene ignorato e lo script continua a eseguire la pagina riservata sottostante. Soluzione: Aggiungere un blocco immediato con reindirizzamento se $utente non esiste:

    PHPif (!$utente) { header('Location: imlogin.php'); exit; }
  2. Redirect Post-Verifica (Pattern PRG): Dopo la verifica del codice a 6 cifre, il codice imposta la sessione ma non ricarica la pagina. Soluzione: Inserire un header('Location: ' . $_SERVER['REQUEST_URI']); exit; subito dopo aver validato il TOTP per pulire la richiesta POST ed evitare gli avvisi del browser al refresh dell'utente.

  3. Pulizia dell'HTML e dell'Output Buffer: Sostituire la chiusura "ibrida" dei tag (</head><body>) svuotando prima l'output buffer (ob_clean()) e servendo una struttura HTML5 completa prima di interrompere lo script con exit.

ATTENZIONE !!! Non occorre toccare la classe TOTP.php.

Di seguito ti lascio il codice corretto del file da sostituire direttamente, se vuoi migliorare ulteriormente il progetto:▼

<?php
require_once(__DIR__ . '/totp/TOTP.php');

// 1. Invalida la sessione 2FA al logout dell'utente su X5
if (empty($_SESSION['im_access_uid'])) {
unset($_SESSION['totp_2fa_ok']);
}

$pa = Configuration::getPrivateArea();
$utente = $pa->whoIsLogged();

// 2. BLOCCO TASSATIVO: Se l'utente non è autenticato, ferma l'esecuzione o reindirizza al login
if (!$utente) {
header('Location: imlogin.php'); // Sostituire con l'URL della tua pagina di login se differente
exit;
}

// 3. SE L'UTENTE È LOGGATO MA NON HA ANCORA SUPERATO LA VERIFICA TOTP
if (empty($_SESSION['totp_2fa_ok'])) {

$email = $utente['email'];
$pdo = new PDO('sqlite:' . __DIR__ . '/totp/totp.sqlite');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

// Inizializzazione tabella TOTP
$pdo->exec("CREATE TABLE IF NOT EXISTS custom_totp (
email TEXT PRIMARY KEY,
totp_secret TEXT NOT NULL,
totp_attivo INTEGER NOT NULL DEFAULT 0
)");

$stmt = $pdo->prepare("SELECT totp_secret, totp_attivo FROM custom_totp WHERE email = ?");
$stmt->execute([$email]);
$riga = $stmt->fetch(PDO::FETCH_ASSOC);

if (!$riga) {
$nuovoSecret = TOTP::generateSecret();
$ins = $pdo->prepare("INSERT INTO custom_totp (email, totp_secret, totp_attivo) VALUES (?, ?, 0)");
$ins->execute([$email, $nuovoSecret]);
$riga = ['totp_secret' => $nuovoSecret, 'totp_attivo' => 0];
}

TOTP::ensureAttemptsTable($pdo);

$errore_totp = '';
$bloccatoSecondi = TOTP::getLockoutSecondsRemaining($pdo, $email, 5, 300);

// Verifica il codice inviato tramite il form
if ($bloccatoSecondi === 0 && $_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['totp_code_2fa'])) {
if (TOTP::verify($riga['totp_secret'], $_POST['totp_code_2fa'])) {
if (!$riga['totp_attivo']) {
$upd = $pdo->prepare("UPDATE custom_totp SET totp_attivo = 1 WHERE email = ?");
$upd->execute([$email]);
}
TOTP::resetAttempts($pdo, $email);
$_SESSION['totp_2fa_ok'] = true;

// Redirect POST-GET per caricare la pagina protetta in modo pulito
header('Location: ' . $_SERVER['REQUEST_URI']);
exit;
} else {
TOTP::registerFailedAttempt($pdo, $email);
$bloccatoSecondi = TOTP::getLockoutSecondsRemaining($pdo, $email, 5, 300);
$errore_totp = $bloccatoSecondi > 0
? "Troppi tentativi errati. Riprova tra " . ceil($bloccatoSecondi / 60) . " minuti."
: "Codice non valido, riprova.";
}
}

// Pulisce eventuali output precedenti generati dal CMS
if (ob_get_length()) {
ob_clean();
}

$primaAttivazione = !$riga['totp_attivo'];
?>
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Autenticazione a Due Fattori</title>
<style>
:root{
--bg:#EEF1F4;
--card:#ffffff;
--border:#DDE2E8;
--text:#161B22;
--muted:#667085;
--accent:#245B4F;
--accent-dark:#1B463D;
--accent-tint:#E3EEEC;
--error:#B3261E;
--error-bg:#FDEDED;
}
*{box-sizing:border-box;}
body{
margin:0;
min-height:100vh;
display:flex;
align-items:center;
justify-content:center;
background:var(--bg);
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
color:var(--text);
padding:24px;
}
.totp-card{
width:100%;
max-width:380px;
background:var(--card);
border:1px solid var(--border);
border-top:4px solid var(--accent);
border-radius:14px;
padding:40px 32px 32px;
text-align:center;
}
.totp-icon{
width:44px;height:44px;margin:0 auto 20px;
display:flex;align-items:center;justify-content:center;
background:var(--accent-tint);
border-radius:12px;
}
.totp-card h1{font-size:19px;font-weight:600;margin:0 0 6px;letter-spacing:-0.01em;}
.totp-card p.sub{font-size:14px;color:var(--muted);margin:0 0 24px;line-height:1.5;}
#qrcode{display:flex;justify-content:center;margin:4px 0 24px;}
#qrcode img{border-radius:8px;border:1px solid var(--border);padding:8px;}
.otp-boxes{display:flex;gap:8px;justify-content:center;margin-bottom:20px;}
.otp-boxes input{
width:42px;height:52px;
text-align:center;
font-size:20px;font-weight:600;
border:1.5px solid var(--border);
border-radius:8px;
outline:none;
transition:border-color .15s;
}
.otp-boxes input:focus{border-color:var(--accent);}
.totp-error{
font-size:13px;color:var(--error);
background:var(--error-bg);
border-radius:8px;
padding:8px 12px;
margin-bottom:16px;
}
.totp-card button{
width:100%;
padding:12px;
background:var(--accent);
color:#fff;
border:none;
border-radius:8px;
font-size:15px;
font-weight:600;
cursor:pointer;
transition:background .15s;
}
.totp-card button:hover{background:var(--accent-dark);}
</style>
</head>
<body>
<div class="totp-card">
<div class="totp-icon">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#245B4F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="11" width="18" height="10" rx="2"/>
<path d="M7 11V7a5 5 0 0 1 10 0v4"/>
</svg>
</div>

<?php if ($primaAttivazione): ?>
<h1>Attiva la verifica in due passaggi</h1>
<p class="sub">Inquadra il codice con Google Authenticator o app simili, poi inserisci il codice generato per confermare.</p>
<div id="qrcode"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>
<script>
new QRCode(document.getElementById("qrcode"), {
text: "<?= htmlspecialchars(TOTP::getProvisioningUri($riga['totp_secret'], $email, 'Tuo sito')) ?>",
width: 168, height: 168
});
</script>
<?php else: ?>
<h1>Verifica la tua identità</h1>
<p class="sub">Inserisci il codice a 6 cifre dalla tua app di autenticazione.</p>
<?php endif; ?>

<?php if ($errore_totp): ?>
<div class="totp-error"><?= htmlspecialchars($errore_totp) ?></div>
<?php endif; ?>

<?php if ($bloccatoSecondi > 0): ?>
<p class="sub" style="margin:0;">Riprova più tardi.</p>
<?php else: ?>
<form method="post" id="totpForm">
<div class="otp-boxes">
<?php for ($i = 0; $i < 6; $i++): ?>
<input type="text" inputmode="numeric" maxlength="1" class="otp-digit" autocomplete="one-time-code">
<?php endfor; ?>
</div>
<input type="hidden" name="totp_code_2fa" id="totp_code_2fa">
<button type="submit">Verifica</button>
</form>
<?php endif; ?>
</div>

<script>
var boxes = document.querySelectorAll('.otp-digit');
var hidden = document.getElementById('totp_code_2fa');
var form = document.getElementById('totpForm');

if (form) {
boxes.forEach(function (box, i) {
box.addEventListener('input', function () {
box.value = box.value.replace(/\D/g, '');
if (box.value && i < boxes.length - 1) boxes[i + 1].focus();
});
box.addEventListener('keydown', function (e) {
if (e.key === 'Backspace' && !box.value && i > 0) boxes[i - 1].focus();
});
box.addEventListener('paste', function (e) {
e.preventDefault();
var digits = (e.clipboardData.getData('text').match(/\d/g) || []).slice(0, 6);
digits.forEach(function (d, idx) { if (boxes[idx]) boxes[idx].value = d; });
boxes[Math.min(digits.length, 5)].focus();
});
});

form.addEventListener('submit', function () {
hidden.value = Array.from(boxes).map(function (b) { return b.value; }).join('');
});

boxes[0].focus();
}
</script>
</body>
</html>
<?php
exit;
}
?>

=========

Buon lavoro.wink

Read more
Posted on the from Roberto M.
Domenico B.
Domenico B.
User
Author

Roberto io ti sono grato e ti ringrazio sempre, hai fatto benissimo a correggere purtroppo anche a me le cose sfuggono perciò chiedo sempre di provare per far si che risolviamo i problemi.

o fatto queste modifiche:

Cosa ho cambiato rispetto alla tua versione:

  • Tolto il redirect esplicito per utente non loggato (ridondante, X5 lo gestisce già più in alto)
  • Tolto ob_clean() e il doppio <!DOCTYPE html><html><head> — torno a chiudere semplicemente il <head> già aperto da X5, documento unico e valido
  • Il redirect dopo verifica riuscita ora usa window.location.href = window.location.href via JavaScript invece di header() — funziona sempre, indipendentemente da cosa X5 ha già inviato al browser

Il risultato pratico per l'utente è lo stesso che volevi tu: dopo aver inserito il codice giusto, la pagina si ricarica automaticamente mostrando il contenuto reale, senza bisogno di premere invio due volte o aggiornare a mano.

Read more
Posted on the from Domenico B.
Domenico B.
Domenico B.
User
Author

Roberto sto lavorando anche a una fotogallery e a una videogallery, sopratutto la fotogallery sto facendo in maniera che l'utente finale debba fare poco e niente 

Grazuie mille!! embarassed

Read more
Posted on the from Domenico B.
Domenico B.
Domenico B.
User
Author

Dimendicavo, lavora su un database suo che crea automaticamente dentro la cartella (database sqlite).

Si crea la cartella chiamata totp nella root si copia il file TOTP.php el file htaccess, ora si va sulla pagina dove si vuole la verifica e si copia il codice del file blocco-2fa-pagina.php prima della chiusura del tag HEAD, fatto! 

Read more
Posted on the from Domenico B.
Roberto M.
Roberto M.
User
Domenico B.
Roberto sto lavorando anche a una fotogallery e a una videogallery, sopratutto la fotogallery sto facendo in maniera che l'utente finale debba fare poco e niente  Grazuie mille!!

Ok ma io vorrei proprio che l'utente finale non debba fare nulla. Io L'ho già fatta e funziona a meraviglia! risultato: meno rotture di scatole. Direttamente plug and play. wink

Read more
Posted on the from Roberto M.
Elbe 2
Elbe 2
User

Hello !

L’authentification à deux facteurs (2FA) via Google Authenticator ou Microsoft Authenticator repose sur un système appelé TOTP (Time‑based One‑Time Password).

Techniquement, cela nécessite :

  • une base de données pour stocker la clé secrète TOTP de chaque utilisateur,
  • un serveur dynamique capable de générer et vérifier les codes (PHP, Node, etc.),
  • une bibliothèque TOTP (ex. OTPHP, GoogleAuthenticator PHP),
  • un workflow de connexion en deux étapes (login → vérification du code),
  • une gestion de sessions sécurisée. 

WebSite X5 ne propose pas ces éléments nativement.

Le système de login intégré dans WX5 est basé sur :

  • des fichiers internes statiques,
  • sans base SQL,
  • sans API,
  • sans gestion avancée des sessions,
  • sans possibilité d’ajouter une étape supplémentaire après le login.

Il s’agit d’un moteur d’authentification hérité des anciennes versions du logiciel, qui n’a pas été conçu pour des mécanismes modernes comme le 2FA TOTP.

Conclusion

Il n’est pas possible d’intégrer Google Authenticator ou Microsoft Authenticator directement dans WX5 avec les outils natifs.

Pour obtenir un vrai 2FA, il faut obligatoirement :

  • utiliser un système externe (PHP + base SQL),
  • ou un service tiers via API,
  • et gérer l’authentification en dehors du moteur interne de WX5.

WX5 peut afficher des pages protégées, mais ne peut pas gérer un workflow de sécurité avancé comme le 2FA TOTP.

Bonne journée,. 

Lionel 

Read more
Posted on the from Elbe 2
Roberto M.
Roberto M.
User

Ciao Elibe:

Se vuoi prendere il login integrato di WX5 e aggiungere magicamente un campo "Codice Google Authenticator" dopo username/password, allora sì:

  • WX5 non offre il 2FA TOTP nativamente.
  • Non puoi modificare il motore interno dell'autenticazione.
  • Non esiste un'impostazione nascosta per attivare Google Authenticator.
  • Il flusso login → verifica OTP non è previsto dal sistema utenti standard.

Quindi su questo punto è corretto e hai ragione.


Di Contro:

Dire che serve obbligatoriamente: un database SQL è falso. Il TOTP non richiede necessariamente SQL.

Serve semplicemente conservare per ogni utente:

JSON1{"utente": "mario", "secret": "JBSWY3DPEHPK3PXP"}

Puoi salvarlo in:

  • MySQL
  • SQLite
  • JSON
  • file TXT
  • qualsiasi archivio persistente

Io stesso nei miei progetti ho utlizzato JSON dappertutto. Per 10, 20 o 50 > utenti un JSON va benissimo.

Per contro il progetto di Domenico pur se macchinoso nella configurazione fa quello che deve fare.

Read more
Posted on the from Roberto M.
Elbe 2
Elbe 2
User

Hello Robert, 

Après la phrase : "Moi-même dans mes projets, (...), la suite du message est bloquée. Impossible de la lire...

S'il était possible d'y remédier ?

Bonne journée,

Lionel

Read more
Posted on the from Elbe 2