feat: Add authentication configuration and validation

- Introduced new authentication settings in config.py including `auth_enabled`, `admin_username`, `admin_password`, and `session_secret`.
- Added validation for `session_secret` to ensure it meets security requirements when authentication is enabled.
- Updated main.py to conditionally mount static files and log warnings if the directory is not found.
- Removed unused email template files and added new authentication and notification setup documentation.
- Implemented authentication configuration validation in validators.py and updated settings display.
- Enhanced the user interface with a new login template and SVG assets for branding.
- Added comprehensive guides for setting up authentication and notifications in the documentation.
This commit is contained in:
Christian Krakau-Louis
2025-04-11 03:44:08 +02:00
parent 05ede35059
commit d79652b494
21 changed files with 785 additions and 181 deletions
+4 -1
View File
@@ -8,7 +8,9 @@ ALLOW_FILE_DELETE=true # Allow deletion of file records
# **Authentication**
AUTH_ENABLED=true
SESSION_SECRET=<atLeast32Characters>
# Generate a secure random string, for example:
# python -c "import secrets; print(secrets.token_hex(32))"
SESSION_SECRET=b39fd43f68d0491ca942f28a16e484b1e763fe9accf4445ca2669a5f3b179eb4
ADMIN_USERNAME=admin
ADMIN_PASSWORD=your_secure_password
@@ -16,6 +18,7 @@ ADMIN_PASSWORD=your_secure_password
AUTHENTIK_CLIENT_ID=<yourAuthentikAppClientID>
AUTHENTIK_CLIENT_SECRET=<yourAuthentikClientSecret>
AUTHENTIK_CONFIG_URL=<ConfigUrlOfYourApp, e.g. https://authentik.example.com/application/o/docuelevate/.well-known/openid-configuration>
OAUTH_PROVIDER_NAME="Authentik SSO"
# **AI/ML Services**
# OpenAI
+117 -10
View File
@@ -1,10 +1,13 @@
import os
import inspect
import hashlib
from functools import wraps
from authlib.integrations.starlette_client import OAuth
from fastapi import APIRouter, Request, status
from starlette.responses import RedirectResponse
from fastapi.templating import Jinja2Templates
import pathlib
from app.config import settings
@@ -12,7 +15,15 @@ oauth = OAuth()
AUTH_ENABLED = settings.auth_enabled
if AUTH_ENABLED:
# Set up templates for authentication
templates_dir = pathlib.Path(__file__).parents[1] / "frontend" / "templates"
templates = Jinja2Templates(directory=str(templates_dir))
# Configure OAuth provider if credentials are provided
OAUTH_CONFIGURED = False
OAUTH_PROVIDER_NAME = "Single Sign-On"
if AUTH_ENABLED and settings.authentik_client_id and settings.authentik_client_secret:
oauth.register(
name="authentik",
client_id=settings.authentik_client_id,
@@ -20,6 +31,8 @@ if AUTH_ENABLED:
server_metadata_url=settings.authentik_config_url,
client_kwargs={"scope": "openid profile email"},
)
OAUTH_CONFIGURED = True
OAUTH_PROVIDER_NAME = settings.oauth_provider_name or "Authentik SSO"
router = APIRouter()
@@ -46,29 +59,123 @@ def require_login(func):
return wrapper
def get_gravatar_url(email):
"""Generate a Gravatar URL for the given email"""
email = email.lower().strip()
email_hash = hashlib.md5(email.encode('utf-8')).hexdigest()
return f"https://www.gravatar.com/avatar/{email_hash}?d=identicon"
if AUTH_ENABLED:
@router.get("/login")
async def login(request: Request):
redirect_uri = request.url_for("auth")
"""Show login page with appropriate authentication options"""
return templates.TemplateResponse(
"login.html",
{
"request": request,
"error": request.query_params.get("error"),
"message": request.query_params.get("message"),
"show_oauth": OAUTH_CONFIGURED,
"oauth_provider_name": OAUTH_PROVIDER_NAME,
"app_version": settings.version # Changed from app_version to version
}
)
@router.get("/oauth-login")
async def oauth_login(request: Request):
"""Handle OAuth login flow"""
if not OAUTH_CONFIGURED:
return RedirectResponse(
url="/login?error=OAuth+not+configured",
status_code=status.HTTP_302_FOUND
)
redirect_uri = request.url_for("oauth_callback")
return await oauth.authentik.authorize_redirect(request, redirect_uri)
@router.get("/auth")
@router.get("/oauth-callback")
async def oauth_callback(request: Request):
"""Handle OAuth callback from provider"""
try:
token = await oauth.authentik.authorize_access_token(request)
userinfo = token.get("userinfo")
if not userinfo:
return RedirectResponse(
url="/login?error=Failed+to+retrieve+user+information",
status_code=status.HTTP_302_FOUND
)
# Store user info in session
user_data = dict(userinfo)
# Add Gravatar picture if no picture is provided
if not user_data.get("picture") and user_data.get("email"):
user_data["picture"] = get_gravatar_url(user_data["email"])
request.session["user"] = user_data
# Log the successful authentication
print(f"User authenticated via OAuth: {user_data.get('email', 'No email')}")
# Redirect to original destination or default
redirect_url = request.session.pop("redirect_after_login", "/upload")
return RedirectResponse(url=redirect_url)
except Exception as e:
print(f"OAuth authentication error: {str(e)}")
return RedirectResponse(
url=f"/login?error=Authentication+failed:+{str(e)}",
status_code=status.HTTP_302_FOUND
)
@router.post("/auth")
async def auth(request: Request):
token = await oauth.authentik.authorize_access_token(request)
userinfo = token.get("userinfo")
request.session["user"] = dict(userinfo)
redirect_url = request.session.pop("redirect_after_login", "/upload")
return RedirectResponse(url=redirect_url)
"""Handle local username/password authentication"""
form_data = await request.form()
username = form_data.get("username")
password = form_data.get("password")
if (username == settings.admin_username and
password == settings.admin_password):
# Create user session
request.session["user"] = {
"id": "admin",
"name": "Administrator",
"email": f"{username}@local.docuelevate",
"preferred_username": username,
"picture": "/static/images/default-avatar.svg",
"is_admin": True
}
# Redirect to original destination or default
redirect_url = request.session.pop("redirect_after_login", "/upload")
return RedirectResponse(url=redirect_url, status_code=302)
else:
return RedirectResponse(
url="/login?error=Invalid+username+or+password",
status_code=302
)
@router.get("/logout")
async def logout(request: Request):
"""Handle user logout"""
request.session.pop("user", None)
return RedirectResponse(url="/")
return RedirectResponse(
url="/login?message=You+have+been+logged+out+successfully",
status_code=302
)
@router.get("/api/auth/whoami")
@require_login
async def whoami(request: Request):
"""API endpoint to get current user information"""
user = request.session.get("user")
return user or {"error": "Not authenticated"}
@router.get("/private")
@require_login
async def private_page(request: Request):
"""A protected endpoint that requires login."""
user = request.session.get("user") # e.g. {"email": "...", ...}
user = request.session.get("user")
return {"message": "This is a protected page.", "user": user}
+16 -1
View File
@@ -37,11 +37,17 @@ class Settings(BaseSettings):
gotenberg_url: str
external_hostname: str = "localhost" # Default to localhost
# Authentication settings
auth_enabled: bool = True # Default to enabled
admin_username: Optional[str] = None
admin_password: Optional[str] = None
session_secret: Optional[str] = None
# Authentik
authentik_client_id: Optional[str] = None
authentik_client_secret: Optional[str] = None
authentik_config_url: Optional[str] = None
auth_enabled: bool = True # Default to enabled
oauth_provider_name: Optional[str] = None # Name to display for the OAuth provider
# IMAP 1
imap1_host: Optional[str] = None
@@ -162,6 +168,15 @@ class Settings(BaseSettings):
return []
return v
@validator('session_secret')
def validate_session_secret(cls, v, values):
"""Validate that session_secret is set and has sufficient length when auth is enabled"""
if values.get('auth_enabled') and not v:
raise ValueError("SESSION_SECRET must be set when AUTH_ENABLED=True")
if values.get('auth_enabled') and v and len(v) < 32:
raise ValueError("SESSION_SECRET must be at least 32 characters long")
return v
# Get build date from environment or file
@property
def build_date(self) -> str:
+7 -3
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env python3
import os
import logging
import pathlib
from fastapi import FastAPI, HTTPException, Request, status
from fastapi.staticfiles import StaticFiles
@@ -43,9 +44,12 @@ app.add_middleware(TrustedHostMiddleware, allowed_hosts=[
"127.0.0.1"
])
# Mount the static folder for CSS/JS:
frontend_static_dir = Path(__file__).parent.parent / "frontend" / "static"
app.mount("/static", StaticFiles(directory=frontend_static_dir), name="static")
# Mount the static files directory
static_dir = pathlib.Path(__file__).parents[1] / "frontend" / "static"
if os.path.exists(static_dir):
app.mount("/static", StaticFiles(directory=str(static_dir)), name="static")
else:
print(f"WARNING: Static directory not found at {static_dir}. Static files will not be served.")
@app.on_event("startup")
def on_startup():
-2
View File
@@ -1,2 +0,0 @@
# This directory contains email templates
# The .gitkeep file ensures the directory is included in git
-161
View File
@@ -1,161 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document from DocuElevate</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.header {
text-align: center;
margin-bottom: 30px;
}
.logo {
max-width: 200px;
margin-bottom: 15px;
}
.container {
background-color: #f9f9f9;
border-radius: 8px;
padding: 25px;
border: 1px solid #e0e0e0;
}
.footer {
margin-top: 30px;
font-size: 12px;
color: #666;
text-align: center;
border-top: 1px solid #eee;
padding-top: 20px;
}
.button {
display: inline-block;
background-color: #3182ce;
color: white !important;
padding: 10px 20px;
text-decoration: none;
border-radius: 4px;
margin-top: 15px;
text-align: center;
}
.message {
margin-bottom: 20px;
}
.attachment-info {
background-color: #ebf8ff;
border-left: 4px solid #3182ce;
padding: 10px 15px;
margin-bottom: 20px;
}
.metadata-section {
background-color: #f0fff4;
border-left: 4px solid #38a169;
padding: 10px 15px;
margin: 20px 0;
}
.metadata-title {
font-weight: bold;
margin-bottom: 10px;
color: #2f855a;
}
.metadata-table {
width: 100%;
border-collapse: collapse;
}
.metadata-table th, .metadata-table td {
text-align: left;
padding: 8px;
border-bottom: 1px solid #ddd;
}
.metadata-table th {
width: 40%;
color: #4a5568;
}
.metadata-value {
word-break: break-word;
}
.document-type {
display: inline-block;
background-color: #3182ce;
color: white;
padding: 4px 10px;
border-radius: 12px;
font-size: 0.85em;
margin-top: 5px;
}
</style>
</head>
<body>
<div class="header">
{% if has_logo %}
<img src="cid:logo" alt="DocuElevate Logo" class="logo">
{% endif %}
<h1>Document Delivery</h1>
</div>
<div class="container">
<p class="message">
{% if custom_message %}
{{ custom_message }}
{% else %}
A document has been shared with you from DocuElevate.
{% endif %}
</p>
<div class="attachment-info">
<strong>Attached Document:</strong> {{ filename }}
</div>
{% if has_metadata %}
<div class="metadata-section">
<div class="metadata-title">Document Metadata</div>
{% if metadata.document_type %}
<span class="document-type">{{ metadata.document_type }}</span>
{% endif %}
<table class="metadata-table">
{% for key, value in metadata.items() %}
{% if key != "document_type" and value %}
<tr>
<th>{{ key | replace("_", " ") | title }}</th>
<td class="metadata-value">
{% if value is mapping %}
{% for subkey, subvalue in value.items() %}
<strong>{{ subkey | replace("_", " ") | title }}:</strong> {{ subvalue }}<br>
{% endfor %}
{% elif value is iterable and value is not string %}
{{ value | join(", ") }}
{% else %}
{{ value }}
{% endif %}
</td>
</tr>
{% endif %}
{% endfor %}
</table>
</div>
{% endif %}
<p>You can find the attached document in this email. Please let us know if you have any questions.</p>
{% if app_url %}
<p>
<a href="{{ app_url }}" class="button">Visit DocuElevate</a>
</p>
{% endif %}
</div>
<div class="footer">
<p>This is an automated message from DocuElevate Document Processing System.</p>
<p>© {{ now().year }} DocuElevate</p>
</div>
</body>
</html>
+2
View File
@@ -6,6 +6,7 @@ from app.utils.config_validator.validators import (
validate_email_config,
validate_storage_configs,
validate_notification_config,
validate_auth_config,
check_all_configs
)
from app.utils.config_validator.masking import mask_sensitive_value
@@ -19,6 +20,7 @@ __all__ = [
'validate_email_config',
'validate_storage_configs',
'validate_notification_config',
'validate_auth_config',
'mask_sensitive_value',
'get_provider_status',
'get_settings_for_display',
+23
View File
@@ -11,6 +11,29 @@ def get_provider_status():
"""
providers = {}
# Add Authentication configuration
auth_enabled = getattr(settings, 'auth_enabled', False)
using_oidc = bool(getattr(settings, 'authentik_client_id', None) and
getattr(settings, 'authentik_client_secret', None) and
getattr(settings, 'authentik_config_url', None))
auth_method = "OIDC" if using_oidc else "Basic Auth" if auth_enabled else "None"
providers["Authentication"] = {
"name": "Authentication",
"icon": "fa-solid fa-lock",
"configured": bool(auth_enabled and
(getattr(settings, 'admin_username', None) or
using_oidc)),
"enabled": auth_enabled,
"description": "Access control and user authentication",
"details": {
"method": auth_method,
"provider_name": getattr(settings, 'oauth_provider_name', 'Not set') if using_oidc else "N/A",
"session_security": "Configured" if getattr(settings, 'session_secret', None) else "Not configured"
}
}
# Add Notification configuration - Make sure this provider is near the top of the list
providers["Notifications"] = {
"name": "Notifications",
@@ -77,9 +77,13 @@ def get_settings_for_display(show_values=False):
],
"Authentication": [
"auth_enabled",
"session_secret",
"admin_username",
"admin_password",
"authentik_client_id",
"authentik_client_secret",
"authentik_config_url"
"authentik_config_url",
"oauth_provider_name"
],
"Email": [
"email_host",
+37
View File
@@ -33,6 +33,35 @@ def validate_email_config():
return issues
def validate_auth_config():
"""Validates authentication configuration settings"""
issues = []
# If auth is enabled, check for required settings
if getattr(settings, 'auth_enabled', False):
# Check for session secret
if not getattr(settings, 'session_secret', None):
issues.append("SESSION_SECRET is not configured but AUTH_ENABLED is True")
elif len(getattr(settings, 'session_secret', '')) < 32:
issues.append("SESSION_SECRET must be at least 32 characters long")
# Check if using simple authentication or OIDC
using_simple_auth = bool(getattr(settings, 'admin_username', None) and
getattr(settings, 'admin_password', None))
using_oidc = bool(getattr(settings, 'authentik_client_id', None) and
getattr(settings, 'authentik_client_secret', None) and
getattr(settings, 'authentik_config_url', None))
if not using_simple_auth and not using_oidc:
issues.append("Neither simple authentication nor OIDC are properly configured")
# If using OIDC, check for provider name
if using_oidc and not getattr(settings, 'oauth_provider_name', None):
issues.append("OAUTH_PROVIDER_NAME is not configured but OIDC is enabled")
return issues
def validate_storage_configs():
"""Validates configuration for all storage providers"""
issues = {}
@@ -176,6 +205,13 @@ def check_all_configs():
if hasattr(settings, 'debug') and settings.debug:
dump_all_settings()
# Check auth config
auth_issues = validate_auth_config()
if auth_issues:
logger.warning(f"Authentication configuration issues: {', '.join(auth_issues)}")
else:
logger.info("Authentication configuration OK")
# Check email config
email_issues = validate_email_config()
if email_issues:
@@ -200,6 +236,7 @@ def check_all_configs():
# Return all identified issues
return {
'auth': auth_issues,
'email': email_issues,
'storage': storage_issues,
'notification': notification_issues
View File
+189
View File
@@ -0,0 +1,189 @@
# Setting up Authentication
This guide explains how to configure authentication for DocuElevate to secure your installation.
## Required Configuration Parameters
| **Variable** | **Description** |
|----------------------------|----------------------------------------------------------|
| `AUTH_ENABLED` | Enable or disable authentication (`True`/`False`) |
| `SESSION_SECRET` | Secret key for session encryption (min 32 characters) |
| `ADMIN_USERNAME` | Username for basic authentication |
| `ADMIN_PASSWORD` | Password for basic authentication |
| `AUTHENTIK_CLIENT_ID` | Client ID for OpenID Connect authentication |
| `AUTHENTIK_CLIENT_SECRET` | Client secret for OpenID Connect authentication |
| `AUTHENTIK_CONFIG_URL` | OpenID Connect discovery URL |
| `OAUTH_PROVIDER_NAME` | Display name for the OAuth provider button |
For a complete list of configuration options, see the [Configuration Guide](ConfigurationGuide.md).
## Authentication Methods
DocuElevate supports two primary authentication methods:
1. **Simple Authentication** - Basic username/password authentication managed by DocuElevate
2. **OpenID Connect** - Integration with identity providers like Authentik, Keycloak, or Auth0
## Session Security
DocuElevate uses FastAPI's session management to maintain user sessions. The session data is stored in cookies that are encrypted and signed using your application's secret key. This prevents tampering with session data while ensuring users remain authenticated between requests.
The `SESSION_SECRET` is automatically used by the web framework to:
1. Encrypt and sign session cookies
2. Protect against cross-site request forgery (CSRF) attacks
3. Secure other session-related functionality
Always use a strong, randomly generated secret key of at least 32 characters for production environments.
### Generating a Secure Session Secret
You can generate a secure random string using Python:
```python
import secrets
print(secrets.token_hex(32)) # Outputs a 64-character hex string (32 bytes)
```
Or using OpenSSL:
```bash
openssl rand -hex 32
```
Make sure to keep this secret value confidential and don't reuse it across different applications.
## Setting up Simple Authentication
For smaller deployments or testing, simple authentication is easy to set up:
1. In your `.env` file, set:
```
AUTH_ENABLED=True
SESSION_SECRET=your-secure-random-string-at-least-32-chars
ADMIN_USERNAME=your_admin_username
ADMIN_PASSWORD=your_secure_password
```
2. Restart DocuElevate to apply the changes
3. When you navigate to the application, you'll be prompted to log in with the credentials you set
## Setting up OpenID Connect with Authentik
For larger deployments or when you need more advanced authentication features, OpenID Connect (OIDC) is recommended:
### 1. Create an Application in Authentik
1. Log in to your Authentik admin interface
2. Navigate to "Applications" > "Applications"
3. Click "Create"
4. Fill in the following details:
- **Name**: DocuElevate
- **Slug**: docuelevate
- **Provider**: Create a new OAuth2/OIDC Provider
- **Launch URL**: The URL of your DocuElevate instance (e.g., https://docuelevate.example.com)
5. For the OAuth2/OIDC Provider settings:
- **Client Type**: Confidential
- **Redirect URIs**: https://docuelevate.example.com/auth (adjust for your domain)
- **Signing Key**: Select an appropriate signing key
- **Scopes**: Select "openid", "email", and "profile" at minimum
6. Save the provider and then the application
7. Note down the **Client ID** and **Client Secret** from the provider details
### 2. Configure DocuElevate
1. In your `.env` file, set:
```
AUTH_ENABLED=True
SESSION_SECRET=your-secure-random-string-at-least-32-chars
AUTHENTIK_CLIENT_ID=your_client_id_from_authentik
AUTHENTIK_CLIENT_SECRET=your_client_secret_from_authentik
AUTHENTIK_CONFIG_URL=https://auth.example.com/application/o/docuelevate/.well-known/openid-configuration
OAUTH_PROVIDER_NAME=Authentik SSO
```
2. Adjust the `AUTHENTIK_CONFIG_URL` to match your Authentik instance and application slug
3. Restart DocuElevate to apply the changes
### 3. Test the Authentication
1. Navigate to your DocuElevate instance
2. You should be redirected to the Authentik login page
3. After successful authentication, you'll be redirected back to DocuElevate
## Using Other OpenID Connect Providers
DocuElevate can work with any OpenID Connect-compliant provider, not just Authentik:
### Keycloak Setup
1. Create a client in Keycloak with:
- **Client ID**: your preferred client ID
- **Access Type**: confidential
- **Valid Redirect URIs**: https://docuelevate.example.com/auth
2. Get the client secret from the "Credentials" tab
3. Configure DocuElevate with:
```
AUTHENTIK_CLIENT_ID=your_keycloak_client_id
AUTHENTIK_CLIENT_SECRET=your_keycloak_client_secret
AUTHENTIK_CONFIG_URL=https://keycloak.example.com/auth/realms/your-realm/.well-known/openid-configuration
OAUTH_PROVIDER_NAME=Keycloak SSO
```
### Auth0 Setup
1. Create a new application in Auth0
2. Get your client ID and secret
3. Set the callback URL to https://docuelevate.example.com/auth
4. Configure DocuElevate with:
```
AUTHENTIK_CLIENT_ID=your_auth0_client_id
AUTHENTIK_CLIENT_SECRET=your_auth0_client_secret
AUTHENTIK_CONFIG_URL=https://your-tenant.auth0.com/.well-known/openid-configuration
OAUTH_PROVIDER_NAME=Auth0
```
## Security Considerations
1. **Always use HTTPS** in production to protect authentication tokens and passwords
2. Generate a strong, random `SESSION_SECRET` (at least 32 characters)
3. Use strong passwords for simple authentication
4. Consider using a password manager to generate and store your admin credentials
5. Restrict the scopes requested from your OIDC provider to only what's needed
6. Consider setting up user groups and permissions in your identity provider
7. If using simple authentication in production, consider implementing rate limiting for login attempts
## Troubleshooting Authentication Issues
If you encounter issues with authentication:
1. **Login failures with simple authentication**:
- Verify that the username and password exactly match the values in your `.env` file
- Check if there are leading or trailing spaces in your credentials
- Ensure your `.env` file is properly loaded by the application
2. **Session issues**:
- Check that your `SESSION_SECRET` is set correctly
- Clear browser cookies and cache if experiencing persistent login issues
3. **OIDC issues**:
- **Redirect URI mismatch**: Ensure the redirect URI in your provider configuration exactly matches your DocuElevate URL + "/auth"
- **SSL-related errors**: Make sure your certificates are valid and trusted
- **Provider connectivity**: Ensure DocuElevate can reach your identity provider
4. **Token validation errors**:
- Check that the clocks are synchronized between DocuElevate and the identity provider
- Verify that the signing keys are correctly configured
5. **Debug OpenID information**:
- For most providers, you can visit the `/.well-known/openid-configuration` endpoint to verify their settings
For more general configuration issues, see the [Configuration Troubleshooting Guide](ConfigurationTroubleshooting.md).
+30 -2
View File
@@ -35,9 +35,13 @@ DocuElevate can monitor multiple IMAP mailboxes for document attachments. Each m
| **Variable** | **Description** |
|-------------------------|---------------------------------------------------------------|
| `AUTH_ENABLED` | Enable or disable authentication (`true`/`false`). |
| `AUTHENTIK_CLIENT_ID` | Client ID for Authentik OAuth2. |
| `AUTHENTIK_CLIENT_SECRET` | Client secret for Authentik OAuth2. |
| `SESSION_SECRET` | Secret key used to encrypt sessions and cookies (at least 32 chars). |
| `ADMIN_USERNAME` | Username for basic authentication (when not using OIDC). |
| `ADMIN_PASSWORD` | Password for basic authentication (when not using OIDC). |
| `AUTHENTIK_CLIENT_ID` | Client ID for Authentik OAuth2/OIDC authentication. |
| `AUTHENTIK_CLIENT_SECRET` | Client secret for Authentik OAuth2/OIDC authentication. |
| `AUTHENTIK_CONFIG_URL` | Configuration URL for Authentik OpenID Connect. |
| `OAUTH_PROVIDER_NAME` | Display name for the OAuth provider button. |
### OpenAI & Azure Document Intelligence
@@ -162,6 +166,18 @@ For detailed setup instructions, see the [OneDrive Setup Guide](OneDriveSetup.md
For detailed setup instructions, see the [Amazon S3 Setup Guide](AmazonS3Setup.md).
### Notification System
| **Variable** | **Description** |
|----------------------------|----------------------------------------------------------|
| `NOTIFICATION_URLS` | Comma-separated list of Apprise notification URLs |
| `NOTIFY_ON_TASK_FAILURE` | Send notifications on task failures (`True`/`False`) |
| `NOTIFY_ON_CREDENTIAL_FAILURE` | Send notifications on credential failures (`True`/`False`) |
| `NOTIFY_ON_STARTUP` | Send notification when system starts (`True`/`False`) |
| `NOTIFY_ON_SHUTDOWN` | Send notification when system shuts down (`True`/`False`)|
For detailed setup instructions, see the [Notifications Setup Guide](NotificationsSetup.md).
### Uptime Kuma
| **Variable** | **Description** |
@@ -209,9 +225,13 @@ AZURE_DOCUMENT_INTELLIGENCE_ENDPOINT=https://...
# Authentication
AUTH_ENABLED=true
SESSION_SECRET=a-very-long-and-secure-random-secret-key-string-for-session-encryption
ADMIN_USERNAME=admin
ADMIN_PASSWORD=your_secure_password
AUTHENTIK_CLIENT_ID=...
AUTHENTIK_CLIENT_SECRET=...
AUTHENTIK_CONFIG_URL=https://auth.example.com/.well-known/openid-configuration
OAUTH_PROVIDER_NAME=Authentik SSO
# Storage services
PAPERLESS_NGX_API_TOKEN=...
@@ -270,6 +290,14 @@ EMAIL_USE_TLS=True
EMAIL_SENDER=DocuElevate System <docuelevate@example.com>
EMAIL_DEFAULT_RECIPIENT=recipient@example.com
# Notification Settings
# Configure notification services using Apprise URL format
NOTIFICATION_URLS=discord://webhook_id/webhook_token,mailto://user:pass@gmail.com,tgram://bot_token/chat_id
NOTIFY_ON_TASK_FAILURE=True
NOTIFY_ON_CREDENTIAL_FAILURE=True
NOTIFY_ON_STARTUP=True
NOTIFY_ON_SHUTDOWN=False
# OneDrive (Personal Account)
ONEDRIVE_CLIENT_ID=12345678-1234-1234-1234-123456789012
ONEDRIVE_CLIENT_SECRET=your_client_secret
+2
View File
@@ -13,6 +13,8 @@ DocuElevate is designed to be highly configurable through environment variables,
- [Dropbox Setup](DropboxSetup.md) - How to set up Dropbox integration
- [OneDrive Setup](OneDriveSetup.md) - How to set up Microsoft OneDrive/Graph integration
- [Amazon S3 Setup](AmazonS3Setup.md) - How to set up Amazon S3 integration
- [Authentication Setup](AuthenticationSetup.md) - How to set up user authentication
- [Notifications Setup](NotificationsSetup.md) - How to set up system notifications
## Configuration File Location
+1
View File
@@ -13,6 +13,7 @@ This guide provides instructions for deploying DocuElevate in various environmen
- Paperless NGX instance
- SMTP server (for email notifications)
- IMAP server(s) (for email attachment processing)
- Notification services (Discord, Telegram, etc. for system alerts)
## Docker Deployment
+219
View File
@@ -0,0 +1,219 @@
# Setting up System Notifications
This guide explains how to set up the notification system for DocuElevate, which allows you to receive alerts about important system events.
## Required Configuration Parameters
| **Variable** | **Description** |
|----------------------------|----------------------------------------------------------|
| `NOTIFICATION_URLS` | Comma-separated list of Apprise notification URLs |
| `NOTIFY_ON_TASK_FAILURE` | Send notifications on task failures (`True`/`False`) |
| `NOTIFY_ON_CREDENTIAL_FAILURE` | Send notifications on credential failures (`True`/`False`) |
| `NOTIFY_ON_STARTUP` | Send notification when system starts (`True`/`False`) |
| `NOTIFY_ON_SHUTDOWN` | Send notification when system shuts down (`True`/`False`)|
For a complete list of configuration options, see the [Configuration Guide](ConfigurationGuide.md).
## Overview
DocuElevate uses the Apprise library to provide a flexible notification system that supports over 70 different notification services, including:
- Email
- SMS
- Messaging apps (Telegram, Discord, Slack, Matrix, etc.)
- Push notification services (Pushover, Pushbullet, etc.)
- Web hooks
- And many more
## Setting up Notifications
### 1. Choose Your Notification Services
First, decide which notification services you want to use. The Apprise library supports a wide range of services, each with its own URL format. Here are some common examples:
- **Discord**: `discord://webhook_id/webhook_token`
- **Telegram**: `tgram://bot_token/chat_id`
- **Email**: `mailto://user:pass@example.com`
- **Pushover**: `pover://user_key/app_token`
- **Slack**: `slack://tokenA/tokenB/tokenC`
- **Matrix**: `matrix://username:password@domain/#room`
- **Microsoft Teams**: `msteams://token_a/token_b/token_c`
- **Gotify**: `gotify://hostname/token`
For a complete list of supported services and their URL formats, see the [Apprise Wiki](https://github.com/caronc/apprise/wiki).
### 2. Configure Your Notification URLs
Set the `NOTIFICATION_URLS` environment variable with a comma-separated list of your notification service URLs:
```dotenv
NOTIFICATION_URLS=discord://webhook_id/webhook_token,mailto://user:pass@gmail.com,tgram://bot_token/chat_id
```
You can specify as many notification services as you need.
### 3. Configure Notification Triggers
DocuElevate can send notifications for various system events. Configure which events should trigger notifications:
```dotenv
NOTIFY_ON_TASK_FAILURE=True # Notify when background tasks fail
NOTIFY_ON_CREDENTIAL_FAILURE=True # Notify when service credentials fail (e.g. API token expired)
NOTIFY_ON_STARTUP=True # Notify when the system starts
NOTIFY_ON_SHUTDOWN=False # Notify when the system shuts down
```
## Automated Credential Checking
DocuElevate includes a powerful credential monitoring system that regularly checks the validity of your configured service credentials. This helps you proactively address authentication issues before they affect your document processing workflows.
### How Credential Checking Works
1. **Regular Monitoring**: The system automatically checks all configured service credentials at regular intervals (every 5 minutes) and at startup.
2. **Smart Notifications**: When credential failures are detected, the system sends notifications through your configured notification channels.
3. **Notification Rate Limiting**: To prevent notification spam, alerts follow a progressive notification strategy:
- First 3 failures: Notification sent for each failure
- Subsequent failures: Notifications suppressed until credential is restored
- Recovery: Notification sent when credentials are working again
4. **Services Monitored**:
- OpenAI API
- Azure Document Intelligence
- Dropbox
- Google Drive
- OneDrive/Microsoft Graph
- Other configured storage providers
### Notification Content
When a credential failure is detected, the notification includes:
- The affected service name
- The specific error message
- A reminder to check and update credentials
Example notification:
```
Subject: Credential Failure: Dropbox
The credentials for Dropbox have failed:
Invalid refresh token: Token has been revoked or expired.
Please check and update the credentials in the system settings.
```
### Configuration Options
To control credential check notifications:
```dotenv
# Enable/disable credential failure notifications
NOTIFY_ON_CREDENTIAL_FAILURE=True
```
When set to `False`, the system will still perform the checks but won't send notifications about failures.
### Troubleshooting Credential Issues
If you receive credential failure notifications:
1. **Check token expiration**: For OAuth-based services (Google Drive, OneDrive, Dropbox), refresh tokens may have expired.
2. **Verify API keys**: Ensure your API keys for services like OpenAI and Azure Document Intelligence are still valid.
3. **Check service status**: The service itself might be experiencing downtime.
4. **Review quota limits**: Some services have usage quotas that might have been exceeded.
5. **Regenerate credentials**: Use the built-in auth wizards to generate new tokens:
- Go to Settings > [Service Name] Setup
- Click "Refresh Token" or "Start Authentication Flow"
- Complete the authentication process to generate new credentials
### Viewing Credential Status
You can view the current status of your service credentials in the system dashboard:
1. Navigate to the Status page in the DocuElevate interface
2. Check the Service Status section
3. Each service will show its current status (Valid, Invalid, or Not Configured)
## Service-Specific Setup Instructions
### Discord Notifications
1. Go to your Discord server
2. Select a channel or create a new one for notifications
3. Go to Server Settings > Integrations > Webhooks
4. Click "New Webhook" and set up a webhook for your channel
5. Copy the webhook URL (it will look like `https://discord.com/api/webhooks/123456789/abcdefg`)
6. Extract the webhook ID and token (the parts after `webhooks/`)
7. Format your Apprise URL as: `discord://123456789/abcdefg`
### Telegram Notifications
1. Start a chat with [@BotFather](https://t.me/botfather) on Telegram
2. Create a new bot using the `/newbot` command
3. Note the bot token provided by BotFather
4. Start a chat with your new bot or add it to a group
5. Get the chat ID:
- For direct messages: send a message to the bot, then visit `https://api.telegram.org/bot<YOUR_BOT_TOKEN>/getUpdates`
- For group chats: add the bot to the group, send a message mentioning the bot, then check the same URL
6. Format your Apprise URL as: `tgram://bot_token/chat_id`
### Email Notifications
To send notifications via email, configure the Apprise URL with your SMTP server details:
```
# Gmail example
mailto://your-email@gmail.com:password@smtp.gmail.com?smtp=587
```
For Gmail, you'll need to use an app password if you have 2FA enabled.
## Task Failure Notifications
In addition to credential monitoring, DocuElevate can notify you about background task failures. When enabled, the system will send notifications whenever a background processing task encounters an error.
To configure task failure notifications:
```dotenv
# Enable/disable task failure notifications
NOTIFY_ON_TASK_FAILURE=True
```
Task failure notifications include:
- Task name and ID
- Error type and message
- Task arguments (for debugging)
## Testing Your Notification Setup
To test your notification setup once configured:
1. Start the DocuElevate application
2. If `NOTIFY_ON_STARTUP=True`, you should receive a notification when the system starts
3. Check the application logs for any errors related to notifications
## Security Considerations
When setting up notifications:
1. **Protect your credentials**: Keep your notification URLs secure, as they often contain access tokens or passwords.
2. **Use environment variables**: Store notification URLs in environment variables rather than hard-coding them.
3. **Limit notification content**: Be mindful of sending sensitive information in notifications.
4. **Consider encryption**: For highly sensitive environments, consider using encrypted notification channels.
## Troubleshooting
If you're not receiving notifications:
1. **Check service connectivity**: Ensure the DocuElevate server can access the notification services.
2. **Verify URL format**: Double-check the format of your notification URLs.
3. **Check application logs**: Look for errors related to the notification system in logs.
4. **Test services individually**: Try configuring one notification service at a time to isolate issues.
5. **Check service-specific limits**: Some services have rate limits on notifications.
For more general configuration issues, see the [Configuration Troubleshooting Guide](ConfigurationTroubleshooting.md).
+2
View File
@@ -13,6 +13,8 @@ Welcome to the DocuElevate documentation. This directory contains comprehensive
- [Dropbox Setup](DropboxSetup.md) - How to set up Dropbox integration
- [OneDrive Setup](OneDriveSetup.md) - How to set up Microsoft OneDrive/Graph integration
- [Amazon S3 Setup](AmazonS3Setup.md) - How to set up Amazon S3 integration
- [Authentication Setup](AuthenticationSetup.md) - How to set up user authentication
- [Notifications Setup](NotificationsSetup.md) - How to set up system notifications
- [Configuration Troubleshooting](ConfigurationTroubleshooting.md) - Solutions to common configuration issues
- [Troubleshooting](Troubleshooting.md) - General troubleshooting and solutions to common issues
+22
View File
@@ -11,6 +11,28 @@ DocuElevate offers an intuitive web interface for uploading, managing, and proce
1. Navigate to your DocuElevate instance (typically at `http://your-server-address:8000`)
2. If authentication is enabled, you'll be prompted to log in using your credentials
### Authentication
DocuElevate supports two main authentication methods:
#### Basic Authentication
If basic authentication is configured:
1. You'll see a simple login form
2. Enter your username and password as configured in the system
3. Click "Log In" to access DocuElevate
#### OpenID Connect (OIDC)
If OpenID Connect authentication is configured:
1. You'll see a login button
2. Clicking this will redirect you to your identity provider (e.g., Authentik, Keycloak, Auth0)
3. Log in with your existing credentials on that platform
4. You'll be redirected back to DocuElevate after successful authentication
#### User Sessions
- Once authenticated, your session will remain active until you log out or it expires
- Click the "Logout" button in the top navigation bar to end your session
- For security, sessions automatically expire after a period of inactivity
### Main Interface
DocuElevate features a simple navigation system with the following main sections:
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M96 128a128 128 0 1 0 256 0A128 128 0 1 0 96 128zm94.5 200.2l18.6 31L175.8 483.1l-36-146.9c-2-8.1-9.8-13.4-17.9-11.3C51.9 342.4 0 405.8 0 481.3c0 17 13.8 30.7 30.7 30.7l131.7 0c0 0 0 0 .1 0l5.5 0 112 0 5.5 0c0 0 0 0 .1 0l131.7 0c17 0 30.7-13.8 30.7-30.7c0-75.5-51.9-138.9-121.9-156.4c-8.1-2-15.9 3.3-17.9 11.3l-36 146.9L238.9 359.2l18.6-31c6.4-10.7-1.3-24.2-13.7-24.2L224 304l-19.7 0c-12.4 0-20.1 13.6-13.7 24.2z"/></svg>

After

Width:  |  Height:  |  Size: 643 B

+21
View File
@@ -0,0 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg" width="140" height="24" viewBox="0 0 124 24">
<style>
.icon {
fill: #3B82F6;
}
.text {
font-family: "Segoe UI", "Helvetica Neue", sans-serif;
font-size: 16px;
font-weight: bold;
fill: #3B82F6;
}
</style>
<!-- Folder+gear icon -->
<g class="icon" transform="translate(0, 0)">
<path d="m17.212 20.404l-.108-.885q-.57-.125-.938-.33q-.368-.204-.7-.577l-.835.334l-.539-.815l.689-.577q-.165-.531-.165-1.035t.165-1.034l-.689-.577l.539-.816l.835.335q.332-.393.7-.588q.369-.195.938-.32l.108-.885h1l.107.885q.57.125.938.32t.7.588l.835-.335l.539.816l-.689.576q.166.531.166 1.035t-.166 1.035l.689.577l-.539.815l-.834-.335q-.333.373-.701.578q-.369.205-.938.33l-.107.885zm.5-1.731q.882 0 1.518-.635q.636-.636.636-1.519t-.636-1.518t-1.518-.636t-1.519.636t-.635 1.518t.635 1.519t1.518.635M4 18V6v4.435V10zm.616 1q-.691 0-1.153-.462T3 17.384V6.616q0-.691.463-1.153T4.615 5h4.981l2 2h7.789q.69 0 1.153.463T21 8.616v2.294q-.238-.152-.479-.265q-.24-.112-.521-.21v-1.82q0-.269-.173-.442T19.385 8h-8.19l-2-2h-4.58q-.269 0-.442.173T4 6.616v10.769q0 .269.173.442t.443.173h6.748q.055.275.131.515t.186.485z"/>
</g>
<!-- Text -->
<text class="text" x="28" y="17">DocuElevate</text>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+87
View File
@@ -0,0 +1,87 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DocuElevate - Login</title>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"
integrity="sha512-DTOQO9RWCH3ppGqcWaEA1BIZOC6xxalwEsw9c2QQeAIftl+Vegovlnee1c9QX4TctnWMn13TZye+giMm8e2LwA=="
crossorigin="anonymous" referrerpolicy="no-referrer" />
</head>
<body class="bg-gray-100 h-screen flex items-center justify-center">
<div class="bg-white rounded-lg shadow-lg p-8 max-w-md w-full">
<div class="flex justify-center mb-6">
<img src="/static/images/logo_writing.svg" alt="DocuElevate Logo" class="h-16">
</div>
<h1 class="text-2xl font-bold text-center text-gray-800 mb-6">Welcome to DocuElevate</h1>
{% if error %}
<div class="bg-red-100 border-l-4 border-red-500 text-red-700 p-4 mb-6" role="alert">
<p>{{ error }}</p>
</div>
{% endif %}
{% if message %}
<div class="bg-blue-100 border-l-4 border-blue-500 text-blue-700 p-4 mb-6" role="alert">
<p>{{ message }}</p>
</div>
{% endif %}
<!-- Local authentication form -->
<div class="mb-8" id="local-auth">
<h2 class="text-lg font-semibold mb-4 text-gray-700">Sign in with username</h2>
<form method="POST" action="/auth" class="space-y-4">
<div>
<label for="username" class="block text-sm font-medium text-gray-700">Username</label>
<input type="text" id="username" name="username" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring focus:ring-blue-500 focus:ring-opacity-50">
</div>
<div>
<label for="password" class="block text-sm font-medium text-gray-700">Password</label>
<input type="password" id="password" name="password" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring focus:ring-blue-500 focus:ring-opacity-50">
</div>
<button type="submit" class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
Sign in
</button>
</form>
</div>
{% if show_oauth %}
<!-- OAuth option -->
<div class="relative">
<div class="absolute inset-0 flex items-center">
<div class="w-full border-t border-gray-300"></div>
</div>
<div class="relative flex justify-center text-sm">
<span class="px-2 bg-white text-gray-500">Or continue with</span>
</div>
</div>
<div class="mt-6 grid grid-cols-1 gap-3">
<a href="/oauth-login"
class="w-full inline-flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
<span class="sr-only">Sign in with SSO</span>
<i class="fas fa-lock mr-2"></i>
{{ oauth_provider_name }}
</a>
</div>
{% endif %}
<div class="mt-8 text-center">
<a href="/" class="text-sm font-medium text-blue-600 hover:text-blue-500">
Return to Home
</a>
</div>
</div>
<!-- Optional footer with version info -->
<div class="fixed bottom-4 text-center w-full text-xs text-gray-500">
DocuElevate {{ app_version|default('', true) }}
</div>
</body>
</html>