TISbackup/libtisbackup/auth
k3nny f12d89f3da feat(auth): add pluggable authentication system for Flask routes
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>
2025-10-05 02:02:46 +02:00
..
__init__.py feat(auth): add pluggable authentication system for Flask routes 2025-10-05 02:02:46 +02:00
base.py feat(auth): add pluggable authentication system for Flask routes 2025-10-05 02:02:46 +02:00
basic_auth.py feat(auth): add pluggable authentication system for Flask routes 2025-10-05 02:02:46 +02:00
example_integration.py feat(auth): add pluggable authentication system for Flask routes 2025-10-05 02:02:46 +02:00
flask_login_auth.py feat(auth): add pluggable authentication system for Flask routes 2025-10-05 02:02:46 +02:00
oauth_auth.py feat(auth): add pluggable authentication system for Flask routes 2025-10-05 02:02:46 +02:00
README.md feat(auth): add pluggable authentication system for Flask routes 2025-10-05 02:02:46 +02:00

TISBackup Authentication Module

Pluggable authentication system for Flask routes.

Features

  • Multiple providers: Basic Auth, Flask-Login, OAuth2
  • Easy integration: Simple decorator-based protection
  • Configurable: INI-based configuration
  • Secure: bcrypt password hashing, OAuth integration
  • Extensible: Easy to add new providers

Quick Start

1. Choose Authentication Provider

from libtisbackup.auth import get_auth_provider

# Get provider from config
auth = get_auth_provider("basic", {
    "username": "admin",
    "password": "$2b$12$...",  # bcrypt hash
    "use_bcrypt": True
})

# Initialize with Flask app
auth.init_app(app)

2. Protect Routes

@app.route("/")
@auth.require_auth
def index():
    user = auth.get_current_user()
    return f"Hello {user['username']}"

Providers

Base Provider (No Auth)

auth = get_auth_provider("none", {})
  • No authentication required
  • All routes publicly accessible
  • Useful for development/testing

Basic Auth

auth = get_auth_provider("basic", {
    "username": "admin",
    "password": "$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewY5GyYWv.5qVQK6",
    "use_bcrypt": True,
    "realm": "TISBackup"
})

Required dependencies:

uv sync --extra auth-basic

Features:

  • HTTP Basic Authentication
  • bcrypt password hashing
  • Custom realm support

Flask-Login

auth = get_auth_provider("flask-login", {
    "users_file": "/etc/tis/users.txt",
    "use_bcrypt": True,
    "login_view": "login"
})

Required dependencies:

uv sync --extra auth-login

Features:

  • Session-based authentication
  • Multiple users support
  • Login/logout pages
  • bcrypt password hashing

User file format (users.txt):

username:bcrypt_hash
admin:$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewY5GyYWv.5qVQK6

OAuth2

auth = get_auth_provider("oauth", {
    "provider": "google",  # or "github", "gitlab", "generic"
    "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"]
})

Required dependencies:

uv sync --extra auth-oauth

Features:

  • OAuth2 authentication
  • Google, GitHub, GitLab support
  • Custom OAuth providers
  • Domain/user restrictions

API Reference

AuthProvider

Base class for all auth providers.

Methods

  • init_app(app) - Initialize with Flask app
  • require_auth(f) - Decorator to protect routes
  • is_authenticated() - Check if current request is authenticated
  • get_current_user() - Get current user info
  • handle_unauthorized() - Handle unauthorized access
  • logout() - Logout current user

BasicAuthProvider

HTTP Basic Authentication provider.

Configuration

{
    "username": str,          # Required
    "password": str,          # Required (plain or bcrypt hash)
    "use_bcrypt": bool,       # Default: False
    "realm": str             # Default: "TISBackup"
}

FlaskLoginProvider

Session-based authentication provider.

Configuration

{
    "users": dict,            # {username: password_hash} or...
    "users_file": str,        # Path to users file
    "use_bcrypt": bool,       # Default: True
    "login_view": str        # Default: "login"
}

Methods

  • verify_password(username, password) - Verify credentials
  • login_user(username) - Login user by username

OAuthProvider

OAuth2 authentication provider.

Configuration

{
    "provider": str,              # "google", "github", "gitlab", "generic"
    "client_id": str,             # Required
    "client_secret": str,         # Required
    "redirect_uri": str,          # Required
    "scopes": list,               # Optional
    "authorized_domains": list,   # Optional
    "authorized_users": list,     # Optional

    # For generic provider:
    "authorization_endpoint": str,
    "token_endpoint": str,
    "userinfo_endpoint": str
}

Methods

  • is_user_authorized(user_info) - Check if user is authorized

Integration Example

See example_integration.py for a complete example.

Minimal Example

from flask import Flask
from libtisbackup.auth import get_auth_provider

app = Flask(__name__)
app.secret_key = "your-secret-key"

# Initialize auth
auth = get_auth_provider("basic", {
    "username": "admin",
    "password": "changeme",
    "use_bcrypt": False
})
auth.init_app(app)

# Protected route
@app.route("/")
@auth.require_auth
def index():
    return "Protected content"

# Public route
@app.route("/health")
def health():
    return "OK"

With Flask-Login

auth = get_auth_provider("flask-login", {
    "users": {
        "admin": "$2b$12$..."
    },
    "use_bcrypt": True
})
auth.init_app(app)

@app.route("/login", methods=["POST"])
def login():
    username = request.form["username"]
    password = request.form["password"]

    if auth.verify_password(username, password):
        auth.login_user(username)
        return redirect("/")
    return "Invalid credentials", 401

@app.route("/")
@auth.require_auth
def index():
    user = auth.get_current_user()
    return f"Hello {user['username']}"

With OAuth

auth = get_auth_provider("oauth", {
    "provider": "google",
    "client_id": "...",
    "client_secret": "...",
    "redirect_uri": "http://localhost:8080/oauth/callback",
    "authorized_domains": ["example.com"]
})
auth.init_app(app)

@app.route("/oauth/login")
def oauth_login():
    return auth.oauth_client.authorize_redirect(
        url_for("oauth_callback", _external=True)
    )

@app.route("/oauth/callback")
def oauth_callback():
    token = auth.oauth_client.authorize_access_token()
    user_info = auth.oauth_client.get(auth.userinfo_endpoint).json()

    if auth.is_user_authorized(user_info):
        session["oauth_user"] = user_info
        return redirect("/")
    return "Unauthorized", 403

Security Considerations

  1. Always use HTTPS in production - Especially for Basic Auth
  2. Use bcrypt for passwords - Never store plain text passwords
  3. Rotate credentials regularly - Change passwords and OAuth secrets
  4. Restrict OAuth access - Use authorized_domains or authorized_users
  5. Set strong Flask secret_key - Use secrets.token_hex(32)
  6. Protect config files - chmod 600 for files with credentials
  7. Use environment variables - For sensitive configuration values

Testing

import unittest
from libtisbackup.auth import get_auth_provider

class TestBasicAuth(unittest.TestCase):
    def test_authentication(self):
        auth = get_auth_provider("basic", {
            "username": "admin",
            "password": "test",
            "use_bcrypt": False
        })

        # Mock request with credentials
        # Test authentication logic
        pass

License

GPL v3.0 - Same as TISBackup