navidrome/model/user.go
Kartik Ohri 362a8826b9 Implement OpenSubsonic's API Key authentication
Ref: https://opensubsonic.netlify.app/docs/extensions/apikeyauth/.

Add functionality for users to create, manage, and use API keys for authentication.

*   **Database:**
    *   Added a new `api_key`(`20250415111500_add_api_key_table.go`) table to store API keys associated with users.
*   **Backend:**
    *   Defined the `APIKey` model.
    *   Implemented `APIKeyRepository` for database operations.
    *   Added `FindByAPIKey` method to `UserRepository` to allow user lookup via API key.
    *   Updated Subsonic API error responses to include API key-related errors and messaging.
    *   Integrated API key authentication into the subsonic authentication middleware.
*   **UI:**
    *   Added new React components for API Key CRUD operations: `ApiKeyList`, `ApiKeyCreate`, and `ApiKeyEdit`.
    *   The API Key management section can be accessed from the settings menu.

# Conflicts:
#	model/user.go
#	server/subsonic/middlewares.go
#	server/subsonic/middlewares_test.go
#	tests/mock_data_store.go
#	tests/mock_user_repo.go
2025-08-14 02:18:11 +05:30

62 lines
2.1 KiB
Go

package model
import (
"time"
)
type User struct {
ID string `structs:"id" json:"id"`
UserName string `structs:"user_name" json:"userName"`
Name string `structs:"name" json:"name"`
Email string `structs:"email" json:"email"`
IsAdmin bool `structs:"is_admin" json:"isAdmin"`
LastLoginAt *time.Time `structs:"last_login_at" json:"lastLoginAt"`
LastAccessAt *time.Time `structs:"last_access_at" json:"lastAccessAt"`
CreatedAt time.Time `structs:"created_at" json:"createdAt"`
UpdatedAt time.Time `structs:"updated_at" json:"updatedAt"`
// Library associations (many-to-many relationship)
Libraries Libraries `structs:"-" json:"libraries,omitempty"`
// This is only available on the backend, and it is never sent over the wire
Password string `structs:"-" json:"-"`
// This is used to set or change a password when calling Put. If it is empty, the password is not changed.
// It is received from the UI with the name "password"
NewPassword string `structs:"password,omitempty" json:"password,omitempty"`
// If changing the password, this is also required
CurrentPassword string `structs:"current_password,omitempty" json:"currentPassword,omitempty"`
}
func (u User) HasLibraryAccess(libraryID int) bool {
if u.IsAdmin {
return true // Admin users have access to all libraries
}
for _, lib := range u.Libraries {
if lib.ID == libraryID {
return true
}
}
return false
}
type Users []User
type UserRepository interface {
CountAll(...QueryOptions) (int64, error)
Get(id string) (*User, error)
Put(*User) error
UpdateLastLoginAt(id string) error
UpdateLastAccessAt(id string) error
FindFirstAdmin() (*User, error)
// FindByUsername must be case-insensitive
FindByUsername(username string) (*User, error)
// FindByUsernameWithPassword is the same as above, but also returns the decrypted password
FindByUsernameWithPassword(username string) (*User, error)
// Library association methods
GetUserLibraries(userID string) (Libraries, error)
SetUserLibraries(userID string, libraryIDs []int) error
// FindByAPIKey finds a user by the provided API key
FindByAPIKey(key string) (*User, error)
}