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>
324 lines
6.9 KiB
Markdown
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
|