package subsonic import ( "encoding/xml" "net/http" "git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq" ) // UserResponse wraps the User payload Subsonic clients fetch right after // login to discover what the authenticated identity can do. Feishin uses // this to gate UI affordances; without it the login flow stalls. type UserResponse struct { Envelope User User `json:"user" xml:"user"` } // User mirrors the role-bag from the Subsonic spec. We expose the full set // even though Minstrel doesn't model fine-grained ACLs yet — admins get // every role, non-admins get the "play music" subset. This keeps clients // from disabling features they could otherwise use. type User struct { XMLName xml.Name `json:"-" xml:"user"` Username string `json:"username" xml:"username,attr"` Email string `json:"email,omitempty" xml:"email,attr,omitempty"` ScrobblingEnabled bool `json:"scrobblingEnabled" xml:"scrobblingEnabled,attr"` AdminRole bool `json:"adminRole" xml:"adminRole,attr"` SettingsRole bool `json:"settingsRole" xml:"settingsRole,attr"` DownloadRole bool `json:"downloadRole" xml:"downloadRole,attr"` UploadRole bool `json:"uploadRole" xml:"uploadRole,attr"` PlaylistRole bool `json:"playlistRole" xml:"playlistRole,attr"` CoverArtRole bool `json:"coverArtRole" xml:"coverArtRole,attr"` CommentRole bool `json:"commentRole" xml:"commentRole,attr"` PodcastRole bool `json:"podcastRole" xml:"podcastRole,attr"` StreamRole bool `json:"streamRole" xml:"streamRole,attr"` JukeboxRole bool `json:"jukeboxRole" xml:"jukeboxRole,attr"` ShareRole bool `json:"shareRole" xml:"shareRole,attr"` VideoConversionRole bool `json:"videoConversionRole" xml:"videoConversionRole,attr"` } // handleGetUser returns the authenticated user when no username is provided // or when the requested username matches; admins may look up any user. Per // spec a non-admin asking about another user gets ErrNotAuthorized. func handleGetUser(w http.ResponseWriter, r *http.Request) { caller, ok := UserFromContext(r.Context()) if !ok { WriteFail(w, r, ErrNotAuthorized, "Not authenticated") return } requested := r.URL.Query().Get("username") if requested != "" && requested != caller.Username && !caller.IsAdmin { WriteFail(w, r, ErrNotAuthorized, "Not authorized to view other users") return } // We don't have a per-user lookup wired up yet; until then admins asking // for another username see the caller's roles. Acceptable while M1 has a // single user; revisit once user management lands. Write(w, r, UserResponse{ Envelope: NewEnvelope("ok"), User: userPayload(caller), }) } func userPayload(u dbq.User) User { return User{ Username: u.Username, ScrobblingEnabled: true, AdminRole: u.IsAdmin, SettingsRole: u.IsAdmin, DownloadRole: true, UploadRole: false, PlaylistRole: true, CoverArtRole: true, CommentRole: false, PodcastRole: false, StreamRole: true, JukeboxRole: false, ShareRole: false, } }