agnes-the-ai-analyst/webapp/auth.py
Petr c6a711aa27 Extract pluggable auth provider system into auth/ package
Replace hardcoded Google OAuth + password auth registration with
auto-discovered auth providers. Each provider in auth/<name>/provider.py
implements AuthProvider ABC and is automatically registered at startup.

- auth/__init__.py: AuthProvider ABC + discover_providers() scanner
- auth/google/: Google OAuth provider (extracted from webapp/auth.py)
- auth/password/: Email/password provider (delegates to webapp/password_auth)
- auth/desktop/: Desktop JWT auth (API-only, not visible on login page)
- webapp/auth.py: stripped to core infra (login_required, /login, /logout)
- webapp/app.py: auto-discovery loop replaces manual blueprint registration
- login.html: dynamic provider buttons via Jinja loop
2026-03-09 13:02:08 +01:00

81 lines
2.2 KiB
Python

"""
Core authentication module - shared infrastructure.
Provides:
- login_required decorator (used by all auth methods)
- validate_email_domain() (used by all auth providers)
- /login route (dynamically renders available auth providers)
- /logout route
Auth provider-specific logic lives in auth/<provider>/provider.py.
"""
import functools
import logging
from flask import Blueprint, flash, redirect, render_template, session, url_for
from .config import Config
logger = logging.getLogger(__name__)
auth_bp = Blueprint("auth", __name__)
def login_required(f):
"""Decorator to require authentication for a route."""
@functools.wraps(f)
def decorated_function(*args, **kwargs):
if "user" not in session:
return redirect(url_for("auth.login"))
return f(*args, **kwargs)
return decorated_function
def validate_email_domain(email: str) -> bool:
"""Check if email belongs to allowed domain or whitelist.
Allows access for:
1. Configured allowed domain (for Google OAuth users)
2. Whitelisted emails (for password auth external users)
"""
if not email:
return False
email_lower = email.lower()
# Check whitelist first (for password auth users)
if email_lower in Config.ALLOWED_EMAILS:
return True
# Check domain (for Google OAuth users)
domain = email_lower.split("@")[-1]
return domain == Config.ALLOWED_DOMAIN.lower()
@auth_bp.route("/login")
def login():
"""Show login page with dynamically discovered auth providers."""
if "user" in session:
return redirect(url_for("dashboard"))
from auth import discover_providers
providers = discover_providers()
login_buttons = [
p.get_login_button()
for p in providers
if p.get_login_button().get("visible", True)
]
return render_template("login.html", login_buttons=login_buttons)
@auth_bp.route("/logout")
def logout():
"""Clear session and redirect to login."""
email = session.get("user", {}).get("email", "unknown")
session.clear()
logger.info(f"User logged out: {email}")
flash("You have been logged out.", "info")
return redirect(url_for("auth.login"))