Phase 3: OpenSubsonic core (response envelope, error codes 42-44, getOpenSubsonicExtensions) and apiKeyAuthentication extension with tokenInfo, web API key management

This commit is contained in:
2026-08-23 08:04:01 -05:00
parent 3b4883780b
commit e38d31d384
17 changed files with 763 additions and 37 deletions
+34 -3
View File
@@ -22,9 +22,22 @@ import (
// surface never reintroduces MD5 or plaintext as VibeSonic's own storage
// format, it only speaks it on the wire where the protocol requires it.
func authenticate(db *sql.DB, encryptionKey []byte, p params) (*database.User, *apiError) {
username, aerr := p.Required("u")
if aerr != nil {
return nil, aerr
apiKey := p.Get("apiKey")
username := p.Get("u")
if apiKey != "" {
// The apiKeyAuthentication extension requires a client not send
// 'u' alongside 'apiKey' — accepting both would leave it
// ambiguous which identity the caller actually intended, so this
// is rejected outright rather than silently preferring one.
if username != "" {
return nil, errConflictingAuth
}
return authenticateAPIKey(db, apiKey)
}
if username == "" {
return nil, errMissingParameter("u")
}
user, err := database.GetUserByUsername(db, username)
@@ -75,6 +88,24 @@ func authenticate(db *sql.DB, encryptionKey []byte, p params) (*database.User, *
return user, nil
}
// authenticateAPIKey implements the OpenSubsonic apiKeyAuthentication
// extension: the key is hashed the same way as VibeSonic's own web
// settings API keys (they are exactly the same keys — one system, not a
// parallel one) and looked up by hash, never by scanning stored keys in
// plaintext.
func authenticateAPIKey(db *sql.DB, rawKey string) (*database.User, *apiError) {
key, err := database.GetAPIKeyByHash(db, vsauth.HashAPIKey(rawKey))
if err != nil {
return nil, errInvalidAPIKey
}
user, err := database.GetUserByID(db, key.UserID)
if err != nil || user.Disabled {
return nil, errInvalidAPIKey
}
_ = database.TouchAPIKey(db, key.ID)
return user, nil
}
// checkVersion rejects clients whose declared protocol version is newer
// than what this server implements. A missing or unparsable v is
// tolerated (treated as compatible) rather than rejected outright, to