[WPB-24977] Allow suspended users to keep their cookies.#5204
Conversation
4f5386a to
1137877
Compare
93e07a6 to
771c355
Compare
Cookies can't be used by suspended accounts for refreshing access tokens (see last commit), so this is safe.
There is a way to auto-suspend inactive users after a configurable time span. This change makes sure that expired cookies that would trigger auto-suspend immediately are removed on re-activation.
771c355 to
ad2c0b9
Compare
There was a problem hiding this comment.
Pull request overview
This PR changes brig’s session-cookie handling so that suspending a user/app no longer revokes their existing cookies, while still preventing suspended accounts from refreshing access tokens via POST /access.
Changes:
- Stop revoking all cookies as part of the “suspend account” flow; introduce a refresh-time status check to block suspended users from minting new access tokens.
- Add a new AuthenticationSubsystem operation intended to clean up stale cookies (expired and/or too old per
suspendInactiveUsers) when account status changes. - Add an integration test covering app token refresh behavior across suspend/unsuspend, and wire the new config field through test/canonical interpreters.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| services/brig/test/integration/API/User.hs | Extends test auth config with suspendInactiveUsers = Nothing. |
| services/brig/src/Brig/User/Auth/Cookie.hs | Minor formatting in inactivity-suspension logic. |
| services/brig/src/Brig/User/Auth.hs | Blocks access-token refresh for suspended users without revoking cookies; removes now-unused Concurrency constraints. |
| services/brig/src/Brig/Team/API.hs | Qualifies AuthenticationSubsystem import to avoid name clashes and updates constraints accordingly. |
| services/brig/src/Brig/CanonicalInterpreter.hs | Plumbs suspendInactiveUsers into AuthenticationSubsystemConfig. |
| services/brig/src/Brig/API/User.hs | Updates account-status change flow to stop revoking all cookies and instead call the new “expired/stale cookie cleanup” operation. |
| services/brig/src/Brig/API/Internal.hs | Removes now-unused Concurrency constraints from handlers. |
| services/brig/src/Brig/API/Auth.hs | Removes now-unused Concurrency constraints from handlers. |
| libs/wire-subsystems/test/unit/Wire/MiniBackend.hs | Updates default auth config with suspendInactiveUsers = Nothing. |
| libs/wire-subsystems/src/Wire/AuthenticationSubsystem/Interpreter.hs | Interprets the new RevokeAllExpiredCookies effect action. |
| libs/wire-subsystems/src/Wire/AuthenticationSubsystem/Cookie.hs | Implements the new cookie cleanup operation (currently has a logic bug vs doc/intent). |
| libs/wire-subsystems/src/Wire/AuthenticationSubsystem/Config.hs | Adds suspendInactiveUsers :: Maybe Timeout to the auth subsystem config. |
| libs/wire-subsystems/src/Wire/AuthenticationSubsystem.hs | Adds the new RevokeAllExpiredCookies effect constructor. |
| integration/test/Test/Apps.hs | Adds an integration test validating refresh behavior across suspend/unsuspend for apps. |
| changelog.d/2-features/WPB-24977-allow-suspended-apps-to-keep-their-cookies | Changelog entry for the behavior change. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
6d9f16f to
6247492
Compare
| @@ -0,0 +1 @@ | |||
| Allow suspended users to keep their cookies. (This has become necessary to allow apps to keep their cookies during (sufficiently brief) suspend periods. It works because instead, there is a new guard now preventing session token refresh for suspended users with valid cookies.) | |||
There was a problem hiding this comment.
| Allow suspended users to keep their cookies. (This has become necessary to allow apps to keep their cookies during (sufficiently brief) suspend periods. It works because instead, there is a new guard now preventing session token refresh for suspended users with valid cookies.) | |
| Allow suspended users to keep their cookies, but disallow them to create/refresh access tokens. |
IMO this is simpler to understand for people who don't work on the codebase. People who work here can read the JIRA.
| testZauthAndApps :: (HasCallStack) => App () | ||
| testZauthAndApps = do | ||
| (owner, tid, []) <- createTeam OwnDomain 1 | ||
| (app, cookie) <- createIt owner tid |
There was a problem hiding this comment.
"createIt" is a really vague name, can we instead just inline this function?
| refreshSucceeds app cookie | ||
| suspendApp app >> refreshFails app cookie | ||
| unsuspendApp app >> refreshSucceeds app cookie |
There was a problem hiding this comment.
This seems extremely cryptic. Can we please just inline these function?
| refreshFails :: (HasCallStack, MakesValue app) => app -> String -> App () | ||
| refreshFails app cookie = | ||
| renewToken app cookie `bindResponse` \resp -> do | ||
| resp.status `shouldMatchInt` 403 |
There was a problem hiding this comment.
We should always assert on labels when we expect failures.
| -- (2) cookie creation time is further in the past than | ||
| -- `env.suspendInactiveUsers` allows. |
There was a problem hiding this comment.
How is suspendInactiveUsers related to cookie being stale?
Also does this have impact on existing users? If so, that must be part of changelog and we should clear it by product.
| -- It is safe to *not* revoke any cookies here; if no valid access | ||
| -- token is available, cookies are only validated when calling `POST | ||
| -- /access`, and access token refresh only works on unsuspended | ||
| -- users. | ||
| -- | ||
| -- Evidence: `git grep -Hn --color=never 'UserToken\b' | grep libs/wire-api/src/Wire/API/Routes/Public/`. | ||
| -- | ||
| -- Having that said, we need to remove cookies here that are no | ||
| -- longer valid for login/inactivity handling (including both | ||
| -- expired cookies and cookies older than the inactivity threshold), | ||
| -- otherwise /login considers the user inactive, see | ||
| -- 'mustSuspendInactiveUser'. | ||
| -- | ||
| -- The intuition is that every change of account status can be | ||
| -- considered an account activity, so users that have their status | ||
| -- changed recently should not be considered inactive, even if they | ||
| -- haven't taken any action themselves. |
There was a problem hiding this comment.
I'm very confused by this reasoning. So far any user with only old cookies was considered inactive and hence suspended. You're saying if we suspend a user they've become active. How have they become active?
| -- NB: no need to call `catchSuspendedUsers` here. `newAccess` is | ||
| -- called in 3 places (login, ssoLogin, legalHoldLogin), and all of | ||
| -- them reject suspended users before calling it. |
There was a problem hiding this comment.
This sounds like a reason to move the call here, instead of making it some hope based contract that every caller should call that function first.
Before this PR, users lose all their cookies when suspended. Problem: Apps can't login, they get their cookies through an awkward manual process in team-management and there currently is no way to get a fresh cookie for an already-registered app, so this is bad.
With this PR, suspended users keep their cookies, and instead we guard getting new session tokens with an account status check.
related ticket
Checklist
changelog.d