feat(api): add personal API tokens and enhance webhook integration UI

- Add ApiToken model with SHA-256 hashed storage and usage tracking
- Create API token CRUD endpoints (POST/GET/DELETE /api/api-tokens/)
- Add Bearer token authentication to require_login decorator
- Exempt Bearer-authenticated requests from CSRF validation
- Add API tokens management page with create/revoke/copy UI
- Enhance webhook integration type with detailed explanation and code snippets
- Add navigation links (desktop + mobile) to API tokens page
- Include 19 tests covering CRUD, auth resolution, and utility functions
- Create migration 024_add_api_tokens

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-03-08 18:42:55 +00:00
parent 906c1ca246
commit c3bb93c197
13 changed files with 1272 additions and 25 deletions
+91 -2
View File
@@ -609,8 +609,97 @@
</div>
</template>
<!-- Generic fallback for types without dedicated fields -->
<template x-if="form.integration_type && !hasFormFields(form.integration_type)">
<!-- Webhook explanation and sample snippets -->
<template x-if="form.integration_type === 'WEBHOOK'">
<div class="space-y-4 border-t border-gray-200 dark:border-gray-700 pt-3">
<p class="text-xs font-semibold text-purple-500 uppercase tracking-wider">
<i class="fas fa-bolt mr-1" aria-hidden="true"></i> Webhook Ingestion
</p>
<div class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4">
<h4 class="text-sm font-semibold text-blue-800 dark:text-blue-200 mb-2">
<i class="fas fa-info-circle mr-1" aria-hidden="true"></i>
How Webhook Ingestion Works
</h4>
<p class="text-sm text-blue-700 dark:text-blue-300 leading-relaxed">
A webhook integration allows external systems to push documents directly into DocuElevate
via the REST API. Instead of DocuElevate polling for new files (like IMAP), <strong>your
application sends files to DocuElevate</strong> using an HTTP request with an API token for
authentication.
</p>
<p class="text-sm text-blue-700 dark:text-blue-300 leading-relaxed mt-2">
This is ideal for <strong>CI/CD pipelines</strong>, <strong>automation scripts</strong>,
<strong>scanner integrations</strong>, or any system that generates documents and needs to
send them for processing.
</p>
</div>
<div class="bg-gray-50 dark:bg-gray-750 rounded-lg p-4">
<h4 class="text-sm font-semibold text-gray-800 dark:text-gray-200 mb-2">
<i class="fas fa-terminal mr-1" aria-hidden="true"></i>
Quick Start
</h4>
<ol class="text-sm text-gray-600 dark:text-gray-400 space-y-2 list-decimal list-inside">
<li>
Go to <a href="/api-tokens" class="text-indigo-600 hover:underline font-medium">API Tokens</a>
and create a personal token.
</li>
<li>Use the token to upload documents via the API:</li>
</ol>
<div class="mt-3 relative">
<pre class="bg-gray-900 text-green-400 rounded-lg p-4 text-xs overflow-x-auto font-mono leading-relaxed"><code>curl -X POST "<span x-text="window.location.origin"></span>/api/files/ui-upload" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-F "file=@/path/to/document.pdf"</code></pre>
<button
type="button"
@click="navigator.clipboard.writeText('curl -X POST \'' + window.location.origin + '/api/files/ui-upload\' \\\n -H \'Authorization: Bearer YOUR_API_TOKEN\' \\\n -F \'file=@/path/to/document.pdf\'')"
class="absolute top-2 right-2 px-2 py-1 bg-gray-700 text-gray-300 rounded text-xs hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-indigo-500"
style="min-height:28px; min-width:28px;"
aria-label="Copy curl example to clipboard"
>
<i class="fas fa-copy" aria-hidden="true"></i>
</button>
</div>
<h4 class="text-sm font-semibold text-gray-800 dark:text-gray-200 mt-4 mb-2">
<i class="fab fa-python mr-1" aria-hidden="true"></i>
Python Example
</h4>
<div class="relative">
<pre class="bg-gray-900 text-green-400 rounded-lg p-4 text-xs overflow-x-auto font-mono leading-relaxed"><code>import requests
response = requests.post(
"<span x-text="window.location.origin"></span>/api/files/ui-upload",
headers={"Authorization": "Bearer YOUR_API_TOKEN"},
files={"file": open("document.pdf", "rb")},
)
print(response.json())</code></pre>
<button
type="button"
@click="navigator.clipboard.writeText('import requests\n\nresponse = requests.post(\n \'' + window.location.origin + '/api/files/ui-upload\',\n headers={\'Authorization\': \'Bearer YOUR_API_TOKEN\'},\n files={\'file\': open(\'document.pdf\', \'rb\')},\n)\nprint(response.json())')"
class="absolute top-2 right-2 px-2 py-1 bg-gray-700 text-gray-300 rounded text-xs hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-indigo-500"
style="min-height:28px; min-width:28px;"
aria-label="Copy Python example to clipboard"
>
<i class="fas fa-copy" aria-hidden="true"></i>
</button>
</div>
</div>
<div class="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-3">
<p class="text-xs text-yellow-800 dark:text-yellow-300">
<i class="fas fa-shield-alt mr-1" aria-hidden="true"></i>
<strong>Security tip:</strong> Create a dedicated API token for each integration and
revoke it immediately if compromised. Tokens can be managed on the
<a href="/api-tokens" class="underline font-medium">API Tokens</a> page.
</p>
</div>
</div>
</template>
<!-- Generic fallback for types without dedicated fields (excludes WEBHOOK) -->
<template x-if="form.integration_type && !hasFormFields(form.integration_type) && form.integration_type !== 'WEBHOOK'">
<div class="space-y-3 border-t border-gray-200 dark:border-gray-700 pt-3">
<p class="text-xs font-semibold text-gray-400 uppercase tracking-wider" x-text="form.integration_type + ' Settings'"></p>
<p class="text-sm text-gray-500 dark:text-gray-400">