fix(oauth): preserve failed consent logout - #26
Conversation
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_ec0d649e-99c2-437e-8237-7a6050b2bf56) |
There was a problem hiding this comment.
Code Review
This pull request refactors the OAuth consent unauthorized handling and introduces a new helper function shouldHandleGlobalLogout to manage global logout logic based on URL, HTTP method, status, and logout headers. It also updates the layout context to use these helpers and adds corresponding unit tests. The review feedback suggests using optional chaining (options?.method) when accessing the method property of the options object in layout.context.tsx to prevent potential runtime TypeErrors if options is undefined.
| if ( | ||
| shouldPreserveOAuthConsentUnauthorized( | ||
| url, | ||
| options.method, | ||
| response.status | ||
| ) | ||
| ) { |
There was a problem hiding this comment.
Using options.method directly can cause a runtime TypeError: Cannot read properties of undefined (reading 'method') if options is not provided (e.g., when a fetch request is initiated without an options object). Since options is potentially optional or undefined, it is safer to use optional chaining (options?.method).
| if ( | |
| shouldPreserveOAuthConsentUnauthorized( | |
| url, | |
| options.method, | |
| response.status | |
| ) | |
| ) { | |
| if ( | |
| shouldPreserveOAuthConsentUnauthorized( | |
| url, | |
| options?.method, | |
| response.status | |
| ) | |
| ) { |
| if ( | ||
| shouldHandleGlobalLogout( | ||
| url, | ||
| options.method, | ||
| response.status, | ||
| Boolean(logout) | ||
| ) && | ||
| !isSecured | ||
| ) { |
There was a problem hiding this comment.
Using options.method directly can cause a runtime TypeError if options is undefined. Use optional chaining (options?.method) to safely handle cases where options is omitted.
| if ( | |
| shouldHandleGlobalLogout( | |
| url, | |
| options.method, | |
| response.status, | |
| Boolean(logout) | |
| ) && | |
| !isSecured | |
| ) { | |
| if ( | |
| shouldHandleGlobalLogout( | |
| url, | |
| options?.method, | |
| response.status, | |
| Boolean(logout) | |
| ) && | |
| !isSecured | |
| ) { |
| if ( | ||
| (response.status === 401 || response?.headers?.get('logout')) && | ||
| !shouldPreserveOAuthConsentUnauthorized( | ||
| response.status === 401 || | ||
| shouldHandleGlobalLogout( | ||
| url, | ||
| options.method, | ||
| response.status, | ||
| Boolean(response?.headers?.get('logout')) | ||
| Boolean(logout) | ||
| ) | ||
| ) { |
There was a problem hiding this comment.
Using options.method directly can cause a runtime TypeError if options is undefined. Use optional chaining (options?.method) to safely handle cases where options is omitted.
| if ( | |
| (response.status === 401 || response?.headers?.get('logout')) && | |
| !shouldPreserveOAuthConsentUnauthorized( | |
| response.status === 401 || | |
| shouldHandleGlobalLogout( | |
| url, | |
| options.method, | |
| response.status, | |
| Boolean(response?.headers?.get('logout')) | |
| Boolean(logout) | |
| ) | |
| ) { | |
| if ( | |
| response.status === 401 || | |
| shouldHandleGlobalLogout( | |
| url, | |
| options?.method, | |
| response.status, | |
| Boolean(logout) | |
| ) | |
| ) { |
Problem
PR #25 preserved an OAuth consent
401, but the layout processed thelogoutheader first and still redirected the browser to/, which Beta maps to/launches.Change
Before every layout redirect side effect, preserve only a failed
POST /oauth/authorizeresponse with status401. This applies even when a logout header is present. GET requests, other paths, other statuses, and normal logout behavior are unchanged.Validation
pnpm exec jest --config tests/bootstrap.jest.cjs --runInBand tests/bootstrap-oauth-consent-error.spec.tspnpm --filter ./apps/frontend exec tsc --noEmitgit diff --checkNote
Medium Risk
Touches shared fetch/auth redirect logic for 401 and logout headers; scope is narrow but mistakes could affect sign-out or OAuth consent error handling.
Overview
Fixes a regression where a
logoutresponse header on a failed OAuth consent still triggered the layout fetch handler to clear session cookies and send users to/(often/launcheson Beta), even when the consent UI should stay on the authorize page.The global
afterRequesthook now bails out first when the response is a401onPOST /oauth/authorize, before auth/logout cookie handling or redirects.shouldPreserveOAuthConsentUnauthorizedis tightened to that case only (POST, 401, authorize path, including absolute URLs).shouldHandleGlobalLogoutcentralizes when a logout header should force the normal global logout path鈥攊t runs only when a logout header is present and the response is not that preserved consent failure.Tests cover method/path/status combinations and that consent
POST+401+ logout does not count as global logout, while other requests still do.Reviewed by Cursor Bugbot for commit 9443ff2. Configure here.