Skip to content

feat: add options to restrict certain namespaces to certain login methods#2876

Open
JivusAyrus wants to merge 10 commits into
mainfrom
suvij/eng-9245-controlplane-different-auth-providers-for-different
Open

feat: add options to restrict certain namespaces to certain login methods#2876
JivusAyrus wants to merge 10 commits into
mainfrom
suvij/eng-9245-controlplane-different-auth-providers-for-different

Conversation

@JivusAyrus
Copy link
Copy Markdown
Member

@JivusAyrus JivusAyrus commented May 20, 2026

Summary by CodeRabbit

  • New Features

    • Multi-provider OIDC management and per-namespace SSO mapping APIs; sessions/WhoAmI now include login method
  • Security Improvements

    • RBAC tightened: analytics, graphs, proposals, persisted-ops, cache-warmers and other endpoints now enforce IdP/namespace access gates; auth contexts now carry login-method and IdP-derived namespace allowlists
  • UI/UX Enhancements

    • Namespace SSO admin page, updated SSO settings, login-method shown in user menu, namespace selector and empty-state reflect gating
  • Chores

    • DB migration and schema additions to support namespace SSO mappings

Review Change Stack

Checklist

  • I have discussed my proposed changes in an issue and have received approval to proceed.
  • I have followed the coding standards of the project.
  • Tests or benchmarks have been added or updated.
  • Documentation has been updated on https://github.com/wundergraph/docs-website.
  • I have read the Contributors Guide.

Open Source AI Manifesto

This project follows the principles of the Open Source AI Manifesto. Please ensure your contribution aligns with its principles.

Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

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

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Implements IdP-aware namespace access gating: adds LoginMethod/OIDC provider and namespace-SSO proto types and RPCs, DB schema and repositories for namespace SSO mappings, resolves login method from identity_provider claims, computes per-session allowed namespaces, enforces an IdP allowlist in RBAC and queries, and adds admin UI for mappings.

Changes

Multi-Provider SSO Namespace Access Gating

Layer / File(s) Summary
Protobuf Contracts and Generated Code
proto/wg/cosmo/platform/v1/platform.proto, connect/src/wg/cosmo/platform/v1/platform*.ts, connect/src/wg/cosmo/platform/v1/platform_pb.ts
LoginMethod message and enum added to WhoAmIResponse; OIDCProvider, ListOIDCProvidersRequest/Response introduced; GetOIDCProviderRequest, DeleteOIDCProviderRequest, UpdateIDPMappersRequest now include provider id field; new GetNamespaceSSOMapping*, NamespaceSSOMapping, UpdateNamespaceSSOMapping* messages and corresponding PlatformService RPCs; Connect Query and client bindings generated for new RPC methods.
Database Schema and Migrations
controlplane/migrations/0139_nappy_fallen_one.sql, controlplane/migrations/meta/_journal.json, controlplane/src/db/schema.ts
New namespace_sso_providers table with namespace/provider FK constraints, XOR check between ssoProviderId and isPasswordLogin, and unique partial indexes; sessions.idp_alias column added; oidc_providers.created_at timestamp added; Drizzle ORM schema updated to match.
Core Types and Configuration
controlplane/src/types/index.ts, controlplane/src/db/models.ts, docker/keycloak/realm.json
LoginMethod union type added for sso/password/api-key variants; AuthContext and UserInfoEndpointResponse extended with identity_provider claim; GraphCompositionDTO includes targetId; audit types updated for namespace_sso_mapping; Keycloak realm profile scope adds identity_provider OIDC protocol mapper.
Authentication and Login Method Resolution
controlplane/src/core/auth-utils.ts, controlplane/src/core/controllers/auth.ts, controlplane/src/core/services/WebSessionAuthenticator.ts, controlplane/src/core/services/AccessTokenAuthenticator.ts, controlplane/src/core/services/ApiKeyAuthenticator.ts, controlplane/src/core/services/Authentication.ts
Session creation extracts identity_provider claim from idToken into idpAlias for persistence; /session endpoint derives loginMethod from idpAlias via OIDC provider lookup; WebSessionAuthenticator, AccessTokenAuthenticator, ApiKeyAuthenticator all populate loginMethod in auth context; Authentication service caches per-sessionId and computes idpAllowedNamespaceIds via NamespaceSsoMappingRepository.
RBAC Evaluator and Authorization Gating
controlplane/src/core/services/RBACEvaluator.ts, controlplane/src/core/services/Authorization.ts
RBACEvaluator adds optional idpAllowedNamespaceIds allowlist; all permission-checking methods (canCreate*, hasFederatedGraphReadAccess, etc.) additionally enforce IdP namespace gate via isAllowedByIdpGate; Authorization service gates admin/developer operations on federated graphs and subgraphs by namespace membership in the IdP allowlist.
Repositories with IdP Namespace Gating
controlplane/src/core/repositories/NamespaceSsoMappingRepository.ts, controlplane/src/core/repositories/OidcRepository.ts, controlplane/src/core/repositories/NamespaceRepository.ts, controlplane/src/core/repositories/FederatedGraphRepository.ts, controlplane/src/core/repositories/SubgraphRepository.ts, controlplane/src/core/repositories/FeatureFlagRepository.ts, controlplane/src/core/repositories/GraphCompositionRepository.ts, controlplane/src/core/repositories/UserRepository.ts
NamespaceSsoMappingRepository (new) manages SSO provider-to-namespace mappings and computes allowedNamespaceIds by login method; OidcRepository refactored to support multi-provider org lookups, id-scoped lookup, alias-scoped lookup; namespace/federated-graph/subgraph/feature-flag repos apply IdP allowlist gates to queries; GraphCompositionRepository exposes targetId.
Handler Routes with RBAC and SSO APIs
controlplane/src/core/bufservices/namespace/{getNamespaceSSOMapping,updateNamespaceSSOMapping}.ts, controlplane/src/core/bufservices/sso/listOIDCProviders.ts, controlplane/src/core/bufservices/PlatformService.ts, analytics *.ts, federated-graph *.ts, proposal *.ts, sso *.ts, org *.ts, persisted-operation *.ts, cache-warmer *.ts, user *.ts
New handlers for namespace SSO mapping retrieval/update and OIDC provider listing with org-admin authorization; RBAC guards added to 20+ existing analytics/graph/proposal handlers (hasFederatedGraphReadAccess, hasNamespaceReadAccess); OIDC handlers (get, delete, updateIDPMappers) updated to require provider id; organization group handlers refactored for multi-provider OIDC mapper management; handlers wired into PlatformService; build-server.ts injects OidcRepository and NamespaceSsoMappingRepository into auth stack.
Studio Frontend: Login Method Display and Namespace SSO Configuration
studio/src/pages/[organizationSlug]/namespace-sso.tsx, studio/src/pages/[organizationSlug]/settings.tsx, studio/src/components/app-provider.tsx, studio/src/components/dashboard/namespace-gate-empty-state.tsx, studio/src/components/dashboard/namespace-selector.tsx, studio/src/components/layout/dashboard-layout.tsx, studio/src/components/user-menu.tsx
New namespace-sso.tsx page with two-pane layout: namespace list and SSO mapping detail editor (password login toggle, provider selection, dirty form tracking, mutation save); settings.tsx refactored for multi-provider OIDC management with provider cards, connect dialog, and mapper editing; namespace-gate-empty-state explains gated access via login method; namespace-selector shows "No access" when gated; user-menu displays login method label; dashboard-layout detects namespace-scoped routes and shows empty state when no namespaces accessible; app-provider syncs LoginMethod from session.

🎯 4 (Complex) | ⏱️ ~75 minutes

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 20, 2026

Router image scan passed

✅ No security vulnerabilities found in image:

ghcr.io/wundergraph/cosmo/router:sha-b15973bb6e3e530c02e013fb92dd2eb5cbe0900d

@codecov
Copy link
Copy Markdown

codecov Bot commented May 20, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 40.82%. Comparing base (be9d015) to head (e6a980d).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@             Coverage Diff             @@
##             main    #2876       +/-   ##
===========================================
- Coverage   64.47%   40.82%   -23.66%     
===========================================
  Files         319      151      -168     
  Lines       45359    14630    -30729     
  Branches     4927      661     -4266     
===========================================
- Hits        29245     5972    -23273     
+ Misses      16089     8541     -7548     
- Partials       25      117       +92     

see 470 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

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

⚠️ Outside diff range comments (1)
controlplane/src/core/bufservices/user/updateOrgMemberGroup.ts (1)

63-88: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keycloak client authentication happens after the SSO user checks.

The loop at lines 68-85 calls opts.keycloakClient.client.users.find() for each provider, but opts.keycloakClient.authenticateClient() is only called at line 88, after the loop. This may cause the Keycloak calls inside the loop to fail if the client token isn't already valid.

Consider moving the authentication call before the loop:

🔧 Proposed fix
       // Ensure that the organization member has not signed in with SSO via any
       // of the org's configured providers.
+      await opts.keycloakClient.authenticateClient();
+
       const providers = await oidcRepo.listOidcProvidersByOrganizationId({
         organizationId: authContext.organizationId,
       });
       for (const provider of providers) {
         // checking if the user has logged in using the sso
         const ssoUser = await opts.keycloakClient.client.users.find({
           realm: opts.keycloakRealm,
           email: orgMember.email,
           exact: true,
           idpAlias: provider.alias,
         });

         if (ssoUser.length > 0) {
           return {
             response: {
               code: EnumStatusCode.ERR,
               details: 'User has logged in using the OIDC provider. Please update the group using the provider.',
             },
           };
         }
       }

       // Retrieve the Keycloak user
-      await opts.keycloakClient.authenticateClient();

       const users = await opts.keycloakClient.client.users.find({
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controlplane/src/core/bufservices/user/updateOrgMemberGroup.ts` around lines
63 - 88, The Keycloak client is authenticated after you call
opts.keycloakClient.client.users.find() inside the providers loop (after
listOidcProvidersByOrganizationId), which can cause those calls to fail; fix it
by calling and awaiting opts.keycloakClient.authenticateClient() before entering
the for (const provider of providers) loop so the client has a valid token, and
keep the existing find calls (client.users.find) unchanged; also propagate or
handle any authentication errors from authenticateClient() before proceeding.
🧹 Nitpick comments (2)
controlplane/src/core/services/RBACEvaluator.ts (1)

115-117: 💤 Low value

Minor: Redundant IdP gate check in canCreateContract.

canCreateFederatedGraph already calls isAllowedByIdpGate, so the additional check on line 116 is redundant. This doesn't cause incorrect behavior but adds unnecessary computation.

♻️ Suggested simplification
   canCreateContract(namespace: Namespace) {
-    return this.canCreateFederatedGraph(namespace) && this.isAllowedByIdpGate(namespace.id);
+    return this.canCreateFederatedGraph(namespace);
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controlplane/src/core/services/RBACEvaluator.ts` around lines 115 - 117,
canCreateContract currently performs an extra isAllowedByIdpGate check despite
canCreateFederatedGraph already invoking isAllowedByIdpGate; remove the
redundant check from canCreateContract so it simply returns the result of
canCreateFederatedGraph(namespace) to avoid duplicate computation while
preserving behavior (refer to the canCreateContract, canCreateFederatedGraph,
and isAllowedByIdpGate symbols and the Namespace parameter).
controlplane/src/core/bufservices/organization/whoAmI.ts (1)

4-9: 💤 Low value

Static analysis false positive for generated protobuf types.

The linter flags LoginMethod_Type for not being in camelCase, but this identifier is auto-generated from the protobuf definitions and cannot be renamed here. Consider adding a lint suppression comment if this causes CI failures, or configure the linter to ignore generated import identifiers.

+// eslint-disable-next-line `@typescript-eslint/naming-convention`
 import {
   LoginMethod,
   LoginMethod_Type,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controlplane/src/core/bufservices/organization/whoAmI.ts` around lines 4 - 9,
The import brings in auto-generated protobuf identifiers like LoginMethod_Type
which trigger the linter's camelCase rule; add a local lint suppression so the
generated name is allowed: place an ESLint/TSLint disable comment immediately
above the import line (targeting the naming-convention/camelcase rule) or add a
one-line disable for the specific rule, referencing the imported symbol
LoginMethod_Type to make it clear why the rule is disabled; ensure the comment
is minimal and scoped (not file-wide) so only the generated import is exempted.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@controlplane/src/core/bufservices/sso/deleteOIDCProvider.ts`:
- Around line 46-53: Move the request validation for req.id to run before any
Keycloak client authentication/initialization so invalid input returns
ERR_BAD_REQUEST deterministically; specifically, in deleteOIDCProvider (and
before any calls to getKeycloakClient / keycloakAdminClient.authenticate or
usage of variables like keycloakClient/keycloakAdminClient), check if (!req.id)
and return the ERR_BAD_REQUEST response immediately, then proceed to
create/authenticate the Keycloak client only after validation succeeds.

In `@controlplane/src/core/repositories/NamespaceSsoMappingRepository.ts`:
- Around line 70-81: The getMapping method is declared async but doesn't await
the database promise; remove the unnecessary async keyword from the getMapping
declaration (or alternatively add an await before
this.db.select(...).from(...).where(...).execute()) so the function either
returns the promise directly or properly awaits it; update the getMapping
signature and keep the existing query body referencing namespaceSsoProviders and
eq(namespaceSsoProviders.namespaceId, input.namespaceId).

In `@controlplane/src/core/repositories/OidcRepository.ts`:
- Around line 32-39: The method listOidcProvidersByOrganizationId is declared
async but does not use await; remove the unnecessary async modifier (or
alternatively await the DB promise) to fix the lint warning—update the method
signature in OidcRepository (and mirror the same fix pattern used in
NamespaceSsoMappingRepository) so it returns the DB query promise directly
without async, or add an await before this.db.select(...).execute() if you
prefer an awaited result.

In `@proto/wg/cosmo/platform/v1/platform.proto`:
- Around line 1896-1898: Update the GetOIDCProviderRequest (and analogous
messages at the other locations) to make the id field optional (i.e., mark it as
optional/allow empty) in the proto definition for message GetOIDCProviderRequest
and the similar messages at lines noted, then change the server handlers
GetOIDCProvider, DeleteOIDCProvider, and UpdateIDPMappers to tolerate a missing
id by implementing fallback resolution: if request.id is empty, try to infer the
provider id from the authenticated organization context (or from a single
default provider for that org) and only return a clear ERR_BAD_REQUEST when both
the id is missing and no unique provider can be resolved; ensure validation
error messages explicitly state when an id is required versus when a provider
could not be inferred.
- Around line 3100-3104: Change the UpdateNamespaceSSOMappingRequest proto so
allow_password_login is declared as "optional bool allow_password_login = 3;"
instead of plain bool, and then update the handler that processes
UpdateNamespaceSSOMappingRequest to check the presence of allow_password_login
(i.e., whether it is set/defined) before calling setMapping(); only pass a value
to setMapping() when the field is present (treat absent as “no change”), and
preserve existing behavior for explicit true/false inputs so setMapping()
continues to be called with explicit values when provided.

In `@studio/src/components/dashboard/namespace-selector.tsx`:
- Around line 91-100: The trigger hides the namespace label when
isViewingGraphOrSubgraph is true, which leaves only the caret visible even when
hasNoAccess is true; update the render logic in namespace-selector.tsx so that
when hasNoAccess is true you still render displayName (e.g., "No access" or the
existing displayName) even if isViewingGraphOrSubgraph is true—adjust the
conditional around the span that uses truncateNamespace, hasNoAccess and
displayName so it renders whenever hasNoAccess is true OR
isViewingGraphOrSubgraph is false, and ensure the className still applies
hasNoAccess styling.

In `@studio/src/components/user-menu.tsx`:
- Line 85: The SSO label currently uses loginMethod.ssoProviderName ||
loginMethod.ssoAlias || '' which can produce an empty string; update the logic
that sets ssoLabel (and the similar logic around lines 108-111) to provide a
fallback like 'SSO' instead of ''. For example, change the expression that
defines ssoLabel in component user-menu.tsx to use loginMethod.ssoProviderName
|| loginMethod.ssoAlias || 'SSO' (and apply the same fallback where the other
SSO display label is computed) so the UI never renders "Logged in via " with a
blank provider.

---

Outside diff comments:
In `@controlplane/src/core/bufservices/user/updateOrgMemberGroup.ts`:
- Around line 63-88: The Keycloak client is authenticated after you call
opts.keycloakClient.client.users.find() inside the providers loop (after
listOidcProvidersByOrganizationId), which can cause those calls to fail; fix it
by calling and awaiting opts.keycloakClient.authenticateClient() before entering
the for (const provider of providers) loop so the client has a valid token, and
keep the existing find calls (client.users.find) unchanged; also propagate or
handle any authentication errors from authenticateClient() before proceeding.

---

Nitpick comments:
In `@controlplane/src/core/bufservices/organization/whoAmI.ts`:
- Around line 4-9: The import brings in auto-generated protobuf identifiers like
LoginMethod_Type which trigger the linter's camelCase rule; add a local lint
suppression so the generated name is allowed: place an ESLint/TSLint disable
comment immediately above the import line (targeting the
naming-convention/camelcase rule) or add a one-line disable for the specific
rule, referencing the imported symbol LoginMethod_Type to make it clear why the
rule is disabled; ensure the comment is minimal and scoped (not file-wide) so
only the generated import is exempted.

In `@controlplane/src/core/services/RBACEvaluator.ts`:
- Around line 115-117: canCreateContract currently performs an extra
isAllowedByIdpGate check despite canCreateFederatedGraph already invoking
isAllowedByIdpGate; remove the redundant check from canCreateContract so it
simply returns the result of canCreateFederatedGraph(namespace) to avoid
duplicate computation while preserving behavior (refer to the canCreateContract,
canCreateFederatedGraph, and isAllowedByIdpGate symbols and the Namespace
parameter).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 300c6e7a-cd0b-4e45-bdb0-6e81f7d163be

📥 Commits

Reviewing files that changed from the base of the PR and between 8acd38f and 7011ecf.

⛔ Files ignored due to path filters (2)
  • connect-go/gen/proto/wg/cosmo/platform/v1/platform.pb.go is excluded by !**/*.pb.go, !**/gen/**
  • connect-go/gen/proto/wg/cosmo/platform/v1/platformv1connect/platform.connect.go is excluded by !**/gen/**
📒 Files selected for processing (71)
  • connect/src/wg/cosmo/platform/v1/platform-PlatformService_connectquery.ts
  • connect/src/wg/cosmo/platform/v1/platform_connect.ts
  • connect/src/wg/cosmo/platform/v1/platform_pb.ts
  • controlplane/migrations/0139_nappy_fallen_one.sql
  • controlplane/migrations/meta/0139_snapshot.json
  • controlplane/migrations/meta/_journal.json
  • controlplane/src/core/auth-utils.ts
  • controlplane/src/core/bufservices/PlatformService.ts
  • controlplane/src/core/bufservices/analytics/getAnalyticsView.ts
  • controlplane/src/core/bufservices/analytics/getDashboardAnalyticsView.ts
  • controlplane/src/core/bufservices/analytics/getFieldUsage.ts
  • controlplane/src/core/bufservices/analytics/getGraphMetrics.ts
  • controlplane/src/core/bufservices/analytics/getMetricsErrorRate.ts
  • controlplane/src/core/bufservices/analytics/getOperationClients.ts
  • controlplane/src/core/bufservices/analytics/getOperationContent.ts
  • controlplane/src/core/bufservices/analytics/getOperationDeprecatedFields.ts
  • controlplane/src/core/bufservices/analytics/getOperations.ts
  • controlplane/src/core/bufservices/analytics/getTrace.ts
  • controlplane/src/core/bufservices/cache-warmer/computeCacheWarmerOperations.ts
  • controlplane/src/core/bufservices/cache-warmer/configureCacheWarmer.ts
  • controlplane/src/core/bufservices/feature-flag/getFeatureFlags.ts
  • controlplane/src/core/bufservices/federated-graph/getClientsFromAnalytics.ts
  • controlplane/src/core/bufservices/federated-graph/getCompositionDetails.ts
  • controlplane/src/core/bufservices/federated-graph/migrateFromApollo.ts
  • controlplane/src/core/bufservices/federated-graph/moveFederatedGraph.ts
  • controlplane/src/core/bufservices/namespace/getNamespaceSSOMapping.ts
  • controlplane/src/core/bufservices/namespace/updateNamespaceSSOMapping.ts
  • controlplane/src/core/bufservices/organization/deleteOrganizationGroup.ts
  • controlplane/src/core/bufservices/organization/getOrganizationGroups.ts
  • controlplane/src/core/bufservices/organization/whoAmI.ts
  • controlplane/src/core/bufservices/persisted-operation/getClients.ts
  • controlplane/src/core/bufservices/persisted-operation/getPersistedOperations.ts
  • controlplane/src/core/bufservices/proposal/getNamespaceProposalConfig.ts
  • controlplane/src/core/bufservices/proposal/getProposal.ts
  • controlplane/src/core/bufservices/proposal/getProposalChecks.ts
  • controlplane/src/core/bufservices/schema-version/getChangelogBySchemaVersion.ts
  • controlplane/src/core/bufservices/sso/createOIDCProvider.ts
  • controlplane/src/core/bufservices/sso/deleteOIDCProvider.ts
  • controlplane/src/core/bufservices/sso/getOIDCProvider.ts
  • controlplane/src/core/bufservices/sso/listOIDCProviders.ts
  • controlplane/src/core/bufservices/sso/updateIDPMappers.ts
  • controlplane/src/core/bufservices/user/updateOrgMemberGroup.ts
  • controlplane/src/core/build-server.ts
  • controlplane/src/core/controllers/auth.ts
  • controlplane/src/core/repositories/FeatureFlagRepository.ts
  • controlplane/src/core/repositories/FederatedGraphRepository.ts
  • controlplane/src/core/repositories/GraphCompositionRepository.ts
  • controlplane/src/core/repositories/NamespaceRepository.ts
  • controlplane/src/core/repositories/NamespaceSsoMappingRepository.ts
  • controlplane/src/core/repositories/OidcRepository.ts
  • controlplane/src/core/repositories/SubgraphRepository.ts
  • controlplane/src/core/repositories/UserRepository.ts
  • controlplane/src/core/services/AccessTokenAuthenticator.ts
  • controlplane/src/core/services/ApiKeyAuthenticator.ts
  • controlplane/src/core/services/Authentication.ts
  • controlplane/src/core/services/Authorization.ts
  • controlplane/src/core/services/RBACEvaluator.ts
  • controlplane/src/core/services/WebSessionAuthenticator.ts
  • controlplane/src/core/workers/DeleteOrganizationWorker.ts
  • controlplane/src/db/models.ts
  • controlplane/src/db/schema.ts
  • controlplane/src/types/index.ts
  • docker/keycloak/realm.json
  • proto/wg/cosmo/platform/v1/platform.proto
  • studio/src/components/app-provider.tsx
  • studio/src/components/dashboard/namespace-gate-empty-state.tsx
  • studio/src/components/dashboard/namespace-selector.tsx
  • studio/src/components/layout/dashboard-layout.tsx
  • studio/src/components/user-menu.tsx
  • studio/src/pages/[organizationSlug]/namespace-sso.tsx
  • studio/src/pages/[organizationSlug]/settings.tsx
💤 Files with no reviewable changes (1)
  • controlplane/src/core/bufservices/sso/createOIDCProvider.ts

Comment on lines +46 to +53
if (!req.id) {
return {
response: {
code: EnumStatusCode.ERR_BAD_REQUEST,
details: 'Provider id is required',
},
};
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Move request validation before Keycloak client authentication.

The new req.id validation is good, but it currently executes after external Keycloak auth. On invalid input, this can fail as an infra error instead of returning deterministic ERR_BAD_REQUEST.

Suggested patch
-    await opts.keycloakClient.authenticateClient();
-
-    const organization = await orgRepo.byId(authContext.organizationId);
-    if (!organization) {
-      return {
-        response: {
-          code: EnumStatusCode.ERR_NOT_FOUND,
-          details: `Organization not found`,
-        },
-      };
-    }
-
     if (!req.id) {
       return {
         response: {
           code: EnumStatusCode.ERR_BAD_REQUEST,
           details: 'Provider id is required',
         },
       };
     }
+
+    await opts.keycloakClient.authenticateClient();
+
+    const organization = await orgRepo.byId(authContext.organizationId);
+    if (!organization) {
+      return {
+        response: {
+          code: EnumStatusCode.ERR_NOT_FOUND,
+          details: `Organization not found`,
+        },
+      };
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controlplane/src/core/bufservices/sso/deleteOIDCProvider.ts` around lines 46
- 53, Move the request validation for req.id to run before any Keycloak client
authentication/initialization so invalid input returns ERR_BAD_REQUEST
deterministically; specifically, in deleteOIDCProvider (and before any calls to
getKeycloakClient / keycloakAdminClient.authenticate or usage of variables like
keycloakClient/keycloakAdminClient), check if (!req.id) and return the
ERR_BAD_REQUEST response immediately, then proceed to create/authenticate the
Keycloak client only after validation succeeds.

Comment thread controlplane/src/core/repositories/NamespaceSsoMappingRepository.ts Outdated
Comment thread controlplane/src/core/repositories/OidcRepository.ts Outdated
Comment thread proto/wg/cosmo/platform/v1/platform.proto
Comment on lines +3100 to +3104
message UpdateNamespaceSSOMappingRequest {
string namespace_id = 1;
repeated string allowed_sso_provider_ids = 2;
bool allow_password_login = 3;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify update semantics in handler/repository for omitted allow_password_login.
rg -n -C3 'UpdateNamespaceSSOMapping|allow_password_login' controlplane/src/core/bufservices/namespace controlplane/src/core/repositories controlplane/src/core/services

Repository: wundergraph/cosmo

Length of output: 2492


🏁 Script executed:

cat -n controlplane/src/core/bufservices/namespace/updateNamespaceSSOMapping.ts

Repository: wundergraph/cosmo

Length of output: 4351


🏁 Script executed:

rg -n -A20 'allow_password_login' controlplane/src/core/repositories

Repository: wundergraph/cosmo

Length of output: 43


🏁 Script executed:

cat -n proto/wg/cosmo/platform/v1/platform.proto | sed -n '3095,3110p'

Repository: wundergraph/cosmo

Length of output: 533


🏁 Script executed:

rg -n -A15 'setMapping' controlplane/src/core/repositories/NamespaceSsoMappingRepository.ts

Repository: wundergraph/cosmo

Length of output: 739


🏁 Script executed:

rg -n 'class NamespaceSsoMappingRepository' controlplane/src/core/repositories/

Repository: wundergraph/cosmo

Length of output: 176


🏁 Script executed:

rg -n -A30 'async setMapping' controlplane/src/core/repositories/NamespaceSsoMappingRepository.ts

Repository: wundergraph/cosmo

Length of output: 1000


Use optional bool to prevent unintended password login disabling on updates.

Currently, when a client sends an UpdateNamespaceSSOMappingRequest without specifying allow_password_login, proto3 deserializes the field to false (default), and the handler unconditionally passes this to setMapping(). The repository then deletes all existing SSO provider mappings and does not add a password login row, silently disabling password login even when the client never intended to change that setting.

With optional bool, omitted fields will be undefined, allowing the handler to distinguish "not specified" (no change) from "explicitly false" (disable password login). This is essential for safe update semantics.

Proposed proto adjustment
 message UpdateNamespaceSSOMappingRequest {
   string namespace_id = 1;
   repeated string allowed_sso_provider_ids = 2;
-  bool allow_password_login = 3;
+  optional bool allow_password_login = 3;
 }

The handler must also be updated to check field presence before passing the value to setMapping().

📝 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
message UpdateNamespaceSSOMappingRequest {
string namespace_id = 1;
repeated string allowed_sso_provider_ids = 2;
bool allow_password_login = 3;
}
message UpdateNamespaceSSOMappingRequest {
string namespace_id = 1;
repeated string allowed_sso_provider_ids = 2;
optional bool allow_password_login = 3;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@proto/wg/cosmo/platform/v1/platform.proto` around lines 3100 - 3104, Change
the UpdateNamespaceSSOMappingRequest proto so allow_password_login is declared
as "optional bool allow_password_login = 3;" instead of plain bool, and then
update the handler that processes UpdateNamespaceSSOMappingRequest to check the
presence of allow_password_login (i.e., whether it is set/defined) before
calling setMapping(); only pass a value to setMapping() when the field is
present (treat absent as “no change”), and preserve existing behavior for
explicit true/false inputs so setMapping() continues to be called with explicit
values when provided.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

that is correct by default it has to be false. We create an entry only when its true so it correct right? check updateNamespaceSSOMapping.ts

Comment on lines 91 to 100
{!isViewingGraphOrSubgraph && (
<span className={cn(truncateNamespace && 'max-w-[180px] truncate lg:max-w-xs')}>{namespace.name}</span>
<span
className={cn(
truncateNamespace && 'max-w-[180px] truncate lg:max-w-xs',
hasNoAccess && 'text-muted-foreground',
)}
>
{displayName}
</span>
)}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Show a visible label when access is gated in graph/subgraph view.

When isViewingGraphOrSubgraph is true and hasNoAccess is true, the trigger renders only the caret icon. Please render displayName in that case too (e.g., “No access”) so users understand the state immediately.

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

In `@studio/src/components/dashboard/namespace-selector.tsx` around lines 91 -
100, The trigger hides the namespace label when isViewingGraphOrSubgraph is
true, which leaves only the caret visible even when hasNoAccess is true; update
the render logic in namespace-selector.tsx so that when hasNoAccess is true you
still render displayName (e.g., "No access" or the existing displayName) even if
isViewingGraphOrSubgraph is true—adjust the conditional around the span that
uses truncateNamespace, hasNoAccess and displayName so it renders whenever
hasNoAccess is true OR isViewingGraphOrSubgraph is false, and ensure the
className still applies hasNoAccess styling.


const loginMethod = user.loginMethod;
const hasInvitations = user.invitations.length > 0;
const ssoLabel = loginMethod?.type === 'sso' ? loginMethod.ssoProviderName || loginMethod.ssoAlias || '' : '';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a fallback label for SSO display text.

If both ssoProviderName and ssoAlias are missing, this renders as “Logged in via ”. Use a fallback (e.g., SSO) to avoid an empty label.

Also applies to: 108-111

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

In `@studio/src/components/user-menu.tsx` at line 85, The SSO label currently uses
loginMethod.ssoProviderName || loginMethod.ssoAlias || '' which can produce an
empty string; update the logic that sets ssoLabel (and the similar logic around
lines 108-111) to provide a fallback like 'SSO' instead of ''. For example,
change the expression that defines ssoLabel in component user-menu.tsx to use
loginMethod.ssoProviderName || loginMethod.ssoAlias || 'SSO' (and apply the same
fallback where the other SSO display label is computed) so the UI never renders
"Logged in via " with a blank provider.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
controlplane/src/core/bufservices/organization/whoAmI.ts (1)

5-6: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix ESLint camelcase error for protobuf-generated import.

The pipeline is failing because LoginMethod_Type violates the camelcase rule. Since this is a protobuf-generated identifier that cannot be renamed, add an ESLint disable comment for this import.

🔧 Proposed fix
 import {
   LoginMethod,
+  // eslint-disable-next-line camelcase
   LoginMethod_Type,
   WhoAmIRequest,
   WhoAmIResponse,
 } from '`@wundergraph/cosmo-connect/dist/platform/v1/platform_pb`';

Alternatively, disable inline at each usage site if the import-level disable doesn't work:

// eslint-disable-next-line camelcase
type: LoginMethod_Type.LOGIN_METHOD_TYPE_SSO,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controlplane/src/core/bufservices/organization/whoAmI.ts` around lines 5 - 6,
The import of the protobuf-generated identifier LoginMethod_Type violates
ESLint's camelcase rule; fix it by adding an inline ESLint disable comment for
camelcase on the import line that brings in LoginMethod_Type (i.e., adjust the
import that includes LoginMethod and LoginMethod_Type to include "//
eslint-disable-next-line camelcase" immediately above it) so the generated
identifier is allowed without renaming; if that import-level disable doesn't
resolve linting, alternatively add "// eslint-disable-next-line camelcase" at
the specific usage sites where LoginMethod_Type is referenced (e.g., where type:
LoginMethod_Type.LOGIN_METHOD_TYPE_SSO is used).
🧹 Nitpick comments (1)
controlplane/src/core/bufservices/organization/whoAmI.ts (1)

44-88: 💤 Low value

Inconsistent formatting in switch cases.

The switch statement has unusual blank lines before break; statements (lines 57-58, 67-68, 77-78). This appears to be inconsistent formatting that should be cleaned up for readability.

✨ Suggested formatting
     case 'sso': {
       const oidcRepo = new OidcRepository(opts.db);
       const provider = await oidcRepo.getOidcProviderById({
         id: lm.ssoProviderId,
         organizationId: authContext.organizationId,
       });
       loginMethod = {
         type: LoginMethod_Type.LOGIN_METHOD_TYPE_SSO,
         ssoProviderId: lm.ssoProviderId,
         ssoProviderName: provider?.name ?? '',
         ssoAlias: lm.alias,
       };
-    
       break;
     }
     case 'password': {
       loginMethod = {
         type: LoginMethod_Type.LOGIN_METHOD_TYPE_PASSWORD,
         ssoProviderId: '',
         ssoProviderName: '',
         ssoAlias: '',
       };
-    
       break;
     }
     case 'api-key': {
       loginMethod = {
         type: LoginMethod_Type.LOGIN_METHOD_TYPE_API_KEY,
         ssoProviderId: '',
         ssoProviderName: '',
         ssoAlias: '',
       };
-    
       break;
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controlplane/src/core/bufservices/organization/whoAmI.ts` around lines 44 -
88, The switch on lm?.type in whoAmI.ts (cases handling 'sso', 'password',
'api-key', and default that set loginMethod using LoginMethod_Type and
OidcRepository/provider) contains stray blank lines immediately before each
break; remove those blank lines so each case ends directly with the break
statement, ensure consistent spacing/indentation within each case (no extra
vertical gaps), and run the project formatter to normalize the file.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@controlplane/src/core/bufservices/organization/whoAmI.ts`:
- Around line 5-6: The import of the protobuf-generated identifier
LoginMethod_Type violates ESLint's camelcase rule; fix it by adding an inline
ESLint disable comment for camelcase on the import line that brings in
LoginMethod_Type (i.e., adjust the import that includes LoginMethod and
LoginMethod_Type to include "// eslint-disable-next-line camelcase" immediately
above it) so the generated identifier is allowed without renaming; if that
import-level disable doesn't resolve linting, alternatively add "//
eslint-disable-next-line camelcase" at the specific usage sites where
LoginMethod_Type is referenced (e.g., where type:
LoginMethod_Type.LOGIN_METHOD_TYPE_SSO is used).

---

Nitpick comments:
In `@controlplane/src/core/bufservices/organization/whoAmI.ts`:
- Around line 44-88: The switch on lm?.type in whoAmI.ts (cases handling 'sso',
'password', 'api-key', and default that set loginMethod using LoginMethod_Type
and OidcRepository/provider) contains stray blank lines immediately before each
break; remove those blank lines so each case ends directly with the break
statement, ensure consistent spacing/indentation within each case (no extra
vertical gaps), and run the project formatter to normalize the file.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3529a86f-cc01-4aa6-934b-722a4f739841

📥 Commits

Reviewing files that changed from the base of the PR and between 7011ecf and 4d69a9e.

📒 Files selected for processing (4)
  • controlplane/src/core/bufservices/organization/whoAmI.ts
  • studio/src/components/user-menu.tsx
  • studio/src/pages/[organizationSlug]/namespace-sso.tsx
  • studio/src/pages/[organizationSlug]/settings.tsx

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
controlplane/src/core/services/RBACEvaluator.ts (1)

115-117: 💤 Low value

Redundant IdP gate check in canCreateContract.

canCreateFederatedGraph already includes the isAllowedByIdpGate check, making the additional call here redundant.

♻️ Suggested simplification
 canCreateContract(namespace: Namespace) {
-  return this.canCreateFederatedGraph(namespace) && this.isAllowedByIdpGate(namespace.id);
+  return this.canCreateFederatedGraph(namespace);
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controlplane/src/core/services/RBACEvaluator.ts` around lines 115 - 117, The
canCreateContract method redundantly calls isAllowedByIdpGate because
canCreateFederatedGraph already performs that check; simplify canCreateContract
by returning only the result of canCreateFederatedGraph(namespace) (keep the
Namespace parameter) and remove the extra isAllowedByIdpGate(namespace.id)
invocation so the logic isn’t duplicated in canCreateContract, referencing the
canCreateContract, canCreateFederatedGraph, and isAllowedByIdpGate symbols for
location.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@controlplane/src/core/services/RBACEvaluator.ts`:
- Around line 115-117: The canCreateContract method redundantly calls
isAllowedByIdpGate because canCreateFederatedGraph already performs that check;
simplify canCreateContract by returning only the result of
canCreateFederatedGraph(namespace) (keep the Namespace parameter) and remove the
extra isAllowedByIdpGate(namespace.id) invocation so the logic isn’t duplicated
in canCreateContract, referencing the canCreateContract,
canCreateFederatedGraph, and isAllowedByIdpGate symbols for location.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b408b318-6432-4fd9-9105-909569ce3f6e

📥 Commits

Reviewing files that changed from the base of the PR and between 74c49c9 and 09e262e.

📒 Files selected for processing (5)
  • controlplane/src/core/bufservices/federated-graph/moveFederatedGraph.ts
  • controlplane/src/core/bufservices/organization/whoAmI.ts
  • controlplane/src/core/bufservices/sso/listOIDCProviders.ts
  • controlplane/src/core/services/RBACEvaluator.ts
  • controlplane/src/db/schema.ts

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In `@controlplane/test/oidc-provider.test.ts`:
- Around line 42-45: Replace the non-deterministic providers[0] selection with a
deterministic lookup: after calling client.listOIDCProviders use
Array.prototype.find to locate the provider by a known unique attribute (e.g.,
provider.name or createdProvider.id) and assert it exists, then pass that
provider.id into client.getOIDCProvider; update the same pattern for the other
occurrences (the blocks that call client.listOIDCProviders and then index
providers) so each test selects by name or by the id returned when creating the
provider rather than using providers[0].
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c54a7ace-fd6a-4e38-9e0c-6a8b77a832aa

📥 Commits

Reviewing files that changed from the base of the PR and between 09e262e and 563e720.

📒 Files selected for processing (1)
  • controlplane/test/oidc-provider.test.ts

Comment on lines +42 to +45
const { providers } = await client.listOIDCProviders({});
const providerId = providers[0].id;

const getOIDCProviderResponse = await client.getOIDCProvider({ id: providerId });
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use deterministic provider selection instead of providers[0].

At Line 42 (and the other listed ranges), indexing the first result assumes stable ordering and non-empty data; this can make tests flaky as data/setup evolves. Prefer selecting by known provider name (or created id if returned) and assert it exists.

Suggested pattern
 const { providers } = await client.listOIDCProviders({});
-const providerId = providers[0].id;
+const providerId = providers.find((p) => p.name === 'okta')?.id;
+expect(providerId).toBeTruthy();

Also applies to: 144-147, 221-224, 271-274, 329-332, 427-429

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

In `@controlplane/test/oidc-provider.test.ts` around lines 42 - 45, Replace the
non-deterministic providers[0] selection with a deterministic lookup: after
calling client.listOIDCProviders use Array.prototype.find to locate the provider
by a known unique attribute (e.g., provider.name or createdProvider.id) and
assert it exists, then pass that provider.id into client.getOIDCProvider; update
the same pattern for the other occurrences (the blocks that call
client.listOIDCProviders and then index providers) so each test selects by name
or by the id returned when creating the provider rather than using providers[0].

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant