Skip to content

Feat/google service account#3828

Open
TheodoreSpeaks wants to merge 14 commits intostagingfrom
feat/google-service-account
Open

Feat/google service account#3828
TheodoreSpeaks wants to merge 14 commits intostagingfrom
feat/google-service-account

Conversation

@TheodoreSpeaks
Copy link
Copy Markdown
Collaborator

@TheodoreSpeaks TheodoreSpeaks commented Mar 28, 2026

Summary

Add google service support as a integration. Allows users with admin credentials to assume roles on behalf of their google workspace users.

Created new credential type service_account.

Added documentation to sim docs.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation
  • Other: ___________

Testing

  • Created sim workflow using copilot with all google blocks supported by this.
  • Validated workflows ran with google service account credentials.
  • Validated workflows show impersonate user account when google service account was selected
  • Validated workflows don't show impersonate user account when google account is not service account.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

Screenshots/Videos

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 28, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Apr 1, 2026 2:09am

Request Review

@TheodoreSpeaks
Copy link
Copy Markdown
Collaborator Author

@cursor review

@TheodoreSpeaks
Copy link
Copy Markdown
Collaborator Author

@greptile review

@cursor
Copy link
Copy Markdown

cursor bot commented Mar 28, 2026

PR Summary

High Risk
Adds a new credential type that stores encrypted private keys and introduces a JWT-based token minting path used across multiple Google integrations; mistakes here could expose secrets or broaden access if authorization/scoping is wrong.

Overview
Enables Google Workspace service account credentials (domain-wide delegation) as a first-class credential type, including DB support for storing an encrypted JSON key and workspace-unique naming.

Updates token/credential resolution to handle service_account credentials by minting short-lived Google access tokens via the JWT bearer flow (with optional user impersonation), and wires this into Google tools/selectors (Drive, Gmail labels, Sheets, Calendar, Tasks, BigQuery, Vertex, and provider proxy) with improved error handling.

Extends the UI to let admins add/delete service accounts from Integrations (paste/upload JSON, validation, encrypted storage) and updates Google blocks to show an Impersonated Account field only when a service account credential is selected; adds docs for setup and required scopes.

Written by Cursor Bugbot for commit 54683d7. This will update automatically on new commits. Configure here.

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Autofix Details

Bugbot Autofix prepared fixes for all 3 issues found in the latest run.

  • ✅ Fixed: Impersonation field shown for non-service-account credentials
    • The impersonation input now renders only when the selected credential is a service account by gating on isServiceAccount instead of provider-level support.
  • ✅ Fixed: Return type mismatch: undefined instead of false
    • hasExternalApiCredentials now coalesces the optional chaining result with ?? false so it always returns a boolean.
  • ✅ Fixed: Serializer orphan logic is broader than intended
    • The orphan serialization path was narrowed to only include the known impersonateUserEmail key instead of all orphan sub-blocks with values.

Create PR

Or push these changes by commenting:

@cursor push 035b79165e
Preview (035b79165e)
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/credential-selector.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/credential-selector.tsx
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/credential-selector.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/credential-selector.tsx
@@ -10,7 +10,6 @@
 import {
   getCanonicalScopesForProvider,
   getProviderIdFromServiceId,
-  getServiceAccountProviderForProviderId,
   OAUTH_PROVIDERS,
   type OAuthProvider,
   parseProvider,
@@ -122,11 +121,6 @@
     [selectedCredential]
   )
 
-  const supportsServiceAccount = useMemo(
-    () => !!getServiceAccountProviderForProviderId(effectiveProviderId),
-    [effectiveProviderId]
-  )
-
   const selectedCredentialSet = useMemo(
     () => credentialSets.find((cs) => cs.id === selectedCredentialSetId),
     [credentialSets, selectedCredentialSetId]
@@ -377,7 +371,7 @@
         className={overlayContent ? 'pl-7' : ''}
       />
 
-      {supportsServiceAccount && !isPreview && (
+      {isServiceAccount && !isPreview && (
         <div className='mt-2.5 flex flex-col gap-2.5'>
           <div className='flex items-center gap-1.5 pl-0.5'>
             <Label>

diff --git a/apps/sim/lib/auth/hybrid.ts b/apps/sim/lib/auth/hybrid.ts
--- a/apps/sim/lib/auth/hybrid.ts
+++ b/apps/sim/lib/auth/hybrid.ts
@@ -25,7 +25,7 @@
 export function hasExternalApiCredentials(headers: Headers): boolean {
   if (headers.has(API_KEY_HEADER)) return true
   const auth = headers.get('authorization')
-  return auth?.startsWith(BEARER_PREFIX)
+  return auth?.startsWith(BEARER_PREFIX) ?? false
 }
 
 export interface AuthResult {

diff --git a/apps/sim/serializer/index.ts b/apps/sim/serializer/index.ts
--- a/apps/sim/serializer/index.ts
+++ b/apps/sim/serializer/index.ts
@@ -347,14 +347,17 @@
           )
         )
 
-      const isOrphanWithValue =
-        matchingConfigs.length === 0 && subBlock.value != null && subBlock.value !== ''
+      const isImpersonateUserEmailOrphanWithValue =
+        id === 'impersonateUserEmail' &&
+        matchingConfigs.length === 0 &&
+        subBlock.value != null &&
+        subBlock.value !== ''
 
       if (
         (matchingConfigs.length > 0 && shouldInclude) ||
         hasStarterInputFormatValues ||
         isLegacyAgentField ||
-        isOrphanWithValue
+        isImpersonateUserEmailOrphanWithValue
       ) {
         params[id] = subBlock.value
       }

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 28, 2026

Greptile Summary

This PR adds Google service account (domain-wide delegation) as a first-class credential type in Sim, enabling automated workflows to access Gmail, Drive, Sheets, Calendar, and other Google Workspace APIs without per-user OAuth consent flows.

Key changes:

  • New credential typeservice_account added to the DB enum, with an encrypted_service_account_key column, a workspace-scoped unique index, and a CHECK constraint enforcing the key is always present.
  • JWT two-legged OAuth flowgetServiceAccountToken in oauth/utils.ts constructs and signs an RS256 JWT (RFC 7523) locally via Node crypto, then exchanges it for a short-lived Google access token; userinfo scopes are filtered out since they are rejected for service accounts.
  • Authorizationcredential-access.ts enforces workspace alignment and per-user membership for interactive calls; workflow-execution path is allowed for any workflow in the same workspace (all workspace members are bulk-granted membership at creation time, consistent with env_workspace credentials).
  • UI — Integrations manager gets a JSON paste + drag-and-drop file upload flow; all 13 Google blocks gain isServiceAccount (hidden, framework-set) and impersonateUserEmail (conditionally shown) sub-blocks via the shared SERVICE_ACCOUNT_SUBBLOCKS constant.
  • Selector / tool routingimpersonateUserEmail is threaded through the selector context and included in the OAuth token request payload alongside the canonical provider scopes so that each tool's refreshAccessTokenIfNeeded call reaches getServiceAccountToken with the right arguments.
  • Documentation — comprehensive MDX guide covering GCP setup, domain-wide delegation, scope reference table, and the impersonation workflow.

Confidence Score: 5/5

Safe to merge — all remaining findings are P2 (non-blocking improvements).

The core security properties are solid: service account keys are encrypted at rest, the JWT is signed server-side with the stored private key, scopes are correctly filtered, and authorization checks gate every token-exchange path. DB schema changes are additive with proper constraints. All 13 Google blocks consistently apply the service account subblocks. The only issues found are (1) a swallowed ServiceAccountTokenError message in the token route (debugging UX, not a correctness failure) and (2) the absence of token caching (latency/rate-limit concern for high-frequency workflows). Neither blocks correctness or safety.

apps/sim/app/api/auth/oauth/token/route.ts — error message from ServiceAccountTokenError is swallowed; apps/sim/app/api/auth/oauth/utils.ts — no token caching.

Important Files Changed

Filename Overview
apps/sim/app/api/auth/oauth/utils.ts Core service account JWT implementation: adds getServiceAccountToken (two-legged OAuth via RFC 7523), ServiceAccountTokenError, and extends resolveOAuthAccountId to return credentialType: 'service_account'; correctly filters userinfo scopes; no token caching means one HTTP round-trip per tool call.
apps/sim/app/api/auth/oauth/token/route.ts Service account token exchange added; authorization and credential resolution are correct, but ServiceAccountTokenError is not specially handled in the catch block, so Google's descriptive error message is lost.
apps/sim/lib/auth/credential-access.ts Service account authorization logic added: checks workspace alignment and credential membership for interactive calls; workflow-context path skips membership (all workspace members are bulk-added at creation time) — consistent with existing env_workspace behavior.
apps/sim/app/api/credentials/route.ts Service account credential creation: thorough JSON key validation via serviceAccountJsonSchema, encrypts the key before storing, sets all workspace members as credential members, and deduplicates on (workspaceId, type, providerId, displayName).
packages/db/schema.ts Adds service_account enum value, encrypted_service_account_key text column, unique index on (workspaceId, type, providerId, displayName), and a CHECK constraint enforcing key + providerId are both present; migration in 0184 is correctly registered in _journal.json.
apps/sim/blocks/utils.ts Adds SERVICE_ACCOUNT_SUBBLOCKS constant with a hidden isServiceAccount flag and a conditionally-shown impersonateUserEmail field; spread into all 13 Google block definitions.
apps/sim/tools/index.ts Forwards impersonateUserEmail as impersonateEmail in the token payload and attaches canonical provider scopes; cleans up impersonateUserEmail from contextParams after token fetch.
apps/sim/app/workspace/[workspaceId]/settings/components/integrations/integrations-manager.tsx Adds full UI flow for adding service account credentials (JSON paste + drag-and-drop file upload); service accounts are deleted via deleteCredential rather than the OAuth disconnect flow; client-side JSON validation mirrors server-side validation.
apps/sim/lib/oauth/oauth.ts Adds serviceAccountProviderId: 'google-service-account' to all 13 Google service configs and registers the google-service-account service entry with empty scopes and authType: 'service_account'.

Sequence Diagram

sequenceDiagram
    participant UI as Workflow UI
    participant ToolExec as tools/index.ts
    participant TokenAPI as /api/auth/oauth/token
    participant UtilsFn as getServiceAccountToken
    participant DB as Database
    participant GoogleToken as Google Token Endpoint

    UI->>ToolExec: execute tool (credential=SA, impersonateUserEmail=alice@co)
    ToolExec->>ToolExec: build tokenPayload (scopes, impersonateEmail)
    ToolExec->>TokenAPI: POST /api/auth/oauth/token
    TokenAPI->>DB: resolveOAuthAccountId(credentialId)
    DB-->>TokenAPI: {credentialType: 'service_account', credentialId}
    TokenAPI->>TokenAPI: authorizeCredentialUse()
    TokenAPI->>UtilsFn: getServiceAccountToken(credentialId, scopes, impersonateEmail)
    UtilsFn->>DB: SELECT encrypted_service_account_key
    DB-->>UtilsFn: encrypted key
    UtilsFn->>UtilsFn: decryptSecret → JSON.parse → filter scopes
    UtilsFn->>UtilsFn: createSign(RS256) → JWT assertion
    UtilsFn->>GoogleToken: POST grant_type=jwt-bearer
    GoogleToken-->>UtilsFn: {access_token}
    UtilsFn-->>TokenAPI: accessToken
    TokenAPI-->>ToolExec: {accessToken}
    ToolExec->>ToolExec: call Google API with accessToken
Loading

Reviews (4): Last reviewed commit: "Fix build error" | Re-trigger Greptile

@TheodoreSpeaks
Copy link
Copy Markdown
Collaborator Author

@BugBot review

@TheodoreSpeaks
Copy link
Copy Markdown
Collaborator Author

@greptile review

@TheodoreSpeaks
Copy link
Copy Markdown
Collaborator Author

@BugBot review

@TheodoreSpeaks
Copy link
Copy Markdown
Collaborator Author

@greptile review

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

@TheodoreSpeaks TheodoreSpeaks marked this pull request as ready for review April 1, 2026 05:34
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