2026-09-26 01:50:16 -04:00
grants :
get :
operationId : listGrants
x-module : core
x-scope : read
x-stability-level : alpha
tags : [ auth]
summary : List my grants
description : "The caller's grants, most recently used first. Grants idle long enough to have expired are not listed."
security : [ {bearerAuth : [ ] }]
parameters :
- $ref : ../components/parameters/offset.yaml
- $ref : ../components/parameters/limit.yaml
responses :
'200' :
description : A page of grants.
content :
application/json :
schema :
$ref : ../components/schemas/GrantList.yaml
'400' :
$ref : ../components/responses/BadRequest.yaml
'401' :
$ref : ../components/responses/Unauthorized.yaml
'403' :
$ref : ../components/responses/Forbidden.yaml
'500' :
$ref : ../components/responses/InternalError.yaml
grant :
delete :
operationId : revokeGrant
x-module : core
x-scope : read
x-stability-level : alpha
tags : [ auth]
summary : Revoke one of my grants
refactor(api): use the grant secret as the API v1 bearer credential
API v1 no longer mints short-lived JWT access tokens. Clients send the grant
secret from POST /auth/login or /auth/setup as `Authorization: Bearer` on
every request.
Every request already looked the grant up in the database, so the JWT gave
no speed or revocation benefit and only added a refresh loop, which early
client authors pushed back on. The grant already is an API key: one per
client sign-in, scoped and revocable. Revocation is now immediate on every
node; the contract promises "within one minute".
Removed: POST /auth/token, the grantAuth scheme, the TokenRequest and
AccessToken schemas, the token_expired problem code, the API v1 JWT signer
and its signing key, the grant liveness cache, and PropertyRepository.PutIfAbsent.
ResolveGrant is now Authenticate.
Short-lived tokens return later only as narrow media tokens for
?access_token= on media URLs, together with the media endpoints.
Signed-off-by: Deluan <deluan@navidrome.org>
2026-09-28 21:05:57 -04:00
description : "Revokes the grant; requests with its secret fail from then on. Another user's grant id answers 404."
2026-09-26 01:50:16 -04:00
security : [ {bearerAuth : [ ] }]
parameters :
- name : id
in : path
required : true
description : Grant id.
schema :
type : string
maxLength : 64
responses :
'204' :
description : Revoked.
'400' :
$ref : ../components/responses/BadRequest.yaml
'401' :
$ref : ../components/responses/Unauthorized.yaml
'403' :
$ref : ../components/responses/Forbidden.yaml
'404' :
$ref : ../components/responses/NotFound.yaml
'500' :
$ref : ../components/responses/InternalError.yaml
logout :
post :
operationId : logout
x-module : core
x-scope : read
x-stability-level : alpha
tags : [ auth]
summary : Log out
description : Revokes the grant that made this request.
security : [ {bearerAuth : [ ] }]
responses :
'200' :
description : Logged out.
content :
application/json :
schema :
$ref : ../components/schemas/LogoutResponse.yaml
'401' :
$ref : ../components/responses/Unauthorized.yaml
'403' :
$ref : ../components/responses/Forbidden.yaml
'500' :
$ref : ../components/responses/InternalError.yaml
login :
post :
operationId : login
x-module : password
x-stability-level : alpha
tags : [ auth]
summary : Log in with a password
description : Checks the username and password and returns a new grant. Unknown user and wrong password fail the same way.
security : [ ]
requestBody :
description : The credentials and a description of the client.
required : true
content :
application/json :
schema :
$ref : ../components/schemas/CredentialsRequest.yaml
responses :
'200' :
description : The new grant.
fix(api): consolidate grant deletes and harden API v1 auth after review
- GrantRepository keeps three deletes: DeleteForUser, DeleteStaleEpochs
(replaces DeleteOtherEpochs and DeleteIfEpoch) and DeleteIdle. Delete(id)
is gone; the idle path in ResolveGrant now calls DeleteIdle, so a grant
renewed by another node between the read and the delete survives.
settleEpoch deletes the user's grants below the snapshot's epoch, which
is safe outside the transaction because epochs only move forward.
- The "dead grants on an older epoch are only deleted when presented"
policy note moves from the repository to core ListGrants.
- SQL trace logging no longer prints the args of property writes (signing
keys) or user password writes, both encrypted with a key that may be the
public default. The SQL statement is still logged.
- The spec gate rejects JSON body keys that differ from a declared property
only in case. kin-openapi validates exact names while encoding/json
decodes case-insensitively, so {"scopes":[],"Scopes":null} minted a
token with every scope and a "Client" key skipped maxLength.
It also rejects data after the first JSON value, which the handlers'
decoder ignores and which let a body skip the alias check.
- The liveness cache trims its eviction log on evict, not only on put, so
evict-only traffic stays bounded; the floor still drops stale fills.
- createAccessToken, login and setupFirstAdmin declare Cache-Control:
no-store on their success responses.
2026-09-26 12:52:20 -04:00
headers :
Cache-Control :
$ref : ../components/headers/CacheControlNoStore.yaml
2026-09-26 01:50:16 -04:00
content :
application/json :
schema :
$ref : ../components/schemas/GrantCreated.yaml
'400' :
$ref : ../components/responses/BadRequest.yaml
'401' :
$ref : ../components/responses/Unauthorized.yaml
'413' :
$ref : ../components/responses/PayloadTooLarge.yaml
'429' :
$ref : ../components/responses/TooManyRequests.yaml
'500' :
$ref : ../components/responses/InternalError.yaml
setup :
post :
operationId : setupFirstAdmin
x-module : password
x-stability-level : alpha
tags : [ auth]
summary : Create the first admin
description : "Creates the first administrator while `setupRequired` is true and returns a grant for it. Answers 409 `setup_complete` once any user exists. A server with no setup step always answers 409."
security : [ ]
requestBody :
description : The credentials and a description of the client.
required : true
content :
application/json :
schema :
$ref : ../components/schemas/CredentialsRequest.yaml
responses :
'201' :
description : The admin was created.
fix(api): consolidate grant deletes and harden API v1 auth after review
- GrantRepository keeps three deletes: DeleteForUser, DeleteStaleEpochs
(replaces DeleteOtherEpochs and DeleteIfEpoch) and DeleteIdle. Delete(id)
is gone; the idle path in ResolveGrant now calls DeleteIdle, so a grant
renewed by another node between the read and the delete survives.
settleEpoch deletes the user's grants below the snapshot's epoch, which
is safe outside the transaction because epochs only move forward.
- The "dead grants on an older epoch are only deleted when presented"
policy note moves from the repository to core ListGrants.
- SQL trace logging no longer prints the args of property writes (signing
keys) or user password writes, both encrypted with a key that may be the
public default. The SQL statement is still logged.
- The spec gate rejects JSON body keys that differ from a declared property
only in case. kin-openapi validates exact names while encoding/json
decodes case-insensitively, so {"scopes":[],"Scopes":null} minted a
token with every scope and a "Client" key skipped maxLength.
It also rejects data after the first JSON value, which the handlers'
decoder ignores and which let a body skip the alias check.
- The liveness cache trims its eviction log on evict, not only on put, so
evict-only traffic stays bounded; the floor still drops stale fills.
- createAccessToken, login and setupFirstAdmin declare Cache-Control:
no-store on their success responses.
2026-09-26 12:52:20 -04:00
headers :
Cache-Control :
$ref : ../components/headers/CacheControlNoStore.yaml
2026-09-26 01:50:16 -04:00
content :
application/json :
schema :
$ref : ../components/schemas/GrantCreated.yaml
'400' :
$ref : ../components/responses/BadRequest.yaml
'409' :
$ref : ../components/responses/Conflict.yaml
'413' :
$ref : ../components/responses/PayloadTooLarge.yaml
'429' :
$ref : ../components/responses/TooManyRequests.yaml
'500' :
$ref : ../components/responses/InternalError.yaml
password :
post :
operationId : changePassword
x-module : password
x-scope : password
x-stability-level : alpha
tags : [ auth]
summary : Change my password
2026-09-26 02:21:25 -04:00
description : "Changes the caller's password. By default every other grant of the user is revoked; the calling grant survives. On Navidrome the change also ends the user's sessions on its other APIs, regardless of `revokeOtherGrants`, which only covers API v1 grants. Answers 409 `password_managed_externally` when the password is not stored by this server."
2026-09-26 01:50:16 -04:00
security : [ {bearerAuth : [ ] }]
requestBody :
description : The current and the new password.
required : true
content :
application/json :
schema :
$ref : ../components/schemas/PasswordChangeRequest.yaml
responses :
'204' :
description : Password changed.
'400' :
$ref : ../components/responses/BadRequest.yaml
'401' :
$ref : ../components/responses/Unauthorized.yaml
'403' :
$ref : ../components/responses/Forbidden.yaml
'409' :
$ref : ../components/responses/Conflict.yaml
'413' :
$ref : ../components/responses/PayloadTooLarge.yaml
'429' :
$ref : ../components/responses/TooManyRequests.yaml
'500' :
$ref : ../components/responses/InternalError.yaml