""" Google OAuth authentication provider. Handles Google Sign-In flow with domain validation. Extracted from webapp/auth.py - the OAuth-specific routes. """ import logging from authlib.integrations.flask_client import OAuth from flask import Blueprint, flash, redirect, session, url_for from auth import AuthProvider from webapp.auth import validate_email_domain from webapp.config import Config logger = logging.getLogger(__name__) google_bp = Blueprint("google_auth", __name__) oauth = OAuth() # Google SVG icon for the login button _GOOGLE_ICON_HTML = ( '' '" '" '' '' "" ) @google_bp.route("/login/google") def login_google(): """Initiate Google OAuth flow.""" redirect_uri = url_for("google_auth.authorize", _external=True) return oauth.google.authorize_redirect(redirect_uri) @google_bp.route("/authorize") def authorize(): """Handle OAuth callback from Google.""" try: token = oauth.google.authorize_access_token() userinfo = token.get("userinfo") if not userinfo: logger.warning("No userinfo in OAuth response") flash("Failed to get user information from Google.", "error") return redirect(url_for("auth.login")) email = userinfo.get("email", "") name = userinfo.get("name", "") # Validate domain if not validate_email_domain(email): logger.warning(f"Login attempt from non-allowed domain: {email}") flash( f"Only @{Config.ALLOWED_DOMAIN} email addresses are allowed.", "error" ) return redirect(url_for("auth.login")) # Store user info in session (shared contract across all providers) session["user"] = { "email": email, "name": name, "picture": userinfo.get("picture", ""), } logger.info(f"User logged in via Google: {email}") return redirect(url_for("dashboard")) except Exception as e: logger.exception(f"OAuth error: {e}") flash("Authentication failed. Please try again.", "error") return redirect(url_for("auth.login")) class GoogleAuthProvider(AuthProvider): """Google OAuth authentication provider.""" def get_name(self) -> str: return "google" def get_display_name(self) -> str: return "Google" def get_blueprint(self) -> Blueprint: return google_bp def get_login_button(self) -> dict: return { "text": "Sign in with Google", "url": "/login/google", "icon_html": _GOOGLE_ICON_HTML, "subtitle": f'For @{Config.ALLOWED_DOMAIN} email addresses.', "order": 10, "css_class": "btn-google", "visible": True, } def is_available(self) -> bool: return bool(Config.GOOGLE_CLIENT_ID) def init_app(self, app) -> None: """Initialize OAuth with the Flask app.""" oauth.init_app(app) oauth.register( name="google", client_id=Config.GOOGLE_CLIENT_ID, client_secret=Config.GOOGLE_CLIENT_SECRET, server_metadata_url="https://accounts.google.com/.well-known/openid-configuration", client_kwargs={ "scope": "openid email profile", }, ) # Module-level provider instance for auto-discovery provider = GoogleAuthProvider()