diff --git a/README.md b/README.md index d5acf3b..909e672 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,32 @@ docker compose up -d --build # https://househub.nas.percolouco.com ``` -La base de données est **initialisée automatiquement** depuis `schema.sql` au premier démarrage. +La base de données meta (`househub_meta`) et les permissions sont **initialisées automatiquement** au premier démarrage via `docker/init/00-setup.sql`. -Compte par défaut : `admin` / `password` → **à changer immédiatement** dans la table `pf_users`. +Aucun compte par défaut — le premier utilisateur s'inscrit via `/register.php` et choisit de créer un espace familial. + +## 👥 Gestion multi-tenant + +HouseHub supporte plusieurs familles indépendantes sur la même instance : + +| URL | Description | +|-----|-------------| +| `/register.php` | Créer un compte + nouvel espace, ou rejoindre un espace existant via code | +| `/login.php` | Connexion | +| `/settings.php` | Paramètres du compte : profil, mot de passe, code d'invitation, membres | +| `/admin/` | Panneau d'administration (admin uniquement) | + +### Inviter quelqu'un dans son espace +1. Aller sur `/settings.php` → copier le code d'invitation +2. La personne va sur `/register.php` → onglet "Rejoindre" → coller le code +3. Les deux comptes partagent le même environnement de données + +### Rôle admin +Le premier utilisateur inscrit doit être promu admin manuellement : +```sql +UPDATE househub_meta.users SET is_admin = 1 WHERE username = 'ton_username'; +``` +L'admin peut ensuite promouvoir/désactiver d'autres utilisateurs depuis `/admin/`. --- diff --git a/admin/index.php b/admin/index.php new file mode 100644 index 0000000..2b641d5 --- /dev/null +++ b/admin/index.php @@ -0,0 +1,196 @@ +prepare("UPDATE users SET is_active = 1 - is_active WHERE id = ?")->execute([$id]); + } elseif ($action === 'toggle_family' && $id) { + $meta_pdo->prepare("UPDATE families SET is_active = 1 - is_active WHERE id = ?")->execute([$id]); + } elseif ($action === 'toggle_admin' && $id) { + // Ne peut pas se retirer ses propres droits admin + if ($id !== (int)$_SESSION['user']['id']) { + $meta_pdo->prepare("UPDATE users SET is_admin = 1 - is_admin WHERE id = ?")->execute([$id]); + } + } elseif ($action === 'delete_user' && $id) { + if ($id !== (int)$_SESSION['user']['id']) { + $meta_pdo->prepare("DELETE FROM users WHERE id = ?")->execute([$id]); + } + } elseif ($action === 'regen_invite' && $id) { + $new_code = bin2hex(random_bytes(8)); + $meta_pdo->prepare("UPDATE families SET invite_code = ? WHERE id = ?")->execute([$new_code, $id]); + } + header('Location: /admin/'); + exit; +} + +// ─── Données ────────────────────────────────────────────────────────────────── +$families = $meta_pdo->query(" + SELECT f.*, COUNT(u.id) as member_count + FROM families f + LEFT JOIN users u ON u.family_id = f.id + GROUP BY f.id + ORDER BY f.id +")->fetchAll(); + +$users = $meta_pdo->query(" + SELECT u.*, f.name as family_name + FROM users u + LEFT JOIN families f ON f.id = u.family_id + ORDER BY u.id +")->fetchAll(); + +$pageTitle = "Admin — HouseHub"; +$activePage = "admin"; +require __DIR__ . '/../header.php'; +?> + +
+ +

⚙️ Panneau d'administration

+

Gestion des espaces familiaux et des utilisateurs

+ + +

+ 🏠 Espaces familiaux + +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
#NomDBCode invitationMembresStatutActions
+ + + + + + +
+ + + +
+
+ + + +
+
+
+ + +

+ 👤 Utilisateurs + +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
#UtilisateurEspace familleRôleStatutCréé leActions
+ + @ + (vous) + —' ?> + + Admin + + Membre + + + + + + +
+ +
+ + + +
+
+ + + +
+
+ + + +
+ + + +
+
+
+ +
+ + diff --git a/docker/init/00-setup.sql b/docker/init/00-setup.sql index 17f1853..ba43e80 100644 --- a/docker/init/00-setup.sql +++ b/docker/init/00-setup.sql @@ -1,7 +1,6 @@ -- Initialisation HouseHub — Meta DB + permissions -- Exécuté automatiquement au premier démarrage MariaDB --- Tables meta (dans la DB créée par MYSQL_DATABASE=househub_meta) USE househub_meta; CREATE TABLE IF NOT EXISTS families ( @@ -9,6 +8,7 @@ CREATE TABLE IF NOT EXISTS families ( name VARCHAR(100) NOT NULL, db_name VARCHAR(64) NOT NULL UNIQUE, invite_code VARCHAR(32) NOT NULL UNIQUE, + is_active TINYINT(1) DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; @@ -18,6 +18,8 @@ CREATE TABLE IF NOT EXISTS users ( password_hash VARCHAR(255) NOT NULL, display_name VARCHAR(100) NOT NULL, family_id INT DEFAULT NULL, + is_admin TINYINT(1) DEFAULT 0, + is_active TINYINT(1) DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (family_id) REFERENCES families(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/includes/admin_auth.php b/includes/admin_auth.php new file mode 100644 index 0000000..719b1da --- /dev/null +++ b/includes/admin_auth.php @@ -0,0 +1,8 @@ +403 — Accès réservé aux administrateursRetour'); +} diff --git a/invite.php b/invite.php deleted file mode 100644 index e520d8a..0000000 --- a/invite.php +++ /dev/null @@ -1,64 +0,0 @@ -prepare("SELECT * FROM families WHERE id = ?"); -$family->execute([$_SESSION['user']['family_id']]); -$family = $family->fetch(); - -$members = $meta_pdo->prepare("SELECT display_name, username, created_at FROM users WHERE family_id = ? ORDER BY id"); -$members->execute([$_SESSION['user']['family_id']]); -$members = $members->fetchAll(); - -$pageTitle = "Invitation — HouseHub"; -$activePage = "invite"; -require __DIR__ . '/header.php'; -?> - -
- -

👨‍👩‍👧 Votre espace familial

-

Partagez le code ci-dessous pour inviter quelqu'un dans votre espace.

- -
-

Famille :

-

Code d'invitation

- - - -

✓ Copié !

-

- Lien d'inscription : - /register.php -

-
- -

Membres de l'espace ()

-
- -
-
- - @ -
- -
- -
- -
- - - - diff --git a/login.php b/login.php index 40d3258..523dd81 100644 --- a/login.php +++ b/login.php @@ -21,7 +21,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $error = tr('error_missing_fields'); } else { $stmt = $meta_pdo->prepare(" - SELECT u.id, u.username, u.password_hash, u.display_name, u.family_id, f.db_name + SELECT u.id, u.username, u.password_hash, u.display_name, u.family_id, u.is_admin, u.is_active, f.db_name, f.is_active as family_active FROM users u LEFT JOIN families f ON f.id = u.family_id WHERE u.username = ? @@ -30,16 +30,23 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $user = $stmt->fetch(PDO::FETCH_ASSOC); if ($user && password_verify($password, $user['password_hash'])) { - $_SESSION['user'] = [ - 'id' => (int)$user['id'], - 'username' => $user['username'], - 'display_name' => $user['display_name'], - 'family_id' => (int)$user['family_id'], - ]; - $_SESSION['family_db'] = $user['db_name']; - $redirectTo = $_GET['redirect'] ?? '/index.php'; - header('Location: ' . $redirectTo); - exit; + if (!$user['is_active']) { + $error = "Compte désactivé. Contactez l'administrateur."; + } elseif ($user['family_id'] && !$user['family_active']) { + $error = "Espace familial désactivé. Contactez l'administrateur."; + } else { + $_SESSION['user'] = [ + 'id' => (int)$user['id'], + 'username' => $user['username'], + 'display_name' => $user['display_name'], + 'family_id' => (int)$user['family_id'], + 'is_admin' => (bool)$user['is_admin'], + ]; + $_SESSION['family_db'] = $user['db_name']; + $redirectTo = $_GET['redirect'] ?? '/index.php'; + header('Location: ' . $redirectTo); + exit; + } } else { $error = tr('error_invalid_credentials'); } diff --git a/settings.php b/settings.php new file mode 100644 index 0000000..cff8f6e --- /dev/null +++ b/settings.php @@ -0,0 +1,215 @@ +prepare("UPDATE users SET display_name = ? WHERE id = ?") + ->execute([$display_name, $user_id]); + $_SESSION['user']['display_name'] = $display_name; + $success = "Profil mis à jour."; + } + } + + if ($action === 'change_password') { + $current = $_POST['current_password'] ?? ''; + $new = $_POST['new_password'] ?? ''; + $confirm = $_POST['confirm_password'] ?? ''; + + $row = $meta_pdo->prepare("SELECT password_hash FROM users WHERE id = ?"); + $row->execute([$user_id]); + $row = $row->fetch(); + + if (!password_verify($current, $row['password_hash'])) { + $error = "Mot de passe actuel incorrect."; + } elseif (strlen($new) < 6) { + $error = "Le nouveau mot de passe doit faire au moins 6 caractères."; + } elseif ($new !== $confirm) { + $error = "Les mots de passe ne correspondent pas."; + } else { + $meta_pdo->prepare("UPDATE users SET password_hash = ? WHERE id = ?") + ->execute([password_hash($new, PASSWORD_BCRYPT), $user_id]); + $success = "Mot de passe modifié avec succès."; + } + } + + if ($action === 'update_family_name' && $family_id) { + $name = trim($_POST['family_name'] ?? ''); + if ($name) { + $meta_pdo->prepare("UPDATE families SET name = ? WHERE id = ?") + ->execute([$name, $family_id]); + $success = "Nom de l'espace mis à jour."; + } + } + + if ($action === 'regen_invite' && $family_id) { + $new_code = bin2hex(random_bytes(8)); + $meta_pdo->prepare("UPDATE families SET invite_code = ? WHERE id = ?") + ->execute([$new_code, $family_id]); + $success = "Nouveau code d'invitation généré."; + } +} + +// ─── Chargement données ─────────────────────────────────────────────────────── +$user = $meta_pdo->prepare("SELECT * FROM users WHERE id = ?"); +$user->execute([$user_id]); +$user = $user->fetch(); + +$family = null; +$members = []; +if ($family_id) { + $fam = $meta_pdo->prepare("SELECT * FROM families WHERE id = ?"); + $fam->execute([$family_id]); + $family = $fam->fetch(); + + $mem = $meta_pdo->prepare("SELECT display_name, username, created_at, is_admin FROM users WHERE family_id = ? ORDER BY id"); + $mem->execute([$family_id]); + $members = $mem->fetchAll(); +} + +$pageTitle = "Paramètres — HouseHub"; +$activePage = "settings"; +require __DIR__ . '/header.php'; +?> + +
+ +

⚙️ Paramètres

+ + +
+ + +
+ + + +
+

👤 Mon profil

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

🔒 Changer le mot de passe

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

🏠 Mon espace familial

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

Code d'invitation — partagez-le pour inviter quelqu'un

+
+ + + + ✓ Copié ! +
+ + +
+
+

+ Lien d'inscription : /register.php +

+
+ + +

Membres ()

+
+ +
+
+ + @ + + Admin + +
+ +
+ +
+
+ + + +
+ → Panneau d'administration +
+ + +
+ + + +