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>
This commit is contained in:
2026-10-06 09:42:35 -04:00
co-authored by Claude Opus 5.5
parent 2f3fbccab6
commit 327d49428f
33 changed files with 161 additions and 179 deletions
@@ -0,0 +1,7 @@
-- The keys cannot be recovered from their hashes. Rolling back gives every
-- user a new random key; Subsonic clients using apiKey need the new one.
ALTER TABLE users ADD COLUMN api_token text;
UPDATE users SET api_token = md5(random()::text || id::text || clock_timestamp()::text);
ALTER TABLE users ALTER COLUMN api_token SET NOT NULL;
ALTER TABLE users ADD CONSTRAINT users_api_token_key UNIQUE (api_token);
ALTER TABLE users DROP COLUMN api_token_hash;
@@ -0,0 +1,10 @@
-- API keys (the OpenSubsonic apiKey) are stored as their sha256, hex, the
-- same way session tokens are. A leaked database row or backup no longer
-- hands out working keys. Existing keys are hashed in place, so every
-- Subsonic client keeps working; the key itself can no longer be shown
-- again, only replaced (M462 #4983).
ALTER TABLE users ADD COLUMN api_token_hash text;
UPDATE users SET api_token_hash = encode(sha256(convert_to(api_token, 'UTF8')), 'hex');
ALTER TABLE users ALTER COLUMN api_token_hash SET NOT NULL;
ALTER TABLE users ADD CONSTRAINT users_api_token_hash_key UNIQUE (api_token_hash);
ALTER TABLE users DROP COLUMN api_token;