API Key, Webkit Fixes, Rate Limit Support & Other Misc. Fixes#1094
API Key, Webkit Fixes, Rate Limit Support & Other Misc. Fixes#1094seanmorley15 merged 37 commits intomainfrom
Conversation
…ng and encapsulate comment and close logic
- Implemented API key creation, deletion, and display functionality. - Updated the settings page to fetch and show existing API keys. - Added UI elements for creating new API keys and copying them to clipboard. - Enhanced request handling to ensure proper trailing slashes for API endpoints.
- dompurify: upgraded from 3.3.1 to 3.3.3 - emoji-picker-element: upgraded from 1.29.0 to 1.29.1 - @sveltejs/adapter-node: updated to use @sveltejs/kit@2.55.0 - @sveltejs/adapter-vercel: updated to use @sveltejs/kit@2.55.0 - @sveltejs/kit: upgraded from 2.53.3 to 2.55.0 - @types/node: upgraded from 22.19.13 to 22.19.15 - autoprefixer: updated postcss version from 8.5.6 to 8.5.8 - baseline-browser-mapping: upgraded from 2.10.0 to 2.10.8 - daisyui: updated postcss version from 8.5.6 to 8.5.8 - prettier-plugin-svelte: upgraded from 3.5.0 to 3.5.1 - svelte-check: updated postcss version from 8.5.6 to 8.5.8 - devalue: upgraded from 5.6.3 to 5.6.4 - electron-to-chromium: upgraded from 1.5.302 to 1.5.313 - caniuse-lite: upgraded from 1.0.30001774 to 1.0.30001780 - mlly: upgraded from 1.8.0 to 1.8.1 - node-releases: upgraded from 2.0.27 to 2.0.36 - tar: upgraded from 7.5.9 to 7.5.11 - tinyexec: upgraded from 1.0.2 to 1.0.4
Currently translated at 99.9% (1091 of 1092 strings) Translation: AdventureLog/Web App Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/fr/
Currently translated at 100.0% (1092 of 1092 strings) Translation: AdventureLog/Web App Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/ko/
Currently translated at 100.0% (1092 of 1092 strings) Translation: AdventureLog/Web App Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/de/
Currently translated at 100.0% (1092 of 1092 strings) Translation: AdventureLog/Web App Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/sv/
Currently translated at 1.2% (14 of 1092 strings) Translation: AdventureLog/Web App Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/ca/
* Refactor AdventureLog Bot workflow to improve issue validation handling and encapsulate comment and close logic (#1068) * Reorder immich API permissions to natural order --------- Co-authored-by: Sean Morley <git@seanmorley.com>
Currently translated at 100.0% (1093 of 1093 strings) Translation: AdventureLog/Web App Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/tr/
Currently translated at 100.0% (1093 of 1093 strings) Translation: AdventureLog/Web App Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/sv/
Currently translated at 100.0% (1093 of 1093 strings) Translation: AdventureLog/Web App Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/de/
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…atting for API keys
There was a problem hiding this comment.
Pull request overview
This PR adds API key–based authentication and management across the Django backend and SvelteKit frontend, introduces a mobile QR-based login helper, and makes backend rate limiting configurable while also applying several small UI/accessibility and localization fixes.
Changes:
- Implement API key model, DRF authentication backend, management endpoints, and admin registration; add API-key-aware CSRF and protected media fallback auth.
- Add settings UI for creating/revoking API keys and a “Mobile Login” modal that generates/displays a QR code.
- Add
ENABLE_RATE_LIMITStoggle for throttling/rate limits; apply various WebKit/a11y tabindex tweaks and translation/doc updates.
Reviewed changes
Copilot reviewed 56 out of 58 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/src/routes/settings/+page.svelte | Adds API key management UI (create/copy/revoke) to Settings → Security. |
| frontend/src/routes/settings/+page.server.ts | Loads the user’s API key list from the backend for initial render. |
| frontend/src/routes/locations/+page.svelte | Adjusts dropdown focus behavior via tabindex change. |
| frontend/src/routes/locations/[id]/+page.svelte | Adjusts dropdown focus behavior via tabindex change. |
| frontend/src/routes/collections/+page.svelte | Adjusts dropdown focus behavior via tabindex change. |
| frontend/src/routes/collections/[id]/+page.svelte | Adjusts dropdown focus behavior via tabindex change. |
| frontend/src/routes/auth/[...path]/+server.ts | Ensures trailing slashes for new auth endpoints (api-keys, mobile-qr) when proxying to backend. |
| frontend/src/locales/zh.json | Adds api_keys.* strings and locations.best_happened_at. |
| frontend/src/locales/uk.json | Adds api_keys.* strings and locations.best_happened_at. |
| frontend/src/locales/tr.json | Adds api_keys.*, navbar.mobile_login, and various Turkish string cleanups. |
| frontend/src/locales/sv.json | Adds api_keys.*, navbar.mobile_login, locations.best_happened_at, and multiple Swedish string edits. |
| frontend/src/locales/sk.json | Adds api_keys.* strings and locations.best_happened_at. |
| frontend/src/locales/ru.json | Adds api_keys.* strings and locations.best_happened_at. |
| frontend/src/locales/pt-br.json | Adds api_keys.* strings and locations.best_happened_at. |
| frontend/src/locales/pl.json | Adds api_keys.* strings and locations.best_happened_at. |
| frontend/src/locales/no.json | Adds api_keys.* strings and locations.best_happened_at. |
| frontend/src/locales/nl.json | Adds api_keys.* strings and locations.best_happened_at. |
| frontend/src/locales/ko.json | Adds api_keys.* strings, locations.best_happened_at, and tweaks an existing label. |
| frontend/src/locales/ja.json | Adds api_keys.* strings and locations.best_happened_at. |
| frontend/src/locales/it.json | Adds api_keys.* strings and locations.best_happened_at. |
| frontend/src/locales/hu.json | Adds api_keys.* strings and locations.best_happened_at. |
| frontend/src/locales/fr.json | Adds api_keys.*, locations.best_happened_at, and multiple French translation fixes. |
| frontend/src/locales/es.json | Adds api_keys.* strings and locations.best_happened_at. |
| frontend/src/locales/de.json | Adds api_keys.*, navbar.mobile_login, and locations.best_happened_at. |
| frontend/src/locales/ca.json | Introduces a new (partial) Catalan locale file (navbar only). |
| frontend/src/locales/ar.json | Adds api_keys.* strings and locations.best_happened_at. |
| frontend/src/lib/types.ts | Adds APIKey frontend type. |
| frontend/src/lib/config.ts | Bumps appVersion string. |
| frontend/src/lib/components/StravaActivityCard.svelte | Changes dropdown tabindex to reduce a11y warnings / improve WebKit behavior. |
| frontend/src/lib/components/shared/CurrencyDropdown.svelte | Makes dropdown content non-tabbable via tabindex="-1". |
| frontend/src/lib/components/Navbar.svelte | Makes dropdown menus non-tabbable via tabindex="-1". |
| frontend/src/lib/components/MobileQR.svelte | Adds new modal component for generating/viewing mobile QR login and managing the “mobile” API key. |
| frontend/src/lib/components/map/MapStyleSelector.svelte | Changes dropdown tabindex to -1. |
| frontend/src/lib/components/collections/CollectionItineraryPlanner.svelte | Changes dropdown tabindex to -1. |
| frontend/src/lib/components/CategoryDropdown.svelte | Changes dropdown tabindex to -1. |
| frontend/src/lib/components/cards/CollectionCard.svelte | Changes dropdown tabindex to -1 in multiple menus. |
| frontend/src/lib/components/Avatar.svelte | Adds “Mobile Login” menu item and mounts MobileQR modal; changes dropdown tabindex to -1. |
| frontend/pnpm-lock.yaml | Updates pinned frontend dependency versions (SvelteKit and related tooling and libs). |
| frontend/package.json | Updates @tailwindcss/typography version range. |
| frontend/package-lock.json | Updates npm lockfile to reflect dependency upgrades. |
| documentation/docs/install/docker.md | Documents ENABLE_RATE_LIMITS env var. |
| documentation/docs/configuration/immich_integration.md | Adjusts Immich permission list ordering/content. |
| documentation/docs/configuration/api_keys.md | Adds new documentation page describing API key usage and management. |
| documentation/.vitepress/config.mts | Adds “API Keys” link to the documentation navigation. |
| CONTRIBUTING.md | Updates the documented documentation folder path (currently incorrect). |
| backend/server/users/views.py | Adds API key list/create/delete endpoints and mobile QR endpoints. |
| backend/server/users/serializers.py | Adds serializers for listing and creating API keys. |
| backend/server/users/models.py | Adds APIKey model with hash-only storage and helper methods. |
| backend/server/users/migrations/0007_apikey.py | Adds migration creating the APIKey table. |
| backend/server/users/authentication.py | Adds DRF authentication backend for X-API-Key / Authorization: Api-Key .... |
| backend/server/users/admin.py | Registers API keys in Django admin. |
| backend/server/main/views.py | Adds API key auth fallback for protected media serving when session auth isn’t present. |
| backend/server/main/urls.py | Registers API key and mobile QR endpoints. |
| backend/server/main/settings.py | Adds API key auth to DRF, introduces ENABLE_RATE_LIMITS, and wires in new CSRF middleware. |
| backend/server/adventures/middleware.py | Adds middleware to skip CSRF checks for API-key-authenticated requests. |
| backend/server/.env.example | Adds ENABLE_RATE_LIMITS example configuration. |
Files not reviewed (2)
- frontend/package-lock.json: Language not supported
- frontend/pnpm-lock.yaml: Language not supported
CONTRIBUTING.md
Outdated
|
|
||
| ``` | ||
| /documentation | ||
| /docs |
There was a problem hiding this comment.
The contributing guide now points contributors to update docs in /docs, but this repository doesn’t contain a top-level docs/ directory (it uses documentation/). This will send contributors to the wrong location; update the path to the actual documentation folder (or add a /docs directory/symlink if that’s the new intended layout).
| /docs | |
| /documentation |
| def process_request(self, request): | ||
| if request.headers.get('X-API-Key'): | ||
| setattr(request, '_dont_enforce_csrf_checks', True) | ||
| return | ||
|
|
There was a problem hiding this comment.
This middleware disables CSRF enforcement purely based on the presence of X-API-Key (and similarly for Authorization: Api-Key ...). If a request also carries a valid session cookie, CSRF could be bypassed for session-authenticated Django views. Consider only skipping CSRF when no session cookie is present, or validating the API key before setting _dont_enforce_csrf_checks.
| setTimeout(() => (keyCopied = false), 2000); | ||
| } catch { | ||
| addToast('error', 'Could not copy — please select the key and copy manually.'); | ||
| } |
There was a problem hiding this comment.
New user-facing strings are hardcoded in English (e.g. the copy failure toast and the guidance text about how to use the key) while the rest of this section uses i18n keys. Please move these strings into the locale files (or use $t(..., { default: ... })) so the API key UI is fully translatable.
| <p class="text-xs text-base-content/50 mt-2 pl-1"> | ||
| Use this key in the <code class="font-mono">X-API-Key</code> header or as | ||
| <code class="font-mono">Authorization: Api-Key <token></code> | ||
| </p> |
There was a problem hiding this comment.
This help text about using the key in request headers is currently hardcoded in English and won’t be translated with the rest of the API key section. Consider moving it into the api_keys.* locale entries (or using $t(..., { default: ... })) for consistency.
| "data_override_warning": "Data åsidosättande varning", | ||
| "data_override_warning_desc": "Återställa data kommer helt att ersätta alla befintliga data (som ingår i säkerhetskopian) i ditt konto. \nDenna åtgärd kan inte ångras.", | ||
| "data_override_warning_desc": "Återställande av data kommer helt att ersätta all befintlig data (som ingår \t\t\t\t\t\t\t\t\t\t\t\ti säkerhetskopian) i ditt konto. Denna åtgärd kan inte ångras.", | ||
| "integrations_settings": "Integrationsinställningar", |
There was a problem hiding this comment.
This localized string contains embedded tab escape sequences (\t\t...) inside the sentence, which will render as large/odd whitespace in the UI. Please remove the tab escapes and use normal spaces so the warning reads correctly.
… dependency management
…ss multiple components
frontend/Dockerfile
Outdated
| # Copy source, then build and prune to production dependencies only. | ||
| COPY . . | ||
| RUN rm -f .env && pnpm run build && pnpm prune --prod |
There was a problem hiding this comment.
pnpm prune --prod removes devDependencies, but this SvelteKit app uses @sveltejs/adapter-node (and @sveltejs/kit) from devDependencies. The built Node server commonly requires these packages at runtime, so pruning can make the container fail to start (module not found). Consider either (1) moving required runtime packages to dependencies, or (2) not pruning dev deps in the image, or (3) copying only the adapter-node output’s required deps via a separate production install step that includes SvelteKit runtime deps.
| # Copy source, then build and prune to production dependencies only. | |
| COPY . . | |
| RUN rm -f .env && pnpm run build && pnpm prune --prod | |
| # Copy source, then build while keeping installed dependencies for the Node runtime. | |
| COPY . . | |
| RUN rm -f .env && pnpm run build |
backend/server/users/views.py
Outdated
| # Create QR code data with proper structure for mobile app | ||
| qr_data = { | ||
| "version": 1, | ||
| "server_url": getattr(settings, 'PUBLIC_URL', 'http://localhost:8015'), |
There was a problem hiding this comment.
The fallback server_url in the QR payload defaults to http://localhost:8015, but the backend’s PUBLIC_URL default elsewhere is http://localhost:8000 and this value is used for API/media URLs. If PUBLIC_URL isn’t set, the QR code will point mobile clients at the frontend port instead of the API base URL. Use the same default as settings.PUBLIC_URL (or getenv('PUBLIC_URL')) to avoid generating broken QR codes.
| "server_url": getattr(settings, 'PUBLIC_URL', 'http://localhost:8015'), | |
| "server_url": getattr(settings, 'PUBLIC_URL', getenv('PUBLIC_URL', 'http://localhost:8000')), |
| onMount(async () => { | ||
| modal = document.querySelector('#mobile-qr-modal') as HTMLDialogElement; | ||
| modal.showModal(); | ||
| await checkExistingKey(); | ||
| }); |
There was a problem hiding this comment.
document.querySelector('#mobile-qr-modal') as HTMLDialogElement can be null if the element isn’t found (or if multiple instances exist), and modal.showModal() would then throw at runtime. Prefer bind:this={modal} on the <dialog> element (or add a null check) to avoid a hard crash when opening the modal.
| async function deleteApiKey() { | ||
| if (!confirm('Are you sure you want to delete this mobile API key?')) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
This confirmation prompt and the subsequent error strings are hardcoded English (and bypass the app’s i18n), so they won’t be translated and are inconsistent with the rest of the UI. Consider moving the confirm message and errors to svelte-i18n keys (and reusing those keys for the alert content).
| <!-- Close Button --> | ||
| <button class="btn btn-ghost btn-square" on:click={closeModal}> | ||
| <Close class="w-5 h-5" /> | ||
| </button> |
There was a problem hiding this comment.
The modal close button is missing type="button" and an accessible label/title. In other modals you’ve added type="button" and aria-label/title (e.g. using $t('about.close')); matching that pattern here avoids accidental form-submit behavior and improves screen-reader/tooltip support.
| <button | ||
| class="btn btn-sm shrink-0 transition-all {keyCopied | ||
| ? 'btn-success' | ||
| : 'btn-ghost'}" | ||
| on:click={copyKey} | ||
| title="Copy to clipboard" | ||
| > |
There was a problem hiding this comment.
The copy button’s tooltip text is hardcoded (title="Copy to clipboard") while the rest of this section is localized via $t(...). Consider using a translated string (and ideally aria-label as well) so the tooltip is consistent across locales and accessible when the icon/text changes.
…ages and improve server URL configuration
…on in requirements
…ments to improve accessibility in CollectionCard and CollectionItineraryPlanner components
This pull request introduces a comprehensive API key authentication system for programmatic access, along with related improvements to security, middleware, and rate limiting. The main changes include adding API key models and authentication, updating middleware to exempt API-key requests from CSRF, exposing new endpoints for API key management, and making rate limiting configurable. There are also minor documentation and workflow improvements.
Fixes #1039
Fixes #1070
Fixes #1088
API Key Authentication and Management
APIKeymodel that securely stores hashed API keys, associates them with users, and tracks usage. Keys are generated securely and only the hash is stored; the raw key is shown once at creation. (backend/server/users/models.py,backend/server/users/migrations/0007_apikey.py) [1] [2]APIKeyAuthentication) that authenticates requests using either theX-API-KeyorAuthorization: Api-Key ...header. (backend/server/users/authentication.py)backend/server/main/urls.py,backend/server/users/admin.py,backend/server/users/serializers.py,backend/server/users/views.py) [1] [2] [3] [4] [5] [6]Security and Middleware Enhancements
DisableCSRFForAPIKeyMiddlewareto exempt API key-authenticated requests from CSRF checks, ensuring compatibility with DRF and direct Django views. (backend/server/adventures/middleware.py,backend/server/main/settings.py) [1] [2]backend/server/main/views.py)Rate Limiting and Configuration
ENABLE_RATE_LIMITSenvironment variable, defaulting to disabled for local development. Updated DRF and allauth settings to respect this flag. (backend/server/main/settings.py,backend/server/.env.example) [1] [2] [3]Developer Experience and Documentation
CONTRIBUTING.mdto reference the correct/docsfolder.Workflow Improvements
pull_request_targetfor better permission management. (.github/workflows/adventurelog-bot.yml) [1] [2] [3] [4] [5]