alpinux.site.2026/dynamic/routes/auth.py
Cédrix 27847dfad3 feat: dynamic.alpinux.org — quiz interactifs Flask + AlpID OIDC
App Flask complète pour https://dynamic.alpinux.org :
- 10 quiz Linux, 5 niveaux (Découverte → Expert), 50+ questions
- Public : Découverte, Débutant, Intermédiaire (6 quiz)
- Membres AlpID : Avancé, Expert (4 quiz — Git, Admin, Sécurité, Bash)
- Navigation question par question avec avance automatique après choix
- Score calculé côté serveur, enregistré en SQLite si connecté
- Page profil : meilleurs scores par quiz + historique des tentatives

Authentification :
- OIDC via authlib + AlpID (Keycloak), SSO partagé avec Gitea/Nextcloud
- Décorateur @login_required, redirection post-login sur l'URL d'origine
- /auth/login, /auth/callback, /auth/logout

Structure :
- dynamic/app.py, db.py, quiz.py, auth_utils.py
- dynamic/routes/ (public.py, auth.py, protected.py)
- dynamic/templates/ (base, index, quiz/*, profil/)
- dynamic/static/ (style.css thème Alpinux, quiz.js vanilla)
- dynamic/data/quizzes.json (source de vérité des questions)
- dynamic/.env.example

Infrastructure :
- scripts/dynamic.alpinux.org.vhost.conf (Apache reverse proxy)
- scripts/dynamic.alpinux.org.service (systemd Gunicorn)
- docs/technique/deploiement-dynamic.md (procédure complète)
- mkdocs.yml : page de déploiement ajoutée à la nav Technique
- .gitignore : exclut venv/ et .env

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 12:09:37 +02:00

34 lines
1.2 KiB
Python

from flask import Blueprint, redirect, url_for, session, current_app, request
auth_bp = Blueprint('auth', __name__)
@auth_bp.route('/login')
def login():
redirect_uri = url_for('auth.callback', _external=True)
return current_app.extensions['oauth'].alpid.authorize_redirect(redirect_uri)
@auth_bp.route('/callback')
def callback():
token = current_app.extensions['oauth'].alpid.authorize_access_token()
userinfo = token.get('userinfo') or \
current_app.extensions['oauth'].alpid.userinfo(token=token)
session['user'] = {
'sub': userinfo['sub'],
'name': userinfo.get('name') or userinfo.get('preferred_username', 'Membre'),
'username': userinfo.get('preferred_username', ''),
'email': userinfo.get('email', ''),
}
return redirect(session.pop('next_url', url_for('public.index')))
@auth_bp.route('/logout')
def logout():
session.clear()
# Déconnexion côté AlpID si end_session_endpoint disponible
end_session = current_app.config.get('ALPID_END_SESSION_URL')
if end_session:
post_logout = url_for('public.index', _external=True)
return redirect(f"{end_session}?post_logout_redirect_uri={post_logout}")
return redirect(url_for('public.index'))