From 5f702ba32a64624a6bc59bdfbfb58ad56ab4d275 Mon Sep 17 00:00:00 2001 From: Tiago Correia Date: Sat, 9 May 2026 23:27:33 +0100 Subject: [PATCH 1/2] fix: declare requests as explicit dependency Authlib's flask_client eagerly imports requests_client at module load, so importing app.extensions fails with ModuleNotFoundError in the hermetic deploy image (pip install --no-index from prebuilt wheels). Authlib does not list requests as a hard install requirement, so it never lands in /wheels. Co-Authored-By: Claude Opus 4.7 (1M context) --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index a29d69e..7bc83f6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,3 +7,4 @@ python-dotenv>=1.0 email_validator boto3>=1.34 Authlib>=1.3 +requests>=2.31 From 90ad8d035732a76b4d54d4f12c4772196e4a9629 Mon Sep 17 00:00:00 2001 From: Tiago Correia Date: Sun, 10 May 2026 00:47:40 +0100 Subject: [PATCH 2/2] fix: route OIDC redirect through https and /auth/callback Login was returning Keycloak's "Invalid parameter: redirect_uri" because the URL the app generated via url_for(_external=True) was wrong on two axes: 1. Wrong path. The auth blueprint was registered without a url_prefix, so its routes mounted at the root (/login, /callback, /logout). The Keycloak client has /auth/callback registered (matching the original prompt sent to the keycloak-config session). Mounting the blueprint under /auth aligns the app with Keycloak. 2. Wrong scheme. The stack is CloudFront(HTTPS) -> ALB(HTTP) -> ECS, so the WSGI scheme inside the container is http. CloudFront stamps X-Forwarded-Proto: https, ALB appends http, so the header reaching the app is "https, http". Adding ProxyFix(x_proto=2) trusts the CloudFront-set value, which makes url_for(_external=True) emit https URLs. x_for=2 mirrors this for client IPs in access logs. Co-Authored-By: Claude Opus 4.7 (1M context) --- app/__init__.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/__init__.py b/app/__init__.py index f7ba442..1203f8f 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,4 +1,5 @@ from flask import Flask +from werkzeug.middleware.proxy_fix import ProxyFix from config import Config from app.extensions import db, migrate, oauth @@ -9,6 +10,13 @@ def create_app(config_class=Config): app = Flask(__name__) app.config.from_object(config_class) + # CloudFront → ALB (HTTP) → ECS. CloudFront stamps `X-Forwarded-Proto: + # https`; ALB appends its own `http`. x_proto=2 picks the CloudFront + # value so `url_for(..., _external=True)` produces https URLs (e.g. + # the OIDC redirect_uri Keycloak validates). x_for=2 mirrors the same + # for client IPs in access logs. + app.wsgi_app = ProxyFix(app.wsgi_app, x_for=2, x_proto=2, x_host=1) + db.init_app(app) migrate.init_app(app, db) @@ -28,7 +36,9 @@ def create_app(config_class=Config): from app.admin import bp as admin_bp from app.about import bp as about_bp from app.borrower import bp as borrower_bp - app.register_blueprint(auth_bp) + # url_prefix="/auth" puts login/callback/logout under /auth/* — must + # match the Valid Redirect URIs registered on the Keycloak client. + app.register_blueprint(auth_bp, url_prefix="/auth") app.register_blueprint(library_bp) app.register_blueprint(admin_bp) app.register_blueprint(about_bp)