fix(auth): set subsonic_password on admin bootstrap
Bootstrap was creating password_hash + api_token but leaving subsonic_password nil, which meant Subsonic clients (Feishin, Symfonium) got ErrTokenNotSupported on t+s auth — the server had no plaintext to hash against. Mirror the bootstrap password into subsonic_password so the admin can sign in to Subsonic clients with the same credential printed on first boot. Plaintext at rest is the cost of Subsonic's legacy auth; matches Navidrome's posture. Also folds in the local dev compose tweaks: dedicated bridge network with postgres unpublished from the host, and a bind-mount aimed at the operator's real library path. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+10
-3
@@ -16,8 +16,10 @@ services:
|
||||
POSTGRES_USER: minstrel
|
||||
POSTGRES_PASSWORD: minstrel
|
||||
POSTGRES_DB: minstrel
|
||||
ports:
|
||||
- "5432:5432"
|
||||
networks:
|
||||
- minstrel
|
||||
# ports:
|
||||
# - "5432:5432"
|
||||
volumes:
|
||||
- minstrel-pgdata:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
@@ -40,12 +42,17 @@ services:
|
||||
# SMARTMUSIC_AUTH_ADMIN_PASSWORD left unset on purpose — bootstrap
|
||||
# generates one and prints it to stderr on first boot. Watch the logs
|
||||
# of the minstrel service the first time you bring the stack up.
|
||||
networks:
|
||||
- minstrel
|
||||
ports:
|
||||
- "4533:4533"
|
||||
volumes:
|
||||
# Point ./music at your test library (symlink, bind-mount, whatever).
|
||||
# Read-only so a buggy scanner can't rewrite your files.
|
||||
- ./music:/music:ro
|
||||
- /mnt/Media/Music:/music:ro
|
||||
|
||||
volumes:
|
||||
minstrel-pgdata:
|
||||
|
||||
networks:
|
||||
minstrel:
|
||||
@@ -64,12 +64,26 @@ func Bootstrap(ctx context.Context, pool *pgxpool.Pool, cfg config.AdminBootstra
|
||||
return fmt.Errorf("auth: insert admin: %w", err)
|
||||
}
|
||||
|
||||
// Mirror the bootstrap password into subsonic_password so Subsonic clients
|
||||
// (Feishin, Symfonium, …) can authenticate via t+s without the operator
|
||||
// having to run a separate SQL update after first boot. This is plaintext
|
||||
// at rest — the cost of Subsonic's legacy auth — and matches what servers
|
||||
// like Navidrome do. Operators who don't want a plaintext copy can clear
|
||||
// it later with SetSubsonicPassword(nil).
|
||||
subPass := password
|
||||
if err := q.SetSubsonicPassword(ctx, dbq.SetSubsonicPasswordParams{
|
||||
ID: user.ID,
|
||||
SubsonicPassword: &subPass,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("auth: set subsonic password: %w", err)
|
||||
}
|
||||
|
||||
logger.Info("admin bootstrapped", "username", cfg.Username, "user_id", user.ID.String())
|
||||
if passwordGenerated {
|
||||
fmt.Fprintf(os.Stderr, "\n--- minstrel admin bootstrap (shown once) ---\nusername: %s\npassword: %s\napi_token: %s\n---\n\n",
|
||||
fmt.Fprintf(os.Stderr, "\n--- minstrel admin bootstrap (shown once) ---\nusername: %s\npassword: %s\napi_token: %s\n(password works for both the native UI and Subsonic clients)\n---\n\n",
|
||||
cfg.Username, password, apiToken)
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "\n--- minstrel admin api token (shown once) ---\nusername: %s\napi_token: %s\n---\n\n",
|
||||
fmt.Fprintf(os.Stderr, "\n--- minstrel admin api token (shown once) ---\nusername: %s\napi_token: %s\n(your configured password now also works for Subsonic clients)\n---\n\n",
|
||||
cfg.Username, apiToken)
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -63,6 +63,9 @@ func TestBootstrap_Integration(t *testing.T) {
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte("hunter2")); err != nil {
|
||||
t.Errorf("bcrypt compare: %v", err)
|
||||
}
|
||||
if user.SubsonicPassword == nil || *user.SubsonicPassword != "hunter2" {
|
||||
t.Errorf("subsonic_password = %v, want \"hunter2\"", user.SubsonicPassword)
|
||||
}
|
||||
|
||||
byToken, err := q.GetUserByAPIToken(ctx, user.ApiToken)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user