unbridled-41 opened a new pull request, #5886:
URL: https://github.com/apache/rocketmq-dashboard/pull/5886
### Which Issue(s) This PR Fixes
Fixes #5820
### Problem / Evidence
`rmq_studio_user.gmt_create`/`gmt_modified` and
`rmq_studio_session.gmt_create` are filled by the database, not by the service:
```java
// AuthService.createUser (line 305)
RmqStudioUser user = new RmqStudioUser();
user.setUsername(username.trim());
...
user.setPasswordChangedAt(now()); // UTC, clock-driven
// gmtCreate / gmtModified left null -> MyBatis-Plus omits the columns
```
```sql
-- server/src/main/resources/db/schema.sql:33
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE
CURRENT_TIMESTAMP,
```
MyBatis-Plus's default insert strategy omits null fields (there is no
`MetaObjectHandler` in `server/src/main`), so `CURRENT_TIMESTAMP` is evaluated
in the **database session's** zone. On the deployment the repository documents,
that is not UTC: `deploy/docker-compose.yml` sets `TZ: ${TZ:-Asia/Shanghai}` on
both the MySQL and the server container and pins `serverTimezone=Asia/Shanghai`
in the JDBC URL.
The console parses those fields as UTC:
```ts
// web/src/pages/studio/UserManagement.tsx:78
// Studio user and session APIs serialize UTC LocalDateTime values without
an offset,
// so the timestamps have to be parsed as UTC before rendering in the
viewer's zone.
const dateTime = (value?: string | null) => formatUtcDateTime(value);
```
and `docs/api-spec.md:2443` states the API contract is "ISO-8601,无时区偏移的 UTC".
So on the documented deployment every Studio user row prints a creation time
**8 hours in the future**, the session drawer's "Created" column is shifted the
same way, and both CSV exports (`Created At`, `Modified At`) inherit it. The
same row also carries `passwordChangedAt`, which *is* stamped from the UTC
clock, so one row shows "Created 18:00" next to "Password changed 10:00" for
one and the same event.
Deterministic proof of the zone split (H2 with the repository's schema
shape, JVM zone `Asia/Shanghai`, insert without `gmt_create`):
```
stored gmt_create (DB default) = 2026-10-10T00:08:44
stored last_seen_at (app UTC) = 2026-10-09T16:08:43 -> delta 8 hours
```
The suite cannot see it because CI's MySQL service container runs with `TZ`
unset (UTC), and no test asserts the audit columns of an inserted row.
### Root cause / Fix
The module already owns the invariant "every zone-less timestamp this API
returns is UTC" (`AuthService.now()` = `LocalDateTime.ofInstant(clock.millis(),
ZoneOffset.UTC)`); the two audit columns were the only values left to the
database default. Stamp them from that same clock:
- `createUser` and `ensureBootstrapUsers`: `setGmtCreate(current)` /
`setGmtModified(current)`.
- `loginDatabaseUser`: the session row gets the same two stamps from the
`current` instant it already uses for `lastSeenAt`/`expiresAt`.
- `changePassword`: add `.set("gmt_modified", now())`, otherwise `ON UPDATE
CURRENT_TIMESTAMP` writes the modified stamp in the server zone.
### Priority and scoring
**PRIORITY 60** — impact 20/40 (a wrong, future-dated creation time for
every account and session, including the CSV export an auditor reads; nothing
crashes), blast radius 14/20 (all Studio users and sessions on any deployment
whose database zone is not UTC, which is the shipped compose default),
reproducibility 16/20 (deterministic given that zone, verified above),
maintenance value 10/20 (removes the one place where this module lets the
database choose a timestamp's zone).
**FIX_CONFIDENCE 85** — the mechanism is proven and the fix follows the
module's existing invariant.
### Tests
`cd server && mvn -o -B -ntp test
-Dtest='org.apache.rocketmq.studio.auth.*Test'`
| Test | Before | After |
|---|---|---|
|
`AuthServiceDatabaseTest#createdUserCarriesTheUtcAuditInstantsTheApiSerializesTest`
| FAIL `expected: 2026-08-13T00:00 but was: null` | PASS |
|
`AuthServiceDatabaseTest#createdSessionCarriesTheUtcAuditInstantsTheApiSerializesTest`
| FAIL `expected: 2026-08-13T00:00 but was: null` | PASS |
| `AuthServiceDatabaseTest#passwordChangeStampsTheUtcModifiedInstantTest` |
FAIL (`gmt_modified` absent from the SET clause) | PASS |
`Tests run: 155, Failures: 0, Errors: 0` for the whole auth package; `mvn -o
-B -ntp checkstyle:check` passes.
### Risk
The three new stamps are values the database would otherwise have written,
so the only behaviour change is the zone of the value stored; no caller reads
these columns for a decision (they are display/export fields). Existing rows
keep their current values, so an upgraded installation keeps whatever the
database wrote until the row is next modified.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]