Skip to content

refactor: move oidc handling to backend and add support for oidc post#923

Open
steveiliop56 wants to merge 25 commits into
mainfrom
refactor/oidc-authorize
Open

refactor: move oidc handling to backend and add support for oidc post#923
steveiliop56 wants to merge 25 commits into
mainfrom
refactor/oidc-authorize

Conversation

@steveiliop56
Copy link
Copy Markdown
Member

@steveiliop56 steveiliop56 commented Jun 6, 2026

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced Quick Actions menu combining language selection, theme switching, and logout functionality.
    • Added ScrollArea UI component for improved content display.
  • Refactors

    • Restructured OIDC authorization endpoint routing with updated completion workflow.
    • Unified authentication parameter handling across login, TOTP, continue, and logout flows.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 6, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

Consolidates UI language/theme controls into QuickActions; adds screen-parameter parsing/serialization; migrates frontend auth pages to login_for-driven routing and ticketed OIDC authorize-complete; refactors backend OAuth/OIDC contracts, controllers, and OIDC ticket lifecycle; updates proxy/middleware and tests.

Changes

OIDC Authorization Flow & Quick Actions UI

Layer / File(s) Summary
Frontend UI consolidation: Quick Actions and scroll primitives
frontend/src/components/layout/layout.tsx, frontend/src/components/quick-actions/quick-actions.tsx, frontend/src/components/ui/scroll-area.tsx, frontend/src/lib/i18n/locales/en-US.json, frontend/src/lib/i18n/locales/en.json
Adds QuickActions replacing separate ThemeToggle/LanguageSelector in layout; implements dropdown with language/theme/logout, introduces ScrollArea/ScrollBar primitives and adds i18n keys for quick-actions.
Screen-parameter utilities and login_for mapping
frontend/src/lib/hooks/screen-params.ts, frontend/src/lib/hooks/login-for.ts, frontend/src/lib/hooks/redirect-uri.ts
Adds useScreenParams and recompileScreenParams for parsing/serializing auth screen params, useLoginFor to map login_for to frontend routes, and updates useRedirectUri signature/guard behavior.
Frontend auth pages: migrate to screen-params & ticketed flow
frontend/src/main.tsx, frontend/src/pages/authorize-page.tsx, frontend/src/pages/continue-page.tsx, frontend/src/pages/login-page.tsx, frontend/src/pages/logout-page.tsx, frontend/src/pages/totp-page.tsx, frontend/src/pages/forgot-password-page.tsx, frontend/src/schemas/oidc-schemas.ts
Replaces useOIDCParams usage with useScreenParams/recompileScreenParams, posts authorize completion to /api/oidc/authorize-complete with a ticket, switches route to /oidc/authorize, and preserves compiled screen params across redirects.
Backend login/contracts and callback params
internal/controller/controller.go, internal/service/auth_service.go, go.mod
Adds FrontendLoginFor type and constants, extends RedirectQuery with LoginFor, replaces OAuthURLParams with OAuthCallbackParams including login_for and OIDC fields, updates AuthService.NewOAuthSession signature, and adds JWT dependency.
OIDC service: authorize-request tickets & JWT decoding
internal/service/oidc_service.go
Adds short-lived authorize-request cache and ticket lifecycle methods, updates AuthorizeRequest to embed jwt.Claims, and provides DecodeAuthorizeJWT to parse request-object JWTs.
Controller routing and authorize-complete flow
internal/controller/oauth_controller.go, internal/controller/oidc_controller.go, internal/bootstrap/router_bootstrap.go, internal/middleware/ui_middleware.go
Routes OIDC callback flow to /oidc/authorize, mounts /authorize on main router for frontend, adds /api/oidc/authorize-complete for JSON completion requiring auth, updates authorizeError to optionally return JSON, and treats authorize paths as UI pass-through in middleware.
Proxy & tests
internal/controller/proxy_controller.go, internal/controller/proxy_controller_test.go, internal/controller/oidc_controller_test.go
Includes login_for=app in proxy redirect queries; relaxes proxy redirect assertions to escaped-substring checks; rewrites OIDC controller tests to a table-driven suite exercising the new endpoints and ticket flows.
Dev tooling
frontend/vite.config.ts
Adds dev server proxy for /authorize → backend /authorize target.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • tinyauthapp/tinyauth#771: Refactors the same OIDC parameter parsing/hooks that this PR removes and replaces.
  • tinyauthapp/tinyauth#772: Modifies OAuth/OIDC session callback parameter preservation, overlapping backend callback-param changes.
  • tinyauthapp/tinyauth#605: Earlier OIDC authorize-page implementation that this PR migrates to a ticketed authorize-complete flow.

Suggested labels

lgtm

Suggested reviewers

  • scottmckendry
  • Rycochet

"I'm a rabbit with a tiny map and hat,
I tucked the language and theme in QuickActions' chat.
Tickets now travel from UI to code,
Params compiled stay on the road.
Hop—deploy—let the auth flow purr! 🥕"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main refactoring: moving OIDC handling logic to the backend and adding support for OIDC POST requests (via the new authorizeComplete endpoint).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/oidc-authorize

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

steveiliop56 and others added 2 commits June 6, 2026 18:05
@codecov
Copy link
Copy Markdown

codecov Bot commented Jun 7, 2026

@steveiliop56 steveiliop56 marked this pull request as ready for review June 8, 2026 09:24
@dosubot dosubot Bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Jun 8, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/controller/oidc_controller.go (1)

280-290: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Missing json: true for consistent JSON error response.

The authorizeComplete handler is designed to return JSON responses for frontend consumption (as noted in the comment on line 212-214). However, the error case at lines 280-290 doesn't set json: true, which would cause a 302 redirect instead of a JSON response, inconsistent with all other error paths in this handler.

Proposed fix
 	err = controller.oidc.DeleteOldSession(c, sub)
 	if err != nil {
 		controller.authorizeError(c, authorizeErrorParams{
 			err:           err,
 			reason:        "Failed to delete old sessions",
 			reasonPublic:  "Failed to delete old sessions",
 			callback:      authorizeReq.RedirectURI,
 			callbackError: "server_error",
 			state:         authorizeReq.State,
+			json:          true,
 		})
 		return
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/controller/oidc_controller.go` around lines 280 - 290, The error
path calls controller.authorizeError with an authorizeErrorParams struct but
omits the json flag, causing a redirect instead of the intended JSON response;
update the authorizeError invocation in the authorizeComplete handler (the block
that constructs authorizeErrorParams using err, reason "Failed to delete old
sessions", callback: authorizeReq.RedirectURI, callbackError: "server_error",
state: authorizeReq.State) to include json: true so the authorizeError function
returns a consistent JSON error response.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/src/components/quick-actions/quick-actions.tsx`:
- Around line 112-120: The menu trigger button in QuickActions (the <button>
rendering Avatar or the Settings icon) lacks an accessible name; update the
button to include an explicit accessible label (e.g., add aria-label="Open user
menu" or a dynamic aria-label based on auth state) and include
aria-haspopup="menu" and aria-expanded tied to the menu's open state (e.g.,
isMenuOpen) so assistive tech knows purpose and state; ensure the label text is
descriptive for both authenticated (Avatar) and unauthenticated (Settings) cases
and update any related state variables or props used by the menu toggle
functions (e.g., the click handler that opens the menu).

In `@frontend/src/lib/hooks/screen-params.ts`:
- Around line 29-33: The current serialization in screen-params.ts lets
undefined become the string "undefined" because the filter only excludes null;
update the filter used when building the URLSearchParams (the
Object.entries(params).filter(...) call that feeds p) to exclude both null and
undefined (e.g., use v != null or explicitly check v !== null && v !==
undefined) so only real string values are passed into URLSearchParams before
calling toString().

In `@frontend/src/pages/totp-page.tsx`:
- Around line 68-73: The redirect for unauthenticated users in the totp pending
check is losing screen params: when !totp.pending && !auth.authenticated the
code returns <Navigate to="/login" replace /> instead of preserving
compiledParams; update this branch to use the same login redirect as the
authenticated branch (use loginForUrl which already includes compiledParams or
append compiledParams to "/login") so both paths preserve screen params (refer
to totp.pending, auth.authenticated, and loginForUrl).

In `@internal/controller/proxy_controller_test.go`:
- Around line 80-82: The tests in proxy_controller_test.go use
assert.Contains(t, location, url.QueryEscape("app")) which can match any "app"
substring; update each check to assert the explicit login_for=app pair by either
parsing the redirect URL (use url.Parse(location) and url.ParseQuery(u.RawQuery)
then assert query["login_for"][0] == "app") or by asserting the encoded pair
directly (e.g., assert.Contains(t, location,
"login_for="+url.QueryEscape("app"))); apply this change for the occurrences
around lines 80-82, 95-97, 111-113, 129-131, 146-148, and 164-166 referencing
the same location variable used in the tests.

---

Outside diff comments:
In `@internal/controller/oidc_controller.go`:
- Around line 280-290: The error path calls controller.authorizeError with an
authorizeErrorParams struct but omits the json flag, causing a redirect instead
of the intended JSON response; update the authorizeError invocation in the
authorizeComplete handler (the block that constructs authorizeErrorParams using
err, reason "Failed to delete old sessions", callback: authorizeReq.RedirectURI,
callbackError: "server_error", state: authorizeReq.State) to include json: true
so the authorizeError function returns a consistent JSON error response.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e974aca6-a8fc-4d8b-a434-014d4a8b7555

📥 Commits

Reviewing files that changed from the base of the PR and between 426eac2 and 4e671ed.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (31)
  • frontend/src/components/language/language.tsx
  • frontend/src/components/layout/layout.tsx
  • frontend/src/components/quick-actions/quick-actions.tsx
  • frontend/src/components/theme-toggle/theme-toggle.tsx
  • frontend/src/components/ui/scroll-area.tsx
  • frontend/src/lib/hooks/login-for.ts
  • frontend/src/lib/hooks/oidc.ts
  • frontend/src/lib/hooks/redirect-uri.ts
  • frontend/src/lib/hooks/screen-params.ts
  • frontend/src/lib/i18n/locales/en-US.json
  • frontend/src/lib/i18n/locales/en.json
  • frontend/src/main.tsx
  • frontend/src/pages/authorize-page.tsx
  • frontend/src/pages/continue-page.tsx
  • frontend/src/pages/forgot-password-page.tsx
  • frontend/src/pages/login-page.tsx
  • frontend/src/pages/logout-page.tsx
  • frontend/src/pages/totp-page.tsx
  • frontend/src/schemas/oidc-schemas.ts
  • frontend/vite.config.ts
  • go.mod
  • internal/bootstrap/router_bootstrap.go
  • internal/controller/controller.go
  • internal/controller/oauth_controller.go
  • internal/controller/oidc_controller.go
  • internal/controller/oidc_controller_test.go
  • internal/controller/proxy_controller.go
  • internal/controller/proxy_controller_test.go
  • internal/middleware/ui_middleware.go
  • internal/service/auth_service.go
  • internal/service/oidc_service.go
💤 Files with no reviewable changes (4)
  • frontend/src/lib/hooks/oidc.ts
  • frontend/src/components/theme-toggle/theme-toggle.tsx
  • frontend/src/schemas/oidc-schemas.ts
  • frontend/src/components/language/language.tsx

Comment on lines +112 to +120
<button className="rounded-full transition-transform duration-200 will-change-transform hover:scale-105 hover:cursor-pointer focus:ring-0 focus:outline-3 focus:outline-ring/50">
{auth.authenticated ? (
<Avatar initial={initial!} />
) : (
<span className="bg-card text-primary border-border size-10 flex items-center justify-center rounded-full border shadow-lg">
<Settings className="size-4" />
</span>
)}
</button>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add an accessible name to the menu trigger.

The icon-only trigger at Line 112 has no aria-label, so assistive tech will announce a generic button with unclear purpose.

Suggested fix
-        <button className="rounded-full transition-transform duration-200 will-change-transform hover:scale-105 hover:cursor-pointer focus:ring-0 focus:outline-3 focus:outline-ring/50">
+        <button
+          aria-label={t("quickActionsOpenMenu")}
+          className="rounded-full transition-transform duration-200 will-change-transform hover:scale-105 hover:cursor-pointer focus:ring-0 focus:outline-3 focus:outline-ring/50"
+        >
// frontend/src/lib/i18n/locales/en.json + en-US.json
+  "quickActionsOpenMenu": "Open quick actions menu",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button className="rounded-full transition-transform duration-200 will-change-transform hover:scale-105 hover:cursor-pointer focus:ring-0 focus:outline-3 focus:outline-ring/50">
{auth.authenticated ? (
<Avatar initial={initial!} />
) : (
<span className="bg-card text-primary border-border size-10 flex items-center justify-center rounded-full border shadow-lg">
<Settings className="size-4" />
</span>
)}
</button>
<button
aria-label={t("quickActionsOpenMenu")}
className="rounded-full transition-transform duration-200 will-change-transform hover:scale-105 hover:cursor-pointer focus:ring-0 focus:outline-3 focus:outline-ring/50"
>
{auth.authenticated ? (
<Avatar initial={initial!} />
) : (
<span className="bg-card text-primary border-border size-10 flex items-center justify-center rounded-full border shadow-lg">
<Settings className="size-4" />
</span>
)}
</button>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/quick-actions/quick-actions.tsx` around lines 112 -
120, The menu trigger button in QuickActions (the <button> rendering Avatar or
the Settings icon) lacks an accessible name; update the button to include an
explicit accessible label (e.g., add aria-label="Open user menu" or a dynamic
aria-label based on auth state) and include aria-haspopup="menu" and
aria-expanded tied to the menu's open state (e.g., isMenuOpen) so assistive tech
knows purpose and state; ensure the label text is descriptive for both
authenticated (Avatar) and unauthenticated (Settings) cases and update any
related state variables or props used by the menu toggle functions (e.g., the
click handler that opens the menu).

Comment on lines +29 to +33
const p = new URLSearchParams(
Object.fromEntries(
Object.entries(params).filter(([, v]) => v !== null),
) as Record<string, string>,
).toString();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Do not serialize undefined values into query params.

At Line 31, filtering only null allows undefined through, which can produce ...=undefined in the URL when optional fields are present but unset.

Suggested fix
   const p = new URLSearchParams(
     Object.fromEntries(
-      Object.entries(params).filter(([, v]) => v !== null),
+      Object.entries(params).filter(([, v]) => v != null),
     ) as Record<string, string>,
   ).toString();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const p = new URLSearchParams(
Object.fromEntries(
Object.entries(params).filter(([, v]) => v !== null),
) as Record<string, string>,
).toString();
const p = new URLSearchParams(
Object.fromEntries(
Object.entries(params).filter(([, v]) => v != null),
) as Record<string, string>,
).toString();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/lib/hooks/screen-params.ts` around lines 29 - 33, The current
serialization in screen-params.ts lets undefined become the string "undefined"
because the filter only excludes null; update the filter used when building the
URLSearchParams (the Object.entries(params).filter(...) call that feeds p) to
exclude both null and undefined (e.g., use v != null or explicitly check v !==
null && v !== undefined) so only real string values are passed into
URLSearchParams before calling toString().

Comment on lines 68 to 73
if (!totp.pending) {
return <Navigate to="/" replace />;
if (auth.authenticated) {
return <Navigate to={loginForUrl} replace />;
}
return <Navigate to="/login" replace />;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Consider preserving screen params when redirecting unauthenticated users to login.

When !totp.pending && !auth.authenticated, the redirect to /login doesn't include compiledParams. Other pages (logout, continue, authorize) preserve screen params in their login redirects. This inconsistency could cause the user to lose their original flow context.

Proposed fix
   if (!totp.pending) {
     if (auth.authenticated) {
       return <Navigate to={loginForUrl} replace />;
     }
-    return <Navigate to="/login" replace />;
+    return <Navigate to={`/login${compiledParams}`} replace />;
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/totp-page.tsx` around lines 68 - 73, The redirect for
unauthenticated users in the totp pending check is losing screen params: when
!totp.pending && !auth.authenticated the code returns <Navigate to="/login"
replace /> instead of preserving compiledParams; update this branch to use the
same login redirect as the authenticated branch (use loginForUrl which already
includes compiledParams or append compiledParams to "/login") so both paths
preserve screen params (refer to totp.pending, auth.authenticated, and
loginForUrl).

Comment on lines +80 to +82
assert.Contains(t, location, url.QueryEscape("https://test.example.com/"))
assert.Contains(t, location, url.QueryEscape("app")) // login_for=app
assert.Contains(t, location, "https://tinyauth.example.com/login")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Assert login_for explicitly instead of matching "app" substring.

assert.Contains(location, url.QueryEscape("app")) only checks for "app" anywhere in the URL and can pass without the login_for key. Assert login_for=app directly (or parse query params and assert key/value).

Suggested assertion tightening
- assert.Contains(t, location, url.QueryEscape("app")) // login_for=app
+ assert.Contains(t, location, "login_for=app")

Also applies to: 95-97, 111-113, 129-131, 146-148, 164-166

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/controller/proxy_controller_test.go` around lines 80 - 82, The tests
in proxy_controller_test.go use assert.Contains(t, location,
url.QueryEscape("app")) which can match any "app" substring; update each check
to assert the explicit login_for=app pair by either parsing the redirect URL
(use url.Parse(location) and url.ParseQuery(u.RawQuery) then assert
query["login_for"][0] == "app") or by asserting the encoded pair directly (e.g.,
assert.Contains(t, location, "login_for="+url.QueryEscape("app"))); apply this
change for the occurrences around lines 80-82, 95-97, 111-113, 129-131, 146-148,
and 164-166 referencing the same location variable used in the tests.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
internal/controller/oidc_controller.go (2)

122-141: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle request objects from POST form bodies too.

With the new POST support, this branch only checks request in the URL query. If a client sends request=<jwt> in the form body, the code falls through to binding.Form, but service.AuthorizeRequest has no request field, so the JWT is dropped and validation runs against an empty request. That breaks POST request-object flows.

Suggested fix
-	// step 1: if we have a request object, decode it and ignore other params. If not, bind the params as usual
-	if raw := reqQueries.Get("request"); raw != "" {
+	// step 1: if we have a request object, decode it and ignore other params. If not, bind the params as usual
+	raw := reqQueries.Get("request")
+	if raw == "" && c.Request.Method == http.MethodPost {
+		raw = c.PostForm("request")
+	}
+	if raw != "" {
 		requestObject, err := controller.oidc.DecodeAuthorizeJWT(raw)
 		if err != nil {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/controller/oidc_controller.go` around lines 122 - 141, The code only
checks reqQueries.Get("request") (query string) and thus ignores a "request" JWT
sent in a POST form body; update the branch that handles request objects so it
also looks for the "request" parameter in POST form data before falling back to
binding. Specifically, inside the authorize handler around
reqQueries/Get("request") and before calling DecodeAuthorizeJWT, check for a
form "request" value when c.Request.Method == http.MethodPost (e.g., use
c.PostForm("request") or ensure c.Request.ParseForm() and inspect
c.Request.Form/PostForm), then call controller.oidc.DecodeAuthorizeJWT(raw) and
handle errors via controller.authorizeError with authorizeErrorParams as
currently done; if decoded, assign to req (the same variable used with
c.ShouldBindWith) and skip the binding. This keeps existing logic in
DecodeAuthorizeJWT, authorizeError, c.ShouldBindWith, binding.Form intact while
supporting POST request-object flows.

280-287: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep /oidc/authorize-complete errors on the JSON contract.

This handler is frontend-facing and already returns JSON on success and on its earlier failures, but these two branches drop back to redirect mode because json: true is missing. A backend error here will produce a 302 to the client callback instead of the {status, redirect_uri} payload the caller expects.

Suggested fix
 	err = controller.oidc.DeleteOldSession(c, sub)
 	if err != nil {
 		controller.authorizeError(c, authorizeErrorParams{
 			err:           err,
 			reason:        "Failed to delete old sessions",
 			reasonPublic:  "Failed to delete old sessions",
 			callback:      authorizeReq.RedirectURI,
 			callbackError: "server_error",
 			state:         authorizeReq.State,
+			json:          true,
 		})
 		return
 	}
@@
 	if err != nil {
 		controller.authorizeError(c, authorizeErrorParams{
 			err:           err,
 			reason:        "Failed to build query",
 			reasonPublic:  "Failed to build query",
 			callback:      authorizeReq.RedirectURI,
 			callbackError: "server_error",
 			state:         authorizeReq.State,
+			json:          true,
 		})
 		return
 	}

Also applies to: 300-307

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/controller/oidc_controller.go` around lines 280 - 287, The authorize
error branches in the authorize-complete handler call controller.authorizeError
without json=true, causing redirects instead of the expected JSON {status,
redirect_uri} contract; update both calls that construct authorizeErrorParams
(the one passing reason "Failed to delete old sessions" and the later similar
branch around the authorizeError invocation at the 300-307 range) to include
json: true so controller.authorizeError returns a JSON response; locate the
calls to controller.authorizeError and add json: true to the
authorizeErrorParams passed to it.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@internal/controller/oidc_controller.go`:
- Around line 122-141: The code only checks reqQueries.Get("request") (query
string) and thus ignores a "request" JWT sent in a POST form body; update the
branch that handles request objects so it also looks for the "request" parameter
in POST form data before falling back to binding. Specifically, inside the
authorize handler around reqQueries/Get("request") and before calling
DecodeAuthorizeJWT, check for a form "request" value when c.Request.Method ==
http.MethodPost (e.g., use c.PostForm("request") or ensure c.Request.ParseForm()
and inspect c.Request.Form/PostForm), then call
controller.oidc.DecodeAuthorizeJWT(raw) and handle errors via
controller.authorizeError with authorizeErrorParams as currently done; if
decoded, assign to req (the same variable used with c.ShouldBindWith) and skip
the binding. This keeps existing logic in DecodeAuthorizeJWT, authorizeError,
c.ShouldBindWith, binding.Form intact while supporting POST request-object
flows.
- Around line 280-287: The authorize error branches in the authorize-complete
handler call controller.authorizeError without json=true, causing redirects
instead of the expected JSON {status, redirect_uri} contract; update both calls
that construct authorizeErrorParams (the one passing reason "Failed to delete
old sessions" and the later similar branch around the authorizeError invocation
at the 300-307 range) to include json: true so controller.authorizeError returns
a JSON response; locate the calls to controller.authorizeError and add json:
true to the authorizeErrorParams passed to it.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 84353ac8-7966-45fb-b3fa-3b3f782cc748

📥 Commits

Reviewing files that changed from the base of the PR and between 4e671ed and 3c9817c.

📒 Files selected for processing (3)
  • internal/controller/oidc_controller.go
  • internal/controller/oidc_controller_test.go
  • internal/service/oidc_service.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • internal/service/oidc_service.go
  • internal/controller/oidc_controller_test.go

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

Labels

size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant