Skip to content

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

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

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

Conversation

@steveiliop56

@steveiliop56 steveiliop56 commented Jun 6, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features

    • Quick Actions menu (avatar) with language, theme, and logout controls.
    • ScrollArea UI component for improved scrollable content.
  • Refactors

    • Major OIDC/authorize flow and routing rework with ticket-based authorize completion.
    • Login/continue/totp/logout flows unified to use shared screen-params and a single login-target indicator.
    • Frontend now routes authorize traffic under /oidc/authorize.
  • Localization

    • Added Quick Actions translation keys and updated authorization error message.

@coderabbitai

coderabbitai Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0dbdda6f-5762-44f7-8920-7324c9b5b527

📥 Commits

Reviewing files that changed from the base of the PR and between 6a4d85d and 5c5d7a4.

📒 Files selected for processing (4)
  • frontend/src/lib/hooks/screen-params.ts
  • frontend/src/lib/i18n/locales/en-US.json
  • frontend/src/lib/i18n/locales/en.json
  • frontend/src/pages/authorize-page.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • frontend/src/lib/i18n/locales/en-US.json
  • frontend/src/lib/i18n/locales/en.json
  • frontend/src/pages/authorize-page.tsx

📝 Walkthrough

Walkthrough

Consolidates UI language/theme into QuickActions; adds screen-parameter parsing/serialization and login_for routing; migrates frontend auth pages to ticketed OIDC authorize-complete; and refactors backend OAuth/OIDC contracts, services, controllers, 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

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 primary changes: moving OIDC authorization handling from frontend to backend and adding support for OIDC POST requests via the new authorize-complete 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

codecov Bot commented Jun 7, 2026

Copy link
Copy Markdown

@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

@coderabbitai coderabbitai Bot left a comment

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.

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 thread frontend/src/components/quick-actions/quick-actions.tsx Outdated
Comment thread frontend/src/lib/hooks/screen-params.ts
Comment thread frontend/src/pages/totp-page.tsx
Comment thread internal/controller/proxy_controller_test.go

@coderabbitai coderabbitai Bot left a comment

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.

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

Comment thread frontend/src/pages/authorize-page.tsx
Comment thread frontend/src/lib/hooks/screen-params.ts Outdated
Comment thread frontend/src/pages/authorize-page.tsx Outdated
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