Commit graph

12 commits

Author SHA1 Message Date
Deluan Quintão
4cdffd5633
feat(subsonic): OpenSubsonic API key authentication (#6219)
* feat(persistence): store hashed API keys on players

* feat(core): refresh key-bound players without renaming them

Add Players.Touch, which records usage for a player already identified by
an API key without guessing its identity or overwriting its name. Register
also stops renaming players that have an API key.

Register no longer returns player save errors (or a stale FindMatch
ErrNotFound when the save is rate-limited); save failures are only logged,
and only the transcoding lookup error is returned, same as Touch.

* feat(subsonic): authenticate with OpenSubsonic API keys

Co-authored-by: amCap1712 <amCap1712@users.noreply.github.com>

* feat(subsonic): add tokenInfo and advertise apiKeyAuthentication

* feat(server): add endpoints to generate and revoke player API keys

* feat(ui): manage player API keys

Co-authored-by: amCap1712 <amCap1712@users.noreply.github.com>

* fix(subsonic): throttle API keys per key and IP

A stale key on one device exhausted the shared per-IP bucket and locked out
every valid key from the same IP. The limiter only stores a hash of the bucket
string, so the key is not retained. Also adds e2e coverage of API key auth
through the real repository, and clarifies the player resolution log message.

* fix(ui): keep the new API key dialog open until closed

The key is shown only once, so Escape and backdrop clicks no longer dismiss
it. Also clarifies when the key can be used as a password.

* refactor: simplify API key code paths

Share the player refresh tail between Register and Touch, fold the
ownership-filtered write tail into execOwned, parse the query once for
apiKey conflicts, derive HasAPIKey in the player mock, share the player
form inputs between create and edit, and pick the delete button by key
state instead of spreading conditional props.

* feat(players): set API keys through the player record

The key is a write-only apiKey field applied on save: required and owner-only on create, optional on edit, empty to revoke. Replaces the generate/revoke endpoints.

* fix(players): reject API keys already in use

Creating or editing a player with a key another player already has now returns a validation error instead of a 500, and a create that loses the race no longer leaves a keyless player behind. Ownership is checked before the key on create.

* feat(ui): edit player API keys as a form field

Replaces the show-once dialog, whose icon-less Close button was invisible on mobile. The key is generated in the browser, required and pre-filled on create.

* fix(ui): keep new player API keys out of the record cache

The json-server create response echoes the request body, and undoable edits merge the payload into the cache, so the key could reappear on the edit page. Strip it from the create result and save player edits pessimistically. Also fall back to a prompt when the clipboard write fails.

* fix(ui): polish player API key field

Set userId on the created player record so owner actions show immediately, and show a neutral no-key message to non-owners.

* refactor: simplify player API key create and field

Write the key hash in the create INSERT so the unique index settles
races, re-read the created player instead of hand-building the cached
record, reuse isWritable for the revoke check, and collapse the key
field's derived state and generate/regenerate buttons.

* fix(ui): let the API key field size like other inputs

fullWidth is now opt-in instead of forced.

* fix(ui): align the API key field with other player inputs

Apply react-admin's input className, move the actions (now including Copy) below the field, and use a monospace font so the whole key fits.

* fix(ui): redirect to the player list after create

Matches the other create pages.

* refactor(persistence): name the write-access rule for owned rows

Owned-row writes now say which row they target and who may write it: ownedRow(rowID, ownerOrAdmin|ownerOnly) builds the WHERE, updateOwnedRow applies it, and SetAPIKey uses ownerOnly instead of a hand-built user_id filter. updateOwned/deleteOwned keep their signatures.

* fix(players): apply an edit's key change and fields atomically

Update now runs SetAPIKey and the column update in one transaction. Also shares the key format check, drops FindByAPIKey's unneeded empty-key guard, and sets the context username only on the apiKey path.

* fix(subsonic): treat any credential param sent with apiKey as a conflict

The spec requires error 43 when u, p, t or s is present with apiKey, even with an empty value.

* refactor(subsonic): leave the player cookie code unchanged for key-bound requests

Return early instead of wrapping the cookie block, so the diff (and CodeQL's view of it) matches master.

* fix(subsonic): don't count key lookup errors as failed logins

A database error while checking a key sent as the password now surfaces as a server error instead of a bad password, so it no longer feeds the failed-login limiter.

* feat(players): use nds_ as the API key prefix

Part of a Navidrome secret prefix family (nd + a letter for the kind), alongside ndg_ for API v1 grants.

* feat(ui): make player API keys easier to find

Label the Settings menu entry "Players & API keys", add an API key
filter to the player list, show the key icon in the mobile list, and
add Brazilian Portuguese translations for the new player strings.

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(ui): always show the player API key filter

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(ui): hide the unset Last Seen date in the player list

Players created by hand have no last_seen yet, which showed as 12/31/1.

Signed-off-by: Deluan <deluan@navidrome.org>

---------

Signed-off-by: Deluan <deluan@navidrome.org>
Co-authored-by: amCap1712 <amCap1712@users.noreply.github.com>
2026-09-27 21:56:58 -04:00
Deluan Quintão
b293b96256
refactor(persistence): stateless repositories with per-call context (#6149)
* refactor(persistence): adopt generic deluan/rest repository API

Pin deluan/rest to the refactor branch. REST-facing repository methods
take a context and return typed values. Drop DataStore.Resource and
ResourceRepository; the native API names typed repositories directly
through a per-request adapter that later commits remove.

* refactor(persistence): base repository helpers take a context

* refactor(persistence): LibraryRepository takes a context per call

* refactor(persistence): PropertyRepository takes a context per call

* refactor(persistence): UserPropsRepository takes a context per call

* refactor(persistence): TranscodingRepository takes a context per call

* refactor(persistence): ShareRepository takes a context per call

* refactor(persistence): PlayerRepository takes a context per call

* refactor(persistence): RadioRepository takes a context per call

* refactor(persistence): PlayQueueRepository takes a context per call

* refactor(persistence): Tag and Genre repositories take a context per call

* refactor(persistence): PluginRepository takes a context per call

* refactor(persistence): Scrobble repositories take a context per call

* refactor(persistence): FolderRepository takes a context per call

* refactor(persistence): Artwork repositories take a context per call

* refactor(persistence): UserRepository takes a context per call

* refactor(persistence): ArtistRepository takes a context per call

ReadAll no longer rewrites the shared sort mappings for the role filter;
it works on a per-call copy.

* test(persistence): assert artist role sort sanitization in ReadAll

* refactor(persistence): AlbumRepository takes a context per call

* test(persistence): pass the test context to album repository helpers

* refactor(persistence): MediaFileRepository takes a context per call

* refactor(persistence): Playlist repositories take a context per call

* refactor(persistence): build all repositories once per store

* refactor(core): REST repository wrappers are built once

* refactor(persistence): repositories are stateless

Remove the context field from the base repository and the per-request
REST adapter. Enable the containedctx linter so no repository can hold a
request context again.

* chore(lint): skip containedctx in test files

* refactor: share simplifications from the stateless repositories sweep

Add deleteOwnedAll on sqlRepository and use it in player/share Delete
to remove the duplicated bulk-delete loop; have Share.Repository()
return model.ShareRepository so subsonic sharing.go drops its repeated
type assertions.

* chore(core): assert REST wrappers implement Persistable

* chore: reformat imports

* perf(persistence): build repositories on first use

Each transaction store used to construct all 21 repositories up front,
paying for filter and sort mapping setup the block never touched. Fields
are now sync.OnceValue thunks, so a store only builds what it uses.

* fix(persistence): clean plugin references per deleted user

A bulk user delete that fails on a later id had already removed the
earlier rows but skipped their plugin cleanup. Cleanup now runs right
after each successful delete.

* fix(core): unload disabled plugins even when a user delete fails

A bulk delete can fail on a later id after earlier users were removed
and their plugins auto-disabled. The wrapper returned before unloading,
leaving those plugins running until the next successful delete or a
restart.

* chore(deps): pin deluan/rest to v1.0.1

Replaces the pseudo-version of the refactor branch with the tagged
release. REST error messages now name the bare type (Artist, not
model.Artist).

* test: use the spec context instead of context.Background()

Replace the context.Background()/context.TODO() calls this branch added
to tests with the spec's ctx, GinkgoT().Context(), or t/b.Context(), so
repository calls are bound to the running spec's lifetime.

* test: declare the spec context once per Describe

Set ctx from GinkgoT().Context() first in each top-level BeforeEach and reuse it, building user contexts on top of it instead of repeating inline calls.
2026-09-25 18:06:10 -04:00
Deluan
1806552ef6 chore: remove more outdated TODOs
Signed-off-by: Deluan <deluan@navidrome.org>
2025-03-23 11:53:43 -04:00
Deluan Quintão
6c11649b06
fix(insights): fix issues and improve reports (#3558)
* fix(insights): show error whn reading library counts

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(insights): wait 30 mins before send first report

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(insights): send number of active players, grouped by client type

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(insights): disable reports when running in dev mode

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(insights): add Dockerfile to the docker build, to avoid `vcs.modified=true`

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(insights): add more linux fs types

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(insights): need admin permissions to retrieve library counts

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(insights): dev flag to disable player insights

Signed-off-by: Deluan <deluan@navidrome.org>

---------

Signed-off-by: Deluan <deluan@navidrome.org>
2024-12-18 20:37:35 -05:00
Kendall Garner
fa85e2a781
Use userId in player, other fixes (#3182)
* [bugfix] player: use userId, other fixes

This PR primarily resolves #1928 by switching the foreign key of `player` from `user.user_name` to `user.id`.
There are also a few other fixes/changes:

- For some bizarre reason, `ip_address` is never returned from `read`/`get`. Change the field to `ip`, which works. Somehow
- Update `players_test.go` mock to also check for user agent, replicating the actual code
- Update `player_repository.go` `isPermitted` to check user id. I don't know how this worked before...
- tests!
- a few places referred to `typ`, when it is really `userAgent`. Change the field names

* baseRequest -> selectPlayer

* remove comment

* update migration, make all of persistence foreign key enabled

* maybe don't forget to save the file first
2024-08-03 13:37:21 -04:00
Deluan Quintão
0ca0d5da22
Replace beego/orm with dbx (#2693)
* Start migration to dbx package

* Fix annotations and bookmarks bindings

* Fix tests

* Fix more tests

* Remove remaining references to beego/orm

* Add PostScanner/PostMapper interfaces

* Fix importing SmartPlaylists

* Renaming

* More renaming

* Fix artist DB mapping

* Fix playlist updates

* Remove bookmarks at the end of the test

* Remove remaining `orm` struct tags

* Fix user timestamps DB access

* Fix smart playlist evaluated_at DB access

* Fix search3
2023-12-09 13:52:17 -05:00
Aleksey Lobanov
457e1fc97b
Base SQL metrics in MetricsWorker (#2002)
* feat: Add metrics worker

* refactor: Add todos for useful for metrics methods

* feat: Run MetricsWorker is Prometheus is Enabled

* refactor: Unused low-level variable was removed in metrics

* feat: No worker for metrics, add more

* refactor: Unnecessary todo removed

* refactor: Remove dead unused constant

* Reduce metrics public interface

Co-authored-by: Deluan <deluan@navidrome.org>
2023-01-24 19:26:07 -05:00
Deluan
c831dc4cdf Use structs lib to map models to DB. Fix #1266 2021-08-01 12:04:45 -04:00
Deluan
abe8015745 Add option to disable external scrobbling per player 2021-06-23 17:55:58 -04:00
Deluan
97434c1789 Fix GetNowPlaying endpoint showing only the last play 2021-06-20 10:39:19 -04:00
Deluan
975579ab26 Add option for player to report real paths in Subsonic API. Closes #625 2020-11-28 10:25:23 -05:00
Deluan
8ec78900c5 feat: transcoding and player datastores and configuration 2020-03-17 15:20:35 -04:00