Commit Graph
4 Commits
Author SHA1 Message Date
bvandeusenandClaude Opus 5.5 a8a2d20307 feat: stored credentials are sealed at rest, so a copy of the database alone reveals none of them (#6767)
release / govulncheck (push) Successful in 28s
release / web (push) Successful in 1m18s
release / go (push) Successful in 1m35s
release / android (push) Successful in 4m45s
release / Build signed APK (push) Successful in 4m57s
release / integration (push) Successful in 5m3s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 1m14s
release / Verify release artifacts (tag releases only) (push) Skipped
Minstrel keeps credentials it has to read back: indexer, Prowlarr and
download-client keys and passwords, the AcoustID key, cover-art and tag
provider keys, the SMTP password, and each user's ListenBrainz token and
Subsonic password. They were stored in the clear; they are now sealed with
AES-256-GCM under a per-column HKDF key derived from the stream secret, each
value bound to its row.

- internal/secret: Seal/Open/Reveal, and Columns, the one list of sealed
  columns (a backup will leave them out). Install takes the master key at
  startup; SealStored seals any value still in the clear, so an existing
  database is brought over in place on the first boot.
- Every write seals and every read opens: acquire (one read path, getService
  / usableServices), acoustid settings, SMTP, ListenBrainz, Subsonic auth and
  password generation, cover-art and tag provider settings.
- A value that does not open (the stream secret changed) is logged and reads
  as unset, to be entered again in Settings. docs/security.md says so.
- Tests: secret unit tests (wrong owner/purpose/key, malformed, no key), a
  SealStored integration test over all eight columns plus a schema check that
  each listed column exists, and the existing tests now assert sealed storage.
  dbtest installs a test key for integration tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 13:23:19 -05:00
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 Opus 4.7 937a1930ad test: add dbtest.ResetDB helper that preserves admin user
Integration tests previously TRUNCATEd the users table at setup,
which wiped the operator's admin login every time the suite ran
against a Postgres instance shared with their dev environment.

This commit:

- Adds internal/dbtest/reset.go exposing ResetDB(t, pool) and
  TestUserPrefix. ResetDB truncates every data table EXCEPT users,
  then deletes only users whose username starts with TestUserPrefix.
- Migrates 9 test files (subsonic/scrobble, subsonic/star,
  recommendation/candidates, scrobble/queue, similarity/worker,
  playevents/writer, playsessions/service, api/auth, api/me) to
  call ResetDB instead of issuing TRUNCATE-with-users.
- Renames hardcoded test usernames to use TestUserPrefix so ResetDB
  cleans them between runs. seedUser in api/auth_test now prefixes
  internally; existing call sites pass bare names.
- Leaves auth/bootstrap_test.go alone — it legitimately tests
  first-time admin bootstrap and must wipe users.

Verified locally: full integration suite with -p 1 runs cleanly
under the existing MINSTREL_TEST_DATABASE_URL setup, and the admin
row in the shared dev DB survives the run.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-29 11:55:06 -04:00
bvandeusenandClaude Sonnet 4.6 dba1deaad1 feat(scrobble): add MaybeEnqueue with idempotent insert + threshold gate
Bridges closed play_events to the scrobble_queue: loads the event,
applies the Qualifies() threshold, checks user LB config (enabled + token),
and inserts via ON CONFLICT DO NOTHING for idempotency. Best-effort —
callers (playevents.Writer hooks, Task 8) will log and swallow errors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 09:13:11 -04:00