"""
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()