diff --git a/.env.demo b/.env.demo index 20486c8c..a8c75828 100644 --- a/.env.demo +++ b/.env.demo @@ -96,6 +96,22 @@ MAX_UPLOAD_SIZE=1073741824 # Allowed request headers (use * to allow all) # CORS_ALLOWED_HEADERS=* +# **Audit Logging & SIEM Integration** (see docs/ConfigurationGuide.md#audit-logging) +# Enable HTTP request audit logging middleware +AUDIT_LOGGING_ENABLED=true +# Include client IP in audit log entries (disable for GDPR-sensitive deployments) +AUDIT_LOG_INCLUDE_CLIENT_IP=true + +# Forward audit events to an external SIEM system (Syslog, Splunk, Logstash, Grafana, etc.) +# AUDIT_SIEM_ENABLED=false +# AUDIT_SIEM_TRANSPORT=syslog # syslog | http +# AUDIT_SIEM_SYSLOG_HOST=localhost +# AUDIT_SIEM_SYSLOG_PORT=514 +# AUDIT_SIEM_SYSLOG_PROTOCOL=udp # udp | tcp +# AUDIT_SIEM_HTTP_URL= # e.g. https://splunk:8088/services/collector/event +# AUDIT_SIEM_HTTP_TOKEN= # Bearer / HEC token +# AUDIT_SIEM_HTTP_CUSTOM_HEADERS= # Comma-separated Key:Value pairs + # **Rate Limiting** (see SECURITY_AUDIT.md and docs/API.md) # Protects against DoS attacks and API abuse by limiting request rates per IP/user # Enabled by default - highly recommended for production diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 677150c2..0c206c46 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,6 +17,7 @@ concurrency: env: IMAGE_NAME: christianlouis/docuelevate + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true jobs: # ══════════════════════════════════════════════════════════════════════════ diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index d1f3781c..a7879042 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -8,6 +8,9 @@ on: schedule: - cron: '37 1 * * 1' +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + jobs: analyze: name: Analyze (${{ matrix.language }}) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 56466fcb..275e0565 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,6 +12,9 @@ permissions: pull-requests: write packages: write +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + jobs: release: name: Semantic Release diff --git a/.github/workflows/ruff-auto-fix.yml b/.github/workflows/ruff-auto-fix.yml index a834b523..6e1a6748 100644 --- a/.github/workflows/ruff-auto-fix.yml +++ b/.github/workflows/ruff-auto-fix.yml @@ -16,6 +16,9 @@ permissions: contents: write pull-requests: write +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + jobs: ruff-auto-fix: name: Auto-fix Ruff Issues diff --git a/BUILD_DATE b/BUILD_DATE index ce1b9463..68ca454f 100644 --- a/BUILD_DATE +++ b/BUILD_DATE @@ -1 +1 @@ -2026-03-09T23:00:57Z +2026-03-11T11:44:20Z diff --git a/CHANGELOG.md b/CHANGELOG.md index 1568c12d..1c3bdaaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,81 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 +## Unreleased + +### Continuous Integration + +- Opt into Node.js 24 for all GitHub Actions workflows + ([`c306d80`](https://github.com/christianlouis/DocuElevate/commit/c306d80755193e4b12222c73ed4da442a2d5e23c)) + +### Documentation + +- **changelog**: Update changelog [skip ci] + ([`c5b67fe`](https://github.com/christianlouis/DocuElevate/commit/c5b67fe36464b0756553cc54614d0f77273772ec)) + +- **changelog**: Update changelog [skip ci] + ([`eb22a58`](https://github.com/christianlouis/DocuElevate/commit/eb22a580651b4de477b3b5cc945581776a6775fa)) + + +## Unreleased + +### Continuous Integration + +- Opt into Node.js 24 for all GitHub Actions workflows + ([`c306d80`](https://github.com/christianlouis/DocuElevate/commit/c306d80755193e4b12222c73ed4da442a2d5e23c)) + +### Documentation + +- **changelog**: Update changelog [skip ci] + ([`eb22a58`](https://github.com/christianlouis/DocuElevate/commit/eb22a580651b4de477b3b5cc945581776a6775fa)) + + +## Unreleased + +### Continuous Integration + +- Opt into Node.js 24 for all GitHub Actions workflows + ([`c306d80`](https://github.com/christianlouis/DocuElevate/commit/c306d80755193e4b12222c73ed4da442a2d5e23c)) + + +## v0.116.0 (2026-03-11) + +### Documentation + +- **changelog**: Update changelog [skip ci] + ([`d6fb787`](https://github.com/christianlouis/DocuElevate/commit/d6fb78715ae550f25a925bdc629eb2945f455009)) + +### Testing + +- **views**: Add comprehensive coverage tests for 11 view modules + ([`c8987d9`](https://github.com/christianlouis/DocuElevate/commit/c8987d966bd949634d222a7809ec52c3ed258f81)) + +- **views**: Boost coverage for 11 view modules toward 100% + ([`6fb1df1`](https://github.com/christianlouis/DocuElevate/commit/6fb1df1aade8a8c98c27fe50dbe44045cba1724d)) + + +## Unreleased + +### Testing + +- **views**: Add comprehensive coverage tests for 11 view modules + ([`c8987d9`](https://github.com/christianlouis/DocuElevate/commit/c8987d966bd949634d222a7809ec52c3ed258f81)) + +- **views**: Boost coverage for 11 view modules toward 100% + ([`6fb1df1`](https://github.com/christianlouis/DocuElevate/commit/6fb1df1aade8a8c98c27fe50dbe44045cba1724d)) + + +## v0.115.0 (2026-03-10) + + +## v0.114.1 (2026-03-10) + +### Bug Fixes + +- **db**: Add migration to create shared_links table for databases that skipped 025 + ([`289dcc3`](https://github.com/christianlouis/DocuElevate/commit/289dcc375c111c8d71bd04ef31f184a0e6a3f6f2)) + + ## v0.114.0 (2026-03-09) ### Bug Fixes diff --git a/GIT_SHA b/GIT_SHA index 082dbf0f..40d0eb2d 100644 --- a/GIT_SHA +++ b/GIT_SHA @@ -1 +1 @@ -5fd3f06 +9f7d6c8 diff --git a/RUNTIME_INFO b/RUNTIME_INFO index 0d52e60a..2c455bf9 100644 --- a/RUNTIME_INFO +++ b/RUNTIME_INFO @@ -1,10 +1,10 @@ DocuElevate Build Information ============================== -Version: 0.114.0 -Build Date: 2026-03-09T23:00:57Z -Git Commit: 5fd3f0661b8d86ba6b3f92481675d820aec0d53c -Git Short SHA: 5fd3f06 +Version: 0.116.0 +Build Date: 2026-03-11T11:44:20Z +Git Commit: 9f7d6c85488081f546e7c392bc46b2827bb1caa6 +Git Short SHA: 9f7d6c8 Git Branch: main -Commit Date: 2026-03-10T00:00:39+01:00 -Build Timestamp: 2026-03-09T23:00:57Z +Commit Date: 2026-03-11T12:43:59+01:00 +Build Timestamp: 2026-03-11T11:44:20Z ============================== diff --git a/VERSION b/VERSION index 18455b77..4c08787e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.114.0 +0.116.0 diff --git a/docs/ConfigurationGuide.md b/docs/ConfigurationGuide.md index 009dc5a9..131324e4 100644 --- a/docs/ConfigurationGuide.md +++ b/docs/ConfigurationGuide.md @@ -398,6 +398,61 @@ default overage buffer applied across all plans. DocuElevate supports HTTP security headers to improve browser-side security. **These headers are disabled by default** since most deployments use a reverse proxy (Traefik, Nginx, etc.) that already adds them. Enable only if deploying directly without a reverse proxy. See [Deployment Guide - Security Headers](DeploymentGuide.md#security-headers) for detailed configuration examples. +### Audit Logging + +DocuElevate provides comprehensive audit logging that records significant actions (logins, document CRUD, settings changes) to an append-only database table. Every entry captures the timestamp, user, action, resource, client IP, and optional JSON details. + +| **Variable** | **Description** | **Default** | +|--------------------------------|---------------------------------------------------------------------------------------------------|-------------| +| `AUDIT_LOGGING_ENABLED` | Enable the HTTP request audit-logging middleware. | `true` | +| `AUDIT_LOG_INCLUDE_CLIENT_IP` | Include the client IP address in audit log entries. Disable for GDPR-sensitive deployments. | `true` | + +#### SIEM Integration + +Audit events can be forwarded in real time to external SIEM systems for centralised monitoring, alerting, and long-term retention. Two transports are supported: + +* **Syslog** – RFC 5424 structured-data messages over UDP or TCP. Works with rsyslog, syslog-ng, Graylog, Datadog, etc. +* **HTTP** – JSON POST payloads compatible with Splunk HEC, Logstash HTTP input, Grafana Loki push API, and any generic webhook. + +| **Variable** | **Description** | **Default** | +|-------------------------------------|---------------------------------------------------------------------------------------------------|---------------| +| `AUDIT_SIEM_ENABLED` | Enable forwarding of audit events to an external SIEM system. | `false` | +| `AUDIT_SIEM_TRANSPORT` | Transport: `syslog` or `http`. | `syslog` | +| `AUDIT_SIEM_SYSLOG_HOST` | Hostname or IP of the syslog receiver. | `localhost` | +| `AUDIT_SIEM_SYSLOG_PORT` | Port of the syslog receiver. | `514` | +| `AUDIT_SIEM_SYSLOG_PROTOCOL` | Protocol for syslog: `udp` or `tcp`. | `udp` | +| `AUDIT_SIEM_HTTP_URL` | HTTP endpoint URL for SIEM delivery (e.g. Splunk HEC, Logstash, Loki). | *(empty)* | +| `AUDIT_SIEM_HTTP_TOKEN` | Bearer / HEC token for the SIEM HTTP endpoint. | *(empty)* | +| `AUDIT_SIEM_HTTP_CUSTOM_HEADERS` | Comma-separated `Key:Value` extra headers for SIEM HTTP requests. | *(empty)* | + +**Example – Syslog to rsyslog:** + +```bash +AUDIT_SIEM_ENABLED=true +AUDIT_SIEM_TRANSPORT=syslog +AUDIT_SIEM_SYSLOG_HOST=syslog.internal.example.com +AUDIT_SIEM_SYSLOG_PORT=514 +AUDIT_SIEM_SYSLOG_PROTOCOL=udp +``` + +**Example – Splunk HEC:** + +```bash +AUDIT_SIEM_ENABLED=true +AUDIT_SIEM_TRANSPORT=http +AUDIT_SIEM_HTTP_URL=https://splunk.example.com:8088/services/collector/event +AUDIT_SIEM_HTTP_TOKEN=your-hec-token +``` + +**Example – Logstash HTTP input:** + +```bash +AUDIT_SIEM_ENABLED=true +AUDIT_SIEM_TRANSPORT=http +AUDIT_SIEM_HTTP_URL=https://logstash.example.com:8080 +AUDIT_SIEM_HTTP_TOKEN= +``` + ### Rate Limiting DocuElevate implements rate limiting to protect against DoS attacks and API abuse. **Rate limiting is enabled by default** and uses Redis for distributed rate limiting across multiple workers. diff --git a/docs/InternationalizationGuide.md b/docs/InternationalizationGuide.md new file mode 100644 index 00000000..2b192602 --- /dev/null +++ b/docs/InternationalizationGuide.md @@ -0,0 +1,231 @@ +# Internationalization (i18n) & Localization (l10n) Guide + +DocuElevate supports **10 languages** for its web UI, with automatic browser +language detection, user-preference persistence, and an AI-powered fallback +translator for strings that haven't been manually translated yet. + +## Supported Languages + +| Code | Language | Native Name | Priority | +|------|------------|-------------|----------| +| `en` | English | English | Tier 1 | +| `de` | German | Deutsch | Tier 1 | +| `fr` | French | Français | Tier 1 | +| `es` | Spanish | Español | Tier 1 | +| `it` | Italian | Italiano | Tier 1 | +| `pt` | Portuguese | Português | Tier 1 | +| `nl` | Dutch | Nederlands | Tier 2 | +| `pl` | Polish | Polski | Tier 2 | +| `zh` | Chinese | 中文 | Tier 2 | +| `ru` | Russian | Русский | Tier 2 | + +> **Tier 1** languages (European priority) have complete, manually-reviewed +> translations. **Tier 2** languages have complete translations but may +> receive less frequent updates. + +## How Language Is Detected + +DocuElevate resolves the display language in the following priority order: + +1. **User profile preference** — stored in the database (`UserProfile.preferred_language`) + and loaded into the session on login +2. **Cookie** — `docuelevate_lang` cookie (30-day expiry, set when user selects a language) +3. **Browser `Accept-Language` header** — the highest-priority match among supported languages +4. **Default** — English (`en`) + +## Selecting Your Language + +### Via the Navigation Bar + +Click the 🌐 **globe icon** in the top navigation bar. A dropdown menu shows all +available languages with their native names and flag emoji. The current language +is highlighted with a blue checkmark. + +### Via the API + +```bash +# Set language to German +curl -X POST http://localhost:8000/api/i18n/language \ + -H "Content-Type: application/json" \ + -d '{"language": "de"}' + +# List all available languages +curl http://localhost:8000/api/i18n/languages +``` + +### Via Cookie (Programmatic) + +Set the `docuelevate_lang` cookie to any supported language code: + +```javascript +document.cookie = "docuelevate_lang=fr; max-age=2592000; path=/"; +location.reload(); +``` + +## For Developers + +### Translation File Structure + +Translations are stored as flat JSON files in `frontend/translations/`: + +``` +frontend/translations/ +├── en.json # English (base / reference) +├── de.json # German +├── fr.json # French +├── es.json # Spanish +├── it.json # Italian +├── pt.json # Portuguese +├── nl.json # Dutch +├── pl.json # Polish +├── zh.json # Chinese +└── ru.json # Russian +``` + +Each file is a flat key-value dictionary with dot-notation namespacing: + +```json +{ + "nav.dashboard": "Dashboard", + "nav.upload": "Upload", + "upload.max_size": "Maximum file size: {size}", + "footer.copyright": "DocuElevate {year}" +} +``` + +### Using Translations in Templates + +The `_()` function is available globally in all Jinja2 templates: + +```jinja2 +{# Simple translation #} +

{{ _("dashboard.title") }}

+ +{# Translation with placeholders #} +

{{ _("upload.max_size", size="50 MB") }}

+ +{# Translation in attributes #} + +``` + +### Using Translations in Python + +```python +from app.utils.i18n import translate + +# Basic translation +text = translate("nav.dashboard", "de") # → "Übersicht" + +# With placeholders +text = translate("footer.copyright", "fr", year="2025") # → "DocuElevate 2025" +``` + +### Localization Helpers + +Format dates, times, and numbers according to locale conventions: + +```jinja2 +{# In templates — locale is automatically detected #} +{{ format_date_l10n(document.created_at) }} +{{ format_number_l10n(file_count) }} +``` + +```python +# In Python +from app.utils.i18n import format_date, format_number + +format_date(date(2025, 3, 15), "de") # → "15. March 2025" +format_date(date(2025, 3, 15), "de", short=True) # → "15.03.2025" +format_number(1234567, "de") # → "1.234.567" +format_number(1234.56, "en") # → "1,234.56" +``` + +### Adding a New Translation Key + +1. Add the key and English text to `frontend/translations/en.json` +2. Add translations for all other languages in their respective files +3. Use `{{ _("your.new.key") }}` in templates + +### AI Fallback Translation + +When a translation key exists in English but not in the target language, +DocuElevate can use the configured AI provider (OpenAI, Anthropic, etc.) +to translate the string on-the-fly: + +```python +from app.utils.i18n import translate_with_ai_fallback + +# Falls back to AI if no manual translation exists +translated = translate_with_ai_fallback("Welcome to our platform", "de") +``` + +The AI fallback: +- Uses the `AI_MODEL` or `OPENAI_MODEL` setting +- Caches results in memory for the process lifetime +- Returns the original English text if the AI call fails +- Is designed for graceful degradation — the UI never breaks + +### Adding a New Language + +1. Create a new JSON file in `frontend/translations/` (e.g., `ja.json`) +2. Copy the structure from `en.json` and translate all values +3. Add the language to `SUPPORTED_LANGUAGES` in `app/utils/i18n.py`: + ```python + {"code": "ja", "name": "Japanese", "native": "日本語", "flag": "🇯🇵"}, + ``` +4. Add locale formatting rules to `_LOCALE_FORMATS` in the same file +5. Create a database migration if needed (the `preferred_language` column + already accepts any string up to 10 characters) + +### Database Migration + +Migration `027_add_user_language_preference` adds a `preferred_language` +column to the `user_profiles` table. This column stores the user's chosen +UI language as an ISO 639-1 code (e.g., `"de"`, `"fr"`). A `NULL` value +means "auto-detect from browser settings." + +### API Reference + +#### `GET /api/i18n/languages` + +Returns all supported languages and the current active language. + +**Response:** +```json +{ + "languages": [ + {"code": "en", "name": "English", "native": "English", "flag": "🇬🇧"}, + {"code": "de", "name": "German", "native": "Deutsch", "flag": "🇩🇪"} + ], + "current": "en", + "default": "en" +} +``` + +#### `POST /api/i18n/language` + +Set the preferred UI language. Persists in session, cookie, and database. + +**Request:** +```json +{"language": "de"} +``` + +**Response:** +```json +{ + "language": "de", + "message": "Language changed to Deutsch" +} +``` + +## Configuration + +No additional configuration is required. The i18n system works out of the box +with the default English language and automatically detects browser preferences. + +| Setting | Default | Description | +|---------|---------|-------------| +| Browser `Accept-Language` | Auto-detected | Used when no explicit preference is set | +| `docuelevate_lang` cookie | Not set | Set when user selects a language via the UI | +| `UserProfile.preferred_language` | `NULL` | Stored in DB for authenticated users | diff --git a/frontend/templates/audit_logs.html b/frontend/templates/audit_logs.html new file mode 100644 index 00000000..92073816 --- /dev/null +++ b/frontend/templates/audit_logs.html @@ -0,0 +1,222 @@ +{% extends "base.html" %} + +{% block title %}Audit Logs - DocuElevate{% endblock %} + +{% block content %} +
+ + +
+
+

+ Audit Logs +

+

+ Comprehensive, append-only record of all significant actions. +

+
+
+ {% if siem_enabled %} + + SIEM: {{ siem_transport|upper }} + + {% else %} + + SIEM: Off + + {% endif %} + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+ + Loading… +
+ + +
+
+ + + + + + + + + + + + + + + + + + +
TimestampSeverityUserActionResourceIPDetails
+ +

No audit events recorded yet.

+

Significant actions (logins, document operations, settings changes) will appear here.

+
+
+
+ + + + +
+ + +{% endblock %} diff --git a/frontend/templates/base.html b/frontend/templates/base.html index e4bb200a..338346dd 100644 --- a/frontend/templates/base.html +++ b/frontend/templates/base.html @@ -1,5 +1,5 @@ - + {% block title %}DocuElevate{% endblock %} @@ -37,10 +37,10 @@ data-multi-user="{{ 'true' if multi_user_enabled else 'false' }}" data-allow-signup="{{ 'true' if allow_signup else 'false' }}"> - + -