OpenConceptLab/ocl_issues#2856 | Sign-in from openconceptlab.org links works first time; menu no longer blanks the app - #58
Conversation
…string no longer fails with "Incorrect redirect_uri" Visitors from openconceptlab.org land on #/?referrer=…, and Sign in used the page URL, query string included, as the OIDC redirect_uri. The callback rebuilt the redirect_uri for the code exchange from the path alone, so Keycloak refused the exchange. Retrying from the callback page then carried its stale state and code into the next sign-in. Sign-in now always uses LOGIN_REDIRECT_URL, and the page to return to (its hash route) waits in this tab's sessionStorage, as the community site already does. The callback still honors next for sign-ins started before this deploy. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…anks the app for people who follow a user with a profile picture
UserIcon spread its sx prop into the <img> style. LeftMenu passes sx as
an array for followed items, so the image got style {0: {...}}, React
threw setting style[0], and the whole app unmounted. Only a plain-object
sx is used as the image style now. The MUI icons get sx unchanged, so an
array sx also keeps its color there.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
paynejd
left a comment
There was a problem hiding this comment.
Codex adversarial review, pass 1 (codex-cli 0.159.2, commit 8035c1e)
Found three P2 issues. I’d fix the first two before merging. No critical security issue found. Reviewed both commits with git diff origin/main...HEAD; no files edited.
-
P2 — Non-root callback URLs override the saved destination.
OIDLoginCallback.jsx:43 and OIDLoginCallback.jsx:92
WithLOGIN_REDIRECT_URL=https://host/oidc/login/,index.htmlsuppliesnext=/oidc/login/. The exchange succeeds, butnexttakes precedence over the stored/search/?q=abc, leaving the user on the blank callback route. Other non-root callback paths likewise become the destination. The comment claimingnextidentifies old sign-ins is incorrect: the rewrite supplies it for new sign-ins too.
Fix: Track the exact redirect URI and destination as separate transaction fields. Prefer the stored destination for new transactions; retainnextas the legacy fallback. -
P2 — Abandoned login destinations leak into registration/password reset.
utils.js:919 and OIDLoginCallback.jsx:36
Start login from/orgs/Abandoned/, return using Back, then start registration or password reset in the same tab. Those helpers replace authentication material without clearingoauth_return_to. A successful callback consumes the abandoned login’s destination and navigates there. Confirmed by executing the actual helpers.
Fix: Initialize or clear the destination for every authentication flow, ideally storing it together with that flow’s state, verifier and redirect URI. -
P2 — Return-route validation still permits authentication entry routes.
utils.js:901
/#/signin?returnTo=https%3A%2F%2Fhost%2F%23%2Fsignupstores/signup. After successful login, mounting that route immediately launches registration. Returning to/signinlaunches another login. This is a remaining validation gap, rather than a newly introduced loop; an infinite loop is not established.
Fix: Reject/signin,/signup, and/oidc/login, including their routed suffixes and query variants, and fall back to/.
Other requested checks:
- Root callback: UserMenu, Error401 and forced logout preserve hash-route queries. GuestDashboard defaults to
/. The callback toast reads the current destination when clicked. - Automatic login:
App.jsx:138still omitssearch, soforceLoginUserloses query parameters. This predates the PR. - Deploy compatibility: Legacy query-free
next=/pathexchanges retain their previous behavior. Already-in-flight redirects containing page queries still cannot be reconstructed exactly and remain subject toIncorrect redirect_uri. - Other tabs: Missing verifier/state still takes the existing “sign in to continue” path. Storage is tab-scoped; rejected callbacks and failed exchanges consume the saved destination, so they cannot recover it on retry.
- Security: No external redirect or script injection found through the stored route: navigation assigns
window.location.hash. Even accepted//evil.test/stays in the fragment. - Rewrite rules: Root query-code and
#state=callbacks remain compatible. The non-rootnextambiguity is finding 1. - UserIcon: No blocking issue found. Plain-object styles remain intact; arrays/functions now reach MUI icons unchanged and are omitted from image styles. Existing LeftMenu arrays contain only icon color, so image dimensions remain unchanged.
Validation used read-only execution of extracted helpers/components. Live Keycloak exchanges and browser visual rendering were not tested.
…next; sign-up and reset clear it; /signin and /signup aren't return pages From the Codex review of #58: - The page saved at sign-in now wins over next, which the callback rewrite also sets when LOGIN_REDIRECT_URL isn't the site root. - Sign-up and password reset clear a page left by an abandoned sign-in in the same tab, so they land on the dashboard as before. - /signin and /signup are refused as return pages, like /oidc/login, since landing on them starts another round trip. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
paynejd
left a comment
There was a problem hiding this comment.
Codex adversarial review, pass 2 (codex-cli 0.159.2, commit c6924a6)
One P2 remains; I would fix it before merging. No P0/P1 findings.
- P2 — Auth-route guard disagrees with router matching: src/common/utils.js:901. The regex is case-sensitive, but React Router matches
/signinand/signupcase-insensitively. A sign-in withreturnTo=https://…/#/SIGNUPstores/SIGNUP; after successful login, the callback navigates there and starts registration./SIGNINsimilarly starts another sign-in./signin#sectionalso bypasses the guard because#is missing from its delimiters. Suggested fix: extract the route pathname before?or#, then reject auth paths case-insensitively, including their subpaths. Verified these bypasses against the installed router.
The other two P2 fixes are resolved: the stored page wins for navigation while next still controls the exchange redirect_uri; register/reset clear stale destinations without storing a replacement. The third is resolved only for lowercase routes without a secondary fragment.
Reviewed every login caller, storage lifecycle, other-tab handling, rewrite rules, and direct/wrapped UserIcon callers. No additional introduced issue found. Stored routes only change the hash, with no external redirect or script execution. Old in-flight path-only sign-ins remain compatible; old query-bearing sign-ins retain their pre-existing exchange failure.
Read-only checks passed for callback precedence, stale clearing, and UserIcon rendering across image/icon and tooltip variants. git diff --check passed. No files edited.
…case-insensitively, like the router From the Codex review: the router matches /SIGNUP and /OIDC/login the same as their lowercase forms, and /signin#section slipped past the delimiters. The guard now tests the path before any ? or #, ignoring case. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…th once, like the router From the Codex review: history decodes a route path once before the router matches it, so /%73ignin or /%53IGNUP opened the sign-in or sign-up page after all. The guard now checks the decoded path, and a malformed encoding, which would make the router throw, isn't saved. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
paynejd
left a comment
There was a problem hiding this comment.
Codex adversarial review, pass 3 (codex-cli 0.159.2, commit c707d33)
One blocking finding remains:
- P2 — Encoded auth paths bypass the guard, src/common/utils.js:901. A sign-in started with
returnTo=https://app.example/#/%73igninsaves/%73ignin. After authentication, the callback restores it; router history appliesdecodeURI, matches/signin, and starts another sign-in. Likewise,/%53IGNUPstarts registration. Suggested fix: applydecodeURIto the extracted path before checking the regex, reject malformed encoding, and preserve the original route for restoration.
The newest commit resolves the reported case-sensitive and /signin#section bypasses. Twelve in-memory route checks passed, including queries, nested routes, and referrer values containing literal or encoded #.
Re-reviewed all four commits and git show HEAD; no other blocking findings. No files edited.
paynejd
left a comment
There was a problem hiding this comment.
Codex adversarial review, pass 4 (codex-cli 0.159.2, commit 9a1f83f)
Nothing blocking remains. No nonblocking findings identified.
Reviewed all five commits with git diff origin/main...HEAD and the newest fix with git show HEAD.
Verified that the guard rejects /%73ignin, /%53IGNUP, and malformed path encodings while preserving the original return route—including /orgs/My%20Org/, queries, and referrers containing encoded or literal #.
Executable checks of the guard, callback, and avatar rendering passed. ESLint for all three changed files and git diff --check passed.
No files edited. Verification used mocked token exchange, not a live Keycloak login.
closes OpenConceptLab/ocl_issues#2856
Two independent bugs, one commit each, then three small commits with fixes from the Codex review.
Sign-in fails with "Incorrect redirect_uri"
Links from openconceptlab.org land on
#/?referrer=…. Sign in used the whole page URL, query string included, as the OIDCredirect_uri, but the callback rebuilt it for the code exchange from the path alone, so Keycloak refused the exchange. Retrying from the callback page then carried that page's stalestateandcodeinto the next sign-in (theredirect_uriisn't URL-encoded, so its query spills into the outer one). That's why it took a few clicks, and why the eventual success landed on the empty/oidc/loginroute.Sign-in now always uses
LOGIN_REDIRECT_URL, and the page to return to (its hash route, query included) waits in this tab'ssessionStorage, next to the PKCE verifier, until the callback has exchanged the code. The community site already works this way. That page wins overnext, which the callback still uses to rebuild theredirect_urifor sign-ins that started before this deploy. Sign-up and password reset clear a page left by an abandoned sign-in, and/oidc/login,/signinand/signupare never return pages, in any letter case or percent-encoding, since the router ignores both (from Codex passes 1–3).Opening the menu blanks the app
UserIconspread itssxinto the<img>style. The left menu passessxas an array for followed items, so a followed user with a profile picture gotstyle={{0: {…}}}, React threw onstyle[0], and the app unmounted. Only people who follow a user with a photo hit it, which is why most accounts can't reproduce it. Only a plain-objectsxis used as the image style now, and the MUI icons getsxunchanged.Verified
#/?referrer=https://openconceptlab.org/failed with "Incorrect redirect_uri", then "Sign in to continue.", and only the third attempt succeeded. Opening the menu while following a user with a photo threwFailed to set an indexed property [0] on 'CSSStyleDeclaration'and emptied#root. With the same data minus the photo, it didn't.redirect_uriat both steps and nothing left insessionStorage.eslint --ext .jsx,.js src/is clean.Not in this PR
The Mapper shares this sign-in code, and its Sign in and start mapping fails the same way for visitors from openconceptlab.org: OpenConceptLab/ocl_issues#2857, fixed the same way in OpenConceptLab/oclmap#87.
🤖 Generated with Claude Code