Skip to content

feat(auth): portal redirect-only login, surface-aware OIDC error bounces, password-ops guard - #20

Merged
JOY (JOY) merged 1 commit into
devfrom
feat/portal-oidc-auto-redirect
Sep 21, 2026
Merged

JOY (JOY) merged 1 commit into
devfrom
feat/portal-oidc-auto-redirect

Conversation

@JOY

Copy link
Copy Markdown

Summary

Completes the Wave 2 redirect-only rollout that #18 started, now that #19 makes portal OIDC logins provision customer-type users:

  • Portal auto-redirect. /support/login now auto-redirects to DOS ID under the same conditions as the staff login: OIDC enabled, password login disabled, WxWork disabled, no break-glass ?direct=1, and no oidcError bounce (loop guard). Portal-origin logins provision customer-type users (fix(oidc): portal-origin logins provision customer-type users #19), so the redirect can never mint staff accounts.
  • Surface-aware error bounces. A failed OIDC round-trip now bounces back to the login surface that started it: /support/login?oidcError= for /support/* targets, /dashboard/login?oidcError= otherwise. The callback recovers the target from the signed state (NextFromState), so a portal customer is never stranded on the staff login page - where a retry would have provisioned them as an employee.
  • Persistent error + retry. Both login pages replace the transient oidcError toast with a persistent alert (title + server message + retry action that restarts the OIDC flow).
  • Password-ops guard (audit follow-up). While password login is disabled, changePassword (the shared choke point for /api/dashboard/user/reset_password and /api/dashboard/change_password) rejects any target that is not on the break-glass allowlist, so an SSO-only deployment never accumulates dormant local passwords that would come alive if the switch were flipped back. Break-glass admins keep the path; mixed mode is untouched.

Evidence

  • go vet -tags dev ./... clean; go test -tags dev green across the CI package list, including new tests: TestOIDCLoginErrorRedirectTargetsOriginSurface, six TestChangeOwnPassword* / TestResetPassword* guard-matrix cases.
  • cd web && pnpm typecheck clean. pnpm lint: changed files clean; the 6 reported errors are the pre-existing react-hooks debt documented as non-blocking in ci.yml (PROC-16).
  • Backend error key added to both zh-CN.yml and en-US.yml; auth.retry added to zh-CN / en-US / vi-VN messages (validated as JSON).

Notes for reviewers

  • UserService.ResetPassword / ChangeOwnPassword now take config.AuthConfig (handlers pass config.Current().Auth), matching how AuthService.Login already receives it - keeps the guard unit-testable without loading global config.
  • The error-bounce fix intentionally covers OIDCLogin (start failure) and both OIDCCallback failure paths (IdP oauth error, token/login failure). WxWork bounces still go to /login?wxworkError= (pre-existing, WxWork disabled in DOS deployments).

@coderabbitai

coderabbitai Bot commented Sep 21, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: b9ac81a4-fade-4886-ae45-14a47878ca4f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@dos dos Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⏱️ Code Review completed (14 files · 25,769 chars · 1 PR unit(s))

⏱️ Adversarial Review completed (Model: qwen3.8-27b)

🔍 Verified Adversarial Review Findings

🔴 BLOCKER

  • web/app/(support)/support/login/_components/login-page.tsx:155: Undefined function startOIDCLogin in SupportLoginPage
    • Failure Trace:
      1. The diff adds a "Try again" button to the OIDC error alert in SupportLoginPage (around line 155 in the new file context).
      2. The onClick handler is set to startOIDCLogin.
      3. The diff for login-page.tsx does not define a function named startOIDCLogin within the SupportLoginPage component scope.
      4. The diff for web/components/login-form.tsx defines startOIDCLogin, but login-page.tsx is a separate component file and does not import it.
      5. When the component renders and the user clicks the "Try again" button, JavaScript throws ReferenceError: startOIDCLogin is not defined, preventing the user from retrying the OIDC login flow.
    • Actionable Fix: Define the startOIDCLogin function within the SupportLoginPage component, mirroring the logic in login-form.tsx.
  const startOIDCLogin = () => {
    window.location.href = `/api/auth/oidc_login?next=${encodeURIComponent(nextDestination)}`
  }

(Insert this function definition inside the SupportLoginPage component, before the return statement.)

🛡️ Dismissed Claims

  • internal/handlers/api/auth_handler.go:120-121 Open Redirect: Dismissed. oidcLoginErrorRedirect uses hardcoded paths (/dashboard/login or /support/login) and does not reflect the next parameter in the redirect URL, preventing open redirects.
  • web/app/(support)/support/login/_components/login-page.tsx:55-58 Null Safety: Dismissed. shouldRedirectToOIDC correctly guards against publicConfig being null/undefined via Boolean(publicConfig && ...), and the useEffect early-returns if false.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request implements auto-redirect to OIDC for the customer support portal when OIDC is the only enabled transport, routes failed OIDC round-trips back to their originating login surface, and enforces a security guard that prevents setting local passwords in SSO-only mode unless the user is a break-glass principal. Feedback from the review highlights potential Open Redirect vulnerabilities on both the backend and frontend, recommending sanitization of the redirect path parameters to ensure only safe local paths are processed.

Comment on lines +103 to +104
next := ctx.Query("next")
loginURL, err := services.OIDCLoginService.BuildOIDCLoginURL(next)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security-high high

The next parameter is retrieved directly from the query string and passed to the OIDC login flow without validation. If an attacker provides a protocol-relative URL (e.g., //evil.com) or a path traversal sequence (e.g., /support/..//evil.com), it can bypass basic prefix checks on the frontend and lead to an Open Redirect vulnerability upon successful authentication. Sanitizing the next parameter here ensures that only safe local paths are processed.

	next := ctx.Query("next")
	if next != "" {
		if !strings.HasPrefix(next, "/") || strings.HasPrefix(next, "//") || strings.HasPrefix(next, "/\\") || strings.Contains(next, "..") || strings.Contains(next, "\\") {
			if services.IsSupportPortalNext(next) {
				next = "/support"
			} else {
				next = "/dashboard"
			}
		}
	}
	loginURL, err := services.OIDCLoginService.BuildOIDCLoginURL(next)

Comment on lines +118 to +120
function startOIDCLogin() {
window.location.href = `/api/auth/oidc_login?next=${encodeURIComponent(redirectPath)}`
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security-medium medium

As a defense-in-depth measure on the frontend, we should sanitize redirectPath before initiating the OIDC login flow to prevent potential open redirects via protocol-relative URLs or path traversal sequences.

Suggested change
function startOIDCLogin() {
window.location.href = `/api/auth/oidc_login?next=${encodeURIComponent(redirectPath)}`
}
function startOIDCLogin() {
const safePath = redirectPath.startsWith("//") || redirectPath.includes("..") ? "/dashboard" : redirectPath
window.location.href = "/api/auth/oidc_login?next=" + encodeURIComponent(safePath)
}

…ces, password-ops guard

- /support/login auto-redirects to OIDC when it is the only enabled
  transport, mirroring the staff login; suppressed on an ?oidcError bounce
  and the break-glass ?direct=1 form. Portal-origin logins provision
  customer-type users, so the redirect can never mint staff accounts
- both auto-redirects wait for the session probe (ready && !session), so
  an already-signed-in visitor is routed to their destination instead of
  being shipped to the IdP for a needless re-authentication
- failed OIDC round-trips bounce back to the surface that started them:
  /support/login?oidcError= for /support/* targets, /dashboard/login
  otherwise (recovered from the signed state on callback failures), so a
  portal customer is never stranded on the staff login page
- both login pages render a persistent DOS ID failure notice with a retry
  action instead of a transient toast
- while password login is disabled, password mutations
  (/api/dashboard/user/reset_password, /api/dashboard/change_password)
  reject any target that is not on the break-glass allowlist, so SSO-only
  deployments never accumulate dormant local passwords
- tests: error-redirect surface routing, password-ops guard matrix
@JOY
JOY (JOY) force-pushed the feat/portal-oidc-auto-redirect branch from 30eacc1 to a2059e1 Compare September 21, 2026 17:29
@JOY
JOY (JOY) merged commit 744364a into dev Sep 21, 2026
5 checks passed
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.

1 participant