Skip to content

refactor(auth): move USER_LAST_ACTIVE_TIME write out of JwtAuthFilter#4888

Merged
Yicong-Huang merged 5 commits into
apache:mainfrom
Yicong-Huang:refactor/auth-filter-no-db-write
May 4, 2026
Merged

refactor(auth): move USER_LAST_ACTIVE_TIME write out of JwtAuthFilter#4888
Yicong-Huang merged 5 commits into
apache:mainfrom
Yicong-Huang:refactor/auth-filter-no-db-write

Conversation

@Yicong-Huang
Copy link
Copy Markdown
Contributor

@Yicong-Huang Yicong-Huang commented May 4, 2026

What changes were proposed in this PR?

JwtAuthFilter did a synchronous USER_LAST_ACTIVE_TIME upsert on every authenticated request — coupling JWT verification to a DB round-trip and mixing user-management into the auth pipeline. Strip the DB write out of the filter.

The replacement:

  • UserActivityTracker (in common/auth) — per-uid in-memory cooldown (5 min default) + single-thread daemon executor, so request threads never wait on DB.
  • UserActivityEventListener (in access-control-service) — a Jersey ApplicationEventListener, not a ContainerRequestFilter. It observes RESOURCE_METHOD_FINISHED and forwards the uid to the tracker. Cannot reject or transform requests.

Listener is registered only in access-control-service. Other services keep JwtAuthFilter but no longer write USER_LAST_ACTIVE_TIME — authenticated client sessions necessarily contact access-control-service often enough (UI navigation, permission checks, LiteLLM proxy) to capture activity with high recall.

Any related issues, documentation, discussions?

Closes #4887

How was this PR tested?

Unit specs cover tracker cooldown/CAS (synchronous executor + injectable clock, no DB) and listener gating (Mockito stubs for RequestEvent, no Jersey runtime).

Was this PR authored or co-authored using generative AI tooling?

Generated-by: Claude Code (Opus 4.7)

@github-actions github-actions Bot added refactor Refactor the code common platform Non-amber Scala service paths labels May 4, 2026
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented May 4, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 44.05%. Comparing base (b16d0db) to head (b1e8cc0).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@             Coverage Diff              @@
##               main    #4888      +/-   ##
============================================
+ Coverage     43.13%   44.05%   +0.92%     
  Complexity     2155     2155              
============================================
  Files           914      958      +44     
  Lines         32196    34063    +1867     
  Branches       3253     3756     +503     
============================================
+ Hits          13887    15008    +1121     
- Misses        17466    18302     +836     
+ Partials        843      753      -90     
Flag Coverage Δ
access-control-service 39.53% <100.00%> (+11.40%) ⬆️
amber 42.80% <ø> (-0.04%) ⬇️
computing-unit-managing-service 0.00% <ø> (ø)
config-service 0.00% <ø> (ø)
file-service 33.24% <ø> (ø)
workflow-compiling-service 47.72% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@Yicong-Huang Yicong-Huang force-pushed the refactor/auth-filter-no-db-write branch from e96d302 to 29b101e Compare May 4, 2026 02:55
@github-actions github-actions Bot added the dependencies Pull requests that update a dependency file label May 4, 2026
JwtAuthFilter previously did a synchronous USER_LAST_ACTIVE_TIME upsert
on every authenticated request, coupling JWT verification to a per-
request DB round-trip and mixing user-management concerns into the auth
pipeline.

Strip the DB write out of the filter. The filter is now pure: extract
token, verify, set SecurityContext.

Add a UserActivityTracker in common/auth that throttles per-uid DB
writes by an in-memory cooldown (default 5 minutes) and runs the
upsert on a single-thread daemon executor so request threads never
wait on DB latency. Tracker is in common/auth so the throttle/CAS
logic and its tests live with the rest of auth-adjacent code.

Add a UserActivityEventListener (Jersey ApplicationEventListener) in
access-control-service. The listener observes RESOURCE_METHOD_FINISHED
at Jersey's monitoring layer (no ContainerRequestFilter semantics,
cannot reject or transform requests) and forwards uid to the tracker.

Listener lives only in access-control-service: USER_LAST_ACTIVE_TIME
is a user-management concern, and authenticated client sessions
necessarily contact this service often (UI navigation, permission
checks, LiteLLM proxy) so other services do not need to mirror this
listener. Other services keep their JwtAuthFilter registration but
no longer incidentally write to USER_LAST_ACTIVE_TIME.

Tests: 5-case spec for tracker cooldown/CAS in common/auth, 5-case
spec for listener trigger gating in access-control-service. Both run
synchronously without DB or Jersey runtime via injected upsert
function and Mockito-based RequestEvent stubs.

Closes apache#4887
@Yicong-Huang Yicong-Huang force-pushed the refactor/auth-filter-no-db-write branch from 29b101e to 2080c42 Compare May 4, 2026 02:55
Copy link
Copy Markdown
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

Refactors user activity tracking so JWT authentication no longer performs a per-request DB upsert, and moves USER_LAST_ACTIVE_TIME writes behind a throttled, asynchronous tracker invoked from access-control-service’s Jersey monitoring layer.

Changes:

  • Remove USER_LAST_ACTIVE_TIME upsert from JwtAuthFilter, leaving it as a pure JWT-to-SecurityContext filter.
  • Add UserActivityTracker (throttled per-uid, async writer) plus unit tests in common/auth.
  • Add UserActivityEventListener in access-control-service to trigger tracking on RESOURCE_METHOD_FINISHED, plus unit tests and service registration.

Reviewed changes

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

Show a summary per file
File Description
common/auth/src/main/scala/org/apache/texera/auth/JwtAuthFilter.scala Removes synchronous activity DB write from auth filter.
common/auth/src/main/scala/org/apache/texera/auth/UserActivityTracker.scala Introduces throttled async activity upsert singleton + testable class form.
common/auth/src/test/scala/org/apache/texera/auth/UserActivityTrackerSpec.scala Adds unit coverage for cooldown/CAS behavior.
common/auth/build.sbt Adds ScalaTest dependency for new common/auth tests.
access-control-service/src/main/scala/org/apache/texera/service/activity/UserActivityEventListener.scala Adds Jersey monitoring listener to invoke tracking after resource method completes.
access-control-service/src/test/scala/org/apache/texera/service/activity/UserActivityEventListenerSpec.scala Adds unit tests for listener behavior across event/principal cases.
access-control-service/src/main/scala/org/apache/texera/service/AccessControlService.scala Registers the new listener in access-control-service.

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

Comment thread common/auth/src/main/scala/org/apache/texera/auth/UserActivityTracker.scala Outdated
Comment thread common/auth/src/main/scala/org/apache/texera/auth/UserActivityTracker.scala Outdated
Comment thread common/auth/src/main/scala/org/apache/texera/auth/UserActivityTracker.scala Outdated
- Wrap upsertFn invocation inside the executor task in try/NonFatal so
  injected implementations cannot leak exceptions, with full stack
  trace logged.
- Replace catch Throwable with NonFatal in defaultUpsert so OOME and
  the like still propagate, and pass the exception to logger to keep
  the stack trace.
- Add evictStale() with TTL cleanup at 2 * writeInterval; schedule a
  daemon ScheduledExecutorService in the production singleton to run
  it once per WRITE_INTERVAL. Bounds lastClaimed for long-lived
  processes with many distinct uids.
- Replace newSingleThreadExecutor (unbounded queue) with a bounded
  ThreadPoolExecutor (capacity 256) and DiscardOldestPolicy. Under
  DB stalls or write storms, oldest pending tasks are dropped; the
  next request from the same uid will re-claim and re-write once
  cooldown elapses.

Adds two tests: evictStale eviction window, and exception
swallowing on a throwing upsertFn.
Refactor UserActivityEventListener:
- Inner anonymous RequestEventListener becomes a SAM-converted lambda,
  removing the anonymous-class boilerplate that jacoco was reporting
  branch-partials on.
- Per-event branching moves to a top-level companion method `handle`,
  visible to tests so the per-event logic is exercised without going
  through the SAM dispatch.
- Replace `event.getType == ...` with `event.getType eq ...`. The
  Type is a Java enum (singletons), so reference equality is
  semantically correct and compiles to a single if_acmpne, avoiding
  Scala's BoxesRunTime.equals branch fan-out (the partial-coverage
  noise on the comparison line).

Tests:
- UserActivityEventListenerSpec rewritten to drive `handle` directly
  for the per-event cases (5), plus 3 listener-level smoke tests
  for the SAM dispatch / lifecycle / default-arg ctor paths.
- New AccessControlServiceRunSpec verifies UserActivityEventListener
  is registered when run() is invoked. Mocks Environment, so this
  also incidentally covers the rest of run() — no longer at 0%.
@Yicong-Huang Yicong-Huang enabled auto-merge (squash) May 4, 2026 03:38
@Yicong-Huang Yicong-Huang merged commit a15d4ff into apache:main May 4, 2026
17 checks passed
@Yicong-Huang Yicong-Huang deleted the refactor/auth-filter-no-db-write branch May 4, 2026 03:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

common dependencies Pull requests that update a dependency file platform Non-amber Scala service paths refactor Refactor the code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Move USER_LAST_ACTIVE_TIME write out of JwtAuthFilter into a Jersey ApplicationEventListener

4 participants