Skip to content

API Key, Webkit Fixes, Rate Limit Support & Other Misc. Fixes#1094

Merged
seanmorley15 merged 37 commits intomainfrom
development
Apr 5, 2026
Merged

API Key, Webkit Fixes, Rate Limit Support & Other Misc. Fixes#1094
seanmorley15 merged 37 commits intomainfrom
development

Conversation

@seanmorley15
Copy link
Copy Markdown
Owner

@seanmorley15 seanmorley15 commented Apr 4, 2026

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

  • Added an APIKey model 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]
  • Implemented a custom DRF authentication backend (APIKeyAuthentication) that authenticates requests using either the X-API-Key or Authorization: Api-Key ... header. (backend/server/users/authentication.py)
  • Added API key management endpoints for listing, creating, and deleting keys, and registered the model in the Django admin. (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

  • Introduced DisableCSRFForAPIKeyMiddleware to 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]
  • Updated protected media serving to fallback to API key authentication if session authentication is not present. (backend/server/main/views.py)

Rate Limiting and Configuration

  • Made global rate limiting and throttling configurable via an ENABLE_RATE_LIMITS environment 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

  • Updated the documentation path in CONTRIBUTING.md to reference the correct /docs folder.

Workflow Improvements

  • Refactored the AdventureLog Bot GitHub workflow for safer handling of PRs, improving error handling and using pull_request_target for better permission management. (.github/workflows/adventurelog-bot.yml) [1] [2] [3] [4] [5]

seanmorley15 and others added 22 commits March 16, 2026 11:40
- 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/
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 4, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
adventurelog Ready Ready Preview, Comment Apr 5, 2026 0:57am

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_LIMITS toggle 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
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
/docs
/documentation

Copilot uses AI. Check for mistakes.
Comment on lines +50 to +54
def process_request(self, request):
if request.headers.get('X-API-Key'):
setattr(request, '_dont_enforce_csrf_checks', True)
return

Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +441 to +444
setTimeout(() => (keyCopied = false), 2000);
} catch {
addToast('error', 'Could not copy — please select the key and copy manually.');
}
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +1025 to +1028
<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 &lt;token&gt;</code>
</p>
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines 726 to 728
"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",
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 72 out of 74 changed files in this pull request and generated 6 comments.

Files not reviewed (1)
  • frontend/pnpm-lock.yaml: Language not supported

Comment on lines +18 to +20
# Copy source, then build and prune to production dependencies only.
COPY . .
RUN rm -f .env && pnpm run build && pnpm prune --prod
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
# 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

Copilot uses AI. Check for mistakes.
# Create QR code data with proper structure for mobile app
qr_data = {
"version": 1,
"server_url": getattr(settings, 'PUBLIC_URL', 'http://localhost:8015'),
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
"server_url": getattr(settings, 'PUBLIC_URL', 'http://localhost:8015'),
"server_url": getattr(settings, 'PUBLIC_URL', getenv('PUBLIC_URL', 'http://localhost:8000')),

Copilot uses AI. Check for mistakes.
Comment on lines +27 to +31
onMount(async () => {
modal = document.querySelector('#mobile-qr-modal') as HTMLDialogElement;
modal.showModal();
await checkExistingKey();
});
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +98 to +101
async function deleteApiKey() {
if (!confirm('Are you sure you want to delete this mobile API key?')) {
return;
}
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Comment on lines +194 to +197
<!-- Close Button -->
<button class="btn btn-ghost btn-square" on:click={closeModal}>
<Close class="w-5 h-5" />
</button>
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +986 to +992
<button
class="btn btn-sm shrink-0 transition-all {keyCopied
? 'btn-success'
: 'btn-ghost'}"
on:click={copyKey}
title="Copy to clipboard"
>
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
…ments to improve accessibility in CollectionCard and CollectionItineraryPlanner components
@seanmorley15 seanmorley15 merged commit 5291c8a into main Apr 5, 2026
14 of 16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

8 participants