TTS-Arena-V2 / auth.py
GitHub Actions
Sync from GitHub repo
5259aa6
raw
history blame
6.5 kB
from flask import Blueprint, redirect, url_for, session, request, current_app, flash
from flask_login import login_user, logout_user, current_user, login_required
from authlib.integrations.flask_client import OAuth
import os
from models import db, User
import requests
from functools import wraps
from datetime import datetime, timedelta
auth = Blueprint("auth", __name__)
oauth = OAuth()
def init_oauth(app):
oauth.init_app(app)
oauth.register(
name="huggingface",
client_id=os.getenv("OAUTH_CLIENT_ID"),
client_secret=os.getenv("OAUTH_CLIENT_SECRET"),
access_token_url="https://huggingface.co/oauth/token",
access_token_params=None,
authorize_url="https://huggingface.co/oauth/authorize",
authorize_params=None,
api_base_url="https://huggingface.co/api/",
client_kwargs={},
)
def is_admin(user):
"""Check if a user is in the ADMIN_USERS environment variable"""
if not user or not user.is_authenticated:
return False
admin_users = os.getenv("ADMIN_USERS", "").split(",")
return user.username in [username.strip() for username in admin_users]
def admin_required(f):
"""Decorator to require admin access for a route"""
@wraps(f)
def decorated_function(*args, **kwargs):
if not current_user.is_authenticated:
flash("Please log in to access this page", "error")
return redirect(url_for("auth.login", next=request.url))
if not is_admin(current_user):
flash("You do not have permission to access this page", "error")
return redirect(url_for("arena"))
return f(*args, **kwargs)
return decorated_function
def check_account_age(username, min_days=30):
"""
Check if a Hugging Face account is at least min_days old.
Returns (is_old_enough, created_date, error_message)
"""
try:
# Fetch user overview from HF API
resp = requests.get(f"https://huggingface.co/api/users/{username}/overview", timeout=10)
if not resp.ok:
return False, None, f"Failed to fetch account information (HTTP {resp.status_code})"
user_data = resp.json()
if "createdAt" not in user_data:
return False, None, "Account creation date not available"
# Parse the creation date
created_at_str = user_data["createdAt"]
# Handle both formats: with and without milliseconds
try:
created_at = datetime.fromisoformat(created_at_str.replace('Z', '+00:00'))
except ValueError:
# Try without milliseconds
created_at = datetime.strptime(created_at_str, "%Y-%m-%dT%H:%M:%S.%fZ")
# Calculate account age
account_age = datetime.utcnow() - created_at.replace(tzinfo=None)
required_age = timedelta(days=min_days)
is_old_enough = account_age >= required_age
return is_old_enough, created_at, None
except requests.RequestException as e:
return False, None, f"Network error checking account age: {str(e)}"
except Exception as e:
return False, None, f"Error parsing account data: {str(e)}"
@auth.route("/login")
def login():
# Store the next URL to redirect after login
next_url = request.args.get("next") or url_for("arena")
session["next_url"] = next_url
redirect_uri = url_for("auth.authorize", _external=True, _scheme="https")
return oauth.huggingface.authorize_redirect(redirect_uri)
@auth.route("/authorize")
def authorize():
try:
# Get token without OpenID verification
token = oauth.huggingface.authorize_access_token()
# Fetch user info manually from HF API
headers = {"Authorization": f'Bearer {token["access_token"]}'}
resp = requests.get("https://huggingface.co/api/whoami-v2", headers=headers)
if not resp.ok:
flash("Failed to fetch user information from Hugging Face", "error")
return redirect(url_for("arena"))
user_info = resp.json()
username = user_info["name"]
# Check account age requirement (30 days minimum)
is_old_enough, created_date, error_msg = check_account_age(username, min_days=30)
if error_msg:
current_app.logger.warning(f"Account age check failed for {username}: {error_msg}")
flash("Unable to verify account age. Please try again later.", "error")
return redirect(url_for("arena"))
if not is_old_enough:
if created_date:
account_age_days = (datetime.utcnow() - created_date.replace(tzinfo=None)).days
flash(f"Your Hugging Face account must be at least 30 days old to use TTS Arena. Your account is {account_age_days} days old. Please try again later.", "error")
else:
flash("Your Hugging Face account must be at least 30 days old to use TTS Arena.", "error")
return redirect(url_for("arena"))
# Check if user exists, otherwise create
user = User.query.filter_by(hf_id=user_info["id"]).first()
if not user:
user = User(
username=username,
hf_id=user_info["id"],
hf_account_created=created_date.replace(tzinfo=None) if created_date else None
)
db.session.add(user)
db.session.commit()
current_app.logger.info(f"Created new user account: {username} (HF account created: {created_date})")
elif not user.hf_account_created and created_date:
# Update existing users with missing creation date
user.hf_account_created = created_date.replace(tzinfo=None)
db.session.commit()
current_app.logger.info(f"Updated HF account creation date for {username}: {created_date}")
# Log in the user
login_user(user, remember=True)
# Redirect to the original page or default
next_url = session.pop("next_url", url_for("arena"))
return redirect(next_url)
except Exception as e:
current_app.logger.error(f"OAuth error: {str(e)}")
flash(f"Authentication error: {str(e)}", "error")
return redirect(url_for("arena"))
@auth.route("/logout")
@login_required
def logout():
logout_user()
flash("You have been logged out", "info")
return redirect(url_for("arena"))