Skip to content

feat: OAuth Connected Accounts — provider-agnostic integration layer#106

Merged
matthiastjong merged 14 commits into
mainfrom
feat/oauth-connected-accounts
Jun 10, 2026
Merged

feat: OAuth Connected Accounts — provider-agnostic integration layer#106
matthiastjong merged 14 commits into
mainfrom
feat/oauth-connected-accounts

Conversation

@matthiastjong

Copy link
Copy Markdown
Owner

Summary

  • Adds a Connected Accounts feature that lets users connect a Microsoft 365 account via OAuth and automatically provisions managed API targets for mail and calendar (Graph API)
  • Provider config is a static code registry — OAuth credentials come from env vars, never stored in the database
  • Managed targets are read-only (only permissions can be changed) and cascade-delete when disconnected
  • Gateway auto-resolves OAuth tokens from the connected account for managed targets, with silent refresh and disconnected status tracking
  • New Integrations dashboard page with connect/disconnect flow, manage access dialog with switch toggles
  • Target detail page shows "Managed by" badge and hides edit controls for managed targets
  • Bootstrap response includes integration context (email + capability) for agents

Architecture

connected_accounts (provider_type, email, OAuth tokens, status)
    └── targets (connected_account_id FK, capability: mail|calendar)
            └── token_permissions (existing, unchanged)

Provider registry: src/lib/server/providers.ts (static, env vars for credentials)
Connected accounts service: src/lib/server/services/connected-accounts.ts
OAuth routes: src/routes/oauth/authorize/ + src/routes/oauth/callback/

Key design decisions

  1. No integration_providers DB table — providers defined in code, credentials from OAUTH_MICROSOFT_CLIENT_ID / OAUTH_MICROSOFT_CLIENT_SECRET env vars
  2. Two managed API targets per account (mail + calendar) — agents use api_request against Graph API, no dedicated calendar tools
  3. Managed targets throw on mutationupdateTarget/deleteTarget throw Error for managed targets
  4. Unique constraint on (provider_type, email) — prevents duplicate accounts
  5. In-memory mutex deduplicates concurrent token refresh calls
  6. Slug suffix (account ID prefix) prevents collision on reconnect

Test plan

  • 296 automated tests passing (11 new: connected-accounts, gateway-managed)
  • Manual E2E: Connect Microsoft 365 → consent → managed targets created → mail + calendar via Graph API verified
  • Shared mailbox access verified (Mail.ReadWrite.Shared scope)
  • Disconnect → managed targets cascade deleted
  • Managed target edit/delete blocked in UI and API
  • Token refresh with status tracking tested

🤖 Generated with Claude Code

Matthias 't Jong and others added 14 commits June 10, 2026 09:01
…ema and targets FK

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…and token refresh

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…nt for managed targets

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… for managed targets

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…d targets

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ariables

Provider credentials (Azure AD client ID/secret) are now configured via
OAUTH_MICROSOFT_CLIENT_ID and OAUTH_MICROSOFT_CLIENT_SECRET env vars.
The seed-providers module auto-creates/updates the Microsoft 365 provider
on startup. Removed the provider CRUD UI from Settings page.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tatic code registry

Provider config (OAuth URLs, scopes) is now defined in code. Credentials
(client_id, client_secret) come from env vars only — never stored in the
database. The integration_providers table, seed-providers module, and
provider CRUD service are removed.

connected_accounts.provider_id (uuid FK) → provider_type (varchar string)
that references the static registry key (e.g. "microsoft_365").

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…n, UI improvements

- Squash 0013+0014 migrations into single clean migration (no integration_providers table)
- Fix slug collision on reconnect by appending account ID suffix
- Add Manage Access dialog with switch toggles for API key permissions
- Add .superpowers/ to .gitignore
- Use /common/ auth URL for personal + work Microsoft accounts
- Add Mail.ReadWrite.Shared and Calendars.ReadWrite.Shared scopes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…nique constraint, race condition

- updateTarget/deleteTarget now throw Error instead of returning { error }
  for managed targets (fixes silent 200 response on API)
- Add unique constraint on (provider_type, email) to prevent duplicate accounts
- Add in-memory mutex to deduplicate concurrent token refresh calls
- Update AGENTS.md with connected accounts architecture

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@matthiastjong matthiastjong merged commit 303693c into main Jun 10, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant