]> git.nothing2do.fr Git - diary-web.git/commitdiff
Move application files to public/ directory for AlwaysData hosting
authorgaby <gaby@nothing2do.fr>
Fri, 7 Aug 2026 12:12:01 +0000 (14:12 +0200)
committergaby <gaby@nothing2do.fr>
Fri, 7 Aug 2026 12:12:01 +0000 (14:12 +0200)
- Move all application files to public/ directory (AlwaysData web root)
- Update WebAuthn RP_ID from nothing2do.fr to dw.nothing2do.fr
- Enable error display for debugging
- Update .htaccess for AlwaysData configuration

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
.htaccess [deleted file]
logout.php [deleted file]
public/.htaccess [new file with mode: 0644]
public/config/config.php [moved from config/config.php with 99% similarity]
public/include/Database.php [moved from include/Database.php with 100% similarity]
public/include/TripletManager.php [moved from include/TripletManager.php with 100% similarity]
public/include/WebAuthnManager.php [moved from include/WebAuthnManager.php with 100% similarity]
public/index.php [moved from index.php with 99% similarity]
public/register.php [new file with mode: 0644]

diff --git a/.htaccess b/.htaccess
deleted file mode 100644 (file)
index 9296d95..0000000
--- a/.htaccess
+++ /dev/null
@@ -1,15 +0,0 @@
-# Diary Web Application
-# Main application entry point
-
-RewriteEngine On
-
-# Check if file exists at root level
-RewriteCond %{DOCUMENT_ROOT}/$1 -f
-RewriteRule ^(.*)$ $1 [L]
-
-# Check if file exists in public directory
-RewriteCond %{DOCUMENT_ROOT}/public/$1 -f
-RewriteRule ^(.*)$ public/$1 [L]
-
-# Otherwise, redirect to root index.php
-RewriteRule ^(.*)$ index.php [L,QSA]
diff --git a/logout.php b/logout.php
deleted file mode 100644 (file)
index ae8a939..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-<?php
-// logout.php - Handle user logout
-
-session_set_cookie_params([
-    'lifetime' => 0,
-    'path' => '/',
-    'domain' => '',
-    'secure' => true,
-    'httponly' => true,
-    'samesite' => 'Lax'
-]);
-
-session_start();
-
-// Destroy all session data
-session_destroy();
-
-// Redirect to home page
-header("Location: index.php");
-exit();
diff --git a/public/.htaccess b/public/.htaccess
new file mode 100644 (file)
index 0000000..4dd46c2
--- /dev/null
@@ -0,0 +1,30 @@
+# Diary Web Application
+# AlwaysData hosting - public directory is web root
+
+RewriteEngine On
+
+# Ensure HTTPS
+RewriteCond %{HTTPS} off
+RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
+
+# If the request is for an existing file, serve it directly
+RewriteCond %{REQUEST_FILENAME} -f
+RewriteRule ^(.*)$ $1 [L]
+
+# If the request is for an existing directory, serve it directly
+RewriteCond %{REQUEST_FILENAME} -d
+RewriteRule ^(.*)$ $1 [L]
+
+# Otherwise, serve through index.php (Front Controller Pattern)
+RewriteCond %{REQUEST_FILENAME} !-f
+RewriteCond %{REQUEST_FILENAME} !-d
+RewriteRule ^(.*)$ index.php [L,QSA]
+
+# Security headers
+Header always set X-Content-Type-Options "nosniff"
+Header always set X-Frame-Options "SAMEORIGIN"
+Header always set X-XSS-Protection "1; mode=block"
+Header always set Referrer-Policy "strict-origin-when-cross-origin"
+
+# Disable directory listing
+Options -Indexes
similarity index 99%
rename from config/config.php
rename to public/config/config.php
index c86244e418e1ff2e30f1c5e36efdd79bed76d650..066cef40fc5af219e932205bc7ce8552e2bee7d0 100644 (file)
@@ -10,7 +10,7 @@ define('DB_PASS', 'une pierre dans le jardeux');
 
 // Configuration WebAuthn
 define('WEBAUTHN_RP_NAME', 'Diary-web');
-define('WEBAUTHN_RP_ID', 'nothing2do.fr');
+define('WEBAUTHN_RP_ID', 'dw.nothing2do.fr');
 define('WEBAUTHN_ORIGIN', 'https://dw.nothing2do.fr');
 
 // Initialize database connection
similarity index 99%
rename from index.php
rename to public/index.php
index 8d0ae0b314192f78e09ba468fee7092b19c57dc4..ed013ac757f25dadad7f990bb9a37c2b3e604c09 100644 (file)
--- a/index.php
@@ -4,7 +4,7 @@
 
 ob_start();
 error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);
-ini_set('display_errors', 0);
+ini_set('display_errors', 1);
 ini_set('log_errors', 1);
 
 // Configure session for HTTPS and security
diff --git a/public/register.php b/public/register.php
new file mode 100644 (file)
index 0000000..17beb9f
--- /dev/null
@@ -0,0 +1,206 @@
+<?php
+// register.php - User registration with YubiKey WebAuthn
+
+ob_start();
+error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);
+ini_set('display_errors', 1);
+ini_set('log_errors', 1);
+
+session_set_cookie_params(['lifetime' => 0, 'path' => '/', 'domain' => '', 'secure' => true, 'httponly' => true, 'samesite' => 'Lax']);
+session_start();
+
+require_once __DIR__ . '/config/config.php';
+require_once __DIR__ . '/include/Database.php';
+require_once __DIR__ . '/include/WebAuthnManager.php';
+
+if (USER_LOGGED_IN) {
+    header("Location: index.php");
+    exit();
+}
+
+$db = new Database();
+$pdo = $db->connect();
+$webAuthnManager = new WebAuthnManager();
+$error = '';
+$success = '';
+
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+    if (isset($_POST['username'])) {
+        $username = trim($_POST['username']);
+        if (!validate_username($username)) {
+            header('Content-Type: application/json');
+            echo json_encode(['success' => false, 'error' => 'Nom d\'utilisateur invalide. 3-64 caractères (lettres, chiffres, _, -, @, .)']);
+            exit();
+        }
+        try {
+            $stmt = $pdo->prepare("SELECT user_id FROM users WHERE username = ?");
+            $stmt->execute([$username]);
+            if ($stmt->fetch()) {
+                header('Content-Type: application/json');
+                echo json_encode(['success' => false, 'error' => 'Nom d\'utilisateur déjà pris']);
+                exit();
+            }
+            $registrationOptions = $webAuthnManager->generateRegistrationOptions($username);
+            $_SESSION['registration_options'] = $registrationOptions;
+            $_SESSION['registration_username'] = $username;
+            header('Content-Type: application/json');
+            echo json_encode(['success' => true, 'options' => $registrationOptions->jsonSerialize()]);
+            exit();
+        } catch (Exception $e) {
+            header('Content-Type: application/json');
+            echo json_encode(['success' => false, 'error' => 'Erreur: ' . $e->getMessage()]);
+            exit();
+        }
+    } elseif (isset($_POST['attestationResponse'])) {
+        $attestationResponse = trim($_POST['attestationResponse']);
+        if (!empty($attestationResponse) && isset($_SESSION['registration_username'])) {
+            $username = $_SESSION['registration_username'];
+            unset($_SESSION['registration_username']);
+            $registrationData = $webAuthnManager->register($attestationResponse);
+            if ($registrationData && isset($registrationData['credentialId']) && isset($registrationData['publicKey'])) {
+                try {
+                    $pdo->beginTransaction();
+                    $stmt = $pdo->prepare("INSERT INTO users (username) VALUES (?) RETURNING user_id");
+                    $stmt->execute([$username]);
+                    $userId = $stmt->fetchColumn();
+                    $stmt = $pdo->prepare("INSERT INTO yubikeys (user_id, credential_id, public_key, counter) VALUES (?, ?, ?, ?)");
+                    $stmt->execute([$userId, $registrationData['credentialId'], $registrationData['publicKey'], $registrationData['counter'] ?? 0]);
+                    $yubikeyId = $pdo->lastInsertId();
+                    $stmt = $pdo->prepare("UPDATE users SET yubikey_id = ? WHERE user_id = ?");
+                    $stmt->execute([$yubikeyId, $userId]);
+                    $pdo->commit();
+                    $_SESSION['user_id'] = $userId;
+                    $_SESSION['username'] = $username;
+                    $_SESSION['status'] = "Inscription réussie ! Bienvenue $username";
+                    $_SESSION['action_processed'] = false;
+                    header('Content-Type: application/json');
+                    echo json_encode(['success' => true, 'redirect' => 'index.php']);
+                    exit();
+                } catch (Exception $e) {
+                    if ($pdo->inTransaction()) $pdo->rollBack();
+                    header('Content-Type: application/json');
+                    echo json_encode(['success' => false, 'error' => 'Erreur: ' . $e->getMessage()]);
+                    exit();
+                }
+            } else {
+                header('Content-Type: application/json');
+                echo json_encode(['success' => false, 'error' => 'Réponse YubiKey invalide']);
+                exit();
+            }
+        } else {
+            header('Content-Type: application/json');
+            echo json_encode(['success' => false, 'error' => 'Session expirée']);
+            exit();
+        }
+    }
+}
+?>
+<!DOCTYPE html>
+<html lang="fr">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
+    <meta name="apple-mobile-web-app-capable" content="yes">
+    <meta name="mobile-web-app-capable" content="yes">
+    <title>Inscription - <?php echo APP_NAME; ?></title>
+    <style>
+        :root {--primary-color: #4a6fa5;--primary-dark: #166088;--secondary-color: #4fc3f7;--success-color: #4caf50;--error-color: #f44336;--background-color: #f5f7fa;--surface-color: #ffffff;--text-primary: #333333;--text-secondary: #666666;--border-color: #e0e0e0;--shadow: 0 2px 8px rgba(0,0,0,0.1);--border-radius: 8px;}
+        * {box-sizing: border-box; margin: 0; padding: 0;}
+        body {font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background-color: var(--background-color); color: var(--text-primary); min-height: 100vh; display: flex; flex-direction: column;}
+        header {background: linear-gradient(135deg, var(--primary-dark), var(--primary-color)); color: white; padding: 16px 20px; box-shadow: var(--shadow);}
+        header h1 {font-size: 1.5rem; font-weight: 600;}
+        main {flex: 1; padding: 20px; max-width: 600px; margin: 0 auto; width: 100%;}
+        .register-form {background-color: var(--surface-color); border-radius: var(--border-radius); padding: 32px; box-shadow: var(--shadow);}
+        .register-form h2 {text-align: center; color: var(--primary-dark); margin-bottom: 24px; font-size: 1.5rem;}
+        .form-group {margin-bottom: 20px;}
+        .form-group label {display: block; margin-bottom: 6px; font-weight: 500;}
+        .form-group input[type="text"] {width: 100%; padding: 12px 14px; border: 2px solid var(--border-color); border-radius: var(--border-radius); font-size: 1rem; transition: all 0.2s;}
+        .form-group input[type="text"]:focus {outline: none; border-color: var(--primary-color); box-shadow: 0 0 0 3px rgba(74, 111, 165, 0.1);}
+        button {padding: 12px 24px; border: none; border-radius: var(--border-radius); font-size: 1rem; font-weight: 500; cursor: pointer; transition: all 0.2s; display: block; width: 100%; margin-bottom: 12px;}
+        .btn-primary {background-color: var(--primary-color); color: white;}
+        .btn-primary:hover {background-color: var(--primary-dark);}
+        .btn-primary:disabled {background-color: #ccc; cursor: not-allowed;}
+        .message {padding: 12px 16px; border-radius: var(--border-radius); margin-bottom: 20px; font-weight: 500;}
+        .message.success {background-color: var(--success-color); color: white;}
+        .message.error {background-color: var(--error-color); color: white;}
+        .message.info {background-color: #e3f2fd; color: var(--primary-dark); border-left: 4px solid var(--secondary-color);}
+        #yubikeySection {display: none; margin-top: 24px; padding: 20px; background-color: #e3f2fd; border-radius: var(--border-radius); border-left: 4px solid var(--secondary-color); text-align: center;}
+        .yubikey-icon {font-size: 36px; margin-bottom: 8px;}
+        .browser-req {margin-top: 20px; padding: 12px; background-color: #fff3cd; border-radius: var(--border-radius); font-size: 0.85rem; color: #856404;}
+        .auth-links {text-align: center; margin-top: 24px; padding-top: 24px; border-top: 1px solid var(--border-color);}
+        .auth-links a {color: var(--primary-color); text-decoration: none;}
+        .auth-links a:hover {text-decoration: underline;}
+        @media (min-width: 600px) {main {padding: 32px;} header h1 {font-size: 1.8rem;}}
+    </style>
+</head>
+<body>
+    <header><h1>Inscription</h1></header>
+    <main>
+        <div class="register-form">
+            <?php if ($error): ?><div class="message error"><?php echo htmlspecialchars($error); ?></div><?php endif; ?>
+            <?php if ($success): ?><div class="message success"><?php echo htmlspecialchars($success); ?></div><?php endif; ?>
+            <div class="message info"><strong>🔐</strong> Cette application utilise WebAuthn (YubiKey). Vous aurez besoin d'une clé compatible.</div>
+            <form method="post" id="registrationForm">
+                <div class="form-group"><label for="username">Nom d'utilisateur :</label>
+                <input type="text" id="username" name="username" required placeholder="3-64 caractères"></div>
+                <button type="submit" id="registerButton" class="btn-primary">S'inscrire</button>
+            </form>
+            <div id="yubikeySection"><div class="yubikey-icon">🔑</div><p>Veuillez toucher votre YubiKey.</p><div id="webauthnMessage"></div></div>
+            <div class="browser-req"><strong>⚠️</strong> HTTPS requis, navigateur moderne, clé WebAuthn/FIDO2.</div>
+            <div class="auth-links"><p>Déjà un compte ? <a href="index.php">Se connecter</a></p></div>
+        </div>
+    </main>
+    <footer style="text-align:center; padding:20px; color:var(--text-secondary); font-size:0.85rem;"><p>© 2026 - <?php echo APP_NAME; ?> | Nothing2Do.fr</p></footer>
+    <script>
+        function arrayBufferToBase64(buffer) {
+            return btoa(String.fromCharCode(...new Uint8Array(buffer))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
+        }
+        document.addEventListener('DOMContentLoaded', function() {
+            const form = document.getElementById('registrationForm'), usernameInput = document.getElementById('username'),
+            registerButton = document.getElementById('registerButton'), yubikeySection = document.getElementById('yubikeySection'),
+            webauthnMessage = document.getElementById('webauthnMessage');
+            let registrationOptions = null;
+            form.addEventListener('submit', async function(e) {
+                e.preventDefault();
+                if (!usernameInput.value.trim()) { alert('Nom d\'utilisateur requis'); return; }
+                if (registrationOptions === null) {
+                    try {
+                        const response = await fetch('register.php', {method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'},
+                            body: 'username='+encodeURIComponent(usernameInput.value)});
+                        const data = JSON.parse(await response.text());
+                        if (data.success) {
+                            registrationOptions = data.options; usernameInput.disabled = true; registerButton.disabled = true;
+                            yubikeySection.style.display = 'block';
+                            webauthnMessage.textContent = '✅ Prêt pour YubiKey...'; webauthnMessage.className = 'message success';
+                            await startWebAuthnRegistration();
+                        } else { webauthnMessage.textContent = data.error; webauthnMessage.className = 'message error'; webauthnMessage.style.display = 'block'; registerButton.disabled = false; }
+                    } catch (error) { webauthnMessage.textContent = 'Erreur: ' + error.message; webauthnMessage.className = 'message error'; registerButton.disabled = false; }
+                }
+            });
+            async function startWebAuthnRegistration() {
+                try {
+                    webauthnMessage.textContent = 'Touchez votre YubiKey...'; webauthnMessage.className = 'message info';
+                    const publicKey = {
+                        challenge: Uint8Array.from(atob(registrationOptions.challenge), c => c.charCodeAt(0)),
+                        rp: registrationOptions.rp, user: registrationOptions.user,
+                        pubKeyCredParams: registrationOptions.pubKeyCredParams,
+                        authenticatorSelection: registrationOptions.authenticatorSelection,
+                        timeout: registrationOptions.timeout, attestation: registrationOptions.attestation
+                    };
+                    const credential = await navigator.credentials.create({ publicKey });
+                    if (credential) {
+                        const attestationResponse = {id: credential.id, rawId: arrayBufferToBase64(credential.rawId),
+                            response: {attestationObject: arrayBufferToBase64(credential.response.attestationObject),
+                                clientDataJSON: arrayBufferToBase64(credential.response.clientDataJSON)}, type: credential.type};
+                        const response = await fetch('register.php', {method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'},
+                            body: 'attestationResponse='+encodeURIComponent(JSON.stringify(attestationResponse))});
+                        const data = JSON.parse(await response.text());
+                        if (data.success) { webauthnMessage.textContent = 'Succès! Redirection...'; window.location.href = data.redirect; }
+                        else { webauthnMessage.textContent = data.error; webauthnMessage.className = 'message error'; registerButton.disabled = false; }
+                    } else { webauthnMessage.textContent = 'Aucune credential'; webauthnMessage.className = 'message error'; registerButton.disabled = false; }
+                } catch (error) { webauthnMessage.textContent = 'Erreur: ' + error.message; webauthnMessage.className = 'message error'; registerButton.disabled = false; }
+            }
+        });
+    </script>
+</body>
+</html>