Implement comprehensive authentication system with support for Basic Auth, Flask-Login, and OAuth2 providers. Features: - Pluggable architecture via factory pattern - Multiple authentication providers: * None: No authentication (development/testing) * Basic Auth: HTTP Basic with bcrypt support * Flask-Login: Session-based with multiple users * OAuth2: Google, GitHub, GitLab, and generic providers - Decorator-based route protection (@auth.require_auth) - User authorization by domain or email (OAuth) - bcrypt password hashing support - Comprehensive documentation and examples Components: - libtisbackup/auth/__init__.py: Factory function and exports - libtisbackup/auth/base.py: Base provider interface - libtisbackup/auth/basic_auth.py: HTTP Basic Auth implementation - libtisbackup/auth/flask_login_auth.py: Flask-Login implementation - libtisbackup/auth/oauth_auth.py: OAuth2 implementation - libtisbackup/auth/example_integration.py: Integration examples - libtisbackup/auth/README.md: API reference and examples Documentation: - AUTHENTICATION.md: Complete authentication guide * Setup instructions for each provider * Configuration examples * Security best practices * Troubleshooting guide * Migration guide - samples/auth-config-examples.ini: Configuration templates Dependencies: - Add optional dependencies in pyproject.toml: * auth-basic: bcrypt>=4.0.0 * auth-login: flask-login>=0.6.0, bcrypt>=4.0.0 * auth-oauth: authlib>=1.3.0, requests>=2.32.0 * auth-all: All auth providers Installation: ```bash # Install specific provider uv sync --extra auth-basic # Install all providers uv sync --extra auth-all ``` Usage: ```python from libtisbackup.auth import get_auth_provider # Initialize auth = get_auth_provider("basic", { "username": "admin", "password": "$2b$12$...", "use_bcrypt": True }) auth.init_app(app) # Protect routes @app.route("/") @auth.require_auth def index(): user = auth.get_current_user() return f"Hello {user['username']}" ``` Security features: - bcrypt password hashing (work factor 12) - OAuth domain/user restrictions - Session-based authentication - Clear separation of concerns - Environment variable support for secrets OAuth providers supported: - Google (OpenID Connect) - GitHub - GitLab - Generic OAuth2 provider Breaking change: None - new feature, backward compatible Users can continue without authentication (type=none) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
122 lines
3.7 KiB
Python
122 lines
3.7 KiB
Python
#!/usr/bin/python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
Example integration of authentication providers with Flask app
|
|
|
|
This file shows how to integrate the authentication system into tisbackup_gui.py
|
|
"""
|
|
|
|
from flask import Flask, jsonify, render_template, request, redirect, url_for, session
|
|
from libtisbackup.auth import get_auth_provider
|
|
|
|
# Example configuration from tisbackup_gui.ini
|
|
auth_config = {
|
|
"type": "basic", # or "flask-login", "oauth", "none"
|
|
"basic": {
|
|
"username": "admin",
|
|
"password": "$2b$12$...", # bcrypt hash
|
|
"use_bcrypt": True,
|
|
"realm": "TISBackup Admin"
|
|
},
|
|
"flask-login": {
|
|
"users_file": "/etc/tis/users.txt", # username:bcrypt_hash per line
|
|
"use_bcrypt": True,
|
|
"login_view": "login"
|
|
},
|
|
"oauth": {
|
|
"provider": "google",
|
|
"client_id": "your-client-id",
|
|
"client_secret": "your-client-secret",
|
|
"redirect_uri": "http://localhost:8080/oauth/callback",
|
|
"authorized_domains": ["example.com"],
|
|
"authorized_users": ["admin@example.com"]
|
|
}
|
|
}
|
|
|
|
|
|
def create_app_with_auth():
|
|
"""Example: Create Flask app with authentication."""
|
|
app = Flask(__name__)
|
|
app.secret_key = "your-secret-key"
|
|
|
|
# Initialize authentication provider
|
|
auth_type = auth_config.get("type", "none")
|
|
provider_config = auth_config.get(auth_type, {})
|
|
auth = get_auth_provider(auth_type, provider_config)
|
|
auth.init_app(app)
|
|
|
|
# Protected routes
|
|
@app.route("/")
|
|
@auth.require_auth
|
|
def index():
|
|
user = auth.get_current_user()
|
|
return render_template("index.html", user=user)
|
|
|
|
@app.route("/api/backups")
|
|
@auth.require_auth
|
|
def api_backups():
|
|
return jsonify({"backups": []})
|
|
|
|
# Public routes (no auth required)
|
|
@app.route("/health")
|
|
def health():
|
|
return jsonify({"status": "ok"})
|
|
|
|
# Flask-Login specific routes
|
|
if auth_type == "flask-login":
|
|
@app.route("/login", methods=["GET", "POST"])
|
|
def login():
|
|
if request.method == "POST":
|
|
username = request.form.get("username")
|
|
password = request.form.get("password")
|
|
|
|
if auth.verify_password(username, password):
|
|
auth.login_user(username)
|
|
return redirect(url_for("index"))
|
|
else:
|
|
return render_template("login.html", error="Invalid credentials")
|
|
|
|
return render_template("login.html")
|
|
|
|
@app.route("/logout")
|
|
def logout():
|
|
auth.logout()
|
|
return redirect(url_for("login"))
|
|
|
|
# OAuth specific routes
|
|
if auth_type == "oauth":
|
|
@app.route("/oauth/login")
|
|
def oauth_login():
|
|
redirect_uri = url_for("oauth_callback", _external=True)
|
|
return auth.oauth_client.authorize_redirect(redirect_uri)
|
|
|
|
@app.route("/oauth/callback")
|
|
def oauth_callback():
|
|
try:
|
|
token = auth.oauth_client.authorize_access_token()
|
|
user_info = auth.oauth_client.get(auth.userinfo_endpoint).json()
|
|
|
|
# Check authorization
|
|
if not auth.is_user_authorized(user_info):
|
|
return "Unauthorized: Your email/domain is not authorized", 403
|
|
|
|
# Store user in session
|
|
session["oauth_user"] = user_info
|
|
session["oauth_token"] = token
|
|
|
|
return redirect(url_for("index"))
|
|
except Exception as e:
|
|
return f"OAuth callback error: {e}", 500
|
|
|
|
@app.route("/logout")
|
|
def logout():
|
|
auth.logout()
|
|
return redirect(url_for("oauth_login"))
|
|
|
|
return app
|
|
|
|
|
|
if __name__ == "__main__":
|
|
app = create_app_with_auth()
|
|
app.run(debug=True, port=8080)
|