Switch from UniAuth to OpenID Connect

This commit is contained in:
Edward Betts 2026-02-17 11:25:55 +00:00
parent 1f026c31ca
commit e0b9e4e719
3 changed files with 50 additions and 16 deletions

View file

@ -2,7 +2,6 @@
from flipflop import WSGIServer from flipflop import WSGIServer
import sys import sys
sys.path.append('/home/edward/src/agenda') # isort:skip sys.path.append('/home/edward/src/agenda') # isort:skip
sys.path.append("/home/edward/src/2024/UniAuth")
from web_view import app # isort:skip from web_view import app # isort:skip
if __name__ == '__main__': if __name__ == '__main__':

View file

@ -87,7 +87,7 @@
</ul> </ul>
<ul class="navbar-nav ms-auto"> <ul class="navbar-nav ms-auto">
{% if g.user.is_authenticated %} {% if g.user.is_authenticated %}
<li class="nav-item"><a class="nav-link" href="{{ url_for("logout", next=request.url) }}">Logout</a></li> <li class="nav-item"><a class="nav-link" href="{{ url_for("logout") }}">Logout</a></li>
{% else %} {% else %}
<li class="nav-item"><a class="nav-link" href="{{ url_for("login", next=request.url) }}">Login</a></li> <li class="nav-item"><a class="nav-link" href="{{ url_for("login", next=request.url) }}">Login</a></li>
{% endif %} {% endif %}

View file

@ -15,10 +15,11 @@ from collections import defaultdict
from datetime import date, datetime, timedelta, timezone from datetime import date, datetime, timedelta, timezone
import flask import flask
import UniAuth.auth
import werkzeug import werkzeug
import werkzeug.debug.tbtools import werkzeug.debug.tbtools
import yaml import yaml
from authlib.integrations.flask_client import OAuth
from werkzeug.middleware.proxy_fix import ProxyFix
import agenda.data import agenda.data
import agenda.error_mail import agenda.error_mail
@ -36,14 +37,33 @@ from agenda.types import StrDict, Trip
app = flask.Flask(__name__) app = flask.Flask(__name__)
app.debug = False app.debug = False
app.config.from_object("config.default") app.config.from_object("config.default")
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1)
agenda.error_mail.setup_error_mail(app) agenda.error_mail.setup_error_mail(app)
oauth = OAuth(app)
authentik_url = app.config["AUTHENTIK_URL"]
oauth.register(
name="authentik",
client_id=app.config["AUTHENTIK_CLIENT_ID"],
client_secret=app.config["AUTHENTIK_CLIENT_SECRET"],
server_metadata_url=f"{authentik_url}/application/o/agenda/.well-known/openid-configuration",
client_kwargs={"scope": "openid email profile"},
)
class User:
"""Simple user object for template compatibility."""
def __init__(self, is_authenticated: bool) -> None:
"""Init."""
self.is_authenticated = is_authenticated
@app.before_request @app.before_request
def handle_auth() -> None: def handle_auth() -> None:
"""Handle authentication and set global user.""" """Set global user from session."""
flask.g.user = UniAuth.auth.get_current_user() flask.g.user = User(is_authenticated=bool(flask.session.get("user")))
@app.errorhandler(werkzeug.exceptions.InternalServerError) @app.errorhandler(werkzeug.exceptions.InternalServerError)
@ -831,23 +851,38 @@ def schengen_report() -> str:
return agenda.trip_schengen.flask_route_schengen_report() return agenda.trip_schengen.flask_route_schengen_report()
@app.route("/callback")
def auth_callback() -> tuple[str, int] | werkzeug.Response:
"""Process the authentication callback."""
return UniAuth.auth.auth_callback()
@app.route("/login") @app.route("/login")
def login() -> werkzeug.Response: def login() -> werkzeug.Response:
"""Login.""" """Redirect to Authentik for OIDC login."""
next_url = flask.request.args["next"] next_url = flask.request.args.get("next", flask.url_for("index"))
return UniAuth.auth.redirect_to_login(next_url) flask.session["login_next"] = next_url
redirect_uri = flask.url_for("auth_callback", _external=True)
return oauth.authentik.authorize_redirect(redirect_uri)
@app.route("/callback")
def auth_callback() -> werkzeug.Response:
"""Handle OIDC callback from Authentik."""
try:
token = oauth.authentik.authorize_access_token()
except Exception:
return flask.redirect(flask.url_for("login"))
userinfo = token.get("userinfo") or oauth.authentik.userinfo()
flask.session["user"] = {
"sub": userinfo["sub"],
"username": userinfo.get("preferred_username"),
"email": userinfo.get("email"),
}
next_url = flask.session.pop("login_next", flask.url_for("index"))
return flask.redirect(next_url)
@app.route("/logout") @app.route("/logout")
def logout() -> werkzeug.Response: def logout() -> werkzeug.Response:
"""Logout.""" """Log out and redirect to Authentik end-session endpoint."""
return UniAuth.auth.redirect_to_logout(flask.request.args["next"]) flask.session.pop("user", None)
end_session_url = f"{authentik_url}/application/o/agenda/end-session/"
return flask.redirect(end_session_url)
if __name__ == "__main__": if __name__ == "__main__":