Skip to content

EDM-2373: Support multiple authentication providers#378

Merged
celdrake merged 5 commits into
flightctl:mainfrom
celdrake:EDM-2373-support-multiple-auth-providers
Nov 24, 2025
Merged

EDM-2373: Support multiple authentication providers#378
celdrake merged 5 commits into
flightctl:mainfrom
celdrake:EDM-2373-support-multiple-auth-providers

Conversation

@celdrake

@celdrake celdrake commented Nov 18, 2025

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • Full Authentication Providers UI: list, create, edit, details, delete, YAML view, per-item actions, and test-connection modal.
    • Login UX: provider selector, provider-redirect flows, Kubernetes token form, and themed (dark/light) login layout.
    • Settings shortcut in top navigation to manage providers.
  • Improvements

    • Provider-driven login flow with clearer loading/error states and permission-gated actions.
    • Expanded localization for authentication provider workflows.
  • Bug Fixes

    • Alerts/auth handling refined (401 treated consistently for alerts/login flows).

✏️ Tip: You can customize this high-level summary in your review settings.

// For 500/501 errors, return the status code for detection
if (response.status === 500 || response.status === 501) {
// The UI proxy should convert alert errors 401 to 501, but we guard against 401 here as well.
const isAlertsDisabled = response.status === 500 || response.status === 501 || response.status === 401;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Now rather than 401 we should be getting proper statusCode 403 when the user does not have permissions, but since it's a bit unstable I'd rather keep converting 401 to 501 and disabling alerts with 401.

Comment thread libs/ui-components/src/utils/k8sProvider.ts Outdated
Comment thread proxy/auth/aap.go
}

func getAAPAuthHandler(authURL string, internalAuthURL *string) (*AAPAuthHandler, error) {
func getAAPAuthHandler(provider *v1alpha1.AuthProvider, aapSpec *v1alpha1.AapProviderSpec) (*AAPAuthHandler, error) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

aap is still WIP, the clientId should be retrieved from the config, etc.


const secretAppClientSecret = 'CLIENT-SECRET-1';

const githubValues = {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I need to remove this, but it's just a quick way to fill in the details and start testing.

Comment thread libs/ui-components/src/components/common/PageNavigation.tsx Outdated
@celdrake celdrake marked this pull request as ready for review November 19, 2025 12:20
@coderabbitai

coderabbitai Bot commented Nov 19, 2025

Copy link
Copy Markdown

Walkthrough

Adds a provider-driven authentication feature across frontend and backend: admin UI for managing auth providers (CRUD, test, details), provider-aware auth handlers with PKCE and token-data model changes, a test-connection endpoint, many new TS types/UI form helpers, YAML/editor and RBAC updates, and removal of two proxy config vars and their docs entries.

Changes

Cohort / File(s) Summary
Configuration & proxy vars
CONFIGURATION.md, proxy/config/config.go
Removed INTERNAL_AUTH_URL and AUTH_CLIENT_ID from documentation and proxy configuration.
Frontend login & routing
apps/standalone/src/app/components/Login/LoginPage.tsx, apps/standalone/src/app/components/Login/LoginPageLayout.tsx, apps/standalone/src/app/routes.tsx, apps/ocp-plugin/src/components/AppContext/AppContext.tsx
Replaced token-centric login with provider-driven flows, added themed LoginPageLayout, provider selector and token form, redirect logic, new auth-provider routes and client-side auth redirects.
Auth context & client utils
apps/standalone/src/app/context/AuthContext.ts, apps/standalone/src/app/utils/apiCalls.ts, libs/ui-components/src/hooks/useAlertsEnabled.ts, apps/standalone/src/app/utils/apiCalls.ts
Enhanced callback/user-info error handling; made redirectToLogin synchronous; unified alerts handling to treat 401 as "alerts disabled" alongside 500/501; changed 401 redirect behavior to always invoke redirectToLogin.
Admin UI — auth-provider feature
libs/ui-components/src/components/AuthProvider/*, libs/ui-components/src/components/Login/*, libs/ui-components/src/components/form/*, libs/ui-components/src/components/common/CodeEditor/YamlEditor.tsx, libs/ui-components/src/components/DetailsPage/DetailsPage.tsx, libs/ui-components/src/components/OverviewPage/Cards/Alerts/AlertsCard.tsx
Large new feature: list/create/edit/detail/YAML views, Formik forms with validation and patching, test-connection modal, delete modal, provider selector and token login form, ListItemField and other form helpers, Row/Table components, and UI text updates including "Authentication provider" label.
Create/edit form utils & hooks
libs/ui-components/src/components/AuthProvider/CreateAuthProvider/*, libs/ui-components/src/components/AuthProvider/useAuthProviders.ts, libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts, .../types.ts
New form components, type guards, init/patch builders, JSON Patch generation, validation schema, role/org assignment UI and helpers, and periodic provider hook.
Types & models (TS)
libs/types/index.ts, libs/types/models/* (many files)
Added many generated types (TokenRequest/TokenResponse/UserInfoResponse, OpenShiftProviderSpec, ApplicationPort/ApplicationResources/VolumeMount/ImageMount/MountVolume/VolumeMount), added AuthDynamicRoleAssignment.separator, removed K8sProviderSpec.externalOpenShiftApiUrl, appended AppType container member, and updated exports.
UI types, RBAC & utils
libs/ui-components/src/types/extraTypes.ts, libs/ui-components/src/types/rbac.ts, libs/ui-components/src/utils/*, libs/ui-components/src/utils/patch.ts
Added DynamicAuthProviderSpec, ProviderType enum, isDynamicAuthProvider, AUTH_PROVIDER resource, getProviderDisplayName, JWT validator and nowInSeconds, and adjusted appendJSONPatch boolean handling.
Proxy auth refactor & handlers (Go)
proxy/auth/*.go (auth.go, common.go, k8s.go, oidc.go, aap.go, openshift.go, oauth2.go, api_client.go)
Major refactor: provider-spec-driven auth handlers, PKCE support, TokenData (IDToken/AccessToken), provider resolution, backend API token-exchange client, new OAuth2/OpenShift handlers, changed method signatures for GetUserInfo/GetLoginRedirectURL and other flows; removed old oauth.go.
Proxy bridge, test endpoint & app
proxy/bridge/handler.go, proxy/bridge/testauth.go, proxy/app.go
Added createAlertsReverseProxy (Alerts 401→501), clear-site-data on backend 401s, and registered /test-auth-provider-connection TestAuthHandler endpoint with discovery and endpoint reachability checks and SSRF protections.
Proxy middleware & routing tweaks
proxy/middleware/auth.go, proxy/middleware/organization.go
Use TokenData.GetAuthToken() when injecting Authorization header; exclude auth config endpoint from org_id query injection.
Deps & i18n
proxy/go.mod, libs/i18n/locales/en/translation.json
Bumped flightctl dependency; removed legacy token localization keys and added extensive auth-provider related translations and renaming ("Auth provider" → "Authentication provider").

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant FE as Frontend (Login)
    participant Proxy as Backend (proxy/auth)
    participant IDP as Identity Provider
    participant Test as TestAuthHandler

    User->>FE: GET /login
    FE->>Proxy: GET /api/auth/config
    Proxy-->>FE: list of providers

    alt Token (k8s) provider (direct token)
        User->>FE: submit token
        FE->>Proxy: POST /login?provider=k8s (token)
        Proxy-->>FE: session/token response
        FE->>User: redirect to /
    else OAuth/OIDC/OpenShift provider (PKCE)
        FE->>FE: generate PKCE (code_challenge, verifier)
        FE->>Proxy: request login redirect (state + code_challenge)
        Proxy->>IDP: redirect user to provider auth endpoint
        IDP-->>Proxy: callback with code & state
        Proxy->>Proxy: exchange code via backend/API server (PKCE)
        Proxy-->>FE: set session cookie / token data
        FE->>User: redirect to /
    end

    Note over User,Test: Admin tests provider connection
    User->>Test: POST /test-auth-provider-connection (config)
    Test->>Test: discovery & endpoint reachability checks (SSRF-safe)
    Test-->>User: field-level validation results
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas to focus during review:

  • proxy/auth/* — provider resolution, PKCE flows, TokenData changes, altered method signatures and API-client interactions.
  • proxy/bridge/testauth.go & bridge/handler.go — endpoint reachability checks, SSRF protections, alerts reverse-proxy behavior.
  • apps/standalone LoginPage and AuthContext — provider-driven login, state/code parsing, redirect behavior and error flows.
  • libs/ui-components AuthProvider create/edit utils/form validation and JSON patch generation, and new UI components wiring.

Possibly related PRs

Suggested reviewers

  • rawagner
  • fzdarsky

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 72.97% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately and concisely summarizes the primary change: adding support for multiple authentication providers across the codebase.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Comment thread proxy/auth/api_client.go Outdated

@coderabbitai coderabbitai 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.

Actionable comments posted: 9

Caution

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

⚠️ Outside diff range comments (1)
proxy/auth/auth.go (1)

289-315: Token provider POST login ignores io.ReadAll errors

In the K8s token login branch:

body, err := io.ReadAll(r.Body)
err = json.Unmarshal(body, &loginParams)
if err != nil { ... }

If io.ReadAll fails, its error is discarded and only the unmarshal error is seen, which can hide I/O issues and potentially mis‑classify bad requests vs server problems. Consider the same pattern you already use in the non‑K8s branch:

- body, err := io.ReadAll(r.Body)
- err = json.Unmarshal(body, &loginParams)
- if err != nil {
+ body, err := io.ReadAll(r.Body)
+ if err != nil {
+   log.GetLogger().WithError(err).Warn("Failed to read request body")
+   w.WriteHeader(http.StatusBadRequest)
+   return
+ }
+ if err := json.Unmarshal(body, &loginParams); err != nil {
    log.GetLogger().WithError(err).Warn("Failed to unmarshal request body")
    w.WriteHeader(http.StatusBadRequest)
    return
  }

This keeps diagnostics consistent across branches.

♻️ Duplicate comments (4)
apps/ocp-plugin/src/utils/apiCalls.ts (1)

56-71: 401-as-disabled behavior is consistent with existing alerts handling

Treating 401 like 500/501 for alerts (returning Error('${response.status}')) aligns with the comment and existing pattern for signaling “alerts disabled” to callers. The special-case 404 handling is preserved, and all other statuses still go through getErrorMsgFromAlertsApiResponse, so behavior remains predictable.

libs/ui-components/src/components/AuthProvider/CreateAuthProvider/CreateAuthProviderForm.tsx (1)

32-56: Remove or behind-flag the hard-coded “test provider” presets before shipping

The blue‑bordered “Test provider” section plus githubValues/googleValues constants are clearly a temporary testing harness, including fake client IDs/secrets and canned org/role settings.

Leaving this in production:

  • Clutters the UX for real users.
  • Risks accidental use of placeholder config.
  • Conflicts with your own earlier note about removing it.

I’d either:

  • Strip this block and related constants entirely, or
  • Hide it behind a dev‑only flag (e.g., build‑time env) if you still need it locally.

Also applies to: 135-203

proxy/auth/aap.go (1)

53-105: AAP handler follows the new pattern but hard‑codes clientId, which should eventually come from config/spec

The AAP changes align with the other providers:

  • getAAPAuthHandler now derives URLs from AapProviderSpec (internal vs external) and builds both external and internal clients.
  • getClient enforces a non‑empty clientId and wires PKCE‑friendly osincli.ClientConfig.
  • GetToken, RefreshToken, GetLoginRedirectURL, and Logout all follow the shared patterns.

However, getAAPAuthHandler currently hard‑codes:

clientId := "flightctl-ui"

while comments and past reviews indicate this should eventually be sourced from configuration / CRD. Combined with getClientIdFromProviderConfig in auth.go still returning an error for ProviderTypeAAP, this leaves AAP in an incomplete, WIP state for the main login/refresh paths.

Non‑blocking for now if AAP isn’t exposed to users yet, but before enabling it in production you’ll want to:

  1. Add a clientId field to the AAP provider spec or a dedicated config source.
  2. Plumb that value into both getAAPAuthHandler and getClientIdFromProviderConfig.
  3. Remove or adjust the hard‑coded "flightctl-ui" default.

Tagging as a duplicate of the earlier clientId comment so it stays on your radar.

Also applies to: 107-181

proxy/auth/auth.go (1)

53-151: Provider lookup / instantiation logic is solid, but AAP cannot yet participate in BFF token flows

The findProviderConfig + getProviderInstance pipeline is a good abstraction and the OpenShift vs vanilla K8s split via ExternalOpenShiftApiUrl is reasonable. One important gap:

  • getClientIdFromProviderConfig currently returns an error for ProviderTypeAAP and for non‑OpenShift K8s. Because Login (POST) and Refresh rely on getClientIdFromProviderConfig for all non‑K8s‑token providers, AAP providers effectively cannot use the new backend BFF token exchange at all; they will consistently hit the "AAP providers client_id needs to be retrieved" path and surface a 500.

If AAP isn’t meant to be enabled yet, that’s acceptable as an intentional limitation, but consider either:

  • Explicitly rejecting AAP providers at configuration time with a clearer message, or
  • Wiring ClientId from the AAP provider spec / config into getClientIdFromProviderConfig as soon as the backend surface is ready.

Otherwise the behavior is confusing: AAP looks like a first‑class provider type but is unusable in the main login/refresh paths.

🧹 Nitpick comments (43)
proxy/bridge/handler.go (1)

60-89: Refactor to eliminate code duplication and verify the 501 status code choice.

This function duplicates most of createReverseProxy. Consider extracting the common proxy setup logic and passing the 401 handling behavior as a parameter or callback.

Additionally, converting 401 to 501 "Not Implemented" may mislead clients and monitoring tools. The comment mentions "disabled", but 501 typically indicates unsupported HTTP methods, not disabled services.

Suggested refactor to reduce duplication:

+type responseModifier func(*http.Response) error
+
+func createReverseProxyWithModifier(apiURL string, modifier responseModifier) (*url.URL, *httputil.ReverseProxy) {
+	target, err := url.Parse(apiURL)
+	if err != nil {
+		log.GetLogger().WithError(err).Errorf("Failed to parse URL '%s'", apiURL)
+		os.Exit(1)
+	}
+	proxy := httputil.NewSingleHostReverseProxy(target)
+	proxy.ModifyResponse = func(r *http.Response) error {
+		filterHeaders := []string{
+			"Access-Control-Allow-Headers",
+			"Access-Control-Allow-Methods",
+			"Access-Control-Allow-Origin",
+			"Access-Control-Expose-Headers",
+		}
+		for _, h := range filterHeaders {
+			r.Header.Del(h)
+		}
+		
+		if modifier != nil {
+			return modifier(r)
+		}
+		return nil
+	}
+	return target, proxy
+}
+
 func createReverseProxy(apiURL string) (*url.URL, *httputil.ReverseProxy) {
-	target, err := url.Parse(apiURL)
-	if err != nil {
-		log.GetLogger().WithError(err).Errorf("Failed to parse URL '%s'", apiURL)
-		os.Exit(1)
-	}
-	proxy := httputil.NewSingleHostReverseProxy(target)
-	proxy.ModifyResponse = func(r *http.Response) error {
-		filterHeaders := []string{
-			"Access-Control-Allow-Headers",
-			"Access-Control-Allow-Methods",
-			"Access-Control-Allow-Origin",
-			"Access-Control-Expose-Headers",
-		}
-		for _, h := range filterHeaders {
-			r.Header.Del(h)
-		}
-
+	return createReverseProxyWithModifier(apiURL, func(r *http.Response) error {
 		// If the backend returns 401 Unauthorized, clear the session cookie
 		// This handles the case where the token in the cookie has expired
 		if r.StatusCode == http.StatusUnauthorized {
 			r.Header.Set("Clear-Site-Data", `"cookies"`)
 			log.GetLogger().Debug("Backend returned 401, clearing session cookies")
 		}
-
 		return nil
-	}
-	return target, proxy
+	})
 }
 
 func createAlertsReverseProxy(apiURL string) (*url.URL, *httputil.ReverseProxy) {
-	target, err := url.Parse(apiURL)
-	if err != nil {
-		log.GetLogger().WithError(err).Errorf("Failed to parse URL '%s'", apiURL)
-		os.Exit(1)
-	}
-	proxy := httputil.NewSingleHostReverseProxy(target)
-	proxy.ModifyResponse = func(r *http.Response) error {
-		filterHeaders := []string{
-			"Access-Control-Allow-Headers",
-			"Access-Control-Allow-Methods",
-			"Access-Control-Allow-Origin",
-			"Access-Control-Expose-Headers",
-		}
-		for _, h := range filterHeaders {
-			r.Header.Del(h)
-		}
-
+	return createReverseProxyWithModifier(apiURL, func(r *http.Response) error {
 		// For alerts API, we may sometimes receive 401 instead of 403.
 		// To prevent login out the user, we convert 401 to 501.
 		if r.StatusCode == http.StatusUnauthorized {
 			r.StatusCode = http.StatusNotImplemented
 			r.Status = "501 Not Implemented"
 			log.GetLogger().Debug("Alerts API returned 401, converting to 501 (disabled)")
 		}
-
 		return nil
-	}
-	return target, proxy
+	})
 }

Regarding the 501 status code:

Please verify that 501 "Not Implemented" is the appropriate choice. Consider these alternatives:

  • 503 Service Unavailable if alerts are temporarily disabled
  • 403 Forbidden if the user lacks permissions for alerts
  • 404 Not Found if the alerts feature is completely disabled/unavailable

You may also want to check whether clients and monitoring systems correctly interpret this status code as "alerts disabled" rather than "server doesn't support this method."

libs/ui-components/src/components/form/NameField.tsx (1)

75-83: Disabled helper text wiring looks correct and non-breaking.

Plumbing helperText into the disabled path via DefaultHelperText is consistent with the existing Formik setup, avoids leaking helperText into TextInput via ...rest, and keeps the public NameFieldProps surface unchanged.

libs/ui-components/src/utils/patch.ts (1)

23-54: Boolean-safe JSON patch behavior is an improvement

Guarding the remove case with typeof newValue !== 'boolean' ensures boolean fields are never dropped when toggled to false, but are instead patched via add/replace, which is more intuitive for flags. One small follow-up you might consider is documenting (or asserting) that encodeB64 is only used for string values, since we now more often pass non-string newValue through this helper as well.

libs/types/models/AuthDynamicRoleAssignment.ts (1)

8-21: Separator field is useful, but consider regenerating from OpenAPI

Adding separator?: string (with the detailed JSDoc) matches the dynamic role semantics used elsewhere and should make UI code safer and clearer. Since this file is tagged as generated by openapi-typescript-codegen, it would be better long term to update the OpenAPI schema and regenerate types so this property doesn’t get lost on the next codegen run.

libs/ui-components/src/components/Login/TokenLoginForm.tsx (1)

33-99: Submit flow and error handling look solid; consider reusing shared utilities

The overall flow (clearing local storage, posting a trimmed token, parsing JSON/non‑JSON errors, setting an expiry, then redirecting) is clean and defensive. Two small follow-ups you might consider:

  • Reuse a shared nowInSeconds helper (and, if one exists elsewhere, a shared EXPIRATION storage key) instead of re‑defining them locally to avoid drift with other login flows.
  • If other callers rely on the same login?provider= endpoint, double‑check that proxyFetch sets the appropriate JSON Content-Type header so the backend can reliably parse { token }.

Neither is blocking, but they would help keep auth behavior consistent across providers.

libs/ui-components/src/components/form/ListItemField.tsx (1)

9-120: Harden ListItemField against undefined initial values and simplify async usage

Nice reusable component; the LabelGroup + EditableLabelControl pattern fits well. Two small robustness refactors to consider:

  • useField<string[]>(name) will give value as undefined if the form’s initialValues forget to initialize this field to []; items.map(...) would then throw. You could defensively normalize with something like const items = Array.isArray(field.value) ? field.value : []; to make the component safer in future forms.
  • updateItems, onDelete, onAdd, and onEdit are marked async and await setItems(...), but Formik’s setValue is synchronous and returns void. Dropping async/await here would simplify the code without changing behavior.

These are non-blocking refinements that make the component more resilient and clearer.

libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts (1)

471-483: Consider avoiding then properties in Yup .when config to satisfy noThenProperty

Biome flags the then keys in the .when('roleAssignmentType', { is, then }) configs as suspicious. You can express the same logic using the function form of when, which avoids a then property entirely and keeps the intent clear.

-    roleClaimPath: Yup.array()
-      .of(Yup.string())
-      .when('roleAssignmentType', {
-        is: RoleAssignmentType.Dynamic,
-        then: (schema) => schema.min(1, t('At least one claim path segment is required for dynamic role assignment')),
-      }),
+    roleClaimPath: Yup.array()
+      .of(Yup.string())
+      .when('roleAssignmentType', (roleAssignmentType, schema) =>
+        roleAssignmentType === RoleAssignmentType.Dynamic
+          ? schema.min(1, t('At least one claim path segment is required for dynamic role assignment'))
+          : schema,
+      ),
@@
-    staticRoles: Yup.array()
-      .of(Yup.string())
-      .when('roleAssignmentType', {
-        is: RoleAssignmentType.Static,
-        then: (schema) => schema.min(1, t('At least one role is required for static role assignment')),
-      }),
+    staticRoles: Yup.array()
+      .of(Yup.string())
+      .when('roleAssignmentType', (roleAssignmentType, schema) =>
+        roleAssignmentType === RoleAssignmentType.Static
+          ? schema.min(1, t('At least one role is required for static role assignment'))
+          : schema,
+      ),

Please double-check against the Yup docs for your exact version to confirm the when builder signature.

proxy/auth/api_client.go (1)

217-227: Optional: fall back to sub when preferred_username is missing

Right now a missing preferred_username is treated as an error even if sub is present. Depending on backend behavior, it might be more robust to fall back to sub (or another stable identifier) instead of failing outright.

-	if userInfoResp.PreferredUsername == nil {
-		return "", fmt.Errorf("userinfo response missing preferred_username")
-	}
-
-	username := *userInfoResp.PreferredUsername
+	var username string
+	if userInfoResp.PreferredUsername != nil {
+		username = *userInfoResp.PreferredUsername
+	} else if userInfoResp.Sub != nil {
+		username = *userInfoResp.Sub
+	} else {
+		return "", fmt.Errorf("userinfo response missing preferred_username and sub")
+	}
libs/ui-components/src/components/AuthProvider/AuthProviderDetails/RoleAssigmentDetails.tsx (1)

14-15: Typo in component name (RoleAssigmentDetails vs RoleAssignmentDetails)

The component/file name is missing an “n” (“Assigment”). Not a functional issue, but it may cause minor confusion when searching the codebase.

If feasible, consider renaming the file and component to RoleAssignmentDetails and updating imports accordingly.

Also applies to: 53-53

proxy/middleware/auth.go (1)

17-21: Using GetAuthToken() and guarding on empty tokens is appropriate

Switching to tokenData.GetAuthToken() and only injecting the header when non-empty aligns this middleware with the new token abstraction.

As a minor tweak, you might prefer r.Header.Set(common.AuthHeaderKey, "Bearer "+token) instead of Add to avoid any chance of multiple Authorization headers if something upstream already set one.

apps/standalone/src/app/routes.tsx (2)

358-399: Auth gating is fine, but consider avoiding direct window access in render

The authentication gating logic is straightforward and correctly skips redirects when authEnabled is false or username is present. However, you might want to eventually:

  • Derive pathname from useLocation() instead of window.location to align with react-router and make testing easier.
  • Consider performing the redirect in an effect (useEffect) using the router’s navigation APIs instead of mutating window.location inside render, unless a full page reload is explicitly required.

This is non-blocking and can be deferred.


406-409: Duplicate /callback route definitions may be unnecessary

There is a top-level /callback route here and another /callback route returned from getAppRoutes. They currently redirect to different targets ('/' vs '/overview'). It’s worth double‑checking that you really need both and that the chosen redirect target is intentional for all callback flows; otherwise, consider consolidating to a single canonical /callback route.

libs/ui-components/src/components/AuthProvider/CreateAuthProvider/Oauth2ProviderFields.tsx (1)

7-21: OAuth2 fields component is straightforward; consider tightening input semantics

The three required fields map cleanly to OAuth2ProviderSpec (authorizationUrl, tokenUrl, userinfoUrl) and use translations consistently. If TextField supports it, consider setting an appropriate type (e.g., "url") for these fields to get native browser validation and better UX around URL entry.

libs/ui-components/src/utils/k8sProvider.ts (1)

4-22: Tighten JWT format check and make K8s provider detection more robust

A few small edge cases worth considering here:

  • isValidJwtTokenFormat (Lines 4–12): the base64url regex ^[A-Za-z0-9_-]+$ will reject segments that include = padding. Some JWT libraries emit padded base64url; if you expect those, consider allowing = at the end, or document that this is intentionally strict.
  • nowInSeconds (Line 14): for epoch seconds, Math.floor(Date.now() / 1000) is more conventional and avoids rounding into the future by up to 0.5s, which can matter for expiry comparisons.
  • isK8sTokenProvider (Lines 16–21): the comment says OpenShift providers are providerType === K8s with externalOpenShiftApiUrl set and different from apiUrl. The current predicate (externalOpenShiftApiUrl === apiUrl) will return false for K8s providers where externalOpenShiftApiUrl is simply unset. If “plain K8s” is represented as externalOpenShiftApiUrl absent/empty, you may want something like:
export const isK8sTokenProvider = (provider: AuthProvider): boolean => {
  if (provider.spec.providerType !== ProviderType.K8s) {
    return false;
  }
  const { externalOpenShiftApiUrl, apiUrl } = provider.spec;
  // Treat as OpenShift only when an explicit, different external OCP URL is configured
  return !externalOpenShiftApiUrl || externalOpenShiftApiUrl === apiUrl;
};

Can you confirm how externalOpenShiftApiUrl is populated for non-OCP K8s providers and whether the undefined/empty case should be treated as a K8s token provider?

apps/standalone/src/app/components/Login/LoginPageLayout.tsx (1)

4-38: Consider reusing shared theme utility and guarding for non-browser environments

Two minor points here:

  • updateThemeClass duplicates the logic from libs/ui-components/src/hooks/useThemePreferences.ts. Since that helper is already exported and pure, you could import and reuse it instead of re-implementing, to keep theme handling consistent.
  • useBrowserTheme calls window.matchMedia and touches document.documentElement during render/effect. That’s fine for a purely browser-based app, but if this component is ever rendered in a non-DOM environment (SSR or certain tests), you may want quick guards around typeof window !== 'undefined' / typeof document !== 'undefined'.

Not blocking, but worth considering for consistency and resilience.

libs/ui-components/src/components/AuthProvider/CreateAuthProvider/AuthOrganizationAssignment.tsx (1)

11-99: Organization assignment logic looks solid; consider DRY’ing prefix/suffix fields

The three org-assignment modes (Static/Dynamic/Per user) are wired correctly to AuthProviderFormValues and the UX text is clear.

The only nit is the duplicated orgNamePrefix / orgNameSuffix FormGroups in the Dynamic and PerUser branches. If this section grows, you might want to extract a small helper component for the shared prefix/suffix fields to reduce repetition, but it’s not required for correctness.

apps/standalone/src/app/utils/apiCalls.ts (1)

58-60: 401 handling and alerts-disabled semantics: double-check for redirect loops and intent

A few behavioral points around the new 401 handling:

  • redirectToLogin (Lines 58–60) is now always called on 401 in handleApiJSONResponse (Lines 73–75), regardless of the current route. If any code on /login itself uses fetchData/fetchWithRetry and gets a 401 (e.g., a misconfigured public endpoint), this can result in a redirect loop. Consider restoring a simple guard:
if (response.status === 401 && window.location.pathname !== '/login') {
  redirectToLogin();
}
  • After redirectToLogin() you still throw new Error(...). The page will be navigating away anyway, so this error mainly adds noise to logs. If you want to avoid that, you could return Promise.reject() or early-return after redirect; otherwise it’s acceptable but a bit redundant.
  • In handleAlertsJSONResponse (Lines 90–94), treating 401 like 500/501 and surfacing it as “alerts disabled” is a reasonable defensive fallback, but it assumes any 401 from the alerts proxy should not trigger a login redirect. Given the earlier pattern for organizations (501 => disabled), it would be good to confirm with the backend/proxy contract that a real auth failure on alerts should never leak through as a bare 401 here.

If the intent is exactly as above, the implementation is fine; otherwise, a small tweak to separate “disabled” vs “unauthorized” cases might be warranted.

Also applies to: 73-75, 90-94

libs/ui-components/src/components/Login/ProviderSelector.tsx (1)

21-28: Tighten provider key generation and duplicate-name details logic

This component is generally well structured; a few small refinements would make it more robust:

  • Key generation (Lines 21–28): getProviderKey falls back to literal 'issuer' / 'clientId' when those fields don’t exist, so two non-OAuth2/OIDC providers with the same metadata.name will share the same React key. If possible, include something guaranteed unique, e.g. metadata.uid or a stable URL:
const getProviderKey = (provider: AuthProvider): string => {
  const name = provider.metadata.name as string;
  const issuerUrl = 'issuer' in provider.spec ? provider.spec.issuer : '';
  const clientId = 'clientId' in provider.spec ? provider.spec.clientId : '';
  const providerType = provider.spec.providerType;
  return `${providerType}-${name}-${issuerUrl}-${clientId}`;
};
  • Type narrowing for details (Lines 80–88): casting provider.spec as DynamicAuthProviderSpec is a bit misleading when a non-dynamic provider shares a display name; you’ll still hit this block, but those specs may not have authorizationUrl/issuer/clientId. You could instead use the existing type guard directly on the wider spec type:
const spec = provider.spec;
if (isOAuth2Provider(spec)) {
  details = spec.authorizationUrl || spec.clientId || '';
} else {
  details = 'issuer' in spec ? spec.issuer || spec.clientId || '' : spec.clientId || '';
}
  • Avoid empty <small> (Lines 80–82, 101): if details ends up as an empty string (e.g., non-dynamic provider), you still render <small>{details}</small>. Consider guarding on details:
{isDuplicateName && details && <small>{details}</small>}

These are all relatively small changes but will make duplicate handling and rendering more predictable.

Also applies to: 39-53, 76-104

libs/ui-components/src/components/common/PageNavigation.tsx (1)

64-108: Gate the Settings button on isAdmin to match RBAC intent

Right now:

  • shouldShowNavigation = showOrganizationSelection || isAdmin;
  • But the Settings cog is always rendered whenever the nav is shown.

That means a non‑admin user who just happens to have multiple organizations will see the “Settings” button and can navigate to the auth providers page, even if they don’t have update/create rights (backend should still block, but UX becomes confusing and exposes an entry point that looks admin‑only).

To align with the “isAdmin === canUpdate/create auth providers” intent from earlier discussion, consider wrapping the Settings toolbar item in an isAdmin check:

-                {showOrganizationSelection && (
+                {showOrganizationSelection && (
                   <ToolbarItem>
                     <OrganizationDropdown
                       organizationName={currentOrgDisplayName}
                       onSwitchOrganization={() => {
                         setShowOrganizationModal(true);
                       }}
                     />
                   </ToolbarItem>
                 )}
-                <ToolbarItem>
-                  <Tooltip content={t('Manage authentication providers')}>
-                    <Button
-                      variant="link"
-                      aria-label={t('Settings')}
-                      onClick={() => navigate(ROUTE.AUTH_PROVIDERS)}
-                      icon={<CogIcon />}
-                    >
-                      {t('Settings')}
-                    </Button>
-                  </Tooltip>
-                </ToolbarItem>
+                {isAdmin && (
+                  <ToolbarItem>
+                    <Tooltip content={t('Manage authentication providers')}>
+                      <Button
+                        variant="link"
+                        aria-label={t('Settings')}
+                        onClick={() => navigate(ROUTE.AUTH_PROVIDERS)}
+                        icon={<CogIcon />}
+                      >
+                        {t('Settings')}
+                      </Button>
+                    </Tooltip>
+                  </ToolbarItem>
+                )}

That keeps shouldShowNavigation behavior (single‑org admins still see the bar) while making the Settings control itself consistently admin‑only.

libs/ui-components/src/components/AuthProvider/AuthProviderDetails/DeleteAuthProviderModal.tsx (1)

16-90: Consider closing the modal automatically on successful delete

handleDelete currently:

await remove(`authproviders/${authProviderId}`);
onDeleteSuccess();

but never calls onClose. In the details page, onDeleteSuccess navigates away so the modal unmounts anyway, but in other potential callers (e.g. a list page that stays on the same route) it’s easy to forget to both refetch and explicitly close the modal.

You could make the component safer to reuse by closing on success:

  const handleDelete = async () => {
    setError(undefined);
    setIsLoading(true);
    try {
      await remove(`authproviders/${authProviderId}`);
-     onDeleteSuccess();
+     onDeleteSuccess();
+     onClose();
    } catch (e) {
      setError(getErrorMessage(e));
    } finally {
      setIsLoading(false);
    }
  };

This preserves current behavior on the details page (it will still navigate) while ensuring the modal always dismisses after a successful delete.

apps/standalone/src/app/components/Login/LoginPage.tsx (1)

50-53: Minor: clean up provider name interpolation string

The translation string has a trailing space and slightly inconsistent spacing around the variable ('{{ providerName}} '). Consider tightening it so localization doesn’t depend on incidental whitespace:

-          t('Failed to initiate login with {{ providerName}} ', {
+          t('Failed to initiate login with {{ providerName }}', {
             providerName: getProviderDisplayName(provider, t) || (provider.metadata.name as string),
           }),
apps/standalone/src/app/context/AuthContext.ts (1)

49-102: Callback and user‑info error handling is much more robust; consider centralizing response parsing

The changes around the /callback flow and /info handling look good:

  • Requiring both code and state before POSTing to ${loginAPI} avoids ambiguous or insecure callback handling.
  • Provider name extraction from state and passing the full encoded state through to the backend aligns with the documented state format and PKCE fallback comments.
  • For both the login POST and /info calls, you now try to extract a detailed error message from JSON or text responses and bail out early, which prevents later logic from running on failed auth.

One small clean‑up opportunity: the error‑body parsing logic for the 401 branch and the generic resp.status !== 200 branch is nearly identical. If you find yourself reusing this pattern in more places, it might be worth factoring into a small helper like async readErrorMessage(resp, defaultMessage) to keep the main control flow tighter.

Please verify that backend error payloads for ${loginAPI} and ${loginAPI}/info consistently use an { error?: string } shape (or plain text) so this parsing logic always yields meaningful messages.

Also applies to: 115-157, 166-169

libs/ui-components/src/components/AuthProvider/AuthProvidersPage.tsx (1)

63-76: Minor text polish for empty state

Two tiny text nits you might want to tidy up:

  • Title: “Add your first authentication providers” → “Add your first authentication provider”
  • Body: “Oauth2” → “OAuth2” for consistency with other labels.

This keeps the copy consistent and a bit more polished.

libs/ui-components/src/components/AuthProvider/CreateAuthProvider/CreateAuthProvider.tsx (1)

29-47: Create/edit flow and RBAC wrapper look correct; consider basing title on authProviderId

The create/edit page wiring looks good overall:

  • When authProviderId is present, you fetch details, show a spinner while loading, and surface errors inline via an Alert.
  • CreateAuthProviderForm gets the existing provider in edit mode and navigates back to the list or details on close/success.
  • CreateAuthProviderWithPermissions correctly checks CREATE vs PATCH via useAccessReview and wraps the inner component in PageWithPermissions.

One minor UX suggestion: the page title is currently driven by !!authProviderDetails, so if fetching an existing provider fails you’ll show “Add authentication provider” while the breadcrumb still includes the ID and an error alert explains that details couldn’t be retrieved. Basing the title on authProviderId instead would keep the edit vs create mode more semantically consistent even when the fetch fails.

-  let title: string;
-  if (!!authProviderDetails) {
-    title = t('Edit authentication provider');
-  } else {
-    title = t('Add authentication provider');
-  }
+  const title = authProviderId
+    ? t('Edit authentication provider')
+    : t('Add authentication provider');

Not critical, but it would make error states a bit clearer to users.

Also applies to: 55-88, 121-135

libs/ui-components/src/components/AuthProvider/AuthProviderDetails/AuthProviderDetailsTab.tsx (3)

49-56: Guard the DynamicAuthProviderSpec cast / orgAssignment access

authProvider.spec is force-cast to DynamicAuthProviderSpec, and organizationAssignment is accessed without checks. If a non-OIDC/OAuth2 provider ever reaches this component (e.g., k8s), this will blow up at runtime.

Consider either:

  • Narrowing the prop type to a dynamic provider-specific type, or
  • Adding a runtime guard on spec.providerType before casting/using organizationAssignment.

96-99: Use the human‑readable provider type label in the configuration title

The title uses spec.providerType directly, which will likely render raw enum values such as "oidc"/"oauth2":

{t('{{ providerType }} configuration', { providerType: spec.providerType })}

You already have getProviderTypeLabel; wiring that in here would keep the title consistent with the “Type” field:

- {t('{{ providerType }} configuration', { providerType: spec.providerType })}
+ {t('{{ providerType }} configuration', {
+   providerType: getProviderTypeLabel(spec.providerType, t),
+ })}

196-230: Defensive handling for orgAssignment.claimPath and optional org name prefix/suffix

For dynamic org assignment you assume orgAssignment.claimPath is always present:

{orgAssignment.type === OrgAssignmentType.Dynamic && (
  ...
  {orgAssignment.claimPath.map(...)}
)}

If the backend ever omits claimPath (or it’s accidentally null), this will throw. Same for organizationNamePrefix / organizationNameSuffix you later render as strings but default to 'N/A'.

Consider normalizing with safe defaults:

- {orgAssignment.claimPath.map((pathSegment, index) => (
+ {(orgAssignment.claimPath ?? []).map((pathSegment, index) => (
    <Label key={`${pathSegment}-${index}`}>{pathSegment}</Label>
  ))}

and similar ?? '' defaults where appropriate before rendering.

libs/ui-components/src/components/AuthProvider/CreateAuthProvider/CreateAuthProviderForm.tsx (3)

229-235: Clarify Client secret requiredness for OAuth2 PKCE vs confidential clients

Client secret is always marked isRequired in the UI:

<FormGroup label={t('Client secret')} isRequired>
  <TextField name="clientSecret" ... type="password" />
</FormGroup>

Given the new PKCE flows and support for public OAuth2 clients without a secret, this visual required marker may be misleading if the validation schema allows an empty secret for those cases.

Consider:

  • Tying isRequired to the same condition you use in authProviderSchema, or
  • Adjusting the label/helper text to “Required for confidential clients” if secrets are optional for some provider types.

282-315: Test connection payload may be too minimal (no client secret / scopes)

handleTestConnection only sends:

const requestBody = {
  providerType: values.providerType,
  issuer: values.issuer,
  authorizationUrl: values.authorizationUrl,
  tokenUrl: values.tokenUrl,
  userinfoUrl: values.userinfoUrl,
  clientId: values.clientId,
};

If the backend’s test-auth-provider-connection endpoint needs clientSecret and/or scopes to properly validate a configuration (especially for confidential clients), the test button could give false negatives or incomplete diagnostics.

It’s probably worth confirming the API contract and, if needed, extending the payload:

       clientId: values.clientId,
+      clientSecret: values.clientSecret,
+      scopes: values.scopes,

351-379: Edit flow passes stale authProvider to onSuccess

In the edit path you compute patches and send them, but on success you call:

await patch<AuthProvider>(`authproviders/${authProvider.metadata.name}`, patches);
onSuccess(authProvider);

This hands the caller the original object, not the updated resource or at least a locally updated copy. If callers rely on the returned AuthProvider for UI state, they’ll be out of sync with what the backend now has.

Consider either:

  • Using the response from patch (if it returns the updated resource), or
  • Manually merging values into authProvider before passing it to onSuccess.

Example:

- const updated = await patch<AuthProvider>(..., patches);
- onSuccess(authProvider);
+ const updated = await patch<AuthProvider>(..., patches);
+ onSuccess(updated);

(Adjust to match your patch helper’s return type.)

proxy/auth/oauth2.go (1)

26-56: PKCE‑only client config: verify behavior for confidential OAuth2 clients

getOAuth2AuthHandler enforces required fields and then builds an osin client with:

ClientSecret:             "",  // Not needed with PKCE for public clients
SendClientSecretInParams: false,
Scope:                    scope,

and never uses oauth2Spec.ClientSecret.

That’s perfect for pure PKCE/public clients, but if you intend to support confidential OAuth2 clients that do require a client secret, they’ll currently be misconfigured:

  • No secret is sent on token exchange.
  • There’s no early validation that a secret was erroneously provided.

I’d suggest either:

  • Explicitly scoping this handler to PKCE/public clients and rejecting specs that include a client secret, or
  • Extending the config to optionally include oauth2Spec.ClientSecret (and configuring osin accordingly) when your provider type indicates a confidential client.
proxy/auth/k8s.go (1)

27-32: Consider populating TokenData.Provider using providerName

You now capture providerName in TokenAuthProvider, but ValidateToken builds:

tokenData := TokenData{
	IDToken:      token,
	RefreshToken: "",
}

Given TokenData has a Provider field (and you’re moving toward multiple providers), setting it here would make downstream logic more robust:

tokenData := TokenData{
    IDToken:      token,
    RefreshToken: "",
+   Provider:     t.providerName,
}

If you don’t plan to use Provider for K8s flows, you could also drop the providerName field entirely to keep the struct minimal.

Also applies to: 78-85

proxy/auth/openshift.go (1)

35-98: OpenShift handler wiring is sound; consider future configurability and clearer naming

The handler follows the same PKCE / exchangeToken / refreshOAuthToken / loginRedirect patterns as the other providers, and the discovery‑based logout is a nice touch. A couple of non‑blocking points:

  • authURL (local var) is really the API server base URL and is then copied to apiServerURL, while the struct field authURL stores the authorize endpoint. Renaming the local variable or the struct field later would reduce confusion when debugging.
  • clientId and scope ("user:full") are currently hard‑coded. Once the CRD/spec grows fields for OpenShift client IDs and scopes, this function will be the place to thread them through instead of relying on the default service account and static scope.

No blockers from this file.

Also applies to: 100-169

proxy/auth/auth.go (3)

196-205: Customer‑token provider detection via *TokenAuthProvider is simple and effective

Using a concrete type check in isProviderWithCustomerToken cleanly separates K8s token providers (direct validation, no PKCE/BFF) from OAuth‑style providers, and it’s reused consistently in Login and Refresh. As long as we don’t introduce additional customer‑token provider types without extending this helper, this remains easy to reason about.


395-457: Refresh routing via provider type is correct; error responses could be slightly more consistent

The refresh handler correctly:

  • Short‑circuits when auth is disabled (Teapot) or session cookie parsing fails.
  • Requires a provider in the session, resolves it via getProviderInstance, and distinguishes customer‑token K8s providers (refresh unsupported) from OAuth‑style providers.
  • Requires a refresh token, resolves client_id from the provider config, and performs a backend /token refresh via exchangeTokenWithApiServer, translating the result into TokenData.

Functionally this is fine. The only small improvement would be to use respondWithError (with a message) instead of raw w.WriteHeader(http.StatusInternalServerError) in the error paths where provider resolution or client_id extraction fails, to keep responses uniform for callers.


608-645: getAuthInfo behavior is fine; log message is slightly misleading

getAuthInfo correctly:

  • Uses the API TLS config to GET /api/v1/auth/config.
  • Interprets HTTP 418 as “auth disabled” and returns (nil, nil) to signal that.
  • Propagates all other errors and unmarshals a valid AuthConfig JSON on success.

Minor nit: the log message "Failed to read terminal session response" is clearly copy‑pasted and should mention “auth config response” instead to avoid confusion when debugging.

libs/ui-components/src/components/AuthProvider/CreateAuthProvider/types.ts (1)

1-96: Type guards and form model look consistent with backend contracts

This module is well‑structured:

  • OrgAssignmentType / RoleAssignmentType and the is* type guards correctly discriminate on the type field for the imported assignment/spec unions.
  • AuthProviderFormValues captures the key OIDC/OAuth2 fields plus org/role assignment data and maps cleanly onto what the backend types expose.
  • FieldValidation and FieldValidationResult mirror the Go structs in proxy/bridge/testauth.go (field names and optional notes/value), keeping the test‑connection pipeline strongly typed on both sides.

Only minor polish you might consider later: DEFAULT_USERNAME_CLAIM / DEFAULT_ROLE_CLAIM are flat strings while the corresponding form fields (usernameClaim, roleClaimPath) are string arrays; a tiny helper to convert those constants into path arrays (e.g. [DEFAULT_USERNAME_CLAIM]) would make the defaults plug‑and‑play.

proxy/auth/oidc.go (1)

37-124: OIDC handler refactor matches the new BFF/PKCE model; consider trimming unused internalAuthURL path

The OIDC handler now:

  • Validates Issuer and ClientId from OIDCProviderSpec.
  • Discovers endpoints via /.well-known/openid-configuration and builds clients with getOIDCClient, including dynamically built scopes (openid profile email plus organization:* when enabled).
  • Stores tokenEndpoint, clientId, and providerName on the handler and uses them in GetToken and GetLoginRedirectURL.
  • Defers user info to the API server via the same “GetUserInfo should be retrieved through the flightctl API” contract used by other providers.
  • Adds client_id to the logout URL, aligning with how the backend and IdPs typically expect end‑session requests.

Functionally this looks good and consistent with the rest of the refactor. The only minor clean‑up is that internalAuthURL is now always nil, so the branch that rebuilds endpoints via replaceBaseURL and re‑creates an external client is currently unreachable. If internal vs external OIDC endpoints are no longer supported, you could remove that code path to reduce cognitive load; otherwise, wiring internalAuthURL from spec when/if it appears again would make the behavior clearer.

Based on learnings

Also applies to: 146-206

proxy/auth/common.go (3)

335-343: Add a timeout to the HTTP client used for token exchange

The custom http.Client for the token exchange inherits the zero Timeout, so a slow or wedged token endpoint could hang a request indefinitely on this path.

Consider setting a reasonable timeout (e.g., 10–30 seconds, or reusing a shared client with standard timeouts) to keep the proxy responsive:

httpClient := &http.Client{
-    Transport: &http.Transport{
-        TLSClientConfig: tlsConfig,
-    },
+    Transport: &http.Transport{
+        TLSClientConfig: tlsConfig,
+    },
+    Timeout: 10 * time.Second,
}

691-751: PKCE verifier cookie handling is sound; consider scope and log level

Storing the code_verifier in an HttpOnly cookie with SameSite=Lax and a short MaxAge, and then clearing it with a MaxAge -1, is consistent with the redirect‑based PKCE flow. A couple of minor considerations:

  • getPKCEVerifierCookie logs a Warn when the cookie is missing or empty, but for the “fallback via state” path that can be an expected condition; using Debug for the missing‑cookie case would reduce noise.
  • Cookie Path = / is fine, but if only the callback endpoint needs it, narrowing the path could slightly reduce exposure in case of other endpoints or subapps reading cookies.

Neither is a blocker, just small polish items.


189-308: Remove unreachable reflection fallback; all call sites provide explicit config values

The reflection code (lines 203–275) that attempts to extract tokenURL, clientID, and redirectURI from osincli.Client.Config is never executed. All four call sites in openshift.go, oidc.go, oauth2.go, and aap.go pass explicit, non-empty values for these parameters, so the condition at line 203 is never true. Simplify exchangeToken() by removing the unused reflection fallback entirely and requiring explicit configuration, or remove the optional parameters altogether if they are always expected to be provided. This eliminates the brittle reflection dependency and clarifies intent.

proxy/bridge/testauth.go (2)

47-91: TestConnection handler structure is solid; consider minor hardening

The handler correctly:

  • Restricts to POST.
  • Validates JSON into TestConnectionRequest.
  • Constructs an http.Client with your tls.Config and a 10s timeout.
  • Dispatches by providerType and returns a JSON results array.

You might optionally handle json.NewEncoder(w).Encode(response) errors (e.g., log at Warn) to cover rare cases where the connection drops while writing, but behavior today is acceptable for this diagnostic endpoint.


93-225: OIDC discovery and endpoint checks are good; normalize issuer URL to avoid false mismatches

The OIDC branch does the right things: calls issuer/.well-known/openid-configuration, validates required endpoints, and then probes the discovered URLs.

One improvement: user‑supplied issuers often vary only by a trailing / compared to the discovery document. Today you:

  • Build discoveryUrl straight from req.Issuer.
  • Compare discovery.Issuer != req.Issuer and record a note, but don’t treat it as an error.

To reduce noisy notes and potential confusion, consider normalizing both values before comparison and when building the discovery URL:

- discoveryUrl := fmt.Sprintf("%s/.well-known/openid-configuration", req.Issuer)
+ issuer := strings.TrimRight(req.Issuer, "/")
+ discoveryUrl := fmt.Sprintf("%s/.well-known/openid-configuration", issuer)
...
- if discovery.Issuer != req.Issuer {
+ if strings.TrimRight(discovery.Issuer, "/") != issuer {
    issuerNotes = append(issuerNotes,
        fmt.Sprintf("Discovery document issuer (%s) does not match provided issuer (%s)", discovery.Issuer, req.Issuer))
}

This keeps the “issuer mismatch” note focused on genuinely different values instead of benign trailing slashes.

Comment thread apps/standalone/src/app/components/Login/LoginPage.tsx
Comment thread libs/ui-components/src/utils/authProvider.ts
Comment thread proxy/auth/api_client.go
Comment thread proxy/auth/common.go
Comment thread proxy/bridge/testauth.go Outdated

@coderabbitai coderabbitai 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.

Actionable comments posted: 0

♻️ Duplicate comments (3)
libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts (2)

177-215: Wrap switch-case declarations in blocks to satisfy noSwitchDeclarations.

As noted in previous reviews, const declarations inside switch cases share lexical scope across cases, which violates noSwitchDeclarations. Wrap the case bodies that declare variables (aClaimPath, bClaimPath) in blocks.

Apply this diff:

   switch (a.type) {
     case OrgAssignmentType.Static:
       if (!isOrgAssignmentStatic(a) || !isOrgAssignmentStatic(b)) {
         return false;
       }
       return (a.organizationName || '') === (b.organizationName || '');
-    case OrgAssignmentType.Dynamic:
+    case OrgAssignmentType.Dynamic: {
       if (!isOrgAssignmentDynamic(a) || !isOrgAssignmentDynamic(b)) {
         return false;
       }
       const aClaimPath = a.claimPath || [];
       const bClaimPath = b.claimPath || [];
       if (aClaimPath.length !== bClaimPath.length) {
         return false;
       }
       if (!aClaimPath.every((val, idx) => val === bClaimPath[idx])) {
         return false;
       }
       return (
         (a.organizationNamePrefix || '') === (b.organizationNamePrefix || '') &&
         (a.organizationNameSuffix || '') === (b.organizationNameSuffix || '')
       );
+    }
     case OrgAssignmentType.PerUser:
       if (!isOrgAssignmentPerUser(a) || !isOrgAssignmentPerUser(b)) {
         return false;
       }
       return (
         (a.organizationNamePrefix || '') === (b.organizationNamePrefix || '') &&
         (a.organizationNameSuffix || '') === (b.organizationNameSuffix || '')
       );

221-255: Wrap switch-case declarations in blocks to satisfy noSwitchDeclarations.

As noted in previous reviews, wrap the case bodies that declare variables (aRoles, bRoles, aClaimPath, bClaimPath) in blocks to isolate their scope.

Apply this diff:

   switch (a.type) {
-    case RoleAssignmentType.Static:
+    case RoleAssignmentType.Static: {
       if (!isRoleAssignmentStatic(a) || !isRoleAssignmentStatic(b)) {
         return false;
       }
       const aRoles = a.roles || [];
       const bRoles = b.roles || [];
       if (aRoles.length !== bRoles.length) {
         return false;
       }
       // Compare roles arrays (order matters for roles)
       return aRoles.every((val, idx) => val === bRoles[idx]);
-    case RoleAssignmentType.Dynamic:
+    }
+    case RoleAssignmentType.Dynamic: {
       if (!isRoleAssignmentDynamic(a) || !isRoleAssignmentDynamic(b)) {
         return false;
       }
       const aClaimPath = a.claimPath || [];
       const bClaimPath = b.claimPath || [];
       if (aClaimPath.length !== bClaimPath.length) {
         return false;
       }
       // Compare claim path arrays (order matters)
       if (!aClaimPath.every((val, idx) => val === bClaimPath[idx])) {
         return false;
       }
       return (a.separator || DEFAULT_ROLE_SEPARATOR) === (b.separator || DEFAULT_ROLE_SEPARATOR);
+    }
     default:
       return false;
   }
apps/standalone/src/app/components/Login/LoginPage.tsx (1)

15-20: Add response validation and error handling to redirectToProviderLogin.

This concern was previously raised and remains unaddressed. The function doesn't validate the HTTP response status or check if the url field exists in the response body. On 4xx/5xx errors or malformed responses, the code will attempt to redirect to undefined, bypassing error handlers.

Apply this diff to add proper validation:

 const redirectToProviderLogin = async (provider: AuthProvider) => {
   // Backend generates PKCE parameters and stores code_verifier in a cookie
   const response = await fetch(`${loginAPI}?provider=${provider.metadata.name}`);
+
+  if (!response.ok) {
+    throw new Error(`Login redirect failed with status ${response.status}`);
+  }
+
-  const { url } = (await response.json()) as { url: string };
+  const { url } = (await response.json()) as { url?: string };
+  if (!url) {
+    throw new Error('Login redirect URL missing in response');
+  }
+
   window.location.href = url;
 };
🧹 Nitpick comments (4)
libs/ui-components/src/utils/k8sProvider.ts (1)

11-11: Prefer Math.floor() for Unix timestamps.

Using Math.round() can produce timestamps up to 0.5 seconds in the future, which may cause edge-case issues with JWT validation (e.g., token appears issued before it actually was). Math.floor() is the conventional choice for Unix timestamps.

Apply this diff:

-export const nowInSeconds = () => Math.round(Date.now() / 1000);
+export const nowInSeconds = () => Math.floor(Date.now() / 1000);
libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts (2)

100-101: Consider adding a fallback for organizationName.

For consistency with line 150 in getOrgAssignment, which uses values.orgName || '', consider adding a fallback here to handle potential undefined values defensively.

Apply this diff:

   } else {
     orgAssignmentType = OrgAssignmentType.Static;
-    orgName = orgAssignment.organizationName;
+    orgName = orgAssignment.organizationName || '';
   }

257-272: Remove unnecessary type assertions.

AuthStaticRoleAssignment and AuthDynamicRoleAssignment are already subtypes of AuthRoleAssignment, so the explicit casts on lines 263 and 270 are unnecessary.

Apply this diff:

   if (values.roleAssignmentType === RoleAssignmentType.Static) {
     const staticAssignment: AuthStaticRoleAssignment = {
       type: RoleAssignmentType.Static,
       roles: values.staticRoles || [],
     };
-    return staticAssignment as AuthRoleAssignment;
+    return staticAssignment;
   } else {
     const dynamicAssignment: AuthDynamicRoleAssignment = {
       type: RoleAssignmentType.Dynamic,
       claimPath: values.roleClaimPath || [],
       separator: values.roleSeparator || DEFAULT_ROLE_SEPARATOR,
     };
-    return dynamicAssignment as AuthRoleAssignment;
+    return dynamicAssignment;
   }
proxy/auth/common.go (1)

190-309: Consider refactoring reflection-based config extraction.

The reflection code (lines 203-276 for config fields, 278-309 for TLS) extracts internal osincli.Client configuration. While functional, this approach is fragile:

  • Breaks if osincli changes internal field names or structure
  • The code tries both Config and config, TokenUrl and tokenUrl to handle variations
  • Relies on unexported fields

Consider requesting these values as explicit parameters to exchangeToken from all call sites, or wrapping osincli.Client in an interface with accessor methods.

The sensitive logging on lines 322-368 has already been flagged and acknowledged as temporary for debugging.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b11e3fa and 6f78b5e.

⛔ Files ignored due to path filters (1)
  • proxy/go.sum is excluded by !**/*.sum
📒 Files selected for processing (22)
  • apps/standalone/src/app/components/Login/LoginPage.tsx (1 hunks)
  • libs/types/index.ts (4 hunks)
  • libs/types/models/AppType.ts (1 hunks)
  • libs/types/models/ApplicationPort.ts (1 hunks)
  • libs/types/models/ApplicationResourceLimits.ts (1 hunks)
  • libs/types/models/ApplicationResources.ts (1 hunks)
  • libs/types/models/ApplicationVolume.ts (1 hunks)
  • libs/types/models/AuthProviderSpec.ts (1 hunks)
  • libs/types/models/ImageApplicationProviderSpec.ts (1 hunks)
  • libs/types/models/ImageMountVolumeProviderSpec.ts (1 hunks)
  • libs/types/models/K8sProviderSpec.ts (0 hunks)
  • libs/types/models/MountVolumeProviderSpec.ts (1 hunks)
  • libs/types/models/OpenShiftProviderSpec.ts (1 hunks)
  • libs/types/models/VolumeMount.ts (1 hunks)
  • libs/ui-components/src/components/AuthProvider/CreateAuthProvider/RoleAssignmentSection.tsx (1 hunks)
  • libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts (1 hunks)
  • libs/ui-components/src/utils/k8sProvider.ts (1 hunks)
  • proxy/auth/api_client.go (1 hunks)
  • proxy/auth/auth.go (7 hunks)
  • proxy/auth/common.go (3 hunks)
  • proxy/auth/openshift.go (1 hunks)
  • proxy/go.mod (1 hunks)
💤 Files with no reviewable changes (1)
  • libs/types/models/K8sProviderSpec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • libs/ui-components/src/components/AuthProvider/CreateAuthProvider/RoleAssignmentSection.tsx
🧰 Additional context used
🧠 Learnings (9)
📓 Common learnings
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 371
File: libs/types/models/AppType.ts:10-10
Timestamp: 2025-10-29T16:47:29.614Z
Learning: PR #371 (flightctl/flightctl-ui) adds the AppTypeQuadlet enum member and related types as a preparatory change. Full implementation of quadlet application support in the UI will be added later, after backend support is available.
📚 Learning: 2025-11-20T07:33:34.630Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 378
File: libs/ui-components/src/utils/authProvider.ts:5-18
Timestamp: 2025-11-20T07:33:34.630Z
Learning: In the flightctl-ui repository, for AuthProvider objects, the metadata.name field is always present at runtime as it serves as the ID of the authprovider. The OpenAPI schema incorrectly defines it as optional (name?: string in ObjectMeta), but the type assertion `as string` is appropriate when accessing provider.metadata.name since it's guaranteed to exist.

Applied to files:

  • libs/types/models/AuthProviderSpec.ts
  • libs/types/models/OpenShiftProviderSpec.ts
  • libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts
📚 Learning: 2025-10-10T11:35:04.458Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 0
File: :0-0
Timestamp: 2025-10-10T11:35:04.458Z
Learning: In the flightctl-ui repository, TypeScript types are generated using hey-api/openapi-ts and output to a single index.ts file (previously used openapi-typescript-codegen with one file per type under /libs/types/models).

Applied to files:

  • libs/types/models/OpenShiftProviderSpec.ts
  • libs/types/index.ts
📚 Learning: 2025-10-29T16:47:29.614Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 371
File: libs/types/models/AppType.ts:10-10
Timestamp: 2025-10-29T16:47:29.614Z
Learning: PR #371 (flightctl/flightctl-ui) adds the AppTypeQuadlet enum member and related types as a preparatory change. Full implementation of quadlet application support in the UI will be added later, after backend support is available.

Applied to files:

  • libs/types/models/AppType.ts
  • libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts
  • proxy/auth/api_client.go
  • proxy/auth/common.go
📚 Learning: 2025-03-24T12:33:54.002Z
Learnt from: rawagner
Repo: flightctl/flightctl-ui PR: 209
File: proxy/auth/oidc.go:17-19
Timestamp: 2025-03-24T12:33:54.002Z
Learning: OIDC (OpenID Connect) providers return usernames in the `preferred_username` field, as implemented in the OIDCUserInfo struct in proxy/auth/oidc.go.

Applied to files:

  • proxy/auth/auth.go
  • proxy/auth/common.go
📚 Learning: 2025-02-17T08:41:57.993Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 0
File: :0-0
Timestamp: 2025-02-17T08:41:57.993Z
Learning: All files in `libs/types/models` are auto-generated using OpenAPI Generator from the schema defined in https://github.com/flightctl/flightctl/blob/main/api/v1alpha1/openapi.yaml. Changes to these files should be made in the schema instead of modifying the generated files directly.

Applied to files:

  • libs/types/index.ts
  • proxy/auth/api_client.go
  • proxy/go.mod
📚 Learning: 2025-03-20T12:34:33.199Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 240
File: libs/types/models/FileSpec.ts:8-8
Timestamp: 2025-03-20T12:34:33.199Z
Learning: Files in the types directory are autogenerated from the OpenAPI spec in the flightctl project and should not be directly modified.

Applied to files:

  • proxy/auth/api_client.go
📚 Learning: 2025-06-04T06:41:15.969Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 308
File: libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts:204-204
Timestamp: 2025-06-04T06:41:15.969Z
Learning: In the flightctl-ui codebase, volume functionality is not yet implemented in the backend. TODO comments related to volume preservation (like in toAPIApplication function) are intentional placeholders for when backend volume support is added.

Applied to files:

  • proxy/auth/api_client.go
📚 Learning: 2025-10-21T13:53:29.934Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 357
File: proxy/auth/auth.go:228-237
Timestamp: 2025-10-21T13:53:29.934Z
Learning: In proxy/auth/auth.go, the token cleanup cycles in `startTokenCleanup` and `cleanupExpiredTokens` are designed for memory management of unretrieved tokens, not as a primary security control. The single-use deletion in `getSingleUseApiTokenMapping` provides the security guarantee. TTL enforcement on retrieval is intentionally not implemented because tokens are expected to be retrieved immediately after creation.

Applied to files:

  • proxy/auth/api_client.go
🧬 Code graph analysis (16)
libs/types/models/MountVolumeProviderSpec.ts (1)
libs/types/models/VolumeMount.ts (1)
  • VolumeMount (8-13)
libs/types/models/AuthProviderSpec.ts (2)
libs/types/index.ts (1)
  • OpenShiftProviderSpec (143-143)
libs/types/models/OpenShiftProviderSpec.ts (1)
  • OpenShiftProviderSpec (8-49)
libs/types/models/ImageMountVolumeProviderSpec.ts (1)
libs/types/models/VolumeMount.ts (1)
  • VolumeMount (8-13)
libs/types/models/VolumeMount.ts (1)
libs/types/index.ts (1)
  • VolumeMount (187-187)
libs/types/models/ApplicationPort.ts (1)
libs/types/index.ts (1)
  • ApplicationPort (10-10)
apps/standalone/src/app/components/Login/LoginPage.tsx (4)
libs/types/models/AuthProvider.ts (1)
  • AuthProvider (10-21)
apps/standalone/src/app/utils/apiCalls.ts (1)
  • loginAPI (17-17)
libs/ui-components/src/utils/authProvider.ts (1)
  • getProviderDisplayName (5-18)
libs/types/models/AuthConfig.ts (1)
  • AuthConfig (6-23)
libs/types/models/OpenShiftProviderSpec.ts (1)
libs/types/index.ts (1)
  • OpenShiftProviderSpec (143-143)
libs/types/models/ApplicationResources.ts (2)
libs/types/index.ts (2)
  • ApplicationResources (13-13)
  • ApplicationResourceLimits (12-12)
libs/types/models/ApplicationResourceLimits.ts (1)
  • ApplicationResourceLimits (8-17)
libs/types/models/ApplicationVolume.ts (1)
libs/types/models/ImageVolumeProviderSpec.ts (1)
  • ImageVolumeProviderSpec (6-8)
libs/types/models/ApplicationResourceLimits.ts (1)
libs/types/index.ts (1)
  • ApplicationResourceLimits (12-12)
proxy/auth/auth.go (5)
proxy/auth/common.go (8)
  • AuthProvider (70-76)
  • ProviderTypeOpenShift (31-31)
  • ProviderTypeK8s (27-27)
  • ProviderTypeOIDC (28-28)
  • ProviderTypeAAP (29-29)
  • ProviderTypeOAuth2 (30-30)
  • LoginParameters (65-68)
  • ParseSessionCookie (95-120)
proxy/auth/k8s.go (2)
  • TokenAuthProvider (17-21)
  • TokenLoginParameters (23-25)
proxy/config/config.go (1)
  • BaseUiUrl (16-16)
libs/types/models/TokenRequest.ts (1)
  • TokenRequest (8-37)
libs/types/models/TokenResponse.ts (1)
  • TokenResponse (8-33)
libs/types/models/ImageApplicationProviderSpec.ts (3)
libs/types/models/ApplicationVolumeProviderSpec.ts (1)
  • ApplicationVolumeProviderSpec (6-11)
libs/types/models/ApplicationPort.ts (1)
  • ApplicationPort (8-8)
libs/types/models/ApplicationResources.ts (1)
  • ApplicationResources (9-11)
libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts (5)
libs/ui-components/src/components/AuthProvider/CreateAuthProvider/types.ts (8)
  • AuthProviderFormValues (56-82)
  • DEFAULT_ROLE_SEPARATOR (23-23)
  • isOrgAssignmentDynamic (35-37)
  • isOrgAssignmentPerUser (39-41)
  • isRoleAssignmentStatic (48-50)
  • isRoleAssignmentDynamic (52-54)
  • isOAuth2Provider (28-29)
  • isOrgAssignmentStatic (31-33)
libs/ui-components/src/types/extraTypes.ts (1)
  • DynamicAuthProviderSpec (82-82)
libs/types/models/AuthDynamicRoleAssignment.ts (1)
  • AuthDynamicRoleAssignment (8-21)
libs/ui-components/src/utils/patch.ts (1)
  • appendJSONPatch (23-54)
libs/ui-components/src/components/form/validations.ts (1)
  • validKubernetesDnsSubdomain (151-166)
proxy/auth/api_client.go (7)
libs/types/models/TokenRequest.ts (1)
  • TokenRequest (8-37)
libs/types/models/TokenResponse.ts (1)
  • TokenResponse (8-33)
proxy/config/config.go (1)
  • FctlApiUrl (10-10)
proxy/log/log.go (1)
  • GetLogger (17-19)
proxy/auth/auth.go (1)
  • UserInfoResponse (19-21)
libs/types/models/UserInfoResponse.ts (1)
  • UserInfoResponse (9-30)
proxy/auth/common.go (1)
  • TokenData (40-55)
proxy/auth/openshift.go (5)
proxy/auth/common.go (3)
  • AuthProvider (70-76)
  • LoginParameters (65-68)
  • TokenData (40-55)
libs/types/models/OpenShiftProviderSpec.ts (1)
  • OpenShiftProviderSpec (8-49)
proxy/bridge/common.go (1)
  • GetAuthTlsConfig (40-65)
proxy/config/config.go (1)
  • BaseUiUrl (16-16)
proxy/log/log.go (1)
  • GetLogger (17-19)
proxy/auth/common.go (2)
proxy/log/log.go (1)
  • GetLogger (17-19)
proxy/config/config.go (2)
  • BaseUiUrl (16-16)
  • TlsCertPath (15-15)
🪛 Biome (2.1.2)
libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts

[error] 192-192: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 193-193: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 231-231: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 232-232: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 242-242: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 243-243: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 476-476: Do not add then to an object.

(lint/suspicious/noThenProperty)


[error] 483-483: Do not add then to an object.

(lint/suspicious/noThenProperty)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build ocp plugin
🔇 Additional comments (37)
libs/types/models/ApplicationVolume.ts (1)

6-11: Syntactic type alias change is safe and keeps semantics identical

Rewriting the alias as { ... } & ImageVolumeProviderSpec (without the wrapping parentheses) does not change the resulting type and stays consistent with typical TS style. No issues from this change.

libs/ui-components/src/utils/k8sProvider.ts (1)

1-9: LGTM! JWT format validation is correctly implemented.

The function properly validates the three-part structure of JWT tokens and uses the correct base64url character set. The requirement for non-empty parts is a good security practice for production use.

libs/types/models/AppType.ts (1)

11-11: Resolve: This is a preparatory update from the OpenAPI spec, intentionally included and requires no immediate UI handler changes.

This change follows the same pattern as PR #371 where AppTypeQuadlet was added as a preparatory enum member before backend implementation. The codebase currently supports only one application type value ("compose"), and the UI has not yet implemented per-type handlers. The AppTypeContainer enum member is automatically generated from OpenAPI specs and will be integrated when backend support becomes available.

libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts (1)

56-534: Well-structured utility module with comprehensive validation.

The implementation is solid:

  • Proper handling of different provider types (OIDC vs OAuth2) and assignment types (Static, Dynamic, PerUser)
  • Deep equality checks for org and role assignments prevent unnecessary patches
  • Conditional validation schema adapts to form state
  • Defensive programming with fallbacks (|| [], ?? true)

The patch generation logic correctly handles provider type transitions and secret updates.

apps/standalone/src/app/components/Login/LoginPage.tsx (1)

32-98: Well-structured provider selection and initialization logic.

The implementation properly handles:

  • Prevention of multiple concurrent redirects with the isRedirecting flag
  • Error handling in both manual provider selection and auto-redirect paths
  • Appropriate user feedback via error messages
  • Proper filtering and sorting of enabled providers
libs/types/models/ApplicationPort.ts (1)

1-8: Generated type definition looks correct.

This auto-generated type properly defines the ApplicationPort format with clear documentation. Based on learnings, changes to these files should be made in the OpenAPI schema rather than directly.

libs/types/models/VolumeMount.ts (1)

1-14: Generated type definition looks correct.

This auto-generated type properly defines the VolumeMount structure with clear documentation for the mount path configuration.

libs/types/models/ImageMountVolumeProviderSpec.ts (1)

1-14: Generated type definition looks correct.

This auto-generated type properly composes ImageVolumeSource and VolumeMount types to define an image-backed volume mount specification.

libs/types/models/ApplicationResources.ts (1)

1-12: Generated type definition looks correct.

This auto-generated type properly defines resource constraints with an optional ApplicationResourceLimits field.

libs/types/models/MountVolumeProviderSpec.ts (1)

1-12: Generated type definition looks correct.

This auto-generated type properly defines a named volume mount configuration using the VolumeMount type.

libs/types/models/AuthProviderSpec.ts (1)

9-10: OpenShift provider type properly integrated.

The union type correctly includes OpenShiftProviderSpec alongside the existing provider types, enabling OpenShift-based authentication flows in the UI.

proxy/go.mod (1)

8-8: The flightctl dependency commit exists with no known security issues, but manual verification of breaking changes is recommended.

The commit 1919682154b5754b9767e08ec5e5255131d873bd exists in the upstream flightctl/flightctl repository and corresponds to a merge PR about helm chart deployment changes. No recent security advisories were detected. However, review the actual code changes in PR #1961 to ensure this commit doesn't introduce breaking changes to the API or exported functionality that would impact this proxy module.

libs/types/models/ImageApplicationProviderSpec.ts (1)

5-17: LGTM! Type additions align with OpenAPI schema.

The new ports and resources fields appropriately extend ImageApplicationProviderSpec with container port mappings and resource specifications. The type definitions are properly imported and maintain type safety.

Based on learnings

libs/types/models/ApplicationResourceLimits.ts (1)

1-17: LGTM! Clean type definition for resource limits.

The type correctly models CPU and memory limits with clear documentation of expected formats (cores for CPU, Podman units for memory).

Based on learnings

libs/types/models/OpenShiftProviderSpec.ts (1)

1-49: LGTM! Well-structured OpenShift provider specification.

The type appropriately models OpenShift OAuth provider configuration with a discriminator field (providerType: 'openshift') and comprehensive OAuth2 fields. The OpenShift-specific clusterControlPlaneUrl field enables proper cluster URL derivation.

Based on learnings

proxy/auth/api_client.go (3)

22-103: Token exchange implementation looks solid.

The function properly handles:

  • TLS configuration
  • Request marshaling and error handling
  • HTTP status checks (418 for auth not configured)
  • OAuth2 error response parsing
  • Appropriate error propagation

The sensitive logging on lines 46-47 and 60-61 has already been flagged and acknowledged as temporary for debugging.


106-191: User info retrieval looks correct with proper username normalization.

The function appropriately:

  • Fetches user info from the backend API
  • Handles error responses (418, non-200 status)
  • Strips the Kubernetes service account prefix when present (lines 186-188)

The sensitive logging on lines 124-133 and 146-147 has already been flagged and acknowledged as temporary for debugging.


194-219: LGTM! Token response conversion is correct.

The function properly:

  • Maps API response fields to internal TokenData structure
  • Handles optional fields appropriately
  • Converts expires_in from int to int64
  • Sets both IDToken and AccessToken for OIDC compatibility (lines 199-204)
libs/types/index.ts (1)

10-187: LGTM! Export additions align with new model types.

The new exports properly expose the newly added types: application resources/ports, volume mounts, OpenShift provider spec, and authentication-related types (TokenRequest, TokenResponse, UserInfoResponse).

Based on learnings

proxy/auth/auth.go (9)

53-66: LGTM! Provider lookup is straightforward.

The function correctly searches the provider array by metadata.name and returns appropriate errors when providers are not configured or not found.


68-146: Provider instantiation logic is well-structured.

The function appropriately:

  • Fetches latest auth config
  • Uses discriminator pattern to determine provider type
  • Creates appropriate provider instance per type
  • Returns both provider and config to avoid duplicate API calls

The switch statement covers all provider types (OpenShift, K8s, OIDC, AAP, OAuth2) with proper error handling for unknown types.


148-185: Client ID extraction handles all provider types correctly.

The function appropriately:

  • Uses discriminator to determine provider type
  • Extracts client_id from the right spec field per provider
  • Returns clear errors for AAP (needs retrieval) and K8s (doesn't use endpoint)
  • Validates OpenShift ClientId is non-empty (lines 175-177)

209-263: LGTM! PKCE initialization for GET /login is correct.

The code appropriately:

  • Extracts provider from query parameter
  • Handles token providers differently (no PKCE needed)
  • Generates PKCE verifier and challenge
  • Stores verifier in cookie
  • Embeds verifier in state as fallback
  • Returns login redirect URL

PKCE generation failure correctly returns 500 with clear error message (lines 241-246).


264-386: POST /login token exchange flow looks solid.

The implementation correctly:

  • Handles K8s token providers separately (direct validation)
  • Retrieves PKCE verifier from cookie with fallback to state (lines 326-346)
  • Requires PKCE verifier (fails with clear error if missing)
  • Clears PKCE cookie after use (line 356)
  • Exchanges code for token via backend API
  • Converts response to internal TokenData

The PKCE verifier retrieval logic with cookie → state fallback is well-designed for resilience.


388-449: Token refresh implementation is correct.

The function appropriately:

  • Returns 418 if auth disabled
  • Parses session cookie for provider and refresh token
  • Gets provider instance
  • Rejects refresh for K8s token providers (no refresh support)
  • Extracts client_id from provider config
  • Exchanges refresh token via backend API
  • Handles OAuth errors appropriately

480-528: GetUserInfo routing through backend is well-implemented.

The function correctly:

  • Returns 418 if auth disabled
  • Parses session cookie
  • Validates provider is specified
  • Clears session and returns 401 on missing token/provider
  • Routes ALL providers to backend API (line 510-511)
  • Clears session on userinfo failure (appropriate auth failure treatment)
  • Validates username is non-empty

The unified backend routing simplifies the flow and ensures consistent behavior.


544-598: Logout implementation handles all cases correctly.

The function appropriately:

  • Returns 418 if auth disabled
  • Always clears cookies (lines 555-558, 568-571, 583-584) even on errors
  • Calls provider-specific Logout if provider and token available
  • Returns redirect URL if provider supplies one
  • Uses Clear-Site-Data header for robust cookie clearing

The unconditional cookie clearing ensures logout always succeeds from the proxy perspective.


600-637: LGTM! Auth config retrieval is simplified.

The function now returns only (*v1alpha1.AuthConfig, error), removing the extra URL pointer. This simplifies the signature and aligns with how the config is used throughout the file.

proxy/auth/openshift.go (2)

37-128: OpenShift handler initialization is well-designed.

The function appropriately:

  • Derives apiServerURL from ClusterControlPlaneUrl with fallback to AuthorizationUrl base (lines 40-59)
  • Strips /oauth/authorize path when deriving from AuthorizationUrl (lines 50-52)
  • Provides sensible defaults for authURL and tokenURL based on apiServerURL
  • Requires ClientId (lines 79-83) - correct, as this is mandatory for OAuth
  • Sets default scope "user:full" with override support (lines 90-94)
  • Configures OAuth client with TLS (lines 107-114)

The URL derivation logic with fallbacks ensures flexibility while maintaining correctness.


146-195: Logout with discovery is robustly implemented.

The function appropriately:

  • Discovers logout URL from .well-known/oauth-authorization-server (line 149)
  • Uses TLS config for discovery request
  • Handles all error cases gracefully (returns empty string, allows logout to proceed)
  • Parses issuer from discovery and constructs logout URL
  • Falls back to no redirect URL on any failure

The graceful error handling ensures logout always succeeds even if discovery fails.

proxy/auth/common.go (7)

25-38: LGTM! Provider constants and defaults are well-defined.

Using backend v1alpha1 constants for provider types (lines 26-31) ensures consistency across the codebase. The default claim constants follow OAuth2/OIDC standards.


40-63: TokenData refactoring correctly separates ID and access tokens.

The split into IDToken and AccessToken fields with clear usage documentation (lines 41-51) properly handles the different token types across providers:

  • OIDC/K8s: Use IDToken for API auth
  • OAuth2/AAP: Use AccessToken for API auth

The GetAuthToken() method (lines 57-63) provides the correct precedence: IDToken first, then AccessToken.


65-76: Interface updates align with PKCE flow.

Adding CodeVerifier to LoginParameters (line 67) and updating AuthProvider interface methods (lines 72, 75) correctly supports the PKCE authentication flow.


458-512: LGTM! Login redirect with PKCE and state encoding is correct.

The function appropriately:

  • Generates authorization URL via osincli
  • Embeds provider name in state parameter (line 478)
  • Optionally encodes code_verifier in state as fallback (lines 479-484)
  • Adds PKCE parameters (code_challenge, code_challenge_method) to query (lines 506-507)
  • Logs missing code_challenge as an error (lines 499-503)

The dual storage (cookie + state) for code_verifier provides resilience.


665-752: PKCE cookie management is well-implemented.

The functions correctly:

  • Use provider-specific cookie names (lines 694, 712, 741)
  • Set SameSite=Lax (not Strict) to allow cookies on OAuth redirect (line 700, 749)
  • Set 10-minute expiry matching authorization code TTL (line 702)
  • Log cookie operations for debugging (lines 706-707, 720-721)
  • Handle missing cookies gracefully (lines 725-727)

The SameSite=Lax setting is crucial for OAuth callback flows.


668-690: PKCE verifier and challenge generation follows RFC 7636.

  • generateCodeVerifier: Uses crypto/rand (secure randomness), generates 32 bytes, encodes as base64url (lines 671-680)
  • generateCodeChallenge: Uses SHA-256, encodes as base64url (lines 685-689)

Both implementations correctly follow RFC 7636 PKCE specification.


761-787: State parameter parsing correctly handles multiple formats.

extractCodeVerifierFromState (lines 764-787) appropriately:

  • Validates state starts with expected provider prefix
  • Handles format with encoded verifier: provider:<name>:<base64url>
  • Handles legacy format without verifier: provider:<name>
  • Decodes base64url-encoded verifier with error handling

The backward compatibility ensures smooth transitions.

@celdrake celdrake force-pushed the EDM-2373-support-multiple-auth-providers branch from 6f78b5e to 63acf96 Compare November 20, 2025 08:43

@coderabbitai coderabbitai 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.

Actionable comments posted: 6

Caution

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

⚠️ Outside diff range comments (1)
libs/ui-components/src/components/form/SwitchField.tsx (1)

6-31: Fix API mismatch: FormGroup uses labelHelp, not labelIcon.

PatternFly's FormGroup component does not support the labelIcon prop—it was renamed to labelHelp (use the FormGroupLabelHelp element).

Line 25 passes labelIcon to FormGroup, but this prop is unsupported and will not work. Update the implementation to use labelHelp or the FormGroupLabelHelp element as per current PatternFly API.

♻️ Duplicate comments (5)
libs/ui-components/src/components/AuthProvider/TestConnectionModal/TestConnectionModal.tsx (1)

27-40: Handle empty results and use danger severity for invalid rows.

As noted in a previous review, when results = [], every() returns true and some() returns false, showing a false success. Additionally, using status="warning" for invalid rows doesn't clearly convey failure severity.

Also applies to: 69-70

proxy/auth/api_client.go (1)

46-62: Temporary debug logging contains sensitive data.

As noted in previous review and acknowledged by @celdrake, the logging at these lines includes sensitive authentication material (tokens, auth headers, request/response bodies). This is temporary for debugging purposes and tracked for removal.

Also applies to: 125-147

proxy/auth/aap.go (1)

53-72: AAPAuthHandler wiring is sound; hard-coded clientId remains a known TODO

The AAP handler correctly derives URLs from the new spec (internal vs external), reuses GetAuthTlsConfig, uses PKCE for token acquisition, and routes logout/refresh through the internal URL and internalClient. The one notable gap is the hard‑coded clientId := "flightctl-ui" / data.Set("client_id", a.clientId): as you already noted in earlier discussions, this should eventually come from config or the provider spec once the backend exposes it. The current structure (getClient taking an explicit clientId) makes that easy to swap in later.

Also applies to: 78-92, 94-105, 107-135, 137-147, 149-173, 175-181

libs/ui-components/src/components/AuthProvider/CreateAuthProvider/CreateAuthProviderForm.tsx (1)

32-77: Test provider block + radio ids need cleanup before shipping

The “Test provider” section with hard‑coded GitHub/Google presets is very handy during development, but in its current form it will surface in production (blue border, fake client IDs/secrets, etc.). On top of that, both GitHub radios share the same id="github-test-provider-radio", which is an accessibility issue and can confuse label/ARIA associations.

If you plan to keep this helper, I’d strongly suggest:

  • Either hiding it behind a dev/feature flag or removing it entirely before release.
  • Giving each radio its own stable id (e.g., github-test-provider-radio-with-secret / ...-without-secret).
  • Having onClearTestProvider fully reset the preset state (e.g., setValues(initialValues) and clear any testProvider field) instead of merging { ...values, ...initialValues }, which currently leaves extra fields like testProvider untouched.

Also applies to: 129-141, 143-203

proxy/auth/common.go (1)

190-450: exchangeToken PKCE flow is solid, but logging still exposes raw token responses

The PKCE enforcement, reflection-based extraction of token URL/client ID/redirect URI and TLS config, and flexible parsing of JSON vs form-encoded responses are all reasonable. However, several logs in this function still include the full token response body or parsed values:

  • Info log with Body preview logs the start of the token JSON/form body.
  • Warn logs on non-200 responses and JSON/form parse errors log string(body).
  • The debug log for form-encoded responses logs the full url.Values, which will usually include access_token, id_token, and possibly refresh_token.

These logs leak high-value secrets into log storage and were already flagged in an earlier review; they should not ship into any non-dev environment. Recommend:

  • Stop logging the raw body/values; only log non-sensitive metadata (status code, content type, length, and key names).
  • Avoid including string(body) in returned error strings if those errors are ever logged or surfaced.

For example:

-	log.GetLogger().Warnf("Token exchange failed with status %d. Response body: %s", resp.StatusCode, string(body))
-	return ret, nil, fmt.Errorf("token exchange failed with status %d: %s", resp.StatusCode, string(body))
+	log.GetLogger().Warnf("Token exchange failed with status %d", resp.StatusCode)
+	return ret, nil, fmt.Errorf("token exchange failed with status %d", resp.StatusCode)

-	bodyPreview := string(body)
-	if len(bodyPreview) > 200 {
-		bodyPreview = bodyPreview[:200] + "..."
-	}
-	log.GetLogger().Infof("Token response Content-Type: %s, Body length: %d, Body preview: %s", contentType, len(body), bodyPreview)
+	log.GetLogger().Infof("Token response Content-Type: %s, Body length: %d", contentType, len(body))

-	log.GetLogger().Warnf("Failed to parse form-encoded token response. Status: %d, Content-Type: %s, Body: %s", resp.StatusCode, contentType, string(body))
+	log.GetLogger().Warnf("Failed to parse form-encoded token response. Status: %d, Content-Type: %s, Body redacted", resp.StatusCode, contentType)

-	log.GetLogger().Warnf("Failed to parse token response as JSON. Status: %d, Content-Type: %s, Body: %s", resp.StatusCode, contentType, string(body))
+	log.GetLogger().Warnf("Failed to parse token response as JSON. Status: %d, Content-Type: %s, Body redacted", resp.StatusCode, contentType)

-	log.GetLogger().Debugf("Parsed form-encoded response values: %v", values)
+	log.GetLogger().Debugf("Parsed form-encoded response keys: %v", getMapKeys(tokenResponse))

(and similarly for any other logs that include the full body or token-bearing values).

🧹 Nitpick comments (13)
proxy/bridge/handler.go (1)

37-58: 401 handling split between general and alerts proxy looks correct; duplication is acceptable

The new response handling does what the comments describe:

  • For the main reverse proxy, a 401 now triggers Clear-Site-Data: "cookies" plus a debug log, which is an appropriate way to force session cookie cleanup when the backend token has expired.
  • For the alerts proxy, using a dedicated createAlertsReverseProxy and converting 401 → 501 avoids logging the user out when Alertmanager returns 401 instead of 403, while still clearly signaling “feature not implemented/disabled” to the frontend.

Functionally this looks solid. There is some duplication between createReverseProxy and createAlertsReverseProxy (URL parsing, CORS header stripping) that could be factored into a shared helper, but that’s purely an optional clean‑up and not required for this PR.

Also applies to: 60-89, 111-119

apps/standalone/src/app/utils/apiCalls.ts (1)

58-60: Login redirect centralization looks good; consider mirroring the AuthContext guard

The synchronous redirectToLogin and unconditional 401 redirect in handleApiJSONResponse are fine. If any API calls are made from the /login route, you may want to add the same window.location.pathname !== '/login' guard used in AuthContext to avoid redundant navigations, but this is optional.

Also applies to: 73-77

libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts (1)

29-47: Label helpers work; consider future-proofing provider labels

getAssignmentTypeLabel/getProviderTypeLabel are straightforward and correctly wired to i18n for the known types. Today, anything outside OIDC/OAuth2 falls back to 'N/A'. If ProviderType grows (e.g., additional dynamic providers), it would be safer to either:

  • Narrow the type used here to just the dynamic provider subset, or
  • Add explicit cases for new provider types instead of relying on the default.

This keeps the UI from silently showing 'N/A' for newly introduced provider kinds.

Also applies to: 49-61

libs/ui-components/src/components/Login/TokenLoginForm.tsx (1)

35-35: Remove duplicate function definition.

The nowInSeconds function is already exported from libs/ui-components/src/utils/k8sProvider.ts (line 10). Import it instead of redefining it here.

Apply this diff:

 import { isValidJwtTokenFormat } from '../../utils/k8sProvider';
+import { nowInSeconds } from '../../utils/k8sProvider';
 import { useTranslation } from '../../hooks/useTranslation';
 import { useFetch } from '../../hooks/useFetch';
 import { getErrorMessage } from '../../utils/error';
 import FlightCtlForm from '../../components/form/FlightCtlForm';
 
 const EXPIRATION = 'expiration';
 
-const nowInSeconds = () => Math.floor(Date.now() / 1000);
-
 type TokenLoginFormProps = {
apps/standalone/src/app/components/Login/LoginPage.tsx (1)

15-27: Consider adding credentials option for robustness.

The response validation has been properly implemented addressing the previous review feedback. While the backend uses SameSite: Lax cookies (making credentials: 'include' not strictly necessary for same-origin), adding it would improve robustness across different deployment scenarios.

Apply this diff if desired:

 const redirectToProviderLogin = async (provider: AuthProvider) => {
-  const response = await fetch(`${loginAPI}?provider=${provider.metadata.name}`);
+  const response = await fetch(`${loginAPI}?provider=${provider.metadata.name}`, {
+    credentials: 'include',
+  });
 
   if (!response.ok) {
libs/ui-components/src/components/AuthProvider/CreateAuthProvider/CreateAuthProvider.tsx (1)

29-37: Solid create/edit flow; minor UX polish around title/edit state

The create/edit wiring, loading/error states, and permission gating all look good. One small UX nit: the page title (and final breadcrumb item) is driven by !!authProviderDetails, so on an edit route you'll briefly see “Add authentication provider” until the fetch completes, and you’ll also see the “Add” title if the fetch fails, even though the user is clearly on an edit URL. You might consider basing the title on authProviderId (or an explicit isEdit flag) and using the fetched data only for display values, so the mode is unambiguous even on errors or slow loads.

Also applies to: 55-88, 89-101, 121-137

proxy/auth/oauth2.go (1)

25-56: OAuth2AuthHandler aligns well with PKCE/BFF model

The handler setup (required-field validation, TLS via GetAuthTlsConfig, osincli client config with PKCE and no client secret, and delegating user info to the API server) is consistent with the rest of the auth stack and looks correct. If you want more actionable operator errors, you could split the combined required-field check into per‑field validations so the message can say exactly which field is missing, but that’s purely QoL.

Also applies to: 63-79, 81-103

libs/ui-components/src/components/AuthProvider/CreateAuthProvider/CreateAuthProviderForm.tsx (2)

264-281: Form action wiring looks good; consider richer test payload if backend needs it

CreateAuthProviderFormContent nicely centralizes submit vs. test‑connection behavior, with sensible disabled states for the primary/test buttons (dirty gating for new providers, etc.). The test‑connection request currently only sends a minimal subset of fields (providerType, issuer, auth URLs, clientId); if the backend ever needs scopes, clientSecret, or organization/role mapping to perform a more realistic connectivity/claims check, this would be the place to extend the payload. For now the structure is clean and easy to evolve.

Also applies to: 282-316, 324-341


351-380: Patch path ignores server response; consider returning updated provider to onSuccess

In the edit path you compute patches and call:

await patch<AuthProvider>(`authproviders/${authProvider.metadata.name}`, patches);
onSuccess(authProvider);

Given patch is already typed to return an AuthProvider, you could propagate the updated object:

const updated = await patch<AuthProvider>(`authproviders/${authProvider.metadata.name}`, patches);
onSuccess(updated);

Right now onSuccess only uses metadata.name, so this is harmless, but passing the fresh object makes the component more future‑proof if callers later want to reflect updated spec/displayName without a re‑fetch.

proxy/auth/auth.go (1)

27-51: AuthHandler initialization and config retrieval look correct, with room for future optimization

Storing apiTlsConfig and an initial authConfigData snapshot while having getProviderInstance always call getAuthInfo keeps the “auth enabled/disabled” flag cheap to check while ensuring provider definitions are fresh per request. The TLS‑backed getAuthInfo implementation is straightforward and handles the 418 Teapot “auth disabled” case cleanly. If this endpoint ever becomes high‑traffic, you might consider a small cache/TTL for authConfig to avoid repeated unmarshals on bursts of logins, but there’s no correctness issue as written.

Also applies to: 600-637

proxy/auth/openshift.go (1)

146-195: Consider adding timeout/context to logout discovery HTTP client

Logout uses a fresh http.Client with a custom Transport but no Timeout or context, so a slow or hung .well-known/oauth-authorization-server endpoint could tie up the calling goroutine longer than necessary. Consider reusing a shared client with a sane timeout or wiring a context.Context from the handler so this remote call is bounded.

proxy/auth/common.go (2)

25-38: Provider/claim constants are good; could be true constants

Centralising provider type strings and default claim names is helpful. As a minor hardening step, you could switch the provider type vars to const (or a dedicated typed const) to prevent accidental runtime mutation, but this is non-blocking.


668-708: PKCE cookies and Clear-Site-Data behavior

The PKCE helper functions look solid overall:

  • generateCodeVerifier/generateCodeChallenge follow RFC 7636 (base64url, no padding).
  • PKCE verifier cookies are HttpOnly, SameSite=Lax (needed for redirects), and time-bound to 10 minutes.
  • Logging only records cookie names and verifier length, not the verifier itself.

For clearSessionCookie, note that relying solely on Clear-Site-Data: "cookies" may behave differently across browsers and typically requires a secure context; if you need deterministic logout semantics, you may eventually want to resurrect explicit cookie clearing alongside this header once EDM-2612 is resolved.

Also applies to: 710-737, 739-759

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6f78b5e and 63acf96.

⛔ Files ignored due to path filters (1)
  • proxy/go.sum is excluded by !**/*.sum
📒 Files selected for processing (76)
  • CONFIGURATION.md (0 hunks)
  • apps/ocp-plugin/src/components/AppContext/AppContext.tsx (1 hunks)
  • apps/ocp-plugin/src/utils/apiCalls.ts (1 hunks)
  • apps/standalone/src/app/components/Login/LoginPage.tsx (1 hunks)
  • apps/standalone/src/app/components/Login/LoginPageLayout.tsx (1 hunks)
  • apps/standalone/src/app/context/AuthContext.ts (5 hunks)
  • apps/standalone/src/app/routes.tsx (4 hunks)
  • apps/standalone/src/app/utils/apiCalls.ts (3 hunks)
  • libs/ansible/src/const.ts (1 hunks)
  • libs/i18n/locales/en/translation.json (7 hunks)
  • libs/types/index.ts (4 hunks)
  • libs/types/models/AppType.ts (1 hunks)
  • libs/types/models/ApplicationPort.ts (1 hunks)
  • libs/types/models/ApplicationResourceLimits.ts (1 hunks)
  • libs/types/models/ApplicationResources.ts (1 hunks)
  • libs/types/models/ApplicationVolume.ts (1 hunks)
  • libs/types/models/AuthDynamicRoleAssignment.ts (1 hunks)
  • libs/types/models/AuthProviderSpec.ts (1 hunks)
  • libs/types/models/ImageApplicationProviderSpec.ts (1 hunks)
  • libs/types/models/ImageMountVolumeProviderSpec.ts (1 hunks)
  • libs/types/models/K8sProviderSpec.ts (0 hunks)
  • libs/types/models/MountVolumeProviderSpec.ts (1 hunks)
  • libs/types/models/OpenShiftProviderSpec.ts (1 hunks)
  • libs/types/models/TokenRequest.ts (1 hunks)
  • libs/types/models/TokenResponse.ts (1 hunks)
  • libs/types/models/UserInfoResponse.ts (1 hunks)
  • libs/types/models/VolumeMount.ts (1 hunks)
  • libs/ui-components/src/components/AuthProvider/AuthProviderDetails/AuthProviderDetails.tsx (1 hunks)
  • libs/ui-components/src/components/AuthProvider/AuthProviderDetails/AuthProviderDetailsTab.tsx (1 hunks)
  • libs/ui-components/src/components/AuthProvider/AuthProviderDetails/DeleteAuthProviderModal.tsx (1 hunks)
  • libs/ui-components/src/components/AuthProvider/AuthProviderDetails/RoleAssigmentDetails.tsx (1 hunks)
  • libs/ui-components/src/components/AuthProvider/AuthProviderRow.tsx (1 hunks)
  • libs/ui-components/src/components/AuthProvider/AuthProvidersPage.tsx (1 hunks)
  • libs/ui-components/src/components/AuthProvider/CreateAuthProvider/AuthOrganizationAssignment.tsx (1 hunks)
  • libs/ui-components/src/components/AuthProvider/CreateAuthProvider/AuthProviderHelperText.tsx (1 hunks)
  • libs/ui-components/src/components/AuthProvider/CreateAuthProvider/CreateAuthProvider.tsx (1 hunks)
  • libs/ui-components/src/components/AuthProvider/CreateAuthProvider/CreateAuthProviderForm.tsx (1 hunks)
  • libs/ui-components/src/components/AuthProvider/CreateAuthProvider/Oauth2ProviderFields.tsx (1 hunks)
  • libs/ui-components/src/components/AuthProvider/CreateAuthProvider/RoleAssignmentSection.tsx (1 hunks)
  • libs/ui-components/src/components/AuthProvider/CreateAuthProvider/types.ts (1 hunks)
  • libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts (1 hunks)
  • libs/ui-components/src/components/AuthProvider/TestConnectionModal/TestConnectionModal.tsx (1 hunks)
  • libs/ui-components/src/components/AuthProvider/useAuthProviders.ts (1 hunks)
  • libs/ui-components/src/components/DetailsPage/DetailsPage.tsx (1 hunks)
  • libs/ui-components/src/components/Login/ProviderSelector.tsx (1 hunks)
  • libs/ui-components/src/components/Login/TokenLoginForm.tsx (1 hunks)
  • libs/ui-components/src/components/OverviewPage/Cards/Alerts/AlertsCard.tsx (1 hunks)
  • libs/ui-components/src/components/common/CodeEditor/YamlEditor.tsx (3 hunks)
  • libs/ui-components/src/components/common/PageNavigation.tsx (4 hunks)
  • libs/ui-components/src/components/form/ListItemField.tsx (1 hunks)
  • libs/ui-components/src/components/form/NameField.tsx (2 hunks)
  • libs/ui-components/src/components/form/SwitchField.tsx (2 hunks)
  • libs/ui-components/src/hooks/useAlertsEnabled.ts (1 hunks)
  • libs/ui-components/src/hooks/useAppContext.tsx (1 hunks)
  • libs/ui-components/src/hooks/useNavigate.tsx (2 hunks)
  • libs/ui-components/src/types/extraTypes.ts (2 hunks)
  • libs/ui-components/src/types/rbac.ts (1 hunks)
  • libs/ui-components/src/utils/authProvider.ts (1 hunks)
  • libs/ui-components/src/utils/k8sProvider.ts (1 hunks)
  • libs/ui-components/src/utils/patch.ts (1 hunks)
  • proxy/app.go (1 hunks)
  • proxy/auth/aap.go (6 hunks)
  • proxy/auth/api_client.go (1 hunks)
  • proxy/auth/auth.go (7 hunks)
  • proxy/auth/common.go (3 hunks)
  • proxy/auth/k8s.go (4 hunks)
  • proxy/auth/oauth.go (0 hunks)
  • proxy/auth/oauth2.go (1 hunks)
  • proxy/auth/oidc.go (9 hunks)
  • proxy/auth/openshift.go (1 hunks)
  • proxy/bridge/handler.go (2 hunks)
  • proxy/bridge/testauth.go (1 hunks)
  • proxy/config/config.go (0 hunks)
  • proxy/go.mod (1 hunks)
  • proxy/middleware/auth.go (1 hunks)
  • proxy/middleware/organization.go (1 hunks)
💤 Files with no reviewable changes (4)
  • proxy/config/config.go
  • libs/types/models/K8sProviderSpec.ts
  • proxy/auth/oauth.go
  • CONFIGURATION.md
🚧 Files skipped from review as they are similar to previous changes (33)
  • libs/types/models/VolumeMount.ts
  • proxy/middleware/organization.go
  • libs/ui-components/src/utils/patch.ts
  • proxy/app.go
  • libs/ui-components/src/components/OverviewPage/Cards/Alerts/AlertsCard.tsx
  • libs/types/models/TokenResponse.ts
  • libs/ui-components/src/components/AuthProvider/useAuthProviders.ts
  • libs/ui-components/src/components/form/NameField.tsx
  • libs/ui-components/src/components/AuthProvider/AuthProviderDetails/RoleAssigmentDetails.tsx
  • libs/ui-components/src/components/common/CodeEditor/YamlEditor.tsx
  • libs/ui-components/src/hooks/useAlertsEnabled.ts
  • libs/ui-components/src/components/AuthProvider/AuthProviderDetails/DeleteAuthProviderModal.tsx
  • libs/ui-components/src/components/AuthProvider/CreateAuthProvider/Oauth2ProviderFields.tsx
  • libs/ui-components/src/components/form/ListItemField.tsx
  • libs/types/models/AuthProviderSpec.ts
  • libs/types/models/MountVolumeProviderSpec.ts
  • libs/types/models/AppType.ts
  • apps/ocp-plugin/src/components/AppContext/AppContext.tsx
  • libs/ui-components/src/components/AuthProvider/AuthProviderRow.tsx
  • libs/ui-components/src/components/AuthProvider/AuthProviderDetails/AuthProviderDetailsTab.tsx
  • libs/ui-components/src/types/extraTypes.ts
  • libs/ansible/src/const.ts
  • libs/types/models/ApplicationResources.ts
  • libs/ui-components/src/components/Login/ProviderSelector.tsx
  • libs/ui-components/src/utils/authProvider.ts
  • libs/types/models/UserInfoResponse.ts
  • libs/types/models/ImageMountVolumeProviderSpec.ts
  • libs/ui-components/src/utils/k8sProvider.ts
  • libs/types/models/ApplicationResourceLimits.ts
  • libs/types/models/TokenRequest.ts
  • libs/ui-components/src/hooks/useAppContext.tsx
  • proxy/bridge/testauth.go
  • proxy/middleware/auth.go
🧰 Additional context used
🧠 Learnings (14)
📓 Common learnings
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 371
File: libs/types/models/AppType.ts:10-10
Timestamp: 2025-10-29T16:47:29.614Z
Learning: PR #371 (flightctl/flightctl-ui) adds the AppTypeQuadlet enum member and related types as a preparatory change. Full implementation of quadlet application support in the UI will be added later, after backend support is available.
📚 Learning: 2025-02-18T12:04:42.579Z
Learnt from: rawagner
Repo: flightctl/flightctl-ui PR: 209
File: apps/standalone/src/app/utils/apiCalls.ts:14-16
Timestamp: 2025-02-18T12:04:42.579Z
Learning: In the flightctl-ui codebase, error handling for API calls follows a React pattern where the caller component (e.g., AppToolbar.tsx) implements try-catch blocks and manages error states, rather than handling errors within the API utility functions themselves.

Applied to files:

  • libs/ui-components/src/components/Login/TokenLoginForm.tsx
📚 Learning: 2025-02-17T08:52:50.747Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 218
File: libs/ui-components/src/components/Device/DeviceDetails/DeviceDetailsPage.tsx:109-111
Timestamp: 2025-02-17T08:52:50.747Z
Learning: When wrapping a Button component (like `deleteAction`) inside another Button in React/PatternFly, use `component="a"` on the outer Button to avoid accessibility issues caused by button nesting. Include `tabIndex={0}` to ensure proper keyboard navigation.

Applied to files:

  • libs/ui-components/src/components/AuthProvider/AuthProvidersPage.tsx
📚 Learning: 2025-11-17T15:53:01.194Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 376
File: libs/ui-components/src/components/Repository/CreateRepository/CreateRepositoryForm.tsx:274-275
Timestamp: 2025-11-17T15:53:01.194Z
Learning: In PR #376 (flightctl/flightctl-ui), the permission system was updated to fetch all user permissions once at login via the Flight Control API. The `checkPermissions` function from `usePermissionsContext` is a pure calculation on cached permission arrays, not an async fetch, so loading state handling is generally not required at individual component call sites.

Applied to files:

  • libs/ui-components/src/components/AuthProvider/AuthProvidersPage.tsx
  • libs/ui-components/src/components/common/PageNavigation.tsx
📚 Learning: 2025-10-29T16:47:29.614Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 371
File: libs/types/models/AppType.ts:10-10
Timestamp: 2025-10-29T16:47:29.614Z
Learning: PR #371 (flightctl/flightctl-ui) adds the AppTypeQuadlet enum member and related types as a preparatory change. Full implementation of quadlet application support in the UI will be added later, after backend support is available.

Applied to files:

  • libs/ui-components/src/components/AuthProvider/AuthProvidersPage.tsx
  • proxy/auth/api_client.go
  • libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts
  • proxy/auth/common.go
📚 Learning: 2025-02-17T08:41:57.993Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 0
File: :0-0
Timestamp: 2025-02-17T08:41:57.993Z
Learning: All files in `libs/types/models` are auto-generated using OpenAPI Generator from the schema defined in https://github.com/flightctl/flightctl/blob/main/api/v1alpha1/openapi.yaml. Changes to these files should be made in the schema instead of modifying the generated files directly.

Applied to files:

  • proxy/go.mod
  • proxy/auth/api_client.go
📚 Learning: 2025-04-10T12:22:38.239Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 266
File: libs/ansible/src/const.ts:20-20
Timestamp: 2025-04-10T12:22:38.239Z
Learning: In the flightctl-ui project, `appRoutes` is defined as a `Record<ROUTE, string>` across multiple applications, requiring all applications to define entries for all route enum values, even when certain routes (like COMMAND_LINE_TOOLS) are only meant to be used in specific applications (like Standalone). For routes not applicable to a particular application, placeholder values are used with explanatory comments.

Applied to files:

  • apps/standalone/src/app/routes.tsx
  • libs/ui-components/src/hooks/useNavigate.tsx
📚 Learning: 2025-03-20T12:34:33.199Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 240
File: libs/types/models/FileSpec.ts:8-8
Timestamp: 2025-03-20T12:34:33.199Z
Learning: Files in the types directory are autogenerated from the OpenAPI spec in the flightctl project and should not be directly modified.

Applied to files:

  • proxy/auth/api_client.go
📚 Learning: 2025-06-04T06:41:15.969Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 308
File: libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts:204-204
Timestamp: 2025-06-04T06:41:15.969Z
Learning: In the flightctl-ui codebase, volume functionality is not yet implemented in the backend. TODO comments related to volume preservation (like in toAPIApplication function) are intentional placeholders for when backend volume support is added.

Applied to files:

  • proxy/auth/api_client.go
📚 Learning: 2025-10-21T13:53:29.934Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 357
File: proxy/auth/auth.go:228-237
Timestamp: 2025-10-21T13:53:29.934Z
Learning: In proxy/auth/auth.go, the token cleanup cycles in `startTokenCleanup` and `cleanupExpiredTokens` are designed for memory management of unretrieved tokens, not as a primary security control. The single-use deletion in `getSingleUseApiTokenMapping` provides the security guarantee. TTL enforcement on retrieval is intentionally not implemented because tokens are expected to be retrieved immediately after creation.

Applied to files:

  • proxy/auth/api_client.go
📚 Learning: 2025-09-17T12:24:09.201Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 346
File: libs/ui-components/src/components/common/OrganizationGuard.tsx:54-62
Timestamp: 2025-09-17T12:24:09.201Z
Learning: In the Flight Control UI proxy, organization feature detection uses a 501 (Not Implemented) status code to explicitly signal when organizations are disabled via the ORGANIZATIONS_ENABLED setting. Any other status code (including errors like 404, 500) should be treated as organizations being enabled. The logic `organizationsEnabled = response.status !== 501` is correct and more resilient than checking `response.ok`.

Applied to files:

  • apps/ocp-plugin/src/utils/apiCalls.ts
  • apps/standalone/src/app/utils/apiCalls.ts
📚 Learning: 2025-11-20T07:33:34.656Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 378
File: libs/ui-components/src/utils/authProvider.ts:5-18
Timestamp: 2025-11-20T07:33:34.656Z
Learning: In the flightctl-ui repository, for AuthProvider objects, the metadata.name field is always present at runtime as it serves as the ID of the authprovider. The OpenAPI schema incorrectly defines it as optional (name?: string in ObjectMeta), but the type assertion `as string` is appropriate when accessing provider.metadata.name since it's guaranteed to exist.

Applied to files:

  • libs/ui-components/src/components/AuthProvider/CreateAuthProvider/types.ts
  • libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts
📚 Learning: 2025-03-24T12:33:54.002Z
Learnt from: rawagner
Repo: flightctl/flightctl-ui PR: 209
File: proxy/auth/oidc.go:17-19
Timestamp: 2025-03-24T12:33:54.002Z
Learning: OIDC (OpenID Connect) providers return usernames in the `preferred_username` field, as implemented in the OIDCUserInfo struct in proxy/auth/oidc.go.

Applied to files:

  • proxy/auth/oidc.go
  • proxy/auth/auth.go
  • proxy/auth/common.go
📚 Learning: 2025-10-10T11:35:04.458Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 0
File: :0-0
Timestamp: 2025-10-10T11:35:04.458Z
Learning: In the flightctl-ui repository, TypeScript types are generated using hey-api/openapi-ts and output to a single index.ts file (previously used openapi-typescript-codegen with one file per type under /libs/types/models).

Applied to files:

  • libs/types/index.ts
🧬 Code graph analysis (29)
libs/ui-components/src/components/Login/TokenLoginForm.tsx (4)
libs/ui-components/src/utils/k8sProvider.ts (2)
  • nowInSeconds (11-11)
  • isValidJwtTokenFormat (2-9)
libs/ui-components/src/hooks/useTranslation.ts (1)
  • useTranslation (5-8)
libs/ui-components/src/utils/organizationStorage.ts (1)
  • ORGANIZATION_STORAGE_KEY (1-1)
libs/ui-components/src/utils/error.ts (1)
  • getErrorMessage (6-17)
libs/ui-components/src/components/AuthProvider/CreateAuthProvider/CreateAuthProvider.tsx (5)
libs/ui-components/src/hooks/useTranslation.ts (1)
  • useTranslation (5-8)
libs/ui-components/src/hooks/useNavigate.tsx (2)
  • useNavigate (49-72)
  • Link (86-99)
libs/ui-components/src/utils/error.ts (1)
  • getErrorMessage (6-17)
libs/ui-components/src/hooks/useAppContext.tsx (1)
  • useAppContext (123-123)
libs/ui-components/src/components/common/PermissionsContext.tsx (1)
  • usePermissionsContext (105-111)
libs/types/models/ImageApplicationProviderSpec.ts (3)
libs/types/models/ApplicationVolumeProviderSpec.ts (1)
  • ApplicationVolumeProviderSpec (6-11)
libs/types/models/ApplicationPort.ts (1)
  • ApplicationPort (8-8)
libs/types/models/ApplicationResources.ts (1)
  • ApplicationResources (9-11)
libs/ui-components/src/components/AuthProvider/AuthProvidersPage.tsx (5)
libs/ui-components/src/components/Table/Table.tsx (1)
  • ApiSortTableColumn (16-24)
libs/ui-components/src/hooks/useTranslation.ts (1)
  • useTranslation (5-8)
libs/ui-components/src/hooks/useNavigate.tsx (1)
  • useNavigate (49-72)
libs/ui-components/src/components/common/PermissionsContext.tsx (1)
  • usePermissionsContext (105-111)
libs/ui-components/src/components/AuthProvider/useAuthProviders.ts (1)
  • useAuthProviders (4-18)
libs/ui-components/src/components/AuthProvider/TestConnectionModal/TestConnectionModal.tsx (3)
proxy/bridge/testauth.go (1)
  • FieldValidationResult (32-37)
libs/ui-components/src/components/AuthProvider/CreateAuthProvider/types.ts (1)
  • FieldValidationResult (91-96)
libs/ui-components/src/hooks/useTranslation.ts (1)
  • useTranslation (5-8)
libs/types/models/ApplicationPort.ts (1)
libs/types/index.ts (1)
  • ApplicationPort (10-10)
libs/ui-components/src/components/AuthProvider/AuthProviderDetails/AuthProviderDetails.tsx (5)
libs/ui-components/src/hooks/useTranslation.ts (1)
  • useTranslation (5-8)
libs/ui-components/src/hooks/useAppContext.tsx (1)
  • useAppContext (123-123)
libs/ui-components/src/hooks/useFetchPeriodically.ts (1)
  • useFetchPeriodically (8-79)
libs/ui-components/src/hooks/useNavigate.tsx (2)
  • useNavigate (49-72)
  • Route (45-45)
libs/ui-components/src/components/common/PermissionsContext.tsx (1)
  • usePermissionsContext (105-111)
libs/ui-components/src/components/AuthProvider/CreateAuthProvider/CreateAuthProviderForm.tsx (5)
libs/ui-components/src/hooks/useTranslation.ts (1)
  • useTranslation (5-8)
libs/ui-components/src/components/AuthProvider/CreateAuthProvider/types.ts (2)
  • AuthProviderFormValues (56-82)
  • FieldValidationResult (91-96)
libs/ui-components/src/components/common/WithHelperText.tsx (1)
  • FormGroupWithHelperText (51-64)
libs/ui-components/src/utils/error.ts (1)
  • getErrorMessage (6-17)
libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts (4)
  • getInitValues (63-151)
  • authProviderSchema (471-548)
  • getAuthProviderPatches (433-469)
  • getAuthProvider (288-326)
libs/ui-components/src/components/common/PageNavigation.tsx (4)
libs/ui-components/src/hooks/useTranslation.ts (1)
  • useTranslation (5-8)
libs/ui-components/src/hooks/useNavigate.tsx (1)
  • useNavigate (49-72)
libs/ui-components/src/components/common/OrganizationGuard.tsx (1)
  • useOrganizationGuardContext (17-23)
libs/ui-components/src/components/common/PermissionsContext.tsx (1)
  • usePermissionsContext (105-111)
apps/standalone/src/app/routes.tsx (1)
apps/standalone/src/app/context/AuthContext.ts (1)
  • AuthContext (21-26)
apps/standalone/src/app/components/Login/LoginPageLayout.tsx (1)
libs/ui-components/src/hooks/useThemePreferences.ts (1)
  • updateThemeClass (33-39)
apps/standalone/src/app/components/Login/LoginPage.tsx (5)
libs/types/models/AuthProvider.ts (1)
  • AuthProvider (10-21)
apps/standalone/src/app/utils/apiCalls.ts (1)
  • loginAPI (17-17)
libs/ui-components/src/hooks/useTranslation.ts (1)
  • useTranslation (5-8)
libs/ui-components/src/utils/authProvider.ts (1)
  • getProviderDisplayName (5-20)
libs/types/models/AuthConfig.ts (1)
  • AuthConfig (6-23)
libs/ui-components/src/components/AuthProvider/CreateAuthProvider/AuthOrganizationAssignment.tsx (2)
libs/ui-components/src/hooks/useTranslation.ts (1)
  • useTranslation (5-8)
libs/ui-components/src/components/AuthProvider/CreateAuthProvider/types.ts (1)
  • AuthProviderFormValues (56-82)
libs/types/models/ApplicationVolume.ts (1)
libs/types/models/ImageVolumeProviderSpec.ts (1)
  • ImageVolumeProviderSpec (6-8)
libs/ui-components/src/components/AuthProvider/CreateAuthProvider/RoleAssignmentSection.tsx (5)
libs/ui-components/src/hooks/useTranslation.ts (1)
  • useTranslation (5-8)
libs/ui-components/src/components/form/FieldHelperText.tsx (1)
  • DefaultHelperText (17-25)
libs/ui-components/src/components/AuthProvider/CreateAuthProvider/types.ts (1)
  • AuthProviderFormValues (56-82)
libs/ui-components/src/components/common/WithHelperText.tsx (1)
  • FormGroupWithHelperText (51-64)
libs/ui-components/src/components/AuthProvider/CreateAuthProvider/AuthProviderHelperText.tsx (2)
  • RoleClaimHelperText (45-65)
  • RoleSeparatorHelperText (67-78)
proxy/auth/oauth2.go (3)
proxy/auth/common.go (3)
  • AuthProvider (70-76)
  • LoginParameters (65-68)
  • TokenData (40-55)
proxy/bridge/common.go (1)
  • GetAuthTlsConfig (40-65)
proxy/config/config.go (1)
  • BaseUiUrl (16-16)
libs/ui-components/src/components/AuthProvider/CreateAuthProvider/AuthProviderHelperText.tsx (1)
libs/ui-components/src/hooks/useTranslation.ts (1)
  • useTranslation (5-8)
proxy/auth/api_client.go (6)
libs/types/models/TokenRequest.ts (1)
  • TokenRequest (8-37)
libs/types/models/TokenResponse.ts (1)
  • TokenResponse (8-33)
proxy/config/config.go (1)
  • FctlApiUrl (10-10)
proxy/log/log.go (1)
  • GetLogger (17-19)
libs/types/models/UserInfoResponse.ts (1)
  • UserInfoResponse (9-30)
proxy/auth/common.go (1)
  • TokenData (40-55)
proxy/bridge/handler.go (2)
proxy/log/log.go (1)
  • GetLogger (17-19)
proxy/config/config.go (1)
  • AlertManagerApiUrl (13-13)
apps/standalone/src/app/context/AuthContext.ts (1)
apps/standalone/src/app/utils/apiCalls.ts (2)
  • loginAPI (17-17)
  • redirectToLogin (58-60)
proxy/auth/k8s.go (2)
proxy/auth/common.go (2)
  • TokenData (40-55)
  • AuthProvider (70-76)
proxy/bridge/common.go (1)
  • GetTlsConfig (13-38)
libs/types/models/OpenShiftProviderSpec.ts (1)
libs/types/index.ts (1)
  • OpenShiftProviderSpec (143-143)
libs/ui-components/src/components/AuthProvider/CreateAuthProvider/types.ts (2)
libs/types/models/AuthDynamicRoleAssignment.ts (1)
  • AuthDynamicRoleAssignment (8-21)
proxy/bridge/testauth.go (2)
  • FieldValidation (17-21)
  • FieldValidationResult (32-37)
proxy/auth/oidc.go (2)
proxy/auth/common.go (2)
  • AuthProvider (70-76)
  • TokenData (40-55)
proxy/config/config.go (2)
  • IsOrganizationsEnabled (36-38)
  • BaseUiUrl (16-16)
proxy/auth/openshift.go (3)
proxy/auth/common.go (3)
  • AuthProvider (70-76)
  • LoginParameters (65-68)
  • TokenData (40-55)
proxy/bridge/common.go (1)
  • GetAuthTlsConfig (40-65)
proxy/config/config.go (1)
  • BaseUiUrl (16-16)
proxy/auth/aap.go (3)
proxy/auth/common.go (2)
  • AuthProvider (70-76)
  • TokenData (40-55)
proxy/bridge/common.go (1)
  • GetAuthTlsConfig (40-65)
proxy/config/config.go (1)
  • BaseUiUrl (16-16)
proxy/auth/auth.go (7)
proxy/auth/common.go (8)
  • AuthProvider (70-76)
  • ProviderTypeOpenShift (31-31)
  • ProviderTypeK8s (27-27)
  • ProviderTypeOIDC (28-28)
  • ProviderTypeAAP (29-29)
  • ProviderTypeOAuth2 (30-30)
  • LoginParameters (65-68)
  • ParseSessionCookie (95-120)
libs/types/models/AuthProvider.ts (1)
  • AuthProvider (10-21)
libs/types/models/AuthConfig.ts (1)
  • AuthConfig (6-23)
proxy/auth/k8s.go (2)
  • TokenAuthProvider (17-21)
  • TokenLoginParameters (23-25)
proxy/config/config.go (1)
  • BaseUiUrl (16-16)
libs/types/models/TokenRequest.ts (1)
  • TokenRequest (8-37)
libs/types/models/TokenResponse.ts (1)
  • TokenResponse (8-33)
libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts (6)
libs/types/index.ts (8)
  • AuthOrganizationAssignment (23-23)
  • AuthRoleAssignment (28-28)
  • AuthProviderSpec (27-27)
  • AuthProvider (25-25)
  • AuthStaticRoleAssignment (30-30)
  • AuthDynamicRoleAssignment (22-22)
  • PatchRequest (147-147)
  • OAuth2ProviderSpec (139-139)
libs/ui-components/src/components/AuthProvider/CreateAuthProvider/types.ts (8)
  • AuthProviderFormValues (56-82)
  • DEFAULT_ROLE_SEPARATOR (23-23)
  • isOrgAssignmentDynamic (35-37)
  • isOrgAssignmentPerUser (39-41)
  • isRoleAssignmentStatic (48-50)
  • isRoleAssignmentDynamic (52-54)
  • isOAuth2Provider (28-29)
  • isOrgAssignmentStatic (31-33)
libs/ui-components/src/types/extraTypes.ts (1)
  • DynamicAuthProviderSpec (82-82)
libs/types/models/AuthDynamicRoleAssignment.ts (1)
  • AuthDynamicRoleAssignment (8-21)
libs/ui-components/src/utils/patch.ts (1)
  • appendJSONPatch (23-54)
libs/ui-components/src/components/form/validations.ts (1)
  • validKubernetesDnsSubdomain (151-166)
proxy/auth/common.go (2)
proxy/log/log.go (1)
  • GetLogger (17-19)
proxy/config/config.go (2)
  • BaseUiUrl (16-16)
  • TlsCertPath (15-15)
🪛 Biome (2.1.2)
libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts

[error] 490-490: Do not add then to an object.

(lint/suspicious/noThenProperty)


[error] 497-497: Do not add then to an object.

(lint/suspicious/noThenProperty)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: integration-tests
  • GitHub Check: Lint
  • GitHub Check: Build ocp plugin
  • GitHub Check: Build

Comment thread apps/standalone/src/app/components/Login/LoginPageLayout.tsx Outdated
Comment thread proxy/auth/auth.go
Comment thread proxy/auth/common.go
Comment on lines +458 to +512
// loginRedirect generates the OAuth login redirect URL with provider state and PKCE parameters
// If codeChallenge is empty, it will be generated automatically
// codeVerifier can be optionally passed to encode it in the state parameter as a fallback
func loginRedirect(client *osincli.Client, providerName string, codeChallenge string, codeVerifier string) string {
authorizeRequest := client.NewAuthorizeRequest(osincli.CODE)
authURL := authorizeRequest.GetAuthorizeUrl()

// Log the redirect_uri used in authorization request for debugging
// The redirect_uri is included in the authorization URL query parameters
parsedAuthURL, _ := url.Parse(authURL.String())
redirectURI := parsedAuthURL.Query().Get("redirect_uri")
if redirectURI == "" {
// Fallback: use the configured redirect URI from config
redirectURI = config.BaseUiUrl + "/callback"
}
log.GetLogger().Infof("Authorization request for provider %s: redirect_uri=%s", providerName, redirectURI)

// Include provider name in state parameter so callback can identify which provider to use
// Format: "provider:<providerName>" to match frontend expectations
// If codeVerifier is provided, encode it in state as a fallback if cookie fails
state := fmt.Sprintf("provider:%s", providerName)
if codeVerifier != "" {
// Encode code_verifier in state as base64url for fallback retrieval
// Format: "provider:<providerName>:<base64url_encoded_verifier>"
encodedVerifier := b64.URLEncoding.WithPadding(b64.NoPadding).EncodeToString([]byte(codeVerifier))
state = fmt.Sprintf("provider:%s:%s", providerName, encodedVerifier)
}

// Parse the URL and add state parameter
parsedURL, err := url.Parse(authURL.String())
if err != nil {
// If parsing fails, return original URL
return authURL.String()
}

// Add state to query parameters
query := parsedURL.Query()
query.Set("state", state)

// PKCE is required - codeChallenge must be provided
// Note: codeChallenge should be generated by the caller (auth.go) before calling this function
if codeChallenge == "" {
log.GetLogger().Error("PKCE code_challenge is required but not provided - this should not happen")
// This is a programming error - codeChallenge should always be provided by auth.go
// The flow will fail when the provider requires PKCE or when token exchange happens
}

// Add PKCE parameters (required)
query.Set("code_challenge", codeChallenge)
query.Set("code_challenge_method", "S256")

parsedURL.RawQuery = query.Encode()

return parsedURL.String()
}

@coderabbitai coderabbitai Bot Nov 20, 2025

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Encoding code_verifier into the state parameter weakens PKCE’s protection

loginRedirect optionally embeds the raw code_verifier (base64url-encoded) into the state parameter (provider:<name>:<encoded_verifier>), and extractCodeVerifierFromState decodes it. Because state travels over the front channel and is returned alongside the authorization code, any attacker who can intercept the redirect (the attack PKCE is meant to mitigate) will now have both code and the code_verifier, effectively defeating PKCE for those flows.

Given you already store the verifier in an HttpOnly, SameSite=Lax cookie, I’d strongly recommend:

  • Removing the verifier-embedding in state entirely, and relying solely on the cookie for verifier storage; or
  • Gating this fallback behind a dedicated debug flag and never enabling it in production, and ensuring state is never logged or sent to third parties.

As-is, this is a meaningful security regression relative to a standard PKCE setup.

Also applies to: 761-783

🤖 Prompt for AI Agents
In proxy/auth/common.go around lines 458-512 (and also address similar logic at
761-783): the code embeds the code_verifier into the front-channel state
parameter (provider:<name>:<encoded_verifier>), which defeats PKCE if an
attacker intercepts the redirect; remove the verifier-embedding from state
entirely and rely solely on the HttpOnly, SameSite cookie for storing the
code_verifier, ensuring state only contains provider identification (e.g.,
"provider:<name>"); update any code-paths that parse state (e.g.,
extractCodeVerifierFromState) to no longer expect or decode a verifier from
state and instead read the verifier from the cookie, and remove or guard any
logging that might expose state values.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I'll be looking at this in a follow-up

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@celdrake, understood! Since this is a security concern that will be addressed later, would you like me to create a GitHub issue to track this follow-up work? I can include:

  • A summary of the PKCE security concern (code_verifier in state parameter)
  • Reference to this PR and comment thread
  • Recommendation to rely solely on the HttpOnly cookie mechanism

Just let me know if you'd like me to open an issue for tracking.


🧠 Learnings used
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 378
File: libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts:288-326
Timestamp: 2025-11-20T09:24:41.950Z
Learning: In the flightctl-ui repository, the CreateAuthProvider form and its utilities (including getAuthProvider in libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts) are specifically designed for CRUD operations on OIDC and OAuth2 authentication providers only. Other provider types (K8s, AAP, OpenShift) are not supported by this form and should be rejected with an error if loading is attempted for editing.

Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 371
File: libs/types/models/AppType.ts:10-10
Timestamp: 2025-10-29T16:47:29.614Z
Learning: PR #371 (flightctl/flightctl-ui) adds the AppTypeQuadlet enum member and related types as a preparatory change. Full implementation of quadlet application support in the UI will be added later, after backend support is available.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I already created a task for it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@celdrake, perfect—thank you for tracking it! I appreciate you ensuring this security concern is captured for future work. 🐰


🧠 Learnings used
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 378
File: libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts:288-326
Timestamp: 2025-11-20T09:24:41.950Z
Learning: In the flightctl-ui repository, the CreateAuthProvider form and its utilities (including getAuthProvider in libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts) are specifically designed for CRUD operations on OIDC and OAuth2 authentication providers only. Other provider types (K8s, AAP, OpenShift) are not supported by this form and should be rejected with an error if loading is attempted for editing.

Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 371
File: libs/types/models/AppType.ts:10-10
Timestamp: 2025-10-29T16:47:29.614Z
Learning: PR #371 (flightctl/flightctl-ui) adds the AppTypeQuadlet enum member and related types as a preparatory change. Full implementation of quadlet application support in the UI will be added later, after backend support is available.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@ori-amizur, absolutely! Here's a concrete fix to address the PKCE security issue:

Changes needed in proxy/auth/common.go:

1. Update loginRedirect function (remove verifier embedding in state):

func loginRedirect(client *osincli.Client, providerName string, codeChallenge string, codeVerifier string) string {
	authorizeRequest := client.NewAuthorizeRequest(osincli.CODE)
	authURL := authorizeRequest.GetAuthorizeUrl()

	// Log the redirect_uri used in authorization request for debugging
	parsedAuthURL, _ := url.Parse(authURL.String())
	redirectURI := parsedAuthURL.Query().Get("redirect_uri")
	if redirectURI == "" {
		redirectURI = config.BaseUiUrl + "/callback"
	}
	log.GetLogger().Infof("Authorization request for provider %s: redirect_uri=%s", providerName, redirectURI)

	// Include provider name in state parameter so callback can identify which provider to use
	// Format: "provider:<providerName>" 
	// NOTE: We do NOT encode code_verifier in state for security reasons.
	// The verifier is stored securely in an HttpOnly cookie instead.
	state := fmt.Sprintf("provider:%s", providerName)

	// Parse the URL and add state parameter
	parsedURL, err := url.Parse(authURL.String())
	if err != nil {
		return authURL.String()
	}

	// Add state to query parameters
	query := parsedURL.Query()
	query.Set("state", state)

	// PKCE is required - codeChallenge must be provided
	if codeChallenge == "" {
		log.GetLogger().Error("PKCE code_challenge is required but not provided - this should not happen")
	}

	// Add PKCE parameters (required)
	query.Set("code_challenge", codeChallenge)
	query.Set("code_challenge_method", "S256")

	parsedURL.RawQuery = query.Encode()

	return parsedURL.String()
}

2. Simplify extractCodeVerifierFromState (no longer extract verifier):

// extractProviderNameFromState extracts provider name from state parameter
// State format: "provider:<providerName>"
func extractProviderNameFromState(state string) string {
	prefix := "provider:"
	if !strings.HasPrefix(state, prefix) {
		return ""
	}
	return strings.TrimPrefix(state, prefix)
}

3. Update any code that calls extractCodeVerifierFromState to rely only on the cookie:

// In your callback handler (likely in auth.go):
// Remove this:
// codeVerifier := extractCodeVerifierFromState(state, providerName)

// Use only the cookie mechanism:
codeVerifier, err := getPKCEVerifierCookie(r, providerName)
if err != nil || codeVerifier == "" {
    // Handle error - verifier cookie not found or invalid
    respondWithError(w, http.StatusBadRequest, "PKCE verification failed: code_verifier not found")
    return
}

Why these changes improve security:

  1. Removes front-channel exposure: The code_verifier no longer travels through the browser's address bar where it can be intercepted, logged, or leaked via Referer headers.

  2. Relies on secure storage: The HttpOnly, SameSite=Lax cookie provides browser-enforced security that prevents JavaScript access and CSRF attacks.

  3. Maintains PKCE integrity: The separation between the public code_challenge (in authorization request) and private code_verifier (in secure cookie) is now properly maintained.

  4. Cleaner architecture: State parameter now has a single, clear purpose: identifying which provider is being used.


🧠 Learnings used
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 378
File: libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts:288-326
Timestamp: 2025-11-20T09:24:41.950Z
Learning: In the flightctl-ui repository, the CreateAuthProvider form and its utilities (including getAuthProvider in libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts) are specifically designed for CRUD operations on OIDC and OAuth2 authentication providers only. Other provider types (K8s, AAP, OpenShift) are not supported by this form and should be rejected with an error if loading is attempted for editing.

Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 371
File: libs/types/models/AppType.ts:10-10
Timestamp: 2025-10-29T16:47:29.614Z
Learning: PR #371 (flightctl/flightctl-ui) adds the AppTypeQuadlet enum member and related types as a preparatory change. Full implementation of quadlet application support in the UI will be added later, after backend support is available.

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.

@coderabbitai in my code base the function extractProviderNameFromState doesn't exist. Where did you take it from ?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@ori-amizur, great catch! I apologize for the confusion. extractProviderNameFromState doesn't currently exist in your codebase—I was proposing it as a new function to replace the current extractCodeVerifierFromState function as part of the security fix.

Here's the current situation in your codebase (proxy/auth/common.go lines 761-783):

// extractCodeVerifierFromState extracts code_verifier from state parameter if it was encoded there
// State format: "provider:<providerName>:<base64url_encoded_verifier>"
// Also handles legacy format: "provider:<providerName>" (no verifier)
func extractCodeVerifierFromState(state string, providerName string) string {
	prefix := fmt.Sprintf("provider:%s", providerName)
	if !strings.HasPrefix(state, prefix) {
		return ""
	}

	// Check if state has the encoded verifier (format: "provider:<name>:<encoded>")
	if strings.HasPrefix(state, prefix+":") {
		encodedVerifier := strings.TrimPrefix(state, prefix+":")
		if encodedVerifier == "" {
			return ""
		}
		// Decode base64url encoded verifier
		decoded, err := b64.URLEncoding.WithPadding(b64.NoPadding).DecodeString(encodedVerifier)
		if err != nil {
			log.GetLogger().Debugf("Failed to decode code_verifier from state: %v", err)
			return ""
		}
		return string(decoded)
	}

	// Legacy format without verifier
	return ""
}

Here's what you should actually do:

Option 1: Modify the existing function (simpler approach)

// extractProviderNameFromState extracts provider name from state parameter
// State format: "provider:<providerName>"
// NOTE: We no longer embed code_verifier in state for security reasons
func extractProviderNameFromState(state string) string {
	prefix := "provider:"
	if !strings.HasPrefix(state, prefix) {
		return ""
	}
	// Extract just the provider name (everything after "provider:")
	providerName := strings.TrimPrefix(state, prefix)
	// If there was a verifier encoded (legacy format with ":"), take only the first part
	if idx := strings.Index(providerName, ":"); idx > 0 {
		providerName = providerName[:idx]
	}
	return providerName
}

Then in your callback handler where you currently call extractCodeVerifierFromState, change to:

// Extract provider name from state
providerName := extractProviderNameFromState(state)
if providerName == "" {
    respondWithError(w, http.StatusBadRequest, "Invalid state parameter")
    return
}

// Get verifier from cookie ONLY (remove any fallback to state)
codeVerifier, err := getPKCEVerifierCookie(r, providerName)
if err != nil || codeVerifier == "" {
    respondWithError(w, http.StatusBadRequest, "PKCE verification failed")
    return
}

The key changes are:

  1. Rename extractCodeVerifierFromStateextractProviderNameFromState
  2. Remove the verifier decoding logic (the security issue)
  3. Update callers to only get the verifier from the cookie, never from state

Does this clarify the approach?


🧠 Learnings used
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 378
File: libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts:288-326
Timestamp: 2025-11-20T09:24:41.950Z
Learning: In the flightctl-ui repository, the CreateAuthProvider form and its utilities (including getAuthProvider in libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts) are specifically designed for CRUD operations on OIDC and OAuth2 authentication providers only. Other provider types (K8s, AAP, OpenShift) are not supported by this form and should be rejected with an error if loading is attempted for editing.

Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 371
File: libs/types/models/AppType.ts:10-10
Timestamp: 2025-10-29T16:47:29.614Z
Learning: PR #371 (flightctl/flightctl-ui) adds the AppTypeQuadlet enum member and related types as a preparatory change. Full implementation of quadlet application support in the UI will be added later, after backend support is available.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@coderabbitai could you verify the fix in #384?
Seems correct to me but please check

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch PR #384 details including title, description, and files changed
gh pr view 384 --repo flightctl/flightctl-ui --json title,body,files,state,url

echo "---"
echo "Fetching diff for PR #384..."
gh pr diff 384 --repo flightctl/flightctl-ui

Length of output: 12620


@celdrake, I've reviewed PR #384 and the fix looks excellent! ✅ It correctly addresses all the PKCE security concerns. Here's my verification:

✅ Security Issues Fixed

  1. Verifier removed from state parameter: The loginRedirect function no longer embeds code_verifier in the state parameter. State now only contains "provider:<name>" format with a clear security comment.

  2. Cookie-only mechanism: The callback handler in auth.go now relies solely on the cookie to retrieve the verifier, with proper error handling if the cookie is missing.

  3. Function renamed and simplified: extractCodeVerifierFromStateextractProviderNameFromState now only extracts the provider name and includes legacy format handling.

  4. Consistent interface changes: All implementations (OIDC, OAuth2, AAP, OpenShift, K8s) updated to remove the codeVerifier parameter from GetLoginRedirectURL.

  5. Frontend compatibility: Added credentials: 'include' to the fetch request in LoginPage.tsx to ensure cookies are properly sent.

🔍 Minor Observation

There's a small logging inconsistency in proxy/auth/auth.go around line 352:

loginParams.CodeVerifier = codeVerifier
log.GetLogger().Infof("Retrieved PKCE verifier from state parameter for provider %s", providerName)

The log message says "from state parameter" but the code actually retrieves it from the cookie now. Consider updating to:

log.GetLogger().Infof("Retrieved PKCE verifier from cookie for provider %s", providerName)

This is purely cosmetic and doesn't affect security—just helps with accurate debugging logs.

✅ Overall Assessment

The fix is complete and correct. It successfully:

  • Eliminates front-channel exposure of the verifier
  • Maintains PKCE security model integrity
  • Handles errors gracefully
  • Includes backward compatibility for legacy state formats

Great work on the security fix! 🐰


🧬 Code Graph Analysis Results

<no_relevant_code_snippets>


🧠 Learnings used
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 371
File: libs/types/models/AppType.ts:10-10
Timestamp: 2025-10-29T16:47:29.614Z
Learning: PR #371 (flightctl/flightctl-ui) adds the AppTypeQuadlet enum member and related types as a preparatory change. Full implementation of quadlet application support in the UI will be added later, after backend support is available.

Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 376
File: libs/ui-components/src/components/Repository/CreateRepository/CreateRepositoryForm.tsx:274-275
Timestamp: 2025-11-17T15:53:01.194Z
Learning: In PR #376 (flightctl/flightctl-ui), the permission system was updated to fetch all user permissions once at login via the Flight Control API. The `checkPermissions` function from `usePermissionsContext` is a pure calculation on cached permission arrays, not an async fetch, so loading state handling is generally not required at individual component call sites.

Learnt from: rawagner
Repo: flightctl/flightctl-ui PR: 209
File: apps/standalone/src/app/utils/apiCalls.ts:14-16
Timestamp: 2025-02-18T12:04:42.579Z
Learning: In the flightctl-ui codebase, error handling for API calls follows a React pattern where the caller component (e.g., AppToolbar.tsx) implements try-catch blocks and manages error states, rather than handling errors within the API utility functions themselves.

Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 378
File: libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts:288-326
Timestamp: 2025-11-20T09:24:41.991Z
Learning: In the flightctl-ui repository, the CreateAuthProvider form and its utilities (including getAuthProvider in libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts) are specifically designed for CRUD operations on OIDC and OAuth2 authentication providers only. Other provider types (K8s, AAP, OpenShift) are not supported by this form and should be rejected with an error if loading is attempted for editing.

@celdrake celdrake force-pushed the EDM-2373-support-multiple-auth-providers branch from 63acf96 to 18bc90f Compare November 20, 2025 09:49

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

♻️ Duplicate comments (1)
libs/ui-components/src/components/AuthProvider/AuthProvidersPage.tsx (1)

153-157: Consider reusing CreateAuthProviderButton for consistency.

The toolbar button lacks the VERB.CREATE permission check that CreateAuthProviderButton (lines 49-62) provides. While the page itself is LIST-gated, users with LIST but not CREATE permission would see a non-functional button here.

Apply this diff to reuse the permission-aware button:

-                <ToolbarItem>
-                  <Button variant="primary" onClick={() => navigate(ROUTE.AUTH_PROVIDER_CREATE)}>
-                    {t('Add authentication provider')}
-                  </Button>
-                </ToolbarItem>
+                <ToolbarItem>
+                  <CreateAuthProviderButton />
+                </ToolbarItem>
🧹 Nitpick comments (3)
proxy/bridge/testauth.go (1)

92-93: Consider checking error return from JSON encoding.

While JSON encoding of these structs is unlikely to fail, checking the error improves robustness and makes error scenarios explicit.

Apply this diff:

 w.Header().Set("Content-Type", "application/json")
-json.NewEncoder(w).Encode(response)
+if err := json.NewEncoder(w).Encode(response); err != nil {
+  log.GetLogger().Errorf("Failed to encode test auth response: %v", err)
+}
apps/standalone/src/app/components/Login/LoginPageLayout.tsx (1)

6-29: Browser theme hook reuse looks good; consider an optional guard for non‑DOM environments

Using updateThemeClass(document.documentElement, ...) here correctly reuses the shared theming helper and removes the earlier duplication, and the media query listener is wired/cleaned up properly. The only nit is that this assumes window and matchMedia are always available; if this component ever renders in a non‑browser/SSR context, a small guard (e.g., checking typeof window !== 'undefined' and window.matchMedia) would make it more robust. Based on learnings

apps/standalone/src/app/components/Login/LoginPage.tsx (1)

39-63: Consider preserving provider selection UI after redirect failures

When a non‑K8s provider redirect fails in handleProviderSelect, you set error and clear userSelectedProvider; because the render short‑circuits on error, the user sees only the Alert and loses the provider list and any way to retry without reloading the page.

Consider keeping the selector visible with an inline error (or a “Try again” action) so users can switch providers or retry the same one after a transient failure, instead of being forced to reload. For example, you could store a separate redirectError and render it above ProviderSelector while leaving the main error reserved for fatal config‑load issues.

Also applies to: 115-123, 154-160

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 63acf96 and 18bc90f.

📒 Files selected for processing (12)
  • apps/standalone/src/app/components/Login/LoginPage.tsx (1 hunks)
  • apps/standalone/src/app/components/Login/LoginPageLayout.tsx (1 hunks)
  • libs/i18n/locales/en/translation.json (7 hunks)
  • libs/ui-components/src/components/AuthProvider/AuthProviderDetails/AuthProviderDetails.tsx (1 hunks)
  • libs/ui-components/src/components/AuthProvider/AuthProviderRow.tsx (1 hunks)
  • libs/ui-components/src/components/AuthProvider/AuthProvidersPage.tsx (1 hunks)
  • libs/ui-components/src/components/AuthProvider/CreateAuthProvider/CreateAuthProvider.tsx (1 hunks)
  • libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts (1 hunks)
  • libs/ui-components/src/components/common/PageNavigation.tsx (4 hunks)
  • libs/ui-components/src/types/extraTypes.ts (2 hunks)
  • libs/ui-components/src/utils/authProvider.ts (1 hunks)
  • proxy/bridge/testauth.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • libs/ui-components/src/components/AuthProvider/CreateAuthProvider/CreateAuthProvider.tsx
  • libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts
  • libs/ui-components/src/components/AuthProvider/AuthProviderRow.tsx
🧰 Additional context used
🧠 Learnings (10)
📓 Common learnings
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 378
File: libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts:288-326
Timestamp: 2025-11-20T09:24:41.950Z
Learning: In the flightctl-ui repository, the CreateAuthProvider form and its utilities (including getAuthProvider in libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts) are specifically designed for CRUD operations on OIDC and OAuth2 authentication providers only. Other provider types (K8s, AAP, OpenShift) are not supported by this form and should be rejected with an error if loading is attempted for editing.
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 371
File: libs/types/models/AppType.ts:10-10
Timestamp: 2025-10-29T16:47:29.614Z
Learning: PR #371 (flightctl/flightctl-ui) adds the AppTypeQuadlet enum member and related types as a preparatory change. Full implementation of quadlet application support in the UI will be added later, after backend support is available.
📚 Learning: 2025-11-17T15:53:01.194Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 376
File: libs/ui-components/src/components/Repository/CreateRepository/CreateRepositoryForm.tsx:274-275
Timestamp: 2025-11-17T15:53:01.194Z
Learning: In PR #376 (flightctl/flightctl-ui), the permission system was updated to fetch all user permissions once at login via the Flight Control API. The `checkPermissions` function from `usePermissionsContext` is a pure calculation on cached permission arrays, not an async fetch, so loading state handling is generally not required at individual component call sites.

Applied to files:

  • libs/ui-components/src/components/common/PageNavigation.tsx
  • libs/ui-components/src/components/AuthProvider/AuthProvidersPage.tsx
📚 Learning: 2025-11-20T09:24:41.950Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 378
File: libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts:288-326
Timestamp: 2025-11-20T09:24:41.950Z
Learning: In the flightctl-ui repository, the CreateAuthProvider form and its utilities (including getAuthProvider in libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts) are specifically designed for CRUD operations on OIDC and OAuth2 authentication providers only. Other provider types (K8s, AAP, OpenShift) are not supported by this form and should be rejected with an error if loading is attempted for editing.

Applied to files:

  • libs/ui-components/src/components/common/PageNavigation.tsx
  • libs/ui-components/src/types/extraTypes.ts
  • libs/ui-components/src/utils/authProvider.ts
  • libs/ui-components/src/components/AuthProvider/AuthProviderDetails/AuthProviderDetails.tsx
  • libs/ui-components/src/components/AuthProvider/AuthProvidersPage.tsx
  • apps/standalone/src/app/components/Login/LoginPage.tsx
📚 Learning: 2025-11-20T07:33:34.656Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 378
File: libs/ui-components/src/utils/authProvider.ts:5-18
Timestamp: 2025-11-20T07:33:34.656Z
Learning: In the flightctl-ui repository, for AuthProvider objects, the metadata.name field is always present at runtime as it serves as the ID of the authprovider. The OpenAPI schema incorrectly defines it as optional (name?: string in ObjectMeta), but the type assertion `as string` is appropriate when accessing provider.metadata.name since it's guaranteed to exist.

Applied to files:

  • libs/ui-components/src/types/extraTypes.ts
  • libs/ui-components/src/utils/authProvider.ts
📚 Learning: 2025-02-17T08:41:57.993Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 0
File: :0-0
Timestamp: 2025-02-17T08:41:57.993Z
Learning: All files in `libs/types/models` are auto-generated using OpenAPI Generator from the schema defined in https://github.com/flightctl/flightctl/blob/main/api/v1alpha1/openapi.yaml. Changes to these files should be made in the schema instead of modifying the generated files directly.

Applied to files:

  • libs/ui-components/src/types/extraTypes.ts
📚 Learning: 2025-03-20T12:34:33.199Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 240
File: libs/types/models/FileSpec.ts:8-8
Timestamp: 2025-03-20T12:34:33.199Z
Learning: Files in the types directory are autogenerated from the OpenAPI spec in the flightctl project and should not be directly modified.

Applied to files:

  • libs/ui-components/src/types/extraTypes.ts
📚 Learning: 2025-10-29T16:47:29.614Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 371
File: libs/types/models/AppType.ts:10-10
Timestamp: 2025-10-29T16:47:29.614Z
Learning: PR #371 (flightctl/flightctl-ui) adds the AppTypeQuadlet enum member and related types as a preparatory change. Full implementation of quadlet application support in the UI will be added later, after backend support is available.

Applied to files:

  • libs/ui-components/src/utils/authProvider.ts
  • libs/ui-components/src/components/AuthProvider/AuthProvidersPage.tsx
  • apps/standalone/src/app/components/Login/LoginPage.tsx
  • proxy/bridge/testauth.go
📚 Learning: 2025-02-17T08:52:50.747Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 218
File: libs/ui-components/src/components/Device/DeviceDetails/DeviceDetailsPage.tsx:109-111
Timestamp: 2025-02-17T08:52:50.747Z
Learning: When wrapping a Button component (like `deleteAction`) inside another Button in React/PatternFly, use `component="a"` on the outer Button to avoid accessibility issues caused by button nesting. Include `tabIndex={0}` to ensure proper keyboard navigation.

Applied to files:

  • libs/ui-components/src/components/AuthProvider/AuthProvidersPage.tsx
📚 Learning: 2025-05-29T12:07:34.835Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 304
File: libs/ui-components/src/hooks/useThemePreferences.ts:57-65
Timestamp: 2025-05-29T12:07:34.835Z
Learning: When the application runs as a plugin inside the OCP console (indicated by the presence of `consoleTheme`), it should not call `updateThemeClass` to modify DOM theme classes. The OCP console handles theme application, and the plugin should only read the theme value for internal use like setting component themes.

Applied to files:

  • apps/standalone/src/app/components/Login/LoginPageLayout.tsx
📚 Learning: 2025-02-18T12:04:42.579Z
Learnt from: rawagner
Repo: flightctl/flightctl-ui PR: 209
File: apps/standalone/src/app/utils/apiCalls.ts:14-16
Timestamp: 2025-02-18T12:04:42.579Z
Learning: In the flightctl-ui codebase, error handling for API calls follows a React pattern where the caller component (e.g., AppToolbar.tsx) implements try-catch blocks and manages error states, rather than handling errors within the API utility functions themselves.

Applied to files:

  • apps/standalone/src/app/components/Login/LoginPage.tsx
🧬 Code graph analysis (7)
libs/ui-components/src/components/common/PageNavigation.tsx (4)
libs/ui-components/src/hooks/useTranslation.ts (1)
  • useTranslation (5-8)
libs/ui-components/src/hooks/useNavigate.tsx (1)
  • useNavigate (49-72)
libs/ui-components/src/components/common/OrganizationGuard.tsx (1)
  • useOrganizationGuardContext (17-23)
libs/ui-components/src/components/common/PermissionsContext.tsx (1)
  • usePermissionsContext (105-111)
libs/ui-components/src/types/extraTypes.ts (3)
libs/types/models/OIDCProviderSpec.ts (1)
  • OIDCProviderSpec (10-45)
libs/types/models/OAuth2ProviderSpec.ts (1)
  • OAuth2ProviderSpec (10-57)
libs/types/models/AuthProvider.ts (1)
  • AuthProvider (10-21)
libs/ui-components/src/components/AuthProvider/AuthProviderDetails/AuthProviderDetails.tsx (5)
libs/ui-components/src/hooks/useTranslation.ts (1)
  • useTranslation (5-8)
libs/ui-components/src/hooks/useAppContext.tsx (1)
  • useAppContext (123-123)
libs/ui-components/src/hooks/useFetchPeriodically.ts (1)
  • useFetchPeriodically (8-79)
libs/ui-components/src/hooks/useNavigate.tsx (2)
  • useNavigate (49-72)
  • Route (45-45)
libs/ui-components/src/components/common/PermissionsContext.tsx (1)
  • usePermissionsContext (105-111)
libs/ui-components/src/components/AuthProvider/AuthProvidersPage.tsx (5)
libs/ui-components/src/components/Table/Table.tsx (1)
  • ApiSortTableColumn (16-24)
libs/ui-components/src/hooks/useTranslation.ts (1)
  • useTranslation (5-8)
libs/ui-components/src/hooks/useNavigate.tsx (1)
  • useNavigate (49-72)
libs/ui-components/src/components/common/PermissionsContext.tsx (1)
  • usePermissionsContext (105-111)
libs/ui-components/src/components/AuthProvider/useAuthProviders.ts (1)
  • useAuthProviders (4-18)
apps/standalone/src/app/components/Login/LoginPageLayout.tsx (1)
libs/ui-components/src/hooks/useThemePreferences.ts (1)
  • updateThemeClass (33-39)
apps/standalone/src/app/components/Login/LoginPage.tsx (4)
libs/types/models/AuthProvider.ts (1)
  • AuthProvider (10-21)
apps/standalone/src/app/utils/apiCalls.ts (1)
  • loginAPI (17-17)
libs/ui-components/src/utils/authProvider.ts (1)
  • getProviderDisplayName (5-20)
libs/types/models/AuthConfig.ts (1)
  • AuthConfig (6-23)
proxy/bridge/testauth.go (2)
libs/ui-components/src/components/AuthProvider/CreateAuthProvider/types.ts (2)
  • FieldValidationResult (91-96)
  • FieldValidation (85-89)
proxy/log/log.go (1)
  • GetLogger (17-19)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: integration-tests
  • GitHub Check: Lint
  • GitHub Check: Build
  • GitHub Check: Build ocp plugin
🔇 Additional comments (20)
libs/i18n/locales/en/translation.json (2)

40-180: Comprehensive authentication provider localization coverage.

The new keys effectively support the multi-provider authentication flow: CRUD operations (lines 40–50), provider configuration fields including URLs and credentials (lines 52–87), claims and role/organization assignment strategies (lines 63–122), validation messages with specific guidance (lines 157–180), and connection testing results (lines 172–180). The inclusion of localized provider type names (OpenShift, Kubernetes, Ansible Automation Platform at lines 980–982) is appropriate for display purposes, consistent with the design that only OIDC and OAuth2 providers are editable via the form.


719-732: Kubernetes token authentication UI strings properly localized.

The new keys for Kubernetes token-based login (lines 719–732) provide clear guidance on token format, security warnings, and authentication state. These align with the multi-provider strategy and cover the non-OIDC/OAuth2 provider flow.

libs/ui-components/src/components/common/PageNavigation.tsx (4)

1-23: LGTM! Imports are well-organized.

All new imports (Button, Tooltip, CogIcon, ROUTE, RESOURCE, VERB, usePermissionsContext) are utilized in the implementation and support the new admin settings functionality.


65-69: Permission check correctly determines admin status.

The implementation properly derives isAdmin from the AUTH_PROVIDER CREATE permission, which aligns with the approach that admin capabilities are determined by auth provider management permissions. The checkPermissions result is safely destructured, and any falsy value will correctly prevent the Settings button from rendering.


72-79: Excellent visibility logic and defensive programming.

The optional chaining for currentOrgDisplayName (line 72) handles missing organization data gracefully. The shouldShowNavigation logic (line 75) correctly shows the navigation bar when there's either organization selection or admin settings to display, with a clear explanatory comment. The early return pattern optimizes rendering performance.


98-111: Well-implemented Settings button with proper accessibility.

The Settings button implementation follows best practices:

  • Proper accessibility with aria-label (line 103)
  • Descriptive tooltip explaining the action (line 100)
  • Standard CogIcon for settings (line 105)
  • Clean navigation to AUTH_PROVIDERS route (line 104)
  • Internationalization support via translated text (line 107)
proxy/bridge/testauth.go (4)

96-240: LGTM! OIDC discovery and validation logic is comprehensive.

The implementation correctly:

  • Enforces issuer requirement for OIDC providers
  • Performs SSRF validation on the issuer URL
  • Fetches and parses the OIDC discovery document
  • Validates required endpoints (authorization, token) and notes optional ones (userinfo)
  • Tests endpoint reachability while safely skipping SSRF checks for discovered endpoints (they originate from the validated issuer)
  • Provides detailed validation notes for each field

278-293: Verify whether userinfo URL should be required for OAuth2.

The current implementation requires the userinfo URL for OAuth2 providers, but standard OAuth2 (RFC 6749) does not mandate a userinfo endpoint. While OpenID Connect defines a userinfo endpoint, it's typically optional. Requiring this field may be unnecessarily strict and could prevent valid OAuth2 configurations.

Please confirm whether this is intentional for your use case, or if the userinfo URL should be optional for OAuth2 providers (similar to how it's treated as optional in the OIDC discovery validation at line 193-195).


307-344: LGTM! SSRF protections are well-implemented.

The implementation provides solid defense-in-depth by:

  • Blocking localhost and *.localhost hostnames
  • Rejecting loopback, link-local, and private IP ranges using Go's built-in checks
  • Validating URLs before they are used in requests

The TOCTOU risk (DNS changing between validation and request) is acceptable given this is an admin-only feature.

Based on past review comments indicating SSRF concerns were addressed.


346-425: LGTM! Endpoint reachability testing is comprehensive and well-designed.

The implementation:

  • Applies SSRF validation for user-provided URLs while safely skipping it for discovered endpoints
  • Validates URL schemes and structure before making requests
  • Interprets HTTP status codes appropriately for different endpoint types (e.g., token endpoints expect POST, so 404 on GET is acceptable)
  • Provides detailed, user-friendly notes for each validation result
  • Logs failures at appropriate level for security monitoring

The nuanced status code handling (lines 390-418) correctly distinguishes between reachability issues and expected authentication/authorization responses.

apps/standalone/src/app/components/Login/LoginPage.tsx (2)

15-27: Redirect helper now validates responses and avoids bad URLs

redirectToProviderLogin now checks response.ok and the presence of a non‑empty url before redirecting, throwing errors otherwise so callers can surface a proper error state. This addresses the earlier risk of navigating to "undefined" or an invalid URL while keeping the flow simple.


57-59: Review comment is based on incorrect assumptions about i18next behavior

i18next's default interpolation ignores whitespace inside {{ }} delimiters, so spaces like {{ providerName }} versus {{providerName}} have no functional impact. Both will interpolate correctly.

However, the trailing space outside the braces in the first key ('Failed to initiate login with {{ providerName}} ') is a separate issue—it's literal text in the message that may be unintended.

The review comment conflates two distinct problems:

  1. Spaces inside {{ }} (harmless in i18next; no fix needed)
  2. Trailing space outside the braces in the message string (could be cleaned up for code quality)

No code changes are required for interpolation to work correctly. The spaces do not prevent variable substitution.

Likely an incorrect or invalid review comment.

libs/ui-components/src/utils/authProvider.ts (1)

5-20: LGTM!

The display name resolution logic is well-structured with appropriate fallbacks: checks for explicit displayName, provides translated labels for built-in provider types (OpenShift, K8s, AAP), and falls back to metadata.name. The type assertion on line 19 is safe per established context.

Based on learnings

libs/ui-components/src/types/extraTypes.ts (1)

82-95: LGTM!

The DynamicAuthProvider types and isDynamicAuthProvider type guard correctly identify OIDC and OAuth2 providers as the dynamically manageable subset, which aligns with the CRUD form's design constraints. The ProviderType enum provides type-safe constants for all provider types across the system.

Based on learnings

libs/ui-components/src/components/AuthProvider/AuthProvidersPage.tsx (3)

34-47: LGTM!

The column definitions are straightforward and properly translated.


64-78: LGTM!

The empty state correctly reuses CreateAuthProviderButton, ensuring the action button respects RBAC permissions.


80-123: LGTM!

The table implementation properly handles loading states, errors, and the delete flow. The refetch() call on line 117 ensures the list updates after successful deletion. The type assertion on line 97 is safe per documented context.

Based on learnings

libs/ui-components/src/components/AuthProvider/AuthProviderDetails/AuthProviderDetails.tsx (3)

20-23: LGTM!

Permission definitions are correctly scoped to the actions available on this page (edit and delete).


25-99: LGTM!

The details page properly handles data fetching, permission-gated actions (edit/delete), and navigation between details and YAML views. The delete flow correctly navigates back to the providers list on success. Type assertions are safe per documented context.

Based on learnings


101-109: LGTM!

The permission wrapper correctly gates the entire details page with VERB.GET, ensuring users can only view provider details if authorized.

Comment thread libs/i18n/locales/en/translation.json
@celdrake celdrake force-pushed the EDM-2373-support-multiple-auth-providers branch from 18bc90f to a868316 Compare November 20, 2025 10:09

@coderabbitai coderabbitai 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.

Actionable comments posted: 5

♻️ Duplicate comments (2)
libs/i18n/locales/en/translation.json (1)

16-16: Template variable spacing inconsistency persists with additional trailing space in key.

Line 16 has two issues:

  1. The template variable {{ providerName}} is missing a space before closing braces (should be {{ providerName }}).
  2. The key itself has a trailing space: "Failed to initiate login with {{ providerName}} " (space before the closing quote).

This was flagged in a previous review. Additionally, compare with line 20 which uses a different variable name ({{ provider }}).

- "Failed to initiate login with {{ providerName}} ": "Failed to initiate login with {{ providerName}} ",
+ "Failed to initiate login with {{ providerName }}": "Failed to initiate login with {{ providerName }}",
libs/ui-components/src/components/AuthProvider/AuthProvidersPage.tsx (1)

49-76: Consider reusing CreateAuthProviderButton in the toolbar for consistency

You already have CreateAuthProviderButton with an explicit VERB.CREATE check, but the toolbar renders a separate primary button that always shows when the page is visible. Even though the whole page is admin-gated, reusing the same component would avoid duplication and keep RBAC logic centralized:

-                <ToolbarItem>
-                  <Button variant="primary" onClick={() => navigate(ROUTE.AUTH_PROVIDER_CREATE)}>
-                    {t('Add authentication provider')}
-                  </Button>
-                </ToolbarItem>
+                <ToolbarItem>
+                  <CreateAuthProviderButton />
+                </ToolbarItem>

Not required for correctness, but it reduces duplication and keeps future permission changes in a single place.

Also applies to: 144-157

🧹 Nitpick comments (3)
proxy/bridge/testauth.go (2)

119-119: Normalize issuer URL to prevent malformed discovery URLs.

Simple string concatenation can produce malformed URLs if the issuer has a trailing slash or path component. While most OIDC providers use root issuers, robust handling would improve reliability.

Consider normalizing the issuer URL:

+	// Ensure issuer doesn't have trailing slash
+	issuerURL := strings.TrimSuffix(req.Issuer, "/")
-	discoveryUrl := fmt.Sprintf("%s/.well-known/openid-configuration", req.Issuer)
+	discoveryUrl := fmt.Sprintf("%s/.well-known/openid-configuration", issuerURL)

346-425: Consider extracting status code interpretation logic for clarity.

The status code handling (lines 390-418) is correct but involves deeply nested conditionals. Extracting this into a helper function would improve readability and testability.

Example refactor:

type endpointCheckResult struct {
	valid bool
	note  string
}

func interpretStatusCode(statusCode int, fieldName string, isTokenEndpoint bool) endpointCheckResult {
	if statusCode >= 200 && statusCode < 500 {
		switch statusCode {
		case http.StatusNotFound:
			if isTokenEndpoint {
				return endpointCheckResult{true, fmt.Sprintf("%s is reachable (HTTP 404 for GET - endpoint likely accepts POST)", fieldName)}
			}
			return endpointCheckResult{false, fmt.Sprintf("%s returned HTTP 404 - endpoint not found", fieldName)}
		case http.StatusMethodNotAllowed:
			return endpointCheckResult{true, fmt.Sprintf("%s is reachable (HTTP 405 - endpoint exists, GET not supported)", fieldName)}
		case http.StatusUnauthorized, http.StatusForbidden:
			return endpointCheckResult{true, fmt.Sprintf("%s is reachable (HTTP %d - authentication required, as expected)", fieldName, statusCode)}
		case http.StatusBadRequest:
			return endpointCheckResult{true, fmt.Sprintf("%s is reachable (HTTP 400 - endpoint exists)", fieldName)}
		default:
			return endpointCheckResult{true, fmt.Sprintf("%s is reachable (HTTP %d)", fieldName, statusCode)}
		}
	}
	return endpointCheckResult{false, fmt.Sprintf("%s returned HTTP %d - server error", fieldName, statusCode)}
}

Then use it in checkEndpointReachability:

	result := interpretStatusCode(resp.StatusCode, fieldName, isTokenEndpoint)
	notes = append(notes, result.note)
	valid = result.valid
libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts (1)

49-61: Optional: clarify getProviderTypeLabel scope and localize the fallback

Given this module is explicitly scoped to OIDC/OAuth2 providers, handling only ProviderType.OIDC and ProviderType.OAuth2 here is reasonable. Two small improvement options:

  • If you expect this helper to ever be used for K8s/AAP/OpenShift, consider adding explicit cases for those types rather than returning 'N/A'.
  • For the default branch, you may want to use a translated label (e.g., t('N/A') or similar) instead of a bare string to keep everything localizable.

Not strictly necessary today but may help future-proof the helper.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 18bc90f and a868316.

📒 Files selected for processing (12)
  • apps/standalone/src/app/components/Login/LoginPage.tsx (1 hunks)
  • apps/standalone/src/app/components/Login/LoginPageLayout.tsx (1 hunks)
  • libs/i18n/locales/en/translation.json (7 hunks)
  • libs/ui-components/src/components/AuthProvider/AuthProviderDetails/AuthProviderDetails.tsx (1 hunks)
  • libs/ui-components/src/components/AuthProvider/AuthProviderRow.tsx (1 hunks)
  • libs/ui-components/src/components/AuthProvider/AuthProvidersPage.tsx (1 hunks)
  • libs/ui-components/src/components/AuthProvider/CreateAuthProvider/CreateAuthProvider.tsx (1 hunks)
  • libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts (1 hunks)
  • libs/ui-components/src/components/common/PageNavigation.tsx (4 hunks)
  • libs/ui-components/src/types/extraTypes.ts (2 hunks)
  • libs/ui-components/src/utils/authProvider.ts (1 hunks)
  • proxy/bridge/testauth.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • libs/ui-components/src/components/common/PageNavigation.tsx
  • apps/standalone/src/app/components/Login/LoginPageLayout.tsx
  • libs/ui-components/src/components/AuthProvider/AuthProviderRow.tsx
  • libs/ui-components/src/components/AuthProvider/AuthProviderDetails/AuthProviderDetails.tsx
  • libs/ui-components/src/components/AuthProvider/CreateAuthProvider/CreateAuthProvider.tsx
🧰 Additional context used
🧠 Learnings (8)
📓 Common learnings
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 378
File: libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts:288-326
Timestamp: 2025-11-20T09:24:41.950Z
Learning: In the flightctl-ui repository, the CreateAuthProvider form and its utilities (including getAuthProvider in libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts) are specifically designed for CRUD operations on OIDC and OAuth2 authentication providers only. Other provider types (K8s, AAP, OpenShift) are not supported by this form and should be rejected with an error if loading is attempted for editing.
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 371
File: libs/types/models/AppType.ts:10-10
Timestamp: 2025-10-29T16:47:29.614Z
Learning: PR #371 (flightctl/flightctl-ui) adds the AppTypeQuadlet enum member and related types as a preparatory change. Full implementation of quadlet application support in the UI will be added later, after backend support is available.
📚 Learning: 2025-11-20T09:24:41.950Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 378
File: libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts:288-326
Timestamp: 2025-11-20T09:24:41.950Z
Learning: In the flightctl-ui repository, the CreateAuthProvider form and its utilities (including getAuthProvider in libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts) are specifically designed for CRUD operations on OIDC and OAuth2 authentication providers only. Other provider types (K8s, AAP, OpenShift) are not supported by this form and should be rejected with an error if loading is attempted for editing.

Applied to files:

  • libs/ui-components/src/utils/authProvider.ts
  • libs/ui-components/src/components/AuthProvider/AuthProvidersPage.tsx
  • apps/standalone/src/app/components/Login/LoginPage.tsx
  • libs/ui-components/src/types/extraTypes.ts
  • libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts
📚 Learning: 2025-11-20T07:33:34.656Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 378
File: libs/ui-components/src/utils/authProvider.ts:5-18
Timestamp: 2025-11-20T07:33:34.656Z
Learning: In the flightctl-ui repository, for AuthProvider objects, the metadata.name field is always present at runtime as it serves as the ID of the authprovider. The OpenAPI schema incorrectly defines it as optional (name?: string in ObjectMeta), but the type assertion `as string` is appropriate when accessing provider.metadata.name since it's guaranteed to exist.

Applied to files:

  • libs/ui-components/src/utils/authProvider.ts
  • libs/ui-components/src/types/extraTypes.ts
  • libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts
📚 Learning: 2025-10-29T16:47:29.614Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 371
File: libs/types/models/AppType.ts:10-10
Timestamp: 2025-10-29T16:47:29.614Z
Learning: PR #371 (flightctl/flightctl-ui) adds the AppTypeQuadlet enum member and related types as a preparatory change. Full implementation of quadlet application support in the UI will be added later, after backend support is available.

Applied to files:

  • libs/ui-components/src/utils/authProvider.ts
  • libs/ui-components/src/components/AuthProvider/AuthProvidersPage.tsx
  • apps/standalone/src/app/components/Login/LoginPage.tsx
  • proxy/bridge/testauth.go
  • libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts
📚 Learning: 2025-11-17T15:53:01.194Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 376
File: libs/ui-components/src/components/Repository/CreateRepository/CreateRepositoryForm.tsx:274-275
Timestamp: 2025-11-17T15:53:01.194Z
Learning: In PR #376 (flightctl/flightctl-ui), the permission system was updated to fetch all user permissions once at login via the Flight Control API. The `checkPermissions` function from `usePermissionsContext` is a pure calculation on cached permission arrays, not an async fetch, so loading state handling is generally not required at individual component call sites.

Applied to files:

  • libs/ui-components/src/components/AuthProvider/AuthProvidersPage.tsx
📚 Learning: 2025-02-18T12:04:42.579Z
Learnt from: rawagner
Repo: flightctl/flightctl-ui PR: 209
File: apps/standalone/src/app/utils/apiCalls.ts:14-16
Timestamp: 2025-02-18T12:04:42.579Z
Learning: In the flightctl-ui codebase, error handling for API calls follows a React pattern where the caller component (e.g., AppToolbar.tsx) implements try-catch blocks and manages error states, rather than handling errors within the API utility functions themselves.

Applied to files:

  • apps/standalone/src/app/components/Login/LoginPage.tsx
📚 Learning: 2025-02-17T08:41:57.993Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 0
File: :0-0
Timestamp: 2025-02-17T08:41:57.993Z
Learning: All files in `libs/types/models` are auto-generated using OpenAPI Generator from the schema defined in https://github.com/flightctl/flightctl/blob/main/api/v1alpha1/openapi.yaml. Changes to these files should be made in the schema instead of modifying the generated files directly.

Applied to files:

  • libs/ui-components/src/types/extraTypes.ts
📚 Learning: 2025-03-20T12:34:33.199Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 240
File: libs/types/models/FileSpec.ts:8-8
Timestamp: 2025-03-20T12:34:33.199Z
Learning: Files in the types directory are autogenerated from the OpenAPI spec in the flightctl project and should not be directly modified.

Applied to files:

  • libs/ui-components/src/types/extraTypes.ts
🧬 Code graph analysis (5)
libs/ui-components/src/components/AuthProvider/AuthProvidersPage.tsx (5)
libs/ui-components/src/components/Table/Table.tsx (1)
  • ApiSortTableColumn (16-24)
libs/ui-components/src/hooks/useTranslation.ts (1)
  • useTranslation (5-8)
libs/ui-components/src/hooks/useNavigate.tsx (1)
  • useNavigate (49-72)
libs/ui-components/src/components/common/PermissionsContext.tsx (1)
  • usePermissionsContext (105-111)
libs/ui-components/src/components/AuthProvider/useAuthProviders.ts (1)
  • useAuthProviders (4-18)
apps/standalone/src/app/components/Login/LoginPage.tsx (4)
libs/types/models/AuthProvider.ts (1)
  • AuthProvider (10-21)
apps/standalone/src/app/utils/apiCalls.ts (1)
  • loginAPI (17-17)
libs/ui-components/src/utils/authProvider.ts (1)
  • getProviderDisplayName (5-20)
libs/types/models/AuthConfig.ts (1)
  • AuthConfig (6-23)
libs/ui-components/src/types/extraTypes.ts (4)
libs/types/models/OIDCProviderSpec.ts (1)
  • OIDCProviderSpec (10-45)
libs/types/models/OAuth2ProviderSpec.ts (1)
  • OAuth2ProviderSpec (10-57)
proxy/auth/common.go (1)
  • AuthProvider (70-76)
libs/types/models/AuthProvider.ts (1)
  • AuthProvider (10-21)
proxy/bridge/testauth.go (2)
libs/ui-components/src/components/AuthProvider/CreateAuthProvider/types.ts (2)
  • FieldValidationResult (91-96)
  • FieldValidation (85-89)
proxy/log/log.go (1)
  • GetLogger (17-19)
libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts (4)
libs/ui-components/src/components/AuthProvider/CreateAuthProvider/types.ts (8)
  • AuthProviderFormValues (56-82)
  • DEFAULT_ROLE_SEPARATOR (23-23)
  • isOrgAssignmentDynamic (35-37)
  • isOrgAssignmentPerUser (39-41)
  • isRoleAssignmentStatic (48-50)
  • isRoleAssignmentDynamic (52-54)
  • isOAuth2Provider (28-29)
  • isOrgAssignmentStatic (31-33)
libs/ui-components/src/types/extraTypes.ts (1)
  • DynamicAuthProviderSpec (83-83)
libs/ui-components/src/utils/patch.ts (1)
  • appendJSONPatch (23-54)
libs/ui-components/src/components/form/validations.ts (1)
  • validKubernetesDnsSubdomain (151-166)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: integration-tests
  • GitHub Check: Build ocp plugin
  • GitHub Check: Lint
  • GitHub Check: Build
🔇 Additional comments (11)
libs/i18n/locales/en/translation.json (1)

980-982: Verify provider type entries align with form support constraints.

Lines 980–982 add localization keys for "OpenShift", "Kubernetes", and "Ansible Automation Platform" provider types. Per learnings, the CreateAuthProvider form supports only OIDC and OAuth2. Confirm these entries are used in read-only or system contexts (e.g., provider details view, provider type display) and not exposed in the CreateAuthProvider form where they would not be editable.

proxy/bridge/testauth.go (6)

1-15: LGTM!

Package declaration and imports are appropriate for the handler's functionality.


17-48: LGTM!

Type definitions are well-structured with appropriate JSON tags and align with the frontend TypeScript types.


50-58: LGTM!

Standard Go constructor pattern. The TLS config is properly stored for use in HTTP clients.


60-94: LGTM!

The handler correctly validates the request method, decodes the request body, creates an appropriately configured HTTP client, and routes to the correct validator based on provider type.


242-305: LGTM!

OAuth2 validation correctly enforces required fields (authorization, token, and userinfo URLs) and treats issuer as optional. SSRF protection is appropriately applied to all user-provided URLs.


307-344: <!-- [scratchpad_start]
[task verify_go_version 4/4 COMPLETE]
Verify Go 1.17+ support for net.IP.IsPrivate()

[observations]

  • go.mod specifies: go 1.24.0
  • toolchain: go1.24.6
  • Code uses net.IP.IsPrivate() on line 315
  • IsPrivate() was added in Go 1.17

[analysis]

  • Go 1.24.0 > Go 1.17.0
  • Code is compatible with project's Go version requirement
  • No compatibility issue exists

[done]
Verification complete: Go 1.24.0 supports net.IP.IsPrivate()
</scratchpad_end] -->

No compatibility issues found.

The project's go.mod specifies go 1.24.0, which is well above Go 1.17 where net.IP.IsPrivate() was introduced. The SSRF protection code is compatible.

libs/ui-components/src/utils/authProvider.ts (1)

5-19: Display-name resolution logic looks solid and consistent

getProviderDisplayName correctly prioritizes spec.displayName, provides translated labels for OpenShift/K8s/AAP, and safely falls back to provider.metadata.name as string, which is guaranteed to exist for AuthProvider instances in this codebase. Based on learnings.

apps/standalone/src/app/components/Login/LoginPage.tsx (1)

15-27: Provider-driven login flow and redirect error handling look correct

The login page cleanly:

  • Loads AuthConfig, filters enabled providers, and prioritizes the default provider.
  • Distinguishes K8s (token form) from other providers (redirect via redirectToProviderLogin).
  • Guards against multiple clicks with isRedirecting.
  • Validates the redirect endpoint (response.ok and presence of url) so both manual selection and single-provider auto-redirect surface errors via state instead of navigating to invalid URLs.

Overall, the control flow and state transitions for login look sound.

Also applies to: 65-105, 125-163

libs/ui-components/src/types/extraTypes.ts (1)

82-95: Dynamic auth-provider typing and type guard are well-structured

The DynamicAuthProviderSpec / DynamicAuthProvider pairing plus isDynamicAuthProvider cleanly narrow the generated AuthProvider to just OIDC/OAuth2 specs, and the ProviderType enum centralizes the string literals used across the UI. This should make the rest of the auth-provider code safer and easier to reason about.

libs/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.ts (1)

288-407: Patch generation and validation logic for auth providers looks coherent

A few related pieces work together nicely here:

  • getAuthProvider builds a full AuthProvider object from form values, with a clear branch between OAuth2 (adds URLs) and OIDC (issuer-only), matching the dynamic-provider scope of this form.
  • getAuthProviderPatches now correctly detects secret changes by comparing prevSpec.clientSecret vs newSpec.clientSecret, falls back to a full /spec replace when both providerType and secret change, and otherwise:
    • Replaces /spec/providerType if needed,
    • Delegates common fields (incl. org/role assignments) to patchAuthProviderFields with proper skipSecrets behavior,
    • Delegates OAuth2-only fields to patchProviderTypeSpecificFields (adding/removing URLs as types change).
  • authProviderSchema composes a base schema and then adds provider-type-specific and org-assignment-specific constraints, with sensible rules for scopes uniqueness, role assignment, and organization assignment.

This structure should give you precise, minimal patches and robust validation for the OIDC/OAuth2 CRUD flows.

Also applies to: 410-469, 471-555

Comment thread apps/standalone/src/app/components/Login/LoginPage.tsx
Comment on lines +16 to +20
"Failed to initiate login with {{ providerName}} ": "Failed to initiate login with {{ providerName}} ",
"Failed to initiate login": "Failed to initiate login",
"No authentication providers found. Please contact your administrator.": "No authentication providers found. Please contact your administrator.",
"Failed to load the authentication providers": "Failed to load the authentication providers",
"Redirecting to login for {{ provider }}...": "Redirecting to login for {{ provider }}...",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Inconsistent template variable naming across related keys.

Line 16 uses {{ providerName }} while line 20 uses {{ provider }} for what appear to be related error/redirect messages. Standardize the variable name across both keys for consistency.

- "Redirecting to login for {{ provider }}...": "Redirecting to login for {{ provider }}...",
+ "Redirecting to login for {{ providerName }}...": "Redirecting to login for {{ providerName }}...",
📝 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
"Failed to initiate login with {{ providerName}} ": "Failed to initiate login with {{ providerName}} ",
"Failed to initiate login": "Failed to initiate login",
"No authentication providers found. Please contact your administrator.": "No authentication providers found. Please contact your administrator.",
"Failed to load the authentication providers": "Failed to load the authentication providers",
"Redirecting to login for {{ provider }}...": "Redirecting to login for {{ provider }}...",
"Failed to initiate login with {{ providerName}} ": "Failed to initiate login with {{ providerName}} ",
"Failed to initiate login": "Failed to initiate login",
"No authentication providers found. Please contact your administrator.": "No authentication providers found. Please contact your administrator.",
"Failed to load the authentication providers": "Failed to load the authentication providers",
"Redirecting to login for {{ providerName }}...": "Redirecting to login for {{ providerName }}...",
🤖 Prompt for AI Agents
In libs/i18n/locales/en/translation.json around lines 16 to 20, the template
variable names are inconsistent (line 16 uses {{ providerName }} while line 20
uses {{ provider }}); standardize them by choosing one variable name (e.g., {{
provider }}) and update the entries so all related keys use the same
placeholder, ensuring translation keys and any code that references these
templates are adjusted to match the chosen variable name.

Comment thread libs/i18n/locales/en/translation.json
Comment thread proxy/bridge/testauth.go
return;
}

setUserSelectedProvider(provider);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

When user tries again, we should remove the previous error

Suggested change
setUserSelectedProvider(provider);
setError(undefined)
setUserSelectedProvider(provider);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

In general, the error handling did not work well if prefferedUsername was not present. Not a blocker though.

Comment thread apps/standalone/src/app/context/AuthContext.ts
Comment thread apps/standalone/src/app/context/AuthContext.ts
Comment thread apps/standalone/src/app/context/AuthContext.ts
Comment thread apps/standalone/src/app/context/AuthContext.ts
Comment thread apps/standalone/src/app/context/AuthContext.ts
@celdrake

Copy link
Copy Markdown
Collaborator Author

thanks @rawagner , I'll fix the issues in your CR in the follow-up.

@celdrake celdrake merged commit 92b5248 into flightctl:main Nov 24, 2025
6 checks passed
@celdrake celdrake deleted the EDM-2373-support-multiple-auth-providers branch November 24, 2025 07:19
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.

3 participants