Spaces:
Runtime error
Runtime error
File size: 6,033 Bytes
da2ad52 7cf23f7 da2ad52 7435134 6e91d61 7435134 da2ad52 c7fa337 da2ad52 7cf23f7 da2ad52 6e91d61 da2ad52 7cf23f7 da2ad52 7cf23f7 da2ad52 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | """
Crée ou met à jour le compte administrateur initial.
Aucun utilisateur n'était créé nulle part dans le projet : le modèle `User` et la
table `users` existaient depuis la migration 0b21fb6a3651 sans qu'aucun code ne
les alimente. Une fois l'authentification branchée, sans ce script il devient
impossible de se connecter à sa propre instance.
Idempotent : relancé, il ne duplique rien et ne modifie que ce qui doit l'être.
Usage:
python scripts/create_admin.py
python scripts/create_admin.py --email admin@jurix.cm --username admin
python scripts/create_admin.py --reset-password
python scripts/create_admin.py --role superadmin
python scripts/create_admin.py --promouvoir # compte ordinaire deja inscrit
Les valeurs par défaut viennent de .env : ADMIN_EMAIL, ADMIN_USERNAME,
ADMIN_PASSWORD. Si aucun mot de passe n'est fourni ni par option ni par
l'environnement, il est demandé de façon interactive — jamais de valeur par
défaut en dur.
"""
import argparse
import asyncio
import getpass
import os
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
from dotenv import load_dotenv
from pydantic import ValidationError
from sqlalchemy import select
load_dotenv()
from app.core.auth import hash_password
from app.core.database import AsyncSessionLocal
from app.models.user import User
from app.schemas.user import UserCreate
def parse_args() -> argparse.Namespace:
p = argparse.ArgumentParser(description="Crée ou met à jour un administrateur JuriX")
p.add_argument("--email", default=os.getenv("ADMIN_EMAIL", "admin@jurix.cm"))
p.add_argument("--username", default=os.getenv("ADMIN_USERNAME", "admin"))
p.add_argument("--password", default=os.getenv("ADMIN_PASSWORD"))
p.add_argument(
"--role",
default=os.getenv("ADMIN_ROLE", "superadmin"),
choices=["admin", "superadmin"],
)
p.add_argument(
"--reset-password",
action="store_true",
help="Réinitialise le mot de passe d'un compte existant",
)
p.add_argument(
"--promouvoir",
action="store_true",
help="Promeut un compte ORDINAIRE existant ; son mot de passe est alors remplacé",
)
return p.parse_args()
async def main() -> int:
args = parse_args()
password = args.password
if not password:
password = getpass.getpass(f"Mot de passe pour {args.email} : ")
if not password:
print("❌ Aucun mot de passe fourni.", file=sys.stderr)
return 1
# On valide via UserCreate pour que le script et l'API appliquent
# exactement la même politique de mot de passe.
try:
UserCreate(
email=args.email,
username=args.username,
password=password,
role=args.role,
)
except ValidationError as e:
# Jamais str(e) : pydantic y recopie la valeur refusee, donc le mot de
# passe, en clair dans le terminal et dans la sortie du deploiement.
for erreur in e.errors(include_input=False, include_url=False):
champ = ".".join(str(partie) for partie in erreur["loc"])
print(f"❌ Données invalides, {champ} : {erreur['msg']}", file=sys.stderr)
return 1
try:
async with AsyncSessionLocal() as db:
existing = (
await db.execute(select(User).where(User.email == args.email.lower()))
).scalar_one_or_none()
if existing is None:
db.add(
User(
email=args.email.lower(),
username=args.username.lower(),
hashed_password=hash_password(password),
role=args.role,
is_active=True,
is_verified=True,
)
)
await db.commit()
print(f"✅ Compte créé : {args.email} (rôle {args.role})")
return 0
# Un compte ordinaire deja inscrit a cette adresse n'est jamais promu
# en silence. Le site est public avant la creation de
# l'administrateur (demarrer.sh, etape 6) : quiconque s'inscrivait
# sous ADMIN_EMAIL devenait superadmin, avec SON mot de passe.
ordinaire = existing.role not in ("admin", "superadmin")
if ordinaire and not args.promouvoir:
print(
f"❌ {args.email} est déjà un compte ordinaire (rôle {existing.role}) : "
"il n'est pas promu sans confirmation. S'il est bien à vous, relancez avec "
"--promouvoir (son mot de passe sera remplacé) ; sinon, choisissez un autre "
"ADMIN_EMAIL.",
file=sys.stderr,
)
return 1
changed = []
if existing.role != args.role:
existing.role = args.role
changed.append("rôle")
if not existing.is_active:
existing.is_active = True
changed.append("réactivation")
if args.reset_password or ordinaire:
existing.hashed_password = hash_password(password)
changed.append("mot de passe")
if changed:
await db.commit()
print(f"✅ Compte mis à jour : {args.email} ({', '.join(changed)})")
else:
print(
f"ℹ️ Compte déjà conforme : {args.email} (rôle {existing.role}). "
"Utilisez --reset-password pour changer le mot de passe."
)
return 0
except Exception as e:
print(f"❌ Base de données inaccessible : {e}", file=sys.stderr)
print(" Vérifiez DATABASE_URL dans .env et que les migrations sont appliquées.",
file=sys.stderr)
return 1
if __name__ == "__main__":
sys.exit(asyncio.run(main()))
|