feat(credentials): add v2 credential lifecycle APIs - #6664
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryHigh Risk Overview OAuth browser entrypoints no longer trust mutable connect parameters alone. Legacy Docs and agent commands gain a “freeze observable behavior” section plus extra test categories (compatibility, failure sequencing, concurrency, rendering boundaries) for application-operation migrations. Reviewed by Cursor Bugbot for commit 4430cd5. Bugbot is set up for automated code reviews on this repo. Configure here. |
Greptile SummaryThe PR adds the complete v2 credential lifecycle, consolidates authorization and credential operations behind shared application use cases, and hardens OAuth draft handling. The follow-up fixes preserve immutable connection intents, bind Shopify completion to signed per-flow state, and source reconnect audit names from canonical credential records.
Confidence Score: 5/5The PR appears safe to merge because no blocking failure remains in the previously reported credential-draft, OAuth-flow, or reconnect-audit paths. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| apps/sim/lib/credentials/connect-draft.ts | Conflict refreshes preserve the active draft ID and immutable target while permitting credential-ID-based reconnect retries after renames. |
| apps/sim/lib/credentials/application/save-credential-draft.ts | Legacy draft creation now follows the same immutable-intent semantics as the v2 connection path. |
| apps/sim/lib/credentials/draft-hooks.ts | Reconnect audit projection reads the current credential display name from the canonical credential record. |
| apps/sim/app/api/auth/oauth2/authorize/route.ts | OAuth launch validates provider configuration and carries the exact authorized draft through the callback URL. |
| apps/sim/lib/oauth/shopify-state.ts | Shopify state cryptographically binds the user, shop, draft, return destination, nonce, and expiry. |
| apps/sim/app/api/v2/credentials/connections/route.ts | The v2 endpoint creates authorized, human-bound OAuth connection intents and returns the browser authorization URL. |
| apps/sim/app/api/v2/credentials/route.ts | The v2 credentials collection route provides authorized listing and service-account creation through shared contracts and use cases. |
| apps/sim/app/api/v2/credentials/[credentialId]/route.ts | Credential deletion uses the shared authorized lifecycle and presents the standard v2 response envelope. |
Sequence Diagram
sequenceDiagram
participant Client
participant V2 as V2 Credentials API
participant Drafts as Credential Draft Store
participant Browser as Authenticated Browser
participant Provider as OAuth Provider
participant Callback as OAuth Callback
participant Credentials as Credential Application Use Case
Client->>V2: POST /credentials/connections
V2->>Credentials: Authorize connection operation
Credentials->>Drafts: Create or refresh immutable intent
Drafts-->>V2: draftId and expiry
V2-->>Client: authorizationUrl
Client->>Browser: Open authorizationUrl
Browser->>Drafts: Reauthorize exact user-bound draft
Browser->>Provider: Start OAuth with per-flow state
Provider->>Callback: Authorization callback
Callback->>Drafts: Load exact active draft
Callback->>Credentials: Create or reconnect credential
Credentials-->>Browser: Redirect to credential-connected
Reviews (21): Last reviewed commit: "fix(credentials): prevent stale secrets ..." | Re-trigger Greptile
|
@cursor review |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit e4b09dc. Configure here.
|
@cursor review |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 7fcf26f. Configure here.
|
@cursor review |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit ae46594. Configure here.
…-api # Conflicts: # scripts/check-api-validation-contracts.ts
…-api # Conflicts: # apps/sim/app/api/credentials/route.ts # apps/sim/lib/credentials/queries.ts
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit b413183. Configure here.
…-api # Conflicts: # apps/sim/app/api/credentials/[id]/members/route.ts # apps/sim/app/api/credentials/[id]/route.ts # apps/sim/app/api/credentials/draft/route.ts # apps/sim/app/api/credentials/memberships/route.ts # apps/sim/app/api/credentials/route.ts # apps/sim/lib/credentials/application/authorization.ts # apps/sim/lib/credentials/orchestration/index.ts # scripts/check-api-validation-contracts.ts
|
@cursor review |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 4430cd5. Configure here.
Problem:
The V2 API could list stored credentials, but clients could not discover the complete set of credential methods, create service-account credentials, start or reconnect OAuth credentials safely, or disconnect credentials. OAuth still has to cross into an authenticated browser session, and the old browser entrypoint accepted mutable target parameters instead of an API-created connection intent.
Solution:
/api/v2/credentials./oauth/credential-connected.{ data }/{ data, nextCursor }/{ error }envelopes.API shapes:
GET /api/v2/credentials?workspaceId={workspaceId}{ "data": [ { "id": "credential-id", "type": "oauth", "displayName": "Work Gmail", "description": null, "providerId": "google-email", "accountId": "provider-account-id", "hasServiceAccountKey": false, "role": "admin", "createdAt": "2026-08-13T18:00:00.000Z", "updatedAt": "2026-08-13T18:00:00.000Z" } ], "nextCursor": null }GET /api/v2/credentials/providers?workspaceId={workspaceId}OAuth entry:
{ "type": "oauth", "serviceId": "salesforce", "name": "Salesforce", "description": "Connect to Salesforce CRM data and operations.", "providerFamily": "salesforce", "available": true, "supportsReconnect": true, "authorizationOptions": [ { "providerId": "salesforce", "label": "Production" }, { "providerId": "salesforce-sandbox", "label": "Sandbox" } ] }Service-account entry:
{ "type": "service_account", "serviceId": "zoom-service-account", "providerId": "zoom-service-account", "name": "Zoom server-to-server app", "description": "Connect Zoom with a server-to-server app.", "providerFamily": "zoom", "available": true, "docsUrl": "https://docs.sim.ai/integrations/zoom-service-account", "requiresClientGeneratedCredentialId": false, "fields": [ { "id": "clientId", "label": "Client ID", "placeholder": "Paste the client ID", "required": true, "secret": false, "multiline": false }, { "id": "clientSecret", "label": "Client secret", "placeholder": "Paste the client secret", "required": true, "secret": true, "multiline": false }, { "id": "orgId", "label": "Account ID", "placeholder": "Paste the account ID", "required": true, "secret": false, "multiline": false } ] }The endpoint returns
{ "data": [oauthEntry, serviceAccountEntry], "nextCursor": null }.POST /api/v2/credentialsCreates a service-account credential.
displayNameis optional because providers may derive it from the verified account identity.{ "workspaceId": "workspace-id", "type": "service_account", "providerId": "zoom-service-account", "displayName": "Zoom automation", "clientId": "YOUR_CLIENT_ID", "clientSecret": "YOUR_CLIENT_SECRET", "orgId": "YOUR_ACCOUNT_ID" }Returns
201 { "data": credential }for a new credential or200 { "data": credential }for an accepted replay. Secret fields are write-only and never returned.POST /api/v2/credentials/connectionsNew OAuth credential:
{ "workspaceId": "workspace-id", "providerId": "google-email", "displayName": "Work Gmail" }Reconnect an existing OAuth credential:
{ "workspaceId": "workspace-id", "credentialId": "credential-id" }{ "data": { "authorizationUrl": "https://www.sim.ai/api/auth/oauth2/authorize?draftId=draft-id", "expiresAt": "2026-08-13T18:30:00.000Z" } }This write requires a personal API key because the draft is bound to the human who must sign in in the browser. Workspace API keys can still list credentials and providers.
DELETE /api/v2/credentials/{credentialId}?workspaceId={workspaceId}{ "data": { "id": "credential-id", "deleted": true } }Behavior-preservation audit:
findings.txt.Validation:
bun run type-check: 23/23 tasks passedbun run lint:check: 23/23 tasks passedbun run check:audits: 26 audits passed