transfer from private server/git server

This commit is contained in:
M1n-0
2026-08-21 09:12:02 +02:00
parent 45c8982178
commit 6ecb014238
75 changed files with 4344 additions and 274 deletions

86
web/routes/auth.py Normal file
View File

@@ -0,0 +1,86 @@
"""
routes/auth.py — Flux d'authentification OIDC (Authorization Code Flow) avec
Keycloak, via Authlib. 7 étapes documentées dans la rétro-documentation
(RD-3) et le journal de bord :
1. Détection de l'absence de session (décorateur / vérification session)
2. Génération de l'URL d'autorisation (+ nonce anti-rejeu)
3. Redirection de l'utilisateur vers Keycloak
4. Retour sur /auth avec le code d'autorisation
5. Échange du code contre un token (authorize_access_token)
6. Vérification du token (signature, nonce, expiration — parse_id_token)
7. Création de la session applicative Flask
"""
import uuid
from flask import Blueprint, redirect, url_for, session
from config import Config
from services import audit_service
from services.auth_service import client_ip
def register_oauth(oauth):
"""Enregistre le client OIDC Keycloak auprès d'Authlib (appelé depuis app.py)."""
return oauth.register(
name="keycloak",
client_id=Config.OIDC_CLIENT_ID,
client_secret=Config.OIDC_CLIENT_SECRET,
server_metadata_url=Config.OIDC_DISCOVERY_URL,
client_kwargs={"scope": "openid profile email"},
)
def init_auth_routes(oauth):
# Le blueprint est créé ICI (et non au niveau du module) afin que
# chaque appel de create_app() (production, ou une fois par test en
# environnement pytest) produise un blueprint neuf. Un blueprint créé
# au niveau module serait décoré une seule fois puis réutilisé tel
# quel, ce que Flask refuse dès le second register_blueprint().
bp = Blueprint("auth", __name__)
keycloak = register_oauth(oauth)
@bp.route("/login")
def login():
# Étape 2 : génération de l'URL d'autorisation avec un nonce
nonce = uuid.uuid4().hex
session["nonce"] = nonce
redirect_uri = url_for("auth.auth_callback", _external=True, _scheme="https")
# Étape 3 : redirection vers Keycloak
return keycloak.authorize_redirect(redirect_uri, nonce=nonce)
@bp.route("/auth")
def auth_callback():
db = _get_db()
# Étape 5 : échange du code contre un token
token = keycloak.authorize_access_token()
nonce = session.pop("nonce", None)
# Étape 6 : vérification (signature + nonce) du token
userinfo = keycloak.parse_id_token(token, nonce=nonce)
# Étape 7 : création de la session applicative
session["user"] = userinfo
session["id_token"] = token.get("id_token")
audit_service.log(db, "LOGIN", None, "SUCCESS", userinfo.get("sub"), client_ip())
return redirect("/")
@bp.route("/logout")
def logout():
db = _get_db()
user = session.get("user") or {}
id_token = session.get("id_token")
audit_service.log(db, "LOGOUT", None, "SUCCESS", user.get("sub"), client_ip())
session.clear()
return redirect(
f"{Config.OIDC_LOGOUT_URL}"
f"?post_logout_redirect_uri={Config.PUBLIC_BASE_URL}"
f"&id_token_hint={id_token}"
)
return bp
def _get_db():
from flask import current_app
return current_app.config["DB"]