Refonte authentification en crypté

This commit is contained in:
2025-08-16 14:02:01 +02:00
parent 15665f7c4c
commit 726a35c1a6
12 changed files with 325 additions and 37 deletions

41
.gitignore vendored
View File

@@ -1,10 +1,41 @@
.env # ----- Secrets / configs locales -----
*.txt # Ne versionne JAMAIS de .env (mets un .env.example à la place)
*.env
# ----- Python -----
.venv/
__pycache__/ __pycache__/
*.pyc *.pyc
*.pyo
*.pyd
*.log *.log
.DS_Store
# Ignorer tous les fichiers de travail dans Excel/dev # ----- PyInstaller (builds) -----
Excel/dev/* build/
dist/
*.spec
*.toc
*.pkg
*.pyz
*.zip
# ----- Excel -----
# Fichiers temporaires d'Excel (~$fichier.xlsm)
~$*.xls*
~$*.xlsm
# On versionne 1 seul fichier prod "pivot" ; on ignore les exports datés
Excel/prod/Ratio_prod_20*.xlsm Excel/prod/Ratio_prod_20*.xlsm
!Excel/prod/Ratio_prod.xlsm !Excel/prod/Ratio_prod.xlsm
# ----- Dossiers Auth livrables -----
# On nembarque pas les exe ni .env dans le repo (tu livreras ça en zip)
Excel/**/Auth/*.exe
Excel/**/Auth/*.dll
Excel/**/Auth/*.pdb
Excel/**/Auth/.env
# ----- Systèmes / IDE -----
.DS_Store
Thumbs.db
.idea/
.vscode/

View File

@@ -4,7 +4,7 @@
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" /> <excludeFolder url="file://$MODULE_DIR$/.venv" />
</content> </content>
<orderEntry type="jdk" jdkName="Python 3.13" jdkType="Python SDK" /> <orderEntry type="jdk" jdkName="Python 3.11 virtualenv at C:\Users\miche\PycharmProjects\Gestion_sondes\.venv" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
</module> </module>

12
.idea/dataSources.xml generated Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="domo91" uuid="d8bef1d2-2e67-4363-960b-e8d2ef8041bd">
<driver-ref>mariadb</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.mariadb.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mariadb://162.19.78.131:3306/Acces</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

2
.idea/misc.xml generated
View File

@@ -3,5 +3,5 @@
<component name="Black"> <component name="Black">
<option name="sdkName" value="Python 3.13 (Ratio &amp; inventaires)" /> <option name="sdkName" value="Python 3.13 (Ratio &amp; inventaires)" />
</component> </component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13" project-jdk-type="Python SDK" /> <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11 virtualenv at C:\Users\miche\PycharmProjects\Gestion_sondes\.venv" project-jdk-type="Python SDK" />
</project> </project>

View File

@@ -0,0 +1,86 @@
#!/usr/bin/env python3
import os, sys, argparse, datetime
import mysql.connector, bcrypt
from dotenv import load_dotenv
def read_stdin() -> str:
data = sys.stdin.read()
return data.rstrip("\r\n")
def main():
load_dotenv()
ap = argparse.ArgumentParser(description="Auth bcrypt locale pour Excel")
ap.add_argument("--user", required=True, help="NomUtilisateur (clé primaire)")
ap.add_argument("--password-stdin", action="store_true",
help="Lire le mot de passe via STDIN (recommandé)")
args = ap.parse_args()
if not args.password_stdin:
print("ERR|Utiliser --password-stdin et fournir le mot de passe via STDIN", flush=True)
return 2
password = read_stdin()
if not password:
print("ERR|Mot de passe vide", flush=True)
return 2
# Récupération des infos de connexion depuis .env
host = os.getenv("DB_HOST")
db = os.getenv("DB_NAME")
user = os.getenv("DB_USER")
pwd = os.getenv("DB_PASSWORD")
try:
conn = mysql.connector.connect(
host=host, database=db, user=user, password=pwd, connection_timeout=5
)
cur = conn.cursor(dictionary=True)
cur.execute("""
SELECT NomUtilisateur, Nom_complet, Site, MotDePasseHash, DateExpiration
FROM Utilisateurs
WHERE NomUtilisateur = %s
LIMIT 1
""", (args.user,))
row = cur.fetchone()
except Exception as e:
print("ERR|Connexion base impossible", flush=True)
return 3
finally:
try:
cur.close()
conn.close()
except Exception:
pass
if not row or not row.get("MotDePasseHash"):
print("ERR|Utilisateur ou mot de passe invalide", flush=True)
return 1
try:
ok = bcrypt.checkpw(password.encode("utf-8"),
row["MotDePasseHash"].encode("ascii"))
except Exception:
ok = False
if not ok:
print("ERR|Utilisateur ou mot de passe invalide", flush=True)
return 1
# Contrôle expiration (si renseignée)
exp = row.get("DateExpiration")
if exp is not None:
today = datetime.date.today()
exp_date = exp if isinstance(exp, datetime.date) else getattr(exp, "date", lambda: today)()
if exp_date < today:
print(f"ERR|Compte expiré le {exp_date.isoformat()}", flush=True)
return 2
site = row.get("Site") or ""
nom = row.get("Nom_complet") or ""
print(f"OK|Site={site}|NomComplet={nom}", flush=True)
return 0
if __name__ == "__main__":
sys.exit(main())

BIN
Excel/dev/Ratio_dev.xlsm Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -1,39 +1,112 @@
@echo off @echo off
setlocal setlocal EnableExtensions EnableDelayedExpansion
REM === Définition des chemins === REM === PARAMÈTRES A ADAPTER UNE FOIS ===
set "DEV=C:\Users\miche\PycharmProjects\Ratio_Inventaires\Excel\dev\Ratio_dev.xlsm" set "ROOT=C:\Users\miche\PycharmProjects\Ratio_Inventaires"
set "PROD_DIR=C:\Users\miche\PycharmProjects\Ratio_Inventaires\Excel\prod" set "DEV_XLSM=%ROOT%\Excel\dev\Ratio_dev.xlsm"
set "INSTALL_CLIENT=C:\Users\miche\PycharmProjects\Fichiers_Install_Clients\Ratio_Inventaires\Excel" set "DEV_AUTH_PY=%ROOT%\Excel\dev\Auth\auth_cli.py"
set "VENV_PY=%ROOT%\.venv\Scripts\python.exe"
REM === Création du nom de fichier daté === set "PROD_DIR=%ROOT%\Excel\prod"
set "DATESTAMP=%date:~6,4%_%date:~3,2%_%date:~0,2%" set "PROD_XLSM=%PROD_DIR%\Ratio_prod.xlsm"
set "PROD_AUTH_DIR=%PROD_DIR%\Auth"
set "INSTALL_CLIENT=%USERPROFILE%\Desktop\Install_Ratio_Inv" REM <- dossier prêt à livrer au client
set "CLIENT_AUTH_DIR=%INSTALL_CLIENT%\Auth"
REM === DATE ISO robuste (indépendant des paramètres régionaux) ===
for /f %%i in ('powershell -NoProfile -Command "(Get-Date).ToString(\"yyyy_MM_dd\")"') do set "DATESTAMP=%%i"
set "ARCHIVE_FILE=%PROD_DIR%\Ratio_prod_%DATESTAMP%.xlsm" set "ARCHIVE_FILE=%PROD_DIR%\Ratio_prod_%DATESTAMP%.xlsm"
echo Sauvegarde de la version actuelle de PROD... echo.
if exist "%PROD_DIR%\Ratio_prod.xlsm" copy /Y "%PROD_DIR%\Ratio_prod.xlsm" "%ARCHIVE_FILE%" echo === [1/6] Préparation des dossiers ========================================
if not exist "%PROD_DIR%" mkdir "%PROD_DIR%"
echo Mise à jour du fichier Ratio_prod.xlsm depuis DEV... if not exist "%PROD_AUTH_DIR%" mkdir "%PROD_AUTH_DIR%"
copy /Y "%DEV%" "%PROD_DIR%\Ratio_prod.xlsm" if not exist "%INSTALL_CLIENT%" mkdir "%INSTALL_CLIENT%"
if not exist "%CLIENT_AUTH_DIR%" mkdir "%CLIENT_AUTH_DIR%"
echo Copie vers le dossier INSTALL_CLIENT (version sans date)...
copy /Y "%PROD_DIR%\Ratio_prod.xlsm" "%INSTALL_CLIENT%\Ratio_prod.xlsm"
echo. echo.
echo ✔️ Mise à jour complète effectuée (archive, prod, client) echo === [2/6] (Optionnel) Build de auth_cli.exe avec PyInstaller ==============
pause if exist "%VENV_PY%" (
echo Nettoyage des anciennes archives (on garde les 5 plus récentes)... echo Compilation via venv: %VENV_PY%
"%VENV_PY%" -m PyInstaller --onefile --clean --distpath "%PROD_AUTH_DIR%" "%DEV_AUTH_PY%"
pushd "%PROD_DIR%" if errorlevel 1 (
setlocal EnableDelayedExpansion echo [WARN] Echec de compilation. On continue avec l'exe existant s'il est présent.
set count=0 )
) else (
for /f "delims=" %%f in ('dir /b /o-d "Ratio_prod_????_??_??.xlsm"') do ( echo [WARN] VENV introuvable: %VENV_PY% -> build ignore
set /a count+=1
if !count! gtr 5 (
echo Suppression de l'ancienne archive : %%f
del "%%f"
)
) )
echo.
echo === [3/6] Sauvegarde de la prod courante ==================================
if exist "%PROD_XLSM%" (
echo Sauvegarde -> "%ARCHIVE_FILE%"
copy /Y "%PROD_XLSM%" "%ARCHIVE_FILE%" >nul
) else (
echo Pas de prod courante a sauvegarder.
)
echo.
echo === [4/6] Mise a jour de la prod depuis le dev ============================
if not exist "%DEV_XLSM%" (
echo [ERREUR] Fichier DEV introuvable: %DEV_XLSM%
goto :EOF
)
copy /Y "%DEV_XLSM%" "%PROD_XLSM%" >nul
if errorlevel 1 (
echo [ERREUR] Copie du XLSM vers PROD echouee.
goto :EOF
)
REM On s'assure que l'exe est bien present et on deblocque Windows (SmartScreen)
if exist "%PROD_AUTH_DIR%\auth_cli.exe" (
powershell -NoProfile -Command "Unblock-File -Path '%PROD_AUTH_DIR%\auth_cli.exe'" 2>nul
) else (
echo [ERREUR] auth_cli.exe absent dans %PROD_AUTH_DIR%
echo Lance d'abord la compilation ou copie l'exe manuellement.
goto :EOF
)
REM Le .env doit exister dans PROD\Auth (modele ou reel)
if not exist "%PROD_AUTH_DIR%\.env" (
echo [WARN] Aucun .env dans %PROD_AUTH_DIR% -> Pense a y mettre DB_HOST/DB_NAME/DB_USER/DB_PASSWORD
)
echo.
echo === [5/6] Préparation du dossier client ===================================
copy /Y "%PROD_XLSM%" "%INSTALL_CLIENT%\Ratio_prod.xlsm" >nul
if errorlevel 1 (
echo [ERREUR] Copie du XLSM vers INSTALL_CLIENT echouee.
goto :EOF
)
copy /Y "%PROD_AUTH_DIR%\auth_cli.exe" "%CLIENT_AUTH_DIR%\auth_cli.exe" >nul
powershell -NoProfile -Command "Unblock-File -Path '%CLIENT_AUTH_DIR%\auth_cli.exe'" 2>nul
if exist "%PROD_AUTH_DIR%\.env" (
copy /Y "%PROD_AUTH_DIR%\.env" "%CLIENT_AUTH_DIR%\.env" >nul
) else (
echo [INFO] Pas de .env a copier (client). Tu pourras le remplir a la main.
)
echo.
echo === [6/6] Nettoyage: on garde 5 archives les plus recentes ===============
pushd "%PROD_DIR%"
set count=0
for /f "delims=" %%f in ('dir /b /o-d "Ratio_prod_20??_??_??.xlsm"') do (
set /a count+=1
if !count! gtr 5 (
echo Suppression: %%f
del "%%f"
)
)
popd
echo.
echo [OK] Release terminee :
echo - PROD : "%PROD_XLSM%"
echo - AUTH : "%PROD_AUTH_DIR%\auth_cli.exe" + ".env"
echo - CLIENT: "%INSTALL_CLIENT%\Ratio_prod.xlsm" + "Auth\*"
echo.
pause
endlocal endlocal
popd

26
bcrypt_check.py Normal file
View File

@@ -0,0 +1,26 @@
#!/usr/bin/env python3
import sys, argparse, bcrypt
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--hash", required=True)
ap.add_argument("--password-stdin", action="store_true")
args = ap.parse_args()
if not args.password_stdin:
print("ERR")
return 2
password = sys.stdin.read().rstrip("\r\n")
try:
ok = bcrypt.checkpw(password.encode("utf-8"),
args.hash.encode("ascii"))
except Exception:
print("ERR")
return 2
print("OK" if ok else "ERR")
return 0 if ok else 1
if __name__ == "__main__":
sys.exit(main())

3
last_ids.txt Normal file
View File

@@ -0,0 +1,3 @@
Meudon:3
Roissy:8
Saclay:12

44
migre_bcrypt.py Normal file
View File

@@ -0,0 +1,44 @@
import os, sys, bcrypt, mysql.connector
from dotenv import load_dotenv
load_dotenv()
conn = mysql.connector.connect(
host=os.getenv("DB_HOST"),
database=os.getenv("DB_NAME"),
user=os.getenv("DB_USER"),
password=os.getenv("DB_PASSWORD"),
)
try:
cur = conn.cursor(dictionary=True)
cur.execute("""
SELECT NomUtilisateur, MotDePasse
FROM Utilisateurs
WHERE MotDePasseHash IS NULL OR MotDePasseHash = ''
""")
rows = cur.fetchall()
print(f"{len(rows)} utilisateur(s) à migrer…")
for r in rows:
login = r["NomUtilisateur"]
plain = (r["MotDePasse"] or "").encode("utf-8")
if not plain:
print(f"- {login}: mot de passe vide — ignoré")
continue
hashed = bcrypt.hashpw(plain, bcrypt.gensalt(rounds=12)).decode("ascii")
cur.execute("""
UPDATE Utilisateurs
SET MotDePasseHash = %s
WHERE NomUtilisateur = %s
""", (hashed, login))
print(f"- {login}: OK")
conn.commit()
print("Migration terminée.")
except Exception as e:
conn.rollback()
print("Erreur:", e)
sys.exit(1)
finally:
cur.close()
conn.close()

13
mkhash.py Normal file
View File

@@ -0,0 +1,13 @@
#!/usr/bin/env python3
import sys, getpass, bcrypt
def main():
if len(sys.argv) > 1:
password = sys.argv[1]
else:
password = getpass.getpass("Mot de passe: ")
h = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt(rounds=12)).decode("ascii")
print(h)
if __name__ == "__main__":
main()