Skip to content

[Bug]: GET /api/scrobbling/lastfm/link returns 500 under API-key auth (synthetic user id violates FK) #813

Description

@terafin

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I removed passwords, tokens, private URLs, and other secrets from the report.

Affected area

Last.fm scrobbling / account linking (API-key auth path)

Summary

Any request to GET /api/scrobbling/lastfm/link authenticated via X-Api-Key fails with 500 SqliteError: FOREIGN KEY constraint failed (SQLITE_CONSTRAINT_FOREIGNKEY). The same endpoint works when authenticated with a real user identity (session token or Basic auth), and FK-free endpoints (e.g. POST /api/settings, GET /api/scrobbling/status) work fine under API-key auth — so it presents as a mysterious intermittent 500 for anyone automating with an API key.

Root cause (verified against the deployed build, ghcr.io/lklynet/aurral:latest pulled 2026-09-09)

  1. backend/routes/scrobbling.js:132 — the /lastfm/link handler calls createLinkState(req.user.id), which runs insertLinkStateStmt.run(hash(token), userId, hash(browserNonce), expiresAt, now) inside a transaction (backend/routes/scrobbling.js:55).
  2. backend/config/db-sqlite.js:81-88 — lastfm_link_states declares FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE.
  3. But the API-key auth path (backend/middleware/auth.js:323) does not resolve a real user row — it returns a synthetic in-memory identity: { id: -1, username: "api", role: "admin", permissions: buildPermissions("admin") } (the key itself lives in settings.integrations.general).
  4. requireAuth (backend/middleware/requirePermission.js:3-8) only checks req.user truthiness, so the synthetic admin passes; the INSERT then violates the FK on user_id = -1 → 500. lastfm_link_states stays empty.

Broader scope

lastfm_link_states is not the only FK-on-users(id) table — the same constraint exists on sessions, subsonic_stars, play_events, and inbox_items (backend/config/db-sqlite.js). Any route that inserts into these while authenticated via API key will hit the same failure mode, so this is a general consequence of the synthetic identity rather than a one-endpoint bug.

(Related but distinct: #775 covers the same underlying design issue from the attribution side — API-key actions can't be attributed to a real user. #811 looked similar but was a Jellyfin-side empty-GUID problem, not this.)

Steps to reproduce

  1. Configure Aurral with an API key (settings.integrations.general).
  2. GET /api/scrobbling/lastfm/link with X-Api-Key: <key>.
  3. Observe 500 with SqliteError: FOREIGN KEY constraint failed.
  4. Repeat with a session token from /api/auth/login or Basic auth as any real user → 200 with {configured, connected, authorizeUrl}.

Expected behavior

Either the link-state flow works under API-key auth, or the endpoint rejects synthetic identities with a clear 4xx up front instead of a 500 from a constraint violation.

Suggested fixes (any one)

(a) persist a real service-user row for the API-key identity and return its id;
(b) have FK-inserting routes (at minimum /lastfm/link) reject synthetic identities with a 403 explaining that account linking requires a real user;
(c) make resolveApiKeyUser return the sole admin's id when exactly one admin exists — though silently attributing API-key actions to a human admin is probably wrong.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or behaving incorrectly.releasedIncluded in a stable release.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions