fix(sessions): keep the core session for native logout on /qqq/v1/logout #31

Open
james.maes wants to merge 2 commits from fix/logout-keep-core-session-for-native-logout into develop
pull from: fix/logout-keep-core-session-for-native-logout
Owner

Problem

Confirmed on a physical MC9400 with the production 1.0.2 APK against a real Authentik fixture: company sign-out (RP-initiated provider logout) never received nativeLogoutData from POST /qqq/v1/logout {"nativeLogout":{"postLogoutRedirectUri":...}} while the device had an active mobile session. A plain HTTP client without a mobile session binding received the full logout data, so the core path itself was fine.

Cause: MobileScannersRouteProvider.bindDevice special-cases /qqq/v1/logout and called MobileSessionService.recordLogout() before the core handler. That ran end(..., "SIGN_OUT", false) -> cleanupAuthentication(...), which executed the core LogoutExecutor (without the native callback) and QSessionStoreHelper.removeSession(coreUuid). When the core LogoutSpecV1.buildInput then ran, ExecutorSessionUtils.setupSession failed (session already gone), nativePostLogoutRedirectUri was never set, and no nativeLogoutData was returned.

Change

  • MobileSessionService.recordLogout() now ends the mobile session with a new private overload end(id, reason, administrator, coreLogoutFollows). With coreLogoutFollows, the row is tombstoned as before (ENDED, endReason SIGN_OUT, screens removed, screenAvailable/foreground false) and authCleanupStatus is set to DELEGATED (with authCleanupAt) instead of running cleanupAuthentication. The core /qqq/v1/logout handler that follows in the same request prepares native logout data from the still-valid core session and then performs the provider logout and session removal.
  • endCurrent (/sessions/current/end), administrator ends and the bulk End Session process are unchanged: they still run cleanupAuthentication (REQUESTED / RETRY_NEEDED).
  • The /qqq/v1/logout pre-handler keeps its tolerant try/catch: an unbound or invalid session still reaches the core handler.
  • Docs: docs/OPERATIONS.md (cleanup statuses now list DELEGATED) and one entry in docs/SESSION-STATE.md. No OpenAPI contract change; the extension version is not bumped.

Tests (base module)

Three focused tests in MobileScannersTest drive the real route chain in an in-process Javalin server (scanner before("/qqq/*") pre-handler, then the core LogoutSpecV1), using a recording authentication module (QAuthenticationType.CUSTOM extending the mock module) and a recording core session store:

  1. coreLogoutWithNativeLogoutKeepsCoreSessionUntilCoreHandlerRuns: with nativeLogout in the body, the core session is still present and nothing has been removed or logged out when the core handler starts; afterwards the provider logout ran exactly once, the store is empty, and the mobile row is ENDED / SIGN_OUT / DELEGATED.
  2. endCurrentStillPerformsCoreLogout: /sessions/current/end still runs the core logout itself (REQUESTED).
  3. plainCoreLogoutEndsMobileRowAndCoreSessionOnce: a body-less logout ends both the mobile row and the core session exactly once, and a second logout with an unbound cookie still reaches the core handler.

The harness cannot configure a real OAuth2 provider with native logout, so the tests assert the precondition the core handler needs (core session intact when it starts) rather than the nativeLogoutData payload itself; a device rerun against Authentik with a build containing this fix is still needed.

mvnw -o -pl base verify: 59 tests, 0 failures/errors/skips, 0 Checkstyle violations.

🤖 Generated with Claude Code

## Problem Confirmed on a physical MC9400 with the production 1.0.2 APK against a real Authentik fixture: company sign-out (RP-initiated provider logout) never received `nativeLogoutData` from `POST /qqq/v1/logout {"nativeLogout":{"postLogoutRedirectUri":...}}` while the device had an active mobile session. A plain HTTP client without a mobile session binding received the full logout data, so the core path itself was fine. Cause: `MobileScannersRouteProvider.bindDevice` special-cases `/qqq/v1/logout` and called `MobileSessionService.recordLogout()` before the core handler. That ran `end(..., "SIGN_OUT", false)` -> `cleanupAuthentication(...)`, which executed the core `LogoutExecutor` (without the native callback) and `QSessionStoreHelper.removeSession(coreUuid)`. When the core `LogoutSpecV1.buildInput` then ran, `ExecutorSessionUtils.setupSession` failed (session already gone), `nativePostLogoutRedirectUri` was never set, and no `nativeLogoutData` was returned. ## Change - `MobileSessionService.recordLogout()` now ends the mobile session with a new private overload `end(id, reason, administrator, coreLogoutFollows)`. With `coreLogoutFollows`, the row is tombstoned as before (`ENDED`, `endReason SIGN_OUT`, screens removed, `screenAvailable`/`foreground` false) and `authCleanupStatus` is set to **`DELEGATED`** (with `authCleanupAt`) instead of running `cleanupAuthentication`. The core `/qqq/v1/logout` handler that follows in the same request prepares native logout data from the still-valid core session and then performs the provider logout and session removal. - `endCurrent` (`/sessions/current/end`), administrator ends and the bulk End Session process are unchanged: they still run `cleanupAuthentication` (`REQUESTED` / `RETRY_NEEDED`). - The `/qqq/v1/logout` pre-handler keeps its tolerant try/catch: an unbound or invalid session still reaches the core handler. - Docs: `docs/OPERATIONS.md` (cleanup statuses now list `DELEGATED`) and one entry in `docs/SESSION-STATE.md`. No OpenAPI contract change; the extension version is not bumped. ## Tests (base module) Three focused tests in `MobileScannersTest` drive the real route chain in an in-process Javalin server (scanner `before("/qqq/*")` pre-handler, then the core `LogoutSpecV1`), using a recording authentication module (`QAuthenticationType.CUSTOM` extending the mock module) and a recording core session store: 1. `coreLogoutWithNativeLogoutKeepsCoreSessionUntilCoreHandlerRuns`: with `nativeLogout` in the body, the core session is still present and nothing has been removed or logged out when the core handler starts; afterwards the provider logout ran exactly once, the store is empty, and the mobile row is `ENDED` / `SIGN_OUT` / `DELEGATED`. 2. `endCurrentStillPerformsCoreLogout`: `/sessions/current/end` still runs the core logout itself (`REQUESTED`). 3. `plainCoreLogoutEndsMobileRowAndCoreSessionOnce`: a body-less logout ends both the mobile row and the core session exactly once, and a second logout with an unbound cookie still reaches the core handler. The harness cannot configure a real OAuth2 provider with native logout, so the tests assert the precondition the core handler needs (core session intact when it starts) rather than the `nativeLogoutData` payload itself; a device rerun against Authentik with a build containing this fix is still needed. `mvnw -o -pl base verify`: 59 tests, 0 failures/errors/skips, 0 Checkstyle violations. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
james.maes added 1 commit 2026-11-07 13:32:34 +00:00
fix(sessions): keep the core session for native logout on /qqq/v1/logout
maven / build (push) Failing after 59s
build / maven (push) Failing after 59s
e6977b3ccd
Company sign-out from a device with an active mobile session never received
nativeLogoutData: the scanner's /qqq/v1/logout pre-handler ran the core
LogoutExecutor and removed the stored core session before the core
LogoutSpecV1 handler could prepare the native provider logout data. The
pre-handler now only tombstones the mobile session (ENDED, SIGN_OUT,
authCleanupStatus DELEGATED) and leaves the provider logout and session
removal to the core handler in the same request. /sessions/current/end and
administrator ends still run cleanupAuthentication themselves.

Tests drive the real route chain (scanner pre-handler, then core
LogoutSpecV1) with a recording authentication module and session store:
native-logout sign-out keeps the core session until the core handler runs,
/sessions/current/end still performs the core logout, and a plain logout
ends both the mobile row and the core session exactly once.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
james.maes added 1 commit 2026-11-07 13:37:32 +00:00
ci: re-run the push build (Verify step failed with no readable log; local -T1C verify passes)
maven / build (push) Failing after 1m9s
build / maven (push) Failing after 1m9s
1fc9f79f9b
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Some required checks failed
maven / build (push) Failing after 1m9s
build / maven (push) Failing after 1m9s
You are not authorized to merge this pull request.
This pull request can be merged automatically.
This branch is out-of-date with the base branch
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin fix/logout-keep-core-session-for-native-logout:fix/logout-keep-core-session-for-native-logout
git checkout fix/logout-keep-core-session-for-native-logout
Sign in to join this conversation.