import requests from flask import request, session, redirect, flash, url_for from flask_limiter.util import get_remote_address from flask_login import current_user from requests_oauthlib import OAuth2Session from app.auth.base import auth_bp from app.auth.views.login_utils import after_login from app.config import ( PROTON_BASE_URL, PROTON_CLIENT_ID, PROTON_CLIENT_SECRET, PROTON_VALIDATE_CERTS, URL, ) from app.proton.proton_client import HttpProtonClient, convert_access_token from app.proton.proton_callback_handler import ProtonCallbackHandler, Action from app.utils import sanitize_next_url _authorization_base_url = PROTON_BASE_URL + "/oauth/authorize" _token_url = PROTON_BASE_URL + "/oauth/token" # need to set explicitly redirect_uri instead of leaving the lib to pre-fill redirect_uri # when served behind nginx, the redirect_uri is localhost... and not the real url _redirect_uri = URL + "/auth/proton/callback" def extract_action() -> Action: action = request.args.get("action") if action is not None: if action == "link": return Action.Link else: raise Exception(f"Unknown action: {action}") return Action.Login def get_action_from_state() -> Action: oauth_action = session["oauth_action"] if oauth_action == Action.Login.value: return Action.Login elif oauth_action == Action.Link.value: return Action.Link raise Exception(f"Unknown action in state: {oauth_action}") @auth_bp.route("/proton/login") def proton_login(): if PROTON_CLIENT_ID is None or PROTON_CLIENT_SECRET is None: return redirect(url_for("auth.login")) next_url = sanitize_next_url(request.args.get("next")) if next_url: session["oauth_next"] = next_url proton = OAuth2Session(PROTON_CLIENT_ID, redirect_uri=_redirect_uri) authorization_url, state = proton.authorization_url(_authorization_base_url) # State is used to prevent CSRF, keep this for later. session["oauth_state"] = state session["oauth_action"] = extract_action().value return redirect(authorization_url) @auth_bp.route("/proton/callback") def proton_callback(): if PROTON_CLIENT_ID is None or PROTON_CLIENT_SECRET is None: return redirect(url_for("auth.login")) # user clicks on cancel if "error" in request.args: flash("Please use another sign in method then", "warning") return redirect("/") proton = OAuth2Session( PROTON_CLIENT_ID, state=session["oauth_state"], redirect_uri=_redirect_uri, ) def check_status_code(response: requests.Response) -> requests.Response: if response.status_code != 200: raise Exception( f"Bad Proton API response [status={response.status_code}]: {response.json()}" ) return response proton.register_compliance_hook("access_token_response", check_status_code) token = proton.fetch_token( _token_url, client_secret=PROTON_CLIENT_SECRET, authorization_response=request.url, verify=PROTON_VALIDATE_CERTS, method="GET", include_client_id=True, ) credentials = convert_access_token(token["access_token"]) action = get_action_from_state() proton_client = HttpProtonClient( PROTON_BASE_URL, credentials, get_remote_address(), verify=PROTON_VALIDATE_CERTS ) handler = ProtonCallbackHandler(proton_client) if action == Action.Login: res = handler.handle_login() elif action == Action.Link: res = handler.handle_link(current_user) else: raise Exception(f"Unknown Action: {action.name}") if res.flash_message is not None: flash(res.flash_message, res.flash_category) if res.redirect_to_login: return redirect(url_for("auth.login")) if res.redirect: return redirect(res.redirect) next_url = session.get("oauth_next") return after_login(res.user, next_url)