diff --git a/.env.demo b/.env.demo index 8b52e073..b367e06c 100644 --- a/.env.demo +++ b/.env.demo @@ -8,7 +8,9 @@ ALLOW_FILE_DELETE=true # Allow deletion of file records # **Authentication** AUTH_ENABLED=true -SESSION_SECRET= +# 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= AUTHENTIK_CLIENT_SECRET= AUTHENTIK_CONFIG_URL= +OAUTH_PROVIDER_NAME="Authentik SSO" # **AI/ML Services** # OpenAI diff --git a/app/auth.py b/app/auth.py index f597bccd..5060f05d 100644 --- a/app/auth.py +++ b/app/auth.py @@ -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} diff --git a/app/config.py b/app/config.py index 7a1baf28..6d8af64a 100644 --- a/app/config.py +++ b/app/config.py @@ -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: diff --git a/app/main.py b/app/main.py index b7e73d45..5e4cbc85 100644 --- a/app/main.py +++ b/app/main.py @@ -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(): diff --git a/app/templates/email/.gitkeep b/app/templates/email/.gitkeep deleted file mode 100644 index a405d99d..00000000 --- a/app/templates/email/.gitkeep +++ /dev/null @@ -1,2 +0,0 @@ -# This directory contains email templates -# The .gitkeep file ensures the directory is included in git diff --git a/app/templates/email/default.html b/app/templates/email/default.html deleted file mode 100644 index 71d1d15a..00000000 --- a/app/templates/email/default.html +++ /dev/null @@ -1,161 +0,0 @@ - - - - - - Document from DocuElevate - - - -
- {% if has_logo %} - - {% endif %} -

Document Delivery

-
- -
-

- {% if custom_message %} - {{ custom_message }} - {% else %} - A document has been shared with you from DocuElevate. - {% endif %} -

- -
- Attached Document: {{ filename }} -
- - {% if has_metadata %} - - {% endif %} - -

You can find the attached document in this email. Please let us know if you have any questions.

- - {% if app_url %} -

- Visit DocuElevate -

- {% endif %} -
- - - - diff --git a/app/utils/config_validator/__init__.py b/app/utils/config_validator/__init__.py index 661502a7..c64115e4 100644 --- a/app/utils/config_validator/__init__.py +++ b/app/utils/config_validator/__init__.py @@ -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', diff --git a/app/utils/config_validator/providers.py b/app/utils/config_validator/providers.py index 73b7f3c5..1d559ec4 100644 --- a/app/utils/config_validator/providers.py +++ b/app/utils/config_validator/providers.py @@ -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", diff --git a/app/utils/config_validator/settings_display.py b/app/utils/config_validator/settings_display.py index 84e503d7..2bf9fcfc 100644 --- a/app/utils/config_validator/settings_display.py +++ b/app/utils/config_validator/settings_display.py @@ -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", diff --git a/app/utils/config_validator/validators.py b/app/utils/config_validator/validators.py index e8859802..1e2a84d7 100644 --- a/app/utils/config_validator/validators.py +++ b/app/utils/config_validator/validators.py @@ -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 diff --git a/backend/.env.demo b/backend/.env.demo new file mode 100644 index 00000000..e69de29b diff --git a/docs/AuthenticationSetup.md b/docs/AuthenticationSetup.md new file mode 100644 index 00000000..164a17f6 --- /dev/null +++ b/docs/AuthenticationSetup.md @@ -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). diff --git a/docs/ConfigurationGuide.md b/docs/ConfigurationGuide.md index d7f2a04f..04fc0edb 100644 --- a/docs/ConfigurationGuide.md +++ b/docs/ConfigurationGuide.md @@ -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 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 diff --git a/docs/ConfigurationMaster.md b/docs/ConfigurationMaster.md index d0320433..aec61d2b 100644 --- a/docs/ConfigurationMaster.md +++ b/docs/ConfigurationMaster.md @@ -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 diff --git a/docs/DeploymentGuide.md b/docs/DeploymentGuide.md index fe68dea3..12ba6b97 100644 --- a/docs/DeploymentGuide.md +++ b/docs/DeploymentGuide.md @@ -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 diff --git a/docs/NotificationsSetup.md b/docs/NotificationsSetup.md new file mode 100644 index 00000000..cf65c6a4 --- /dev/null +++ b/docs/NotificationsSetup.md @@ -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/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). diff --git a/docs/README.md b/docs/README.md index 8272afbd..c623844f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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 diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 450f6bab..449a2e46 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -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: diff --git a/frontend/static/images/default-avatar.svg b/frontend/static/images/default-avatar.svg new file mode 100644 index 00000000..4ec0cbed --- /dev/null +++ b/frontend/static/images/default-avatar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/static/images/logo_writing.svg b/frontend/static/images/logo_writing.svg new file mode 100644 index 00000000..f2b7b90c --- /dev/null +++ b/frontend/static/images/logo_writing.svg @@ -0,0 +1,21 @@ + + + + + + + + + + DocuElevate + diff --git a/frontend/templates/login.html b/frontend/templates/login.html new file mode 100644 index 00000000..cbb6bfc7 --- /dev/null +++ b/frontend/templates/login.html @@ -0,0 +1,87 @@ + + + + + + DocuElevate - Login + + + + +
+
+ DocuElevate Logo +
+ +

Welcome to DocuElevate

+ + {% if error %} + + {% endif %} + + {% if message %} + + {% endif %} + + +
+

Sign in with username

+
+
+ + +
+ +
+ + +
+ + +
+
+ + {% if show_oauth %} + +
+
+
+
+
+ Or continue with +
+
+ + + {% endif %} + + +
+ + +
+ DocuElevate {{ app_version|default('', true) }} +
+ +