TISbackup/libtisbackup/auth/README.md
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

324 lines
6.9 KiB
Markdown

# 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
```python
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
```python
@app.route("/")
@auth.require_auth
def index():
user = auth.get_current_user()
return f"Hello {user['username']}"
```
## Providers
### Base Provider (No Auth)
```python
auth = get_auth_provider("none", {})
```
- No authentication required
- All routes publicly accessible
- Useful for development/testing
### Basic Auth
```python
auth = get_auth_provider("basic", {
"username": "admin",
"password": "$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewY5GyYWv.5qVQK6",
"use_bcrypt": True,
"realm": "TISBackup"
})
```
**Required dependencies:**
```bash
uv sync --extra auth-basic
```
**Features:**
- HTTP Basic Authentication
- bcrypt password hashing
- Custom realm support
### Flask-Login
```python
auth = get_auth_provider("flask-login", {
"users_file": "/etc/tis/users.txt",
"use_bcrypt": True,
"login_view": "login"
})
```
**Required dependencies:**
```bash
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
```python
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:**
```bash
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
```python
{
"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
```python
{
"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
```python
{
"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](example_integration.py) for a complete example.
### Minimal Example
```python
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
```python
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
```python
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
```python
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