A disabled account's token still works: the check is at login only #143
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Upstream refuses a disabled account on every request, not only at login.
AuthService.AuthenticatethrowsSecurityException("User account has been disabled.")for any request carrying the token(
Emby.Server.Implementations/HttpServer/Security/AuthService.cs), so therefusal survives the token the way the remote-access and parental-schedule
checks now do.
This port checks
IsDisabledat login only(
crates/jellyfin-app/src/session/auth/mod.rs). #134 closed the other twobecause they live in
DefaultAuthorizationHandler, whichcrates/jellyfin-http/src/auth.rsnow models; this one lives one layer up, inthe authentication service, and so was not picked up by that work.
Mostly masked, and that is the reason it is p3 rather than p2.
session/users.rs::set_policyrevokes an account's tokens when it setsis_disabled, so the dashboard path closes the window itself. What is left is atoken issued before the row was disabled by any other route — a restore, a
direct write, or a future route that disables without revoking.
Where to start
crates/jellyfin-http/src/auth.rs, inAuthenticated::ofrather than inadmitted: it is authentication and not a policy, so it applies to every typein that module's table — including
Anonymous, where a token that arrives isstill resolved, and including the websocket, which authenticates by hand.
The answer shape is a
SecurityException, so it isApiError::Forbidden— the403 text/plainone — and not the empty403a policy writes. That distinctionwas measured while closing #142 and the two variants are already there.
Note the cost: it is one more column off a row
Authenticated::ofdoes not readtoday. The permissions are already read there, and
IsDisabledis one of them,so this may be free.
How to know it is done
A token minted for an account, the account then disabled without the revoke, and
the token replayed: refused with the status and body upstream gives, on a route
under each of the policy types. A route test is enough — the fixture has no
disabled account and creating one is a write.
House rules
Tests run under
cargo nextest; upstream's source is the checkout inupstream/at the pin; comments say what the code does, not how it got there. The shared
reference on
:8096is read-only, and disabling an account is a write.Closed by
9775776.The issue had the answer wrong, and so did the review that raised it: it is
401, not403. Upstream refuses a disabled account inAuthService, whichis authentication, and
CustomAuthenticationHandlerturns theSecurityExceptionintoAuthenticateResult.Fail— a challenge, not a forbid.There is a trap in measuring it. Disabling through
POST /Users/{id}/Policyrevokes the account's tokens, and a revoked token also answers
401— so theobvious measurement proves nothing. Isolated by writing
IsDisabledunder alive token on a private instance over a
cp -acopy of the fixture:/Users/Me,/Items,/System/Info,/Localization/Cultures,/ScheduledTasks,/Environment/DefaultDirectoryBrowser/System/Info/Public/Branding/SplashscreenThat last row is the consequence of it being authentication rather than a
policy: a route that wanted no caller anyway goes on without one instead of
refusing.
Authenticated::ofraises it, soOption<Authenticated>andAnonymousseeNone— which is what an absent principal does upstream.Replayed as a pair against that instance afterwards: 8 of 8 routes agree.