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>
34 lines
1.2 KiB
Python
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'))
|