Skip to content

[ZEPPELIN-6643] Log out on session expiry instead of throwing in the interceptor - #5464

Merged
voidmatcha merged 3 commits into
apache:masterfrom
kimyenac:ZEPPELIN-6643
Sep 10, 2026
Merged

[ZEPPELIN-6643] Log out on session expiry instead of throwing in the interceptor#5464
voidmatcha merged 3 commits into
apache:masterfrom
kimyenac:ZEPPELIN-6643

Conversation

@kimyenac

@kimyenac kimyenac commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

What is this PR for?

The New UI never logs out on session expiry. AppHttpInterceptor guards its 405 branch with event.url.contains('logout'), and JavaScript strings have no contains method, so the guard throws a TypeError inside catchError before ticketService.logout() is reached. Both statements after it are skipped: logout never runs, and the caller observes a TypeError instead of the 405 it needs to act on. The expired session stays in place until the user reloads the page by hand.

The guard's intent is right and is kept. It exists so that a 405 on the logout request itself does not call logout again, which would recurse. Only the method name changes:

-} else if (event.status === 405 && !event.url.contains('logout')) {
+} else if (event.status === 405 && !event.url?.includes('logout')) {

includes is the method that exists. The optional chain covers HttpErrorResponse.url being null, which it is whenever the failure carries no resolved url, and which would otherwise throw at the same spot for a different reason. A 405 with no url cannot be identified as the logout call, so it falls through to logout — the same conclusion the branch already draws for every other request, and the safe one when the session is likely gone.

The 401 redirect branch is untouched.

Out of scope, and left alone deliberately: the substring match means a 405 on a path that merely contains logout is also skipped, and the catchError parameter is untyped. Both belong to ZEPPELIN-6469, which waits on this behaviour being correct first.

The spec constructs the interceptor directly with a logout stub rather than starting TestBed, per zeppelin-web-angular/AGENTS.md: no Angular wiring is under test here, only the branch. It pins three things the branch has to get right — a non-logout 405 calls logout exactly once, that 405 reaches the caller unchanged rather than replaced by a TypeError, and a 405 from the logout request itself does not call logout again — plus the null-url path.

What type of PR is it?

Bug Fix

Todos

None

What is the Jira issue?

How should this be tested?

  • npm run test:shell — 53 tests across 12 files, green. The four new ones are in src/app/app-http.interceptor.spec.ts.
  • The assertions were checked by breaking what they cover. Reverting the source line to event.url.contains('logout') fails three of the four, with AssertionError: expected TypeError: event.url.contains is not a function to be HttpErrorResponse. The fourth — the recursion guard — passes either way, because the TypeError also happens to prevent the logout call; it is there to confirm the guard survives the fix, not to reproduce the bug.
  • npx prettier --check on both files, clean. npx eslint on both reports only the two prefer-arrow/prefer-arrow-functions warnings that the named test helpers produce, the same two src/app/services/save-as.service.spec.ts already reports on master.
  • Not run locally: Playwright, and the production builds. Neither is reachable from this change — it is one expression in an interceptor plus a unit spec — but saying so rather than implying otherwise.

Manual reproduction, for a reviewer who wants to see the original failure: with an expired session, any REST call from the New UI answers 405 and the browser console shows event.url.contains is not a function from the interceptor, with no logout request following it. After this change the same 405 is followed by POST /api/login/logout.

Screenshots (if appropriate)

Not applicable.

Questions:

  • Does the license files need to update? No
  • Is there breaking changes for older versions? No. The 405 branch did nothing but throw before this change, so nothing could have depended on it.
  • Does this needs documentation? No

…interceptor

The 405 branch in AppHttpInterceptor guarded logout with
event.url.contains('logout'). JavaScript strings have no contains method,
so the guard threw a TypeError inside catchError before ticketService.logout
was ever reached. The New UI therefore never logged out on session expiry:
the caller observed a TypeError instead of the 405, and the expired session
stayed in place until the user reloaded the page by hand.

The check becomes event.url?.includes('logout'). includes is the method that
exists, and the optional chain covers HttpErrorResponse.url being null, which
it is whenever the failure carries no resolved url. A 405 with no url cannot
be identified as the logout call, so it falls through to logout, which is the
same conclusion the branch already draws for every other request.

The 401 redirect branch is untouched. Tightening the substring match and the
wider typing of this interceptor belong to ZEPPELIN-6469, which waits on this
behaviour being correct first.

The spec constructs the interceptor directly with a logout stub rather than
starting TestBed, since no Angular wiring is involved. It pins the three
things the branch has to get right: a non-logout 405 calls logout exactly
once, that 405 is rethrown to the caller unchanged rather than replaced by a
TypeError, and a 405 from the logout request itself does not call logout
again. Reverting the source line fails three of the four.

@voidmatcha voidmatcha left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The CI trace shows a logout request being redirected to /api/login, returning 405 and triggering another logout. I tried checking the original request URL and preventing duplicate automatic logout requests while one is in progress. The existing 401 handling and propagation of the original error are preserved.

The regression test fails against the original code and passes with the fix. The local authentication E2E, all 58 unit tests, and the build also passed. GitHub frontend and quick CI passed on an earlier commit of the fix.

Could we address this before merging? Here is the approach I tried: https://github.com/voidmatcha/zeppelin/tree/fix/pr5464-logout-followup

@kimyenac

kimyenac commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

You're right, and thanks for the diagnosis and the branch.

The defect is mine: I guarded on event.url, which is the URL after redirects, so a logout POST redirected to /api/login returning 405 no longer matched and logout fired again. My tests missed it because I built the fixture from my own assumption about that field instead of from a redirected response — they confirmed the assumption, not the behaviour.

I've cherry-picked your two commits, authorship preserved. Using httpRequest.url also makes my "a 405 with no url cannot be identified as the logout call" reasoning unnecessary, since the original request is always known.

npm run test:shell: 58 passed. Restoring my original guard fails 5 of the 9 interceptor tests — exactly the cases I should have covered.

On this push both Playwright legs are green, including run-playwright-e2e-tests (auth, 3.9), which is the job that caught the loop. I could not reproduce that failure locally: with shiro enabled behind ng serve, the old and new code both stop at global.setup.ts:42 on "Getting Ticket Data …", short of the line CI fails on, so the CI run is the evidence here rather than a local pass.

Two unrelated reds on the same run, for anyone reading the checks: npm-audit fails on the new js-yaml high advisory (GHSA-2883-xcg3-v3hh), which is hitting other branches today as well, and test-selenium-with-spark-module-for-spark-3-5 errored once in AuthenticationIT.testSimpleAuthentication with ElementClickIntercepted on the classic UI's login modal. Neither is reachable from this change — the branch edits two files under zeppelin-web-angular/src/app/.

@voidmatcha voidmatcha left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for addressing the logout retry and concurrent request handling. I checked the regression tests, and all 58 unit tests and the authenticated, anonymous Playwright CI jobs passed. Approving.

I filed the existing Monaco type declaration issue found during review separately as ZEPPELIN-6703. Feel free to pick it up if you are interested.

@voidmatcha
voidmatcha merged commit 006a3e1 into apache:master Sep 10, 2026
22 of 24 checks passed
@voidmatcha

Copy link
Copy Markdown
Member

Merged into master (006a3e1).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants