Skip to content

fix: revoking device does not invalidate RememberMe session#8372

Draft
Copilot wants to merge 2 commits intomainfrom
copilot/fix-login-session-logout-issue
Draft

fix: revoking device does not invalidate RememberMe session#8372
Copilot wants to merge 2 commits intomainfrom
copilot/fix-login-session-logout-issue

Conversation

Copy link
Contributor

Copilot AI commented Feb 27, 2026

When a user logs in with "keep login session" (RememberMe) selected, revoking their device from the admin panel does not terminate the session — the RememberMe cookie re-authenticates them automatically.

Two bugs contribute to this:

1. Race condition: rememberMeSeriesId is null on newly created devices

In LoginHandlerEnhancerImpl.onLoginSuccess, rememberMeServices.loginSuccess and deviceService.loginSuccess ran concurrently via Mono.when(). The createDevice path synchronously reads REMEMBER_ME_SERIES_REQUEST_NAME from the exchange at subscription time, but rememberMeServices.loginSuccess sets that attribute only after an async session read + DB write complete — so it's always unset when read.

Fix: run rememberMeServices.loginSuccess sequentially first, then the rest concurrently.

// Before
return Mono.when(
    rememberMeServices.loginSuccess(exchange, auth),
    deviceService.loginSuccess(exchange, auth),
    ...
);

// After
return rememberMeServices.loginSuccess(exchange, auth)
    .then(Mono.when(
        deviceService.loginSuccess(exchange, auth),
        ...
    ));

2. updateExistingDevice does not update rememberMeSeriesId on re-login

When a user re-logs in with RememberMe, rememberMeServices.loginSuccess creates a new token with a new series ID. updateExistingDevice correctly removes the old token but saves the device still referencing the old series ID. Subsequent device revocation removes the already-gone old token while the new token survives.

Fix: after removing the old token in updateExistingDevice, update rememberMeSeriesId from the exchange attribute if a new series was set.

What type of PR is this?

/kind bug

What this PR does / why we need it:

Ensures that when a device is revoked, the associated RememberMe token is correctly identified and removed, so the user cannot silently re-authenticate via their cookie.

Which issue(s) this PR fixes:

Special notes for your reviewer:

The sequential ordering change in LoginHandlerEnhancerImpl adds a small latency to the login path (waiting for RememberMe token creation before proceeding with device/OAuth2/audit operations), but correctness requires this ordering.

Does this PR introduce a user-facing change?

Fix: revoking a device now correctly invalidates the associated RememberMe ("keep login session") token, preventing silent re-authentication after revocation.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • checkstyle.org
    • Triggering command: /opt/hostedtoolcache/CodeQL/2.24.2/x64/codeql/tools/linux64/java/bin/java /opt/hostedtoolcache/CodeQL/2.24.2/x64/codeql/tools/linux64/java/bin/java -jar /opt/hostedtoolcache/CodeQL/2.24.2/x64/codeql/xml/tools/xml-extractor.jar --fileList=/tmp/codeql-scratch-dd1d0a2b113c8104/dbs/java/working/files-to-index8078197461987019615.list --sourceArchiveDir=/tmp/codeql-scratch-dd1d0a2b113c8104/dbs/java/src --outputDir=/tmp/codeql-scratch-dd1d0a2b113c8104/dbs/java/trap/java (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Original prompt

This section details on the original issue you should resolve

<issue_title>登录时选择保持登录会话后撤销设备不会被登出</issue_title>
<issue_description>### 前置条件

  • 已经在 issues 列表中搜索了相关问题。
  • 这是 Halo 项目本身存在的问题,如果是非项目本身的问题(如:安装部署问题),建议在 Discussions 提交。
  • 已经尝试过停用所有的插件,排除是插件导致的问题。
  • 如果是插件和主题的问题,请在对应的插件和主题仓库提交。

系统信息

使用的哪种方式运行?

Docker

发生了什么?

2026-01-31.15-46-12.mp4

复现步骤

  1. 登录时选择保持登录会话
  2. 撤销设备

相关日志输出

附加信息

No response</issue_description>

Comments on the Issue (you are @copilot in this section)

@JohnNiang Hi @AR-26710 , thank you for reaching out here!

这确实是一个问题,目前默认只会踢出当前的 Session,而 RememberMe Token 似乎还留存着的,这也就导致后续请求自动重新登录。

/kind bug
/area core
/milestone 2.23.x</comment_new>


🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

@f2c-ci-robot f2c-ci-robot bot added do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. do-not-merge/release-note-label-needed Indicates that a PR should not merge because it's missing one of the release note labels. labels Feb 27, 2026
@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

When user logs in with RememberMe ("keep login session") and admin revokes
the device, the RememberMe token was not being properly removed, allowing
the user to remain logged in.

Two root causes fixed:
1. Race condition: `rememberMeServices.loginSuccess` and
   `deviceService.loginSuccess` ran concurrently, so the
   REMEMBER_ME_SERIES_REQUEST_NAME attribute wasn't set yet when
   createDevice tried to read it. Fixed by running rememberMeServices
   sequentially first.

2. updateExistingDevice didn't update rememberMeSeriesId to the new
   series when user re-logged in with RememberMe. Fixed by reading the
   new series from exchange attribute after removing the old token.

Co-authored-by: ruibaby <21301288+ruibaby@users.noreply.github.com>
@f2c-ci-robot
Copy link

f2c-ci-robot bot commented Feb 27, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please ask for approval from ruibaby. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

Copilot AI changed the title [WIP] Fix logout issue when revoking device in login session Feb 27, 2026
@f2c-ci-robot f2c-ci-robot bot added release-note Denotes a PR that will be considered when it comes time to generate release notes. and removed do-not-merge/release-note-label-needed Indicates that a PR should not merge because it's missing one of the release note labels. labels Feb 27, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a login/device revocation edge case where a revoked device could still re-authenticate via a surviving RememberMe token, by ensuring the device record always tracks the current RememberMe series and by removing an ordering race during login.

Changes:

  • Serialize rememberMeServices.loginSuccess(...) ahead of other login-success side effects to ensure the RememberMe series attribute is available when device creation/update reads it.
  • Update existing devices to refresh rememberMeSeriesId from the exchange after invalidating the previous token.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
application/src/main/java/run/halo/app/security/device/DeviceServiceImpl.java Updates device update path to persist the new RememberMe series id after removing the old token.
application/src/main/java/run/halo/app/security/LoginHandlerEnhancerImpl.java Enforces sequential ordering so RememberMe series attribute is set before device login success processing.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

var newSeriesId =
exchange.<String>getAttribute(REMEMBER_ME_SERIES_REQUEST_NAME);
if (StringUtils.isNotBlank(newSeriesId)) {
device.getSpec().setRememberMeSeriesId(newSeriesId);
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After removing the previous remember-me token, the device spec can still retain the old rememberMeSeriesId when no new series is set on the exchange (e.g., user re-logs in without RememberMe). This leaves the persisted Device pointing at a non-existent series and causes redundant token-removal lookups on later logins/revocations. Consider clearing device.spec.rememberMeSeriesId when the exchange attribute is blank/missing so the Device state stays consistent with the token repository.

Suggested change
device.getSpec().setRememberMeSeriesId(newSeriesId);
device.getSpec().setRememberMeSeriesId(newSeriesId);
} else {
// No new remember-me series; clear any stale series id
device.getSpec().setRememberMeSeriesId(null);
Copilot uses AI. Check for mistakes.
Comment on lines +41 to +49
// rememberMeServices.loginSuccess must run first to set the REMEMBER_ME_SERIES_REQUEST_NAME
// attribute on the exchange before deviceService.loginSuccess reads it.
return rememberMeServices.loginSuccess(exchange, successfulAuthentication)
.then(Mono.when(
deviceService.loginSuccess(exchange, successfulAuthentication),
rememberMeRequestCache.removeRememberMe(exchange),
oauth2LoginHandlerEnhancer.loginSuccess(exchange, successfulAuthentication),
userLoginOrLogoutProcessing.loginProcessing(successfulAuthentication.getName())
));
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change fixes a subtle ordering/race issue between remember-me token creation and device persistence. There are existing remember-me unit tests in the codebase, but no coverage that asserts (a) rememberMeServices runs before deviceService reads the series attribute, and (b) re-login updates the persisted Device.rememberMeSeriesId so revocation removes the correct token. Adding a focused test around the login flow (or DeviceServiceImpl updateExistingDevice) would help prevent regressions.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. release-note Denotes a PR that will be considered when it comes time to generate release notes.

4 participants