Commit Graph
2 Commits
Author SHA1 Message Date
bvandeusenandClaude Opus 5.5 327d49428f feat(auth): store Subsonic API keys hashed; a new key is shown once (M462 #4983)
test-web / test (push) Successful in 2m4s
test-go / test (push) Successful in 2m23s
test-go / integration (push) Successful in 5m28s
release / Build signed APK (releases and dev) (push) Successful in 6m29s
release / Build + push container image (push) Successful in 29s
release / Verify release artifacts (tag releases only) (push) Skipped
users.api_token held each user's apiKey in plaintext and was looked up by
equality, so a leaked row or backup handed out working keys. Migration
0063 replaces it with api_token_hash (sha256, hex), computed in place
from the existing keys so every Subsonic client keeps working.

The key can no longer be read back: GET /api/me/api-token is gone, and
POST returns the new key once. Settings shows it right after Regenerate
with a copy button and a "won't be shown again" note.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 09:42:35 -04:00
bvandeusenandClaude Sonnet 4.6 aa73c93f85 feat(server/m7-user-mgmt): self-service /me endpoints (U3)
Four authenticated endpoints for the user's own account:

- PUT /api/me/password — change own password. Caller must
  supply current_password (verified via bcrypt). Distinct from
  admin-driven reset (which doesn't require knowing the old).
  Audits ActionPasswordChangeSelf.

- PUT /api/me/profile — set display_name + email. Both fields
  are nullable; empty string clears, omitted leaves unchanged.
  Email is lowercased before store + format-validated. Unique
  violation → 409 email_taken.

- GET  /api/me/api-token — returns current API token (for
  copy-paste into Subsonic clients).
- POST /api/me/api-token — regenerates token. Old one stops
  working immediately. Audits ActionTokenRegenerate.

All four use the existing RequireUser middleware on the authed
sub-router; audit writes are best-effort (logged on failure).

Tests cover happy paths, wrong-password 401, password-too-short
400, email-invalid 400, email-taken 409, clear-by-empty-string,
omit-leaves-unchanged, token GET + regen.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 12:38:48 -04:00