Skip to content

feat(auth-profiles): admin-pinned default app profile per connector#764

Merged
buremba merged 6 commits into
mainfrom
feat/oauth-app-profiles-admin
May 16, 2026
Merged

feat(auth-profiles): admin-pinned default app profile per connector#764
buremba merged 6 commits into
mainfrom
feat/oauth-app-profiles-admin

Conversation

@buremba
Copy link
Copy Markdown
Member

@buremba buremba commented May 15, 2026

Summary

Closes the gap surfaced when an org has multiple OAuth app profiles for a connector: today the resolver picks the most-recently-updated one, so members installing a connection get whichever profile an admin edited last. This PR adds an explicit per-connector default flag plus the UX to manage it.

  • Schema: auth_profiles.is_default_for_connector (NOT NULL, DEFAULT false) + partial unique index keeping at most one default per (organization_id, connector_key) for oauth_app rows.
  • Resolver: getPrimaryAuthProfileForKind prefers the flagged profile before falling back to connector_key-match and recency.
  • New action: manage_auth_profiles.set_default_auth_profile (admin-only via the existing OWNER_ADMIN_ACTIONS policy). Pass auth_profile_slug: null to clear the default.
  • Revoke cascade: when an admin flips an oauth_app profile to revoked/error, dependent connections move to pending_auth so the breakage surfaces in the UI. Force-deleting a profile does the same. Revoking the pinned default also clears the flag.
  • Web (owletto-web#134): new /{org}/oauth-apps admin settings page, "Org default" pill + auto-pick in the install sheet, "Create new app profile" hidden from non-admins, "ask an admin" hint when no profile exists.

Gaps explicitly not addressed here

  • Member-side install of OAuth-backed connections (currently manage_connections.create is admin-only via the policy table). This PR is forward-compatible with opening that up later.
  • A REST surface for app-profile management — the admin tool API is still the only entry point.
  • Per-provider vs per-connector default — picked per-connector (matches user choice in scoping decision).

Test plan

  • Run bunx dbmate -d db/migrations up against a local Postgres; verify the column + index materialise.
  • Pin two app profiles for the same provider, set one default, install a connection — connection's app_auth_profile_id resolves to the default.
  • Set default, then admin-revoke that profile via the new settings page — referencing connections flip to pending_auth and is_default_for_connector is cleared.
  • As non-admin: visit /{org}/oauth-apps → "Admins only" empty state. Open an install sheet with no profiles → "ask an admin" hint, no Create button.
  • As admin: visit /{org}/oauth-apps → list with badges, set/clear default, revoke, delete all work via inline two-step confirms.

Summary by CodeRabbit

  • New Features

    • Pin or clear a default OAuth app per connector; admin tool to set/clear defaults.
    • Members can create and reauthenticate their own managed connections.
  • Improvements

    • Non-admin connection creation/reauthentication now enforces connector-default OAuth apps and tighter role checks.
    • OAuth app status changes cascade to dependent connections; deletion is now handled transactionally.
    • Auth profile payloads surface the connector-default flag.
  • Tests

    • Authorization tests updated for the new access rules.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 15, 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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 15a84a70-fcb1-445a-8a36-797a3e4bd885

📥 Commits

Reviewing files that changed from the base of the PR and between fff25dc and 0473232.

📒 Files selected for processing (1)
  • db/schema.sql
🚧 Files skipped from review as they are similar to previous changes (1)
  • db/schema.sql

📝 Walkthrough

Walkthrough

Adds a connector-scoped default flag for oauth_app auth profiles with DB migration and partial-unique index; surfaces the flag in queries/serialization; adds transactional helpers to set/clear defaults and revoke apps with connection-status cascades; introduces a tool action to set/clear defaults; tightens role checks and connection-creation/reauth validation for non-admins.

Changes

Default OAuth App Profile Pinning

Layer / File(s) Summary
Database schema and constraints
db/migrations/20260515170000_auth_profiles_default_for_connector.sql, db/schema.sql
Adds is_default_for_connector boolean NOT NULL DEFAULT false to auth_profiles, creates partial unique index auth_profiles_default_for_connector_unique scoped to profile_kind = 'oauth_app' and is_default_for_connector, and records the migration in schema_migrations.
Authorization and tool-access policies
packages/server/src/auth/tool-access.ts, packages/server/src/auth/__tests__/tool-access.test.ts
Adjusts MEMBER/OWNER-ADMIN action sets: members gain write actions for creating/reauthenticating connections and for certain auth-profile actions (create_auth_profile/update_auth_profile for allowed kinds), while delete_auth_profile and set_default_auth_profile remain owner/admin-only; tests updated accordingly.
Auth profile data model and utility functions
packages/server/src/utils/auth-profiles.ts, packages/server/src/tools/admin/helpers/connection-helpers.ts
Adds is_default_for_connector to AuthProfileRow and SELECT lists; serialization includes the flag; getPrimaryAuthProfileForKind prioritizes connector-pinned oauth_app profiles; adds revokeOAuthAppProfileAtomic and setDefaultAuthProfileForConnector.
Set default auth profile action
packages/server/src/tools/admin/manage_auth_profiles.ts
Adds set_default_auth_profile schema and handler that validates the target profile and calls setDefaultAuthProfileForConnector; returns serialized pinned profile or null.
Profile lifecycle and connection cascading
packages/server/src/tools/admin/manage_auth_profiles.ts
Tightens create/update authorization for non-oauth_account profiles; non-admin updates limited to profiles they created; on oauth_app status changes, dependent connections are synced to active or pending_auth; revocation clears pins and pauses connections before delete.
Connection creation and reauthenticate validation
packages/server/src/tools/admin/manage_connections.ts
handleCreate computes caller role once and, for non-admins, validates provided app_auth_profile_slug exists, is an oauth_app, and is pinned as the connector default; non-admins cannot bind env profiles or use others' oauth_account/browser_session profiles; reauthenticate restricts non-admins to reauth their own connections.
Web subproject update
packages/web
Subproject pointer advanced to a new commit SHA.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant manageAuthProfiles
  participant handleSetDefaultAuthProfile
  participant setDefaultAuthProfileForConnector
  participant Database
  Caller->>manageAuthProfiles: set_default_auth_profile(connector_key, auth_profile_slug|null)
  manageAuthProfiles->>handleSetDefaultAuthProfile: dispatch
  handleSetDefaultAuthProfile->>Database: validate profile exists, is oauth_app, matches connector, active if pinning
  handleSetDefaultAuthProfile->>setDefaultAuthProfileForConnector: transaction to clear siblings and optionally pin slug
  setDefaultAuthProfileForConnector->>Database: UPDATE auth_profiles (clear and set is_default_for_connector)
  setDefaultAuthProfileForConnector-->>handleSetDefaultAuthProfile: flagged row or null
  handleSetDefaultAuthProfile-->>manageAuthProfiles: serialized result
  manageAuthProfiles-->>Caller: response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested labels

skip-size-check

Poem

🐰 A rabbit hops through profiles deep,
Pins a default so connectors keep,
Guards who may bind and who may write,
Connections pause or wake to right,
A tiny flag sets defaults neat.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding an admin-controlled default OAuth app profile per connector, which is the core feature of this PR.
Description check ✅ Passed The description is comprehensive and follows the template structure. It includes a clear Summary, detailed implementation notes, acknowledgment of gaps, and a thorough Test plan with specific validation steps.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/oauth-app-profiles-admin

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

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2fcbe0a8aa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +791 to +792
} else if (authProfile.status === 'active') {
await syncConnectionsForOAuthAppProfile(ctx.organizationId, authProfile.id, true);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Don't reactivate connections on unrelated app-profile edits

When an oauth_app profile is already active, any update to it (renaming it, changing credentials, etc.) reaches this branch and syncConnectionsForOAuthAppProfile(..., true) sets every non-deleted connection using that app profile to active. That overwrites connection statuses that are intentionally not active — for example connections paused via manage_connections.update(status: 'paused'), revoked/error connections, or OAuth-account connections that are pending_auth because the user account is missing scopes (see syncOAuthConnectionsForAuthProfile). Only a transition from revoked/error back to active should consider repairing connections, and even then it should not blindly override unrelated connection-level status.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@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

Caution

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

⚠️ Outside diff range comments (1)
packages/server/src/tools/admin/manage_auth_profiles.ts (1)

505-535: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Enforce oauth_account ownership before member reuse/update.

Line 505+ and Line 668+ allow member-tier operations on oauth_account profiles without verifying profile ownership. A member can target another member’s slug and mutate/reconnect that profile.

Proposed fix
+async function canManageOAuthAccountProfile(
+  profile: { profile_kind: string; created_by: string | null },
+  ctx: ToolContext
+): Promise<boolean> {
+  if (profile.profile_kind !== 'oauth_account') return false;
+  const role = ctx.userId ? await getWorkspaceRole(getDb(), ctx.organizationId, ctx.userId) : null;
+  if (role === 'admin' || role === 'owner') return true;
+  return !!ctx.userId && profile.created_by === ctx.userId;
+}

Apply this check before:

  • reusing an existing oauth_account in handleCreateAuthProfile
  • permitting oauth_account edits in handleUpdateAuthProfile

Also applies to: 668-683

🤖 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 `@packages/server/src/tools/admin/manage_auth_profiles.ts` around lines 505 -
535, The code reuses or reconnects an existing oauth_account without checking
ownership; before allowing reuse in the create flow (the block that inspects
existing from getAuthProfileBySlug/normalizeAuthProfileSlug and the later update
flow in handleUpdateAuthProfile), validate that the profile is owned by the
current user by comparing ctx.userId to the profile owner field (e.g.,
existing.created_by or existing.owner_id) and return an error if they differ;
apply the same ownership check before permitting oauth_account edits in
handleUpdateAuthProfile so members cannot target or mutate other members'
oauth_account profiles.
🧹 Nitpick comments (1)
packages/server/src/utils/auth-profiles.ts (1)

539-547: ⚡ Quick win

Harden connector scoping inside setDefaultAuthProfileForConnector.

Line 543–545 pins by slug + profile_kind only. This helper should enforce connector_key = params.connectorKey itself, or a future caller can accidentally flag a profile from a different connector.

Proposed fix
     const rows = await tx`
       UPDATE auth_profiles
       SET is_default_for_connector = true,
           updated_at = NOW()
       WHERE organization_id = ${params.organizationId}
         AND slug = ${params.slug}
         AND profile_kind = 'oauth_app'
+        AND connector_key = ${params.connectorKey}
+        AND status = 'active'
       RETURNING ${tx.unsafe(AUTH_PROFILE_COLUMNS)}
     `;
🤖 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 `@packages/server/src/utils/auth-profiles.ts` around lines 539 - 547, The
UPDATE in setDefaultAuthProfileForConnector currently restricts by
organization_id, slug and profile_kind only; modify the WHERE clause to also
require connector_key = ${params.connectorKey} so the query updates only
profiles for the intended connector (i.e., add AND connector_key =
${params.connectorKey} to the tx`...` that updates auth_profiles and returns
AUTH_PROFILE_COLUMNS). Ensure the same params.connectorKey symbol is used and
adjust any tests if they relied on the broader behavior.
🤖 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 `@packages/server/src/tools/admin/manage_auth_profiles.ts`:
- Around line 410-417: The UPDATE currently flips every dependent connection to
active; restrict it so only connections that actually need reactivation are
changed (e.g., those with statuses like 'reauthorize_required' or 'pending'
instead of all rows). Modify the SQL in manage_auth_profiles.ts (the UPDATE
block that sets status = ${nextConnectionStatus}) to include an additional WHERE
clause such as AND status IN (...list of reactivation-only statuses...), and
update the caller syncConnectionsForOAuthAppProfile(...) usage (the site passing
true on oauth_app updates) to compute and pass the reactivation flag only when
the oauth_app change actually requires reactivating connections (e.g.,
credential rotation or reenable), not for any active update.

---

Outside diff comments:
In `@packages/server/src/tools/admin/manage_auth_profiles.ts`:
- Around line 505-535: The code reuses or reconnects an existing oauth_account
without checking ownership; before allowing reuse in the create flow (the block
that inspects existing from getAuthProfileBySlug/normalizeAuthProfileSlug and
the later update flow in handleUpdateAuthProfile), validate that the profile is
owned by the current user by comparing ctx.userId to the profile owner field
(e.g., existing.created_by or existing.owner_id) and return an error if they
differ; apply the same ownership check before permitting oauth_account edits in
handleUpdateAuthProfile so members cannot target or mutate other members'
oauth_account profiles.

---

Nitpick comments:
In `@packages/server/src/utils/auth-profiles.ts`:
- Around line 539-547: The UPDATE in setDefaultAuthProfileForConnector currently
restricts by organization_id, slug and profile_kind only; modify the WHERE
clause to also require connector_key = ${params.connectorKey} so the query
updates only profiles for the intended connector (i.e., add AND connector_key =
${params.connectorKey} to the tx`...` that updates auth_profiles and returns
AUTH_PROFILE_COLUMNS). Ensure the same params.connectorKey symbol is used and
adjust any tests if they relied on the broader behavior.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 78a919ca-ed1c-4223-b330-317eaf73c9b5

📥 Commits

Reviewing files that changed from the base of the PR and between 35285ea and 205fbf5.

📒 Files selected for processing (9)
  • db/migrations/20260515170000_auth_profiles_default_for_connector.sql
  • db/schema.sql
  • packages/server/src/auth/__tests__/tool-access.test.ts
  • packages/server/src/auth/tool-access.ts
  • packages/server/src/tools/admin/helpers/connection-helpers.ts
  • packages/server/src/tools/admin/manage_auth_profiles.ts
  • packages/server/src/tools/admin/manage_connections.ts
  • packages/server/src/utils/auth-profiles.ts
  • packages/web

Comment thread packages/server/src/tools/admin/manage_auth_profiles.ts
@buremba
Copy link
Copy Markdown
Member Author

buremba commented May 15, 2026

pi review pass

Reviewed with pi -p against the full diff (parent + submodule). 5 findings, 4 fixed in 49cf999 / owletto-web#fc8bb37.

Fixed

  1. High — non-admin omitting app_auth_profile_slug could still get a recency-picked provider-wide row. Migration starts every existing profile at is_default_for_connector=false; without a pinned default the resolver falls back to updated_at DESC. Now after resolveConnectionAuthSelection we additionally require appAuthProfile.is_default_for_connector === true && connector_key === args.connector_key for non-admins; otherwise reject with the "ask an admin" message. (manage_connections.ts:955-977)
  2. High — member-write update_auth_profile was not ownership-scoped. Any member could update another member's oauth_account profile by slug. Added existing.created_by === ctx.userId gate for non-admins on oauth_account. (manage_auth_profiles.ts:665-694)
  3. Medium — non-admin still saw an enabled <Select> for the OAuth app. Server rejected the override but the UI invited it. Members now get the resolved profile name + default pill, read-only. (connection-sheet.tsx:1610-1641)
  4. Medium — revoke flow wasn't atomic. Status flip + cascade + default-clear ran as three independent statements. Added revokeOAuthAppProfileAtomic which does all three inside sql.begin. (auth-profiles.ts:505-538, manage_auth_profiles.ts:820-832)

Deferred (PR #2)
5. Low — concurrent set_default_auth_profile from two admins can hit the partial unique index. The unflag-then-flag inside one transaction is correct; concurrent transactions can race. Rare (admin-on-admin) — handle by catching the unique-violation and retrying once. Will follow up.

buremba added 3 commits May 16, 2026 16:24
Adds is_default_for_connector to auth_profiles with a partial unique index
keeping at most one default per (org, connector_key) for oauth_app rows.
getPrimaryAuthProfileForKind prefers the flagged profile so member-installed
connections fall through to the admin's choice instead of "most recent".

- New `manage_auth_profiles.set_default_auth_profile` action (admin-only via
  the existing OWNER_ADMIN_ACTIONS policy table).
- Revoke / force-delete on oauth_app now flips dependent connections to
  pending_auth so admin app rotation doesn't silently break running
  connections.
- Revoking the currently-pinned default also clears the flag.
- Bumps packages/web pointer to the new admin settings page + install sheet
  UX (owletto-web#134).
Opens manage_connections.create + manage_connections.reauthenticate and
manage_auth_profiles.{create,update}_auth_profile to member-write. Handler
gates enforce intent so members can't bring their own org-shared
credentials:

- create_auth_profile / update_auth_profile reject non-oauth_account kinds
  for non-admins (env / oauth_app / browser_session remain admin-only).
- manage_connections.create rejects an explicit app_auth_profile_slug from
  a non-admin unless the profile is is_default_for_connector for that
  connector. Omit the slug and the server auto-resolves via
  getPrimaryAuthProfileForKind, which already prefers the admin-pinned
  default after the previous commit.
- 'No app credentials' error rewords for non-admins to 'ask an admin to
  set up the <provider> app'.

Updates the tool-access policy tests to match the new boundaries.
Bumps packages/web pointer for the OAuth-app indicator on connection
settings (admin picker, member-readable).
- handleCreate (manage_connections): non-admins fall through to
  getPrimaryAuthProfileForKind, which can return a recency-picked
  provider-wide row when no default is pinned. Now require the resolved
  profile to be is_default_for_connector === true for the exact
  connector_key when caller is non-admin; otherwise reject with the
  'ask an admin' message.
- handleUpdateAuthProfile: oauth_account ownership check — non-admins can
  only update profiles they created (created_by === userId), so the slug
  alone doesn't grant access to another member's tokens.
- handleUpdateAuthProfile (revoke path): wrap status flip + cascade +
  default-clear in a single transaction via revokeOAuthAppProfileAtomic.
  Removes the window where connections still pointed at a revoked profile.
- Bumps packages/web pointer for the non-admin read-only picker.
@buremba buremba force-pushed the feat/oauth-app-profiles-admin branch from 49cf999 to 9a43247 Compare May 16, 2026 15:25
@codecov-commenter
Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown

@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)
packages/server/src/utils/auth-profiles.ts (1)

580-588: 💤 Low value

Consider adding connector_key to the UPDATE for defense-in-depth.

The handler validates that the profile's connector_key matches before calling this function. However, adding the constraint directly in the UPDATE would guard against future callers that might bypass the handler validation.

♻️ Proposed change
     const rows = await tx`
       UPDATE auth_profiles
       SET is_default_for_connector = true,
           updated_at = NOW()
       WHERE organization_id = ${params.organizationId}
         AND slug = ${params.slug}
+        AND connector_key = ${params.connectorKey}
         AND profile_kind = 'oauth_app'
       RETURNING ${tx.unsafe(AUTH_PROFILE_COLUMNS)}
     `;
🤖 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 `@packages/server/src/utils/auth-profiles.ts` around lines 580 - 588, The
UPDATE to auth_profiles (in the tx`...` block that currently sets
is_default_for_connector for organization_id = ${params.organizationId}, slug =
${params.slug}, and profile_kind = 'oauth_app') should also include the
connector_key check for defense-in-depth; add AND connector_key =
${params.connectorKey} to the WHERE clause (and ensure the caller provides
params.connectorKey) so the UPDATE only affects the intended oauth_app with the
matching connector_key.
🤖 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 `@packages/server/src/utils/auth-profiles.ts`:
- Around line 580-588: The UPDATE to auth_profiles (in the tx`...` block that
currently sets is_default_for_connector for organization_id =
${params.organizationId}, slug = ${params.slug}, and profile_kind = 'oauth_app')
should also include the connector_key check for defense-in-depth; add AND
connector_key = ${params.connectorKey} to the WHERE clause (and ensure the
caller provides params.connectorKey) so the UPDATE only affects the intended
oauth_app with the matching connector_key.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: dedb3fe7-07de-403f-8d57-8a44ae7f956a

📥 Commits

Reviewing files that changed from the base of the PR and between 205fbf5 and 9a43247.

📒 Files selected for processing (9)
  • db/migrations/20260515170000_auth_profiles_default_for_connector.sql
  • db/schema.sql
  • packages/server/src/auth/__tests__/tool-access.test.ts
  • packages/server/src/auth/tool-access.ts
  • packages/server/src/tools/admin/helpers/connection-helpers.ts
  • packages/server/src/tools/admin/manage_auth_profiles.ts
  • packages/server/src/tools/admin/manage_connections.ts
  • packages/server/src/utils/auth-profiles.ts
  • packages/web
✅ Files skipped from review due to trivial changes (2)
  • packages/web
  • packages/server/src/tools/admin/helpers/connection-helpers.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • db/migrations/20260515170000_auth_profiles_default_for_connector.sql
  • packages/server/src/auth/tool-access.ts
  • db/schema.sql
  • packages/server/src/auth/tests/tool-access.test.ts

buremba added 3 commits May 16, 2026 16:57
- manage_connections.create: non-admins cannot bind to env profiles or
  oauth_account/browser_session profiles owned by other users
- manage_connections.create: reject entity_link_overrides from non-admins
  (it mutates connector_definitions org-wide)
- create_auth_profile slug-reuse: non-admins must own the existing slug
  before reconnecting; closes hijack-by-known-slug vector
- reauthenticate: require connection-owner or admin; blocks members from
  disrupting another member's interactive auth
- delete_auth_profile: lock profile row + sync + delete in one tx so a
  concurrent create can't leave an active connection with NULL
  app_auth_profile_id after FK ON DELETE SET NULL fires
… delete

pi second pass flagged two follow-ups on the tx-wrapped delete:
- browser_session delete must still pause dependent feeds + clear
  next_run_at (the previous syncConnectionsForBrowserAuthProfile did this
  and the tx version had dropped it)
- the composite FK is (organization_id, *_auth_profile_id) ON DELETE
  SET NULL; without an explicit column list Postgres would attempt to
  null organization_id too (NOT NULL → constraint violation on force
  delete with active connections). Null the FK column explicitly inside
  the tx so the DELETE itself never depends on FK SET NULL.
@buremba
Copy link
Copy Markdown
Member Author

buremba commented May 16, 2026

pi review — final summary

Three full pi passes after rebasing onto main. All issues closed.

Pass 1 (before rebase): 5 findings, 4 fixed in 49cf9991, #5 deferred (concurrent admin race) — still deferred as a follow-up.

Pass 2 (after rebase): 5 new findings, all fixed in 549d6242:

  1. High — member create can bind to other users'/admin's runtime auth profiles (env / oauth_account / browser_session of other users). Fixed by ownership + admin-only gate per profile_kind.
  2. High — member create accepts entity_link_overrides, which mutates connector_definitions org-wide. Fixed by admin-only gate on the field.
  3. High — create_auth_profile slug-reuse can mint a connect token for another user's pending oauth_account profile. Fixed by ownership check on the reused row.
  4. Medium — reauthenticate is member-write but doesn't verify connections.created_by. Fixed by owner/admin gate.
  5. Medium — delete_auth_profile sync + delete not atomic with concurrent create. Fixed by lock + sync + delete in one transaction.

Pass 3 (after follow-up fixes in fff25dc0):

  • browser_session delete tx now mirrors syncConnectionsForBrowserAuthProfile (pauses feeds + clears next_run_at)
  • Composite FK ON DELETE SET NULL would otherwise null organization_id (NOT NULL) → explicit FK column null inside the tx

Pass 4: All closed, no new issues.

CI: integration suite is failing on main HEAD too (PostgresError: sorry, too many clients already from buildWorkspaceInstructions) — ambient infra issue, not introduced by this PR. All other checks green (typecheck, build-test, unit, security, frontend, CodeQL, migrations, format-lint).

@buremba buremba merged commit 71e9b0f into main May 16, 2026
21 of 22 checks passed
@buremba buremba deleted the feat/oauth-app-profiles-admin branch May 16, 2026 16:09
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.

2 participants