Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 38 additions & 7 deletions docs/api-v2-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,17 @@ curl https://music.example.com/api/v2/artists/ARTIST_UUID \
-H "Authorization: Bearer ACCESS_TOKEN"
```

`GET /api/v2/songs` takes a required `genre` and pages through it; `GET
/api/v2/songs/random` draws a selection in SQL, with optional `genre`,
`from_year` and `to_year`. Both match the genre on its canonical name like
every other genre filter, and both are the native form of a Subsonic method
(`getSongsByGenre`, `getRandomSongs`) resolving through the same service.

`GET /api/v2/search` applies `offset` to all three kinds, and accepts
`artist_offset`, `album_offset` and `song_offset` to page one of them on its
own — which is what a client that has exhausted the artists but not the songs
needs, and what `search3` has always allowed.

Browse and search pages accept `offset >= 0` and `1 <= limit <= 500`.
`GET /api/v2/albums` and `/artists` additionally accept an optional
`library_id`. A `SongItem` contains stable `id`, optional `album_id` and
Expand Down Expand Up @@ -508,13 +519,33 @@ SHA-256 hash is stored, so the secret appears there and never again: the
listing returns names, scopes and timestamps, and a caller who loses a token
issues another rather than reading it back.

**Scopes are enforced.** A token issued with a non-empty `scopes` list is
restricted to it, whatever the account behind it may do: the administrative
routes require the `admin` scope, so a `catalog:read` token belonging to an
administrator is refused with `403`. A token issued **without** scopes is
unrestricted and carries the account's full authority, which is what the CLI
has always produced and what tokens created before this release hold. Sessions
and Authorization Code grants are likewise unrestricted. `DELETE` revokes one; the token
**Scopes are enforced on every route.** A token issued with a non-empty
`scopes` list is restricted to it, whatever the account behind it may do. Two
scopes are checked:

| Scope | Admits |
|---|---|
| `write` | any mutation: playlists, favorites, ratings, the queue, bookmarks, shares, scrobbles, scans, issuing an OAuth code |
| `admin` | the administrative routes, and everything `write` admits |

Reading needs no scope, so a token naming neither is read-only. **A scope this
server does not know grants nothing**, which is why `catalog:read` reads and
does no more: there is no vocabulary to learn, only these two names to use.

A token issued **without** scopes is unrestricted and carries the account's full
authority. That is what the CLI has always produced, what tokens created before
this release hold, and what sessions and Authorization Code grants carry, so
nothing that works today stops working.

The check happens where the caller is resolved, not in each handler, so a route
cannot be added without choosing what it needs — the compiler asks. This
matters because the previous release stored scopes, returned them from the API
and printed them from the CLI while reading them nowhere.

Issuing a token is administrative: an account cannot mint one for itself. A
token carries the authority of the account it belongs to, so who may create one
is a question about the instance rather than about the account, and the answer
is the same from the CLI and from the API. `DELETE` revokes one; the token
stops authenticating immediately, and revoking it again answers `404`,
because it is already not working.

Expand Down
2 changes: 2 additions & 0 deletions docs/rfcs/RFC-002-waveflow-server-v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ Genre matching is one rule across every method. `getGenres` groups by `genre.can

`getArtistInfo` and `getArtistInfo2` resolve the requested artist through tenant-scoped catalogue access and return their standard empty containers until artist biography enrichment is implemented. This preserves compatibility with clients such as DSub without fabricating biography or similar-artist metadata. `getAlbumInfo` and `getAlbumInfo2` behave the same way for albums, for the same reason: Feishin and Symfonium call them as soon as an album page opens, and an unimplemented-method error there reads to the client as a broken album rather than as absent enrichment. They carry one real value, the album's release identifier, as their `musicBrainzId` element; notes and biography images stay absent because WaveFlow queries no remote source. `AlbumInfo` predates the presence rule and its members are elements rather than attributes, so an album with no release id omits the element instead of sending it empty.

API tokens are restricted by their scopes on every route, not only on the administrative ones. `Access` names what a route needs — `Read`, `Write` or `Admin` — and is chosen at the single point where the caller is resolved, so a route cannot exist without answering the question. An empty scope list is unrestricted, which is what sessions, OAuth grants and tokens issued without scopes carry; a non-empty list grants only what it names, and a name the server does not know grants nothing, so no vocabulary has to be enumerated. `admin` implies `write`, because a credential trusted to create accounts is not usefully barred from creating a playlist. Issuing a token stays administrative on both surfaces: a token carries the authority of the account it belongs to, so who may mint one is a question about the instance.

`playlist.owner` and `share.username` carry the authenticated username. Both collections are read scoped to their owner, so no other name is reachable; the empty string previously emitted made Feishin treat every playlist as another account's and refuse to edit it.

Mutation methods whose Subsonic result is empty (`updatePlaylist`, `deletePlaylist`, stars, ratings, scrobbles, queue save, share deletion and user-management writes) return only the successful protocol envelope. They do not add implementation-specific child elements.
Expand Down
Loading
Loading