Skip to content

fix(a11y): add session timeout warning modal (WCAG 2.2.1)#1557

Merged
olexii4 merged 3 commits into
mainfrom
CRW-10290
May 13, 2026
Merged

fix(a11y): add session timeout warning modal (WCAG 2.2.1)#1557
olexii4 merged 3 commits into
mainfrom
CRW-10290

Conversation

@olexii4
Copy link
Copy Markdown
Contributor

@olexii4 olexii4 commented May 6, 2026

What does this PR do?

Adds a session timeout warning modal to satisfy WCAG 2.2.1 Timing Adjustable (Level A).

When a user sits idle on any Dashboard page, the OAuth proxy session eventually expires without any prior warning, causing an abrupt redirect to the login page and potential loss of unsaved form data.

This PR:

  • Backend — exposes sessionTimeout (seconds) through the existing /dashboard/api/server-config endpoint using a two-source strategy:

    1. Primary: reads spec.networking.auth.gateway.oAuthProxy.cookieExpireSeconds from the CheCluster CR (added in che-operator#1760, +kubebuilder:default:=86400).
    2. Fallback: when the CR field is absent (older operators), parses cookie_expire directly from the che-gateway-config-oauth-proxy ConfigMap — which always reflects the value the OAuth proxy actually enforces.
    3. Returns -1 when neither source is available (e.g. local dev without k8s) so the modal is a no-op.
  • Frontend — adds a useSessionTimeout hook that tracks UI inactivity, and a SessionTimeoutModal component (mounted globally in App.tsx) that:

    • appears 60 seconds before the session cookie expires
    • shows a live countdown ring (amber → red/pulsing at ≤ 20 s, suppressed by prefers-reduced-motion)
    • offers Extend session (primary button) and Sign out now (link button)
    • resets the OAuth proxy session cookie by calling GET /dashboard/api/user/id on extend
    • signs out automatically when the countdown reaches zero
    • ignores mouse movement while visible — only explicit button/keyboard interaction dismisses it

The modal is a no-op when sessionTimeout ≤ 0.

Screenshot/screencast of this PR

Normal state (> 20 s remaining) — amber ring:
Знімок екрана 2026-05-07 о 14 21 03

Urgent state (≤ 20 s remaining) — red pulsing ring:
Знімок екрана 2026-05-07 о 04 54 17

What issues does this PR fix or reference?

fixes https://issues.redhat.com/browse/CRW-10290

Is it tested? How?

Tested manually on a local CRC cluster (OpenShift + HTPasswd OAuth) and a Minikube cluster (Dex OIDC).

To trigger the modal quickly without waiting the full session duration, patch the CheCluster CR to shorten cookieExpireSeconds to 3 minutes:

kubectl patch checluster/eclipse-che -n eclipse-che --type=merge \
  -p '{"spec":{"networking":{"auth":{"gateway":{"oAuthProxy":{"cookieExpireSeconds":180}}}}}}'

Verify the Dashboard API reflects the change:

kubectl exec -n eclipse-che deployment/che-dashboard -- \
  /bin/sh -c "curl -s http://localhost:8080/dashboard/api/server-config" \
  | python3 -c "import sys,json; t=json.load(sys.stdin)['timeouts']; print(t['sessionTimeout'], 'seconds')"
# expected: 180 seconds

Test the modal:

  1. Hard-refresh the Dashboard (Cmd+Shift+R / Ctrl+Shift+R)
  2. Log in and stay idle on any page
  3. After ~2 minutes, the warning modal appears with a 60-second countdown
  4. Click Extend session — modal closes, session resets, countdown restarts after another 2 min
  5. Let the countdown reach zero — browser redirects to the login page

Restore when done:

kubectl patch checluster/eclipse-che -n eclipse-che --type=merge \
  -p '{"spec":{"networking":{"auth":{"gateway":{"oAuthProxy":{"cookieExpireSeconds":86400}}}}}}'

Release Notes

Before the session expires, a warning modal now appears with a countdown timer giving users at least 60 seconds to extend their session or sign out gracefully. Satisfies WCAG 2.2.1 Timing Adjustable (Level A).

Docs PR

@olexii4 olexii4 requested review from akurinnoy and ibuziuk as code owners May 6, 2026 17:45
@che-bot
Copy link
Copy Markdown
Contributor

che-bot commented May 6, 2026

Click here to review and test in web IDE: Contribute

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 6, 2026

Docker image build succeeded: quay.io/eclipse/che-dashboard:pr-1557 (linux/amd64, linux/arm64)

kubectl patch command
kubectl patch -n eclipse-che "checluster/eclipse-che" --type=json -p="[{"op": "replace", "path": "/spec/components/dashboard/deployment", "value": {containers: [{image: "quay.io/eclipse/che-dashboard:pr-1557", name: che-dashboard}]}}]"

@olexii4 olexii4 changed the title Crw 10290 fix(a11y): add session timeout warning modal (WCAG 2.2.1) May 6, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented May 6, 2026

Codecov Report

❌ Patch coverage is 94.94949% with 15 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.51%. Comparing base (ffebd42) to head (804f7f1).
⚠️ Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
...ntend/src/components/SessionTimeoutModal/index.tsx 93.06% 7 Missing ⚠️
...src/devworkspaceClient/services/serverConfigApi.ts 93.22% 3 Missing and 1 partial ⚠️
...frontend/src/services/session/useSessionTimeout.ts 97.34% 3 Missing ⚠️
...board-frontend/src/store/ServerConfig/selectors.ts 80.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##             main    #1557    +/-   ##
========================================
  Coverage   92.51%   92.51%            
========================================
  Files         561      563     +2     
  Lines       55867    56164   +297     
  Branches     4225     4258    +33     
========================================
+ Hits        51684    51960   +276     
- Misses       4135     4154    +19     
- Partials       48       50     +2     

☔ 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.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 6, 2026

Docker image build succeeded: quay.io/eclipse/che-dashboard:pr-1557 (linux/amd64, linux/arm64)

kubectl patch command
kubectl patch -n eclipse-che "checluster/eclipse-che" --type=json -p="[{"op": "replace", "path": "/spec/components/dashboard/deployment", "value": {containers: [{image: "quay.io/eclipse/che-dashboard:pr-1557", name: che-dashboard}]}}]"

2 similar comments
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 6, 2026

Docker image build succeeded: quay.io/eclipse/che-dashboard:pr-1557 (linux/amd64, linux/arm64)

kubectl patch command
kubectl patch -n eclipse-che "checluster/eclipse-che" --type=json -p="[{"op": "replace", "path": "/spec/components/dashboard/deployment", "value": {containers: [{image: "quay.io/eclipse/che-dashboard:pr-1557", name: che-dashboard}]}}]"

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 6, 2026

Docker image build succeeded: quay.io/eclipse/che-dashboard:pr-1557 (linux/amd64, linux/arm64)

kubectl patch command
kubectl patch -n eclipse-che "checluster/eclipse-che" --type=json -p="[{"op": "replace", "path": "/spec/components/dashboard/deployment", "value": {containers: [{image: "quay.io/eclipse/che-dashboard:pr-1557", name: che-dashboard}]}}]"

@olexii4 olexii4 force-pushed the CRW-10290 branch 2 times, most recently from 18f35d2 to f001621 Compare May 7, 2026 11:56
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 7, 2026

Docker image build succeeded: quay.io/eclipse/che-dashboard:pr-1557 (linux/amd64, linux/arm64)

kubectl patch command
kubectl patch -n eclipse-che "checluster/eclipse-che" --type=json -p="[{"op": "replace", "path": "/spec/components/dashboard/deployment", "value": {containers: [{image: "quay.io/eclipse/che-dashboard:pr-1557", name: che-dashboard}]}}]"

1 similar comment
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 7, 2026

Docker image build succeeded: quay.io/eclipse/che-dashboard:pr-1557 (linux/amd64, linux/arm64)

kubectl patch command
kubectl patch -n eclipse-che "checluster/eclipse-che" --type=json -p="[{"op": "replace", "path": "/spec/components/dashboard/deployment", "value": {containers: [{image: "quay.io/eclipse/che-dashboard:pr-1557", name: che-dashboard}]}}]"

@olexii4 olexii4 requested a review from svor May 7, 2026 14:03
@olexii4
Copy link
Copy Markdown
Contributor Author

olexii4 commented May 7, 2026

/retest

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 7, 2026

Docker image build succeeded: quay.io/eclipse/che-dashboard:pr-1557 (linux/amd64, linux/arm64)

kubectl patch command
kubectl patch -n eclipse-che "checluster/eclipse-che" --type=json -p="[{"op": "replace", "path": "/spec/components/dashboard/deployment", "value": {containers: [{image: "quay.io/eclipse/che-dashboard:pr-1557", name: che-dashboard}]}}]"

olexii4 added 3 commits May 7, 2026 18:16
Reads spec.networking.auth.gateway.oAuthProxy.cookieExpireSeconds from
the CheCluster CR (che-operator PR #1760, +kubebuilder:default:=86400)
as the primary source. Falls back to parsing cookie_expire from the
che-gateway-config-oauth-proxy ConfigMap for clusters running older
operators where the CR field is absent. Returns -1 when neither source
is available so the frontend hook is a no-op.

- Add cookieExpireSeconds to CheClusterCustomResource networking type
  (field name is oAuthProxy per Go JSON tag in che-operator PR #1760)
- Add parseDurationToSeconds helper (parses Go time.Duration strings;
  returns -1 on empty/unmatched input so the hook disables itself)
- Add async getSessionTimeout(cheCustomResource) with CR-first + ConfigMap
  fallback strategy
- Wire into the server-config route handler
- Add selectSessionTimeout Redux selector; default unloadedState to -1

Fixes: https://issues.redhat.com/browse/CRW-10290
Assisted-by: Claude Sonnet 4.6
Signed-off-by: Oleksii Orel <oorel@redhat.com>
Tracks UI idle time via mousemove/keydown/click/touchstart events and
shows a warning modal 60 s before the OAuth session cookie expires.
Activity events are ignored while the modal is open so the countdown
is never accidentally reset by mouse movement.

- isModalOpenRef updated synchronously alongside React state to
  eliminate the render/effect timing race (stale ref race)
- onExtend uses try/finally so the idle timer restarts on network failure
- No-op when sessionTimeout <= 0 (field absent on older operators)

Fixes: https://issues.redhat.com/browse/CRW-10290
Assisted-by: Claude Sonnet 4.6
Signed-off-by: Oleksii Orel <oorel@redhat.com>
PF6 small modal with amber countdown ring (red/pulsing at <= 20 s,
suppressed by prefers-reduced-motion), Extend Session and Sign Out Now
buttons. Mounted in App.tsx so it appears on every page.

- Icon uses var(--pf-t--global--icon--color--status--warning--default)
- Closing via X button or backdrop extends the session
- Space key listener active only while modal is open

Satisfies WCAG 2.2.1 Timing Adjustable (Level A).

Fixes: https://issues.redhat.com/browse/CRW-10290
Assisted-by: Claude Sonnet 4.6
Signed-off-by: Oleksii Orel <oorel@redhat.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 7, 2026

Docker image build succeeded: quay.io/eclipse/che-dashboard:pr-1557 (linux/amd64, linux/arm64)

kubectl patch command
kubectl patch -n eclipse-che "checluster/eclipse-che" --type=json -p="[{"op": "replace", "path": "/spec/components/dashboard/deployment", "value": {containers: [{image: "quay.io/eclipse/che-dashboard:pr-1557", name: che-dashboard}]}}]"

@olexii4
Copy link
Copy Markdown
Contributor Author

olexii4 commented May 11, 2026

@sskoryk we need your review

@olexii4
Copy link
Copy Markdown
Contributor Author

olexii4 commented May 11, 2026

/retest

Copy link
Copy Markdown
Contributor

@SkorikSergey SkorikSergey left a comment

Choose a reason for hiding this comment

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

Looks good to merge

@openshift-ci
Copy link
Copy Markdown

openshift-ci Bot commented May 12, 2026

@SkorikSergey: changing LGTM is restricted to collaborators

Details

In response to this:

Looks good to merge

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

Copy link
Copy Markdown
Contributor

@svor svor left a comment

Choose a reason for hiding this comment

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

great job!

Image

@openshift-ci
Copy link
Copy Markdown

openshift-ci Bot commented May 12, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: olexii4, SkorikSergey, svor

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

@olexii4 olexii4 merged commit 7707b7d into main May 13, 2026
19 checks passed
@olexii4 olexii4 deleted the CRW-10290 branch May 13, 2026 19:51
olexii4 added a commit that referenced this pull request May 28, 2026
Three bugs in the session-timeout warning introduced by #1557:

1. selectSessionTimeout selector used ?? 86400 as fallback — if the
   backend omits sessionTimeout (old operator), the modal activated with
   a spurious 24 h timeout instead of staying disabled. Changed to ?? -1
   so the modal only activates when the backend explicitly provides a
   positive value.

2. The idle timer reset on UI events (mousemove/click/keydown) but not
   on authenticated HTTP requests. The OAuth proxy refreshes the session
   cookie on every request (including background workspace polling), so
   the JS clock diverged from the real session expiry. Added an Axios
   response interceptor that resets the idle timer on each successful
   response, keeping both clocks in sync.

3. Sessions shorter than 90 s leave less than 30 s of usable warning
   time (WARNING_LEAD_SECONDS = 60). Added a MIN_SESSION_TIMEOUT_SECONDS
   = 90 guard that suppresses the modal for such short timeouts.

Assisted-by: Claude Sonnet 4.5
Signed-off-by: Oleksii Orel <oorel@redhat.com>
artaleks9 pushed a commit that referenced this pull request May 29, 2026
Three bugs in the session-timeout warning introduced by #1557:

1. selectSessionTimeout selector used ?? 86400 as fallback — if the
   backend omits sessionTimeout (old operator), the modal activated with
   a spurious 24 h timeout instead of staying disabled. Changed to ?? -1
   so the modal only activates when the backend explicitly provides a
   positive value.

2. The idle timer reset on UI events (mousemove/click/keydown) but not
   on authenticated HTTP requests. The OAuth proxy refreshes the session
   cookie on every request (including background workspace polling), so
   the JS clock diverged from the real session expiry. Added an Axios
   response interceptor that resets the idle timer on each successful
   response, keeping both clocks in sync.

3. Sessions shorter than 90 s leave less than 30 s of usable warning
   time (WARNING_LEAD_SECONDS = 60). Added a MIN_SESSION_TIMEOUT_SECONDS
   = 90 guard that suppresses the modal for such short timeouts.

Assisted-by: Claude Sonnet 4.5
Signed-off-by: Oleksii Orel <oorel@redhat.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants