Skip to content

feat(credentials): add Atlassian service account credentials#4432

Open
TheodoreSpeaks wants to merge 58 commits intostagingfrom
feat/atlassian-service-account
Open

feat(credentials): add Atlassian service account credentials#4432
TheodoreSpeaks wants to merge 58 commits intostagingfrom
feat/atlassian-service-account

Conversation

@TheodoreSpeaks
Copy link
Copy Markdown
Collaborator

Summary

  • Add atlassian-service-account credential type backed by a scoped Atlassian API token, surfaced in Jira and Confluence credential dropdowns alongside OAuth
  • Validate via the gateway flow: _edge/tenant_info (unauth) to discover cloudId, then api.atlassian.com/ex/jira/{cloudId}/rest/api/3/myself with Bearer to verify the token works
  • Plumb cloudId from the token route through tools and selectors so blocks skip the runtime accessible-resources call (which 401s for scoped service-account tokens)
  • Extract reusable ServiceAccountForm (Google JSON-key paste/upload) and new AtlassianServiceAccountForm (token + domain) from integrations-manager; map typed server error codes to friendly inline copy
  • Lift code from error response bodies into ApiClientError.code so any form/hook can switch on it without re-parsing

Type of Change

  • New feature

Testing

Tested manually end-to-end (credential creation, project picker, runtime block call). bun run lint and bun run check:api-validation:strict both pass.

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)

waleedlatif1 and others added 30 commits April 3, 2026 23:30
…ership workflow edits via sockets, ui improvements
…ration, signup method feature flags, SSO improvements
* feat(posthog): Add tracking on mothership abort (#4023)

Co-authored-by: Theodore Li <theo@sim.ai>

* fix(login): fix captcha headers for manual login  (#4025)

* fix(signup): fix turnstile key loading

* fix(login): fix captcha header passing

* Catch user already exists, remove login form captcha
…nts, secrets performance, polling refactors, drag resources in mothership
…endar triggers, docs updates, integrations/models pages improvements
…mat, logs performance improvements

fix(csp): add missing analytics domains, remove unsafe-eval, fix workspace CSP gap (#4179)
fix(landing): return 404 for invalid dynamic route slugs (#4182)
improvement(seo): optimize sitemaps, robots.txt, and core web vitals across sim and docs (#4170)
fix(gemini): support structured output with tools on Gemini 3 models (#4184)
feat(brightdata): add Bright Data integration with 8 tools (#4183)
fix(mothership): fix superagent credentials (#4185)
fix(logs): close sidebar when selected log disappears from filtered list; cleanup (#4186)
v0.6.46: mothership streaming fixes, brightdata integration
@vercel
Copy link
Copy Markdown

vercel Bot commented May 4, 2026

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

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment May 5, 2026 3:10am

Request Review

@cursor
Copy link
Copy Markdown

cursor Bot commented May 4, 2026

PR Summary

Medium Risk
Adds a new credential creation path that stores and replays Atlassian API tokens and introduces new outbound validation calls, so mistakes could affect authentication behavior and token handling. Changes also alter token-fetch plumbing for Jira/Confluence selectors/tools, which could impact existing Atlassian integrations if contracts are misused.

Overview
Adds a new Atlassian service account credential flow backed by a scoped API token, including a dedicated POST /api/auth/atlassian-service-account route that validates the domain (SSRF-resistant allowlist), discovers cloudId via /_edge/tenant_info, verifies the token via /myself, encrypts the secret blob, and records audit/analytics events.

Updates the OAuth token API/contracts to return provider-specific extras (cloudId, domain) for atlassian-service-account credentials, and threads this bundle through tools/selectors so Jira/Confluence can skip accessible-resources discovery when using scoped service-account tokens.

Refactors the integrations UI by extracting a reusable ServiceAccountForm (JSON key) and adding an AtlassianServiceAccountForm (token + domain) with friendly error-code mapping; client request errors now surface code from response bodies. Adds docs for setup and links it in the integrations docs index.

Reviewed by Cursor Bugbot for commit 287dade. Bugbot is set up for automated code reviews on this repo. Configure here.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 4, 2026

Greptile Summary

Adds atlassian-service-account credentials backed by a scoped Atlassian API token, validated via the _edge/tenant_info + /myself gateway flow and stored encrypted. The PR also extracts the existing Google service-account form into a reusable ServiceAccountForm component, lifts code into ApiClientError, and plumbs cloudId/domain through the token route and selectors so Jira/Confluence blocks skip the OAuth accessible-resources call that 401s for service-account tokens. All P2 findings only.

Confidence Score: 4/5

Safe to merge; all findings are P2 (description copy error, TOCTOU on duplicate check, minor duplication).

No P0 or P1 issues found. The validation flow, encryption, transaction pattern, and selector migration are all correct. Three P2 findings keep the score from a 5.

apps/sim/app/api/auth/atlassian-service-account/route.ts (duplicate-name check outside transaction), apps/sim/lib/oauth/oauth.ts (description copy mismatch).

Important Files Changed

Filename Overview
apps/sim/app/api/auth/atlassian-service-account/route.ts New POST endpoint that validates an Atlassian API token via tenant_info + /myself, encrypts and stores the secret, and inserts credential/member rows in a transaction; duplicate display-name guard runs outside the transaction (TOCTOU race).
apps/sim/app/api/auth/oauth/utils.ts Adds getAtlassianServiceAccountSecret/Token helpers and plumbs providerId through ResolvedCredential; refreshAccessTokenIfNeeded correctly short-circuits for service-account tokens before the scopes check.
apps/sim/app/api/auth/oauth/token/route.ts Early-returns { accessToken, cloudId, domain } for atlassian-service-account credentials, correctly bypassing the scope-based service-account token path.
apps/sim/app/workspace/[workspaceId]/settings/components/integrations/atlassian-service-account-form.tsx New form component for Atlassian service-account credentials; normalizeDomain is duplicated from the route file and should be extracted to a shared utility.
apps/sim/app/workspace/[workspaceId]/settings/components/integrations/service-account-form.tsx Clean extraction of the Google service-account JSON form from integrations-manager into a standalone component with no functional regressions.
apps/sim/app/workspace/[workspaceId]/settings/components/integrations/integrations-manager.tsx Removes ~110 lines of inline service-account form state/handlers, delegates to ServiceAccountForm and AtlassianServiceAccountForm; modal routing on providerId === 'atlassian-service-account' is correct.
apps/sim/hooks/selectors/helpers.ts Adds fetchOAuthTokenBundle returning { accessToken, cloudId?, domain? }; fetchOAuthToken is preserved for backwards compatibility.
apps/sim/hooks/selectors/providers/jira/selectors.ts Switches all four selectors (projects list/detail, issues list/detail) from fetchOAuthToken to fetchOAuthTokenBundle and forwards cloudId to the selector contracts.
apps/sim/hooks/selectors/providers/confluence/selectors.ts Same fetchOAuthTokenBundle migration as Jira; both pages selectors now forward cloudId.
apps/sim/lib/oauth/oauth.ts Adds atlassian provider with atlassian-service-account service; description text incorrectly mentions "email" which the form never collects; serviceAccountProviderId is correctly wired on both Jira and Confluence services.
apps/sim/lib/api/client/request.ts Adds codeFromErrorBody helper and lifts code into ApiClientError; clean, additive change.
apps/sim/tools/index.ts Merges cloudId and domain from the token response into contextParams only if not already set; safe and consistent with existing instanceUrl pattern.

Sequence Diagram

sequenceDiagram
    participant UI as AtlassianServiceAccountForm
    participant API as /api/auth/atlassian-service-account
    participant ATL as Atlassian (_edge/tenant_info)
    participant JIRA as api.atlassian.com/ex/jira/.../myself
    participant DB as Database

    UI->>API: POST { apiToken, domain, displayName }
    API->>ATL: GET https://{domain}/_edge/tenant_info
    ATL-->>API: { cloudId }
    API->>JIRA: GET /myself (Bearer apiToken)
    JIRA-->>API: { accountId, displayName }
    API->>DB: Check duplicate displayName
    DB-->>API: existing?
    API->>DB: transaction: INSERT credential + credentialMember
    API-->>UI: 201 { credential }

    Note over UI,DB: Runtime token fetch
    participant TK as /api/auth/oauth/token
    participant SEL as Jira/Confluence Selectors

    SEL->>TK: POST { credentialId }
    TK->>DB: decrypt encryptedServiceAccountKey
    DB-->>TK: { apiToken, cloudId, domain }
    TK-->>SEL: { accessToken, cloudId, domain }
    SEL->>JIRA: selector call with cloudId (skips accessible-resources)
Loading

Comments Outside Diff (1)

  1. apps/sim/app/workspace/[workspaceId]/settings/components/integrations/atlassian-service-account-form.tsx, line 467-472 (link)

    P2 normalizeDomain is duplicated across two files

    An identical normalizeDomain function (strip https?://, strip trailing slash) lives in both this client component and app/api/auth/atlassian-service-account/route.ts. Per the project's architecture guidelines, shared helpers used by 2+ files belong in lib/. Since the client sends the already-normalized value and the server normalizes again anyway, the duplication is harmless, but the helper is a natural candidate for lib/oauth/utils.ts or a shared lib/atlassian/ module.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Reviews (1): Last reviewed commit: "feat(credentials): add Atlassian service..." | Re-trigger Greptile

Comment thread apps/sim/lib/oauth/oauth.ts Outdated
Comment thread apps/sim/app/api/auth/atlassian-service-account/route.ts Outdated
Comment thread apps/sim/app/api/auth/atlassian-service-account/route.ts
- Collapse fetchOAuthTokenBundle into fetchOAuthToken (returns the bundle)
- Reuse serviceAccountJsonSchema in the JSON form instead of hand-rolled checks
- Use parseAtlassianErrorMessage for log details; drop one-line bearer helper
- Extract ATLASSIAN_SERVICE_ACCOUNT_PROVIDER_ID/_SECRET_TYPE constants
- Use Drizzle .returning() instead of post-insert SELECT
- Helper for the duplicated 401/403 + non-OK pattern in the validator
Comment thread apps/sim/lib/oauth/oauth.ts
- New /integrations/atlassian-service-account doc covers token creation,
  scope selection, and adding the credential to Sim
- Form's "View setup guide" link now points at the doc
- Fix the existing Google form link that pointed to the wrong path

Screenshot TODOs left inline as MDX comments for the docs team.
Comment thread apps/sim/app/api/auth/atlassian-service-account/route.ts
- Auth type picker, Sim add-credential modal, Jira block credential dropdown
- Scope-picker screenshot still TODO
- Drop stale 'email and API token' copy from the service description
  (we only collect a token + domain, no email field)
- Move duplicate display-name check inside the create transaction so
  concurrent POSTs can't both pass the check and insert duplicates
Docs site serves /static/* from apps/docs/public, not apps/sim/public —
matches the existing google-service-account screenshot convention.
…e-account

# Conflicts:
#	scripts/check-api-validation-contracts.ts
- SSRF: only accept *.atlassian.net / *.jira-dev.com hosts before fetching
  tenant_info, blocking probes against localhost/internal IPs
- Confluence spaces selector: pull cloudId from the SA secret instead of
  calling accessible-resources, which 401s for scoped service-account tokens
- Case-insensitive https?:// strip so HTTPS://team.atlassian.net normalizes
  correctly
@gitguardian
Copy link
Copy Markdown

gitguardian Bot commented May 5, 2026

⚠️ GitGuardian has uncovered 1 secret following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secret in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
29606901 Triggered Generic High Entropy Secret a54dcbe apps/sim/providers/utils.test.ts View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secret safely. Learn here the best practices.
  3. Revoke and rotate this secret.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

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 2 potential issues.

Fix All in Cursor

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

Reviewed by Cursor Bugbot for commit 287dade. Configure here.

upstreamStatus: error.status,
...error.logDetail,
})
return NextResponse.json({ code: error.code, error: error.code }, { status: 400 })
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Upstream 5xx errors returned as 400 client error

Low Severity

When Atlassian's API is unreachable (e.g., 502 from the upstream), AtlassianValidationError captures the real upstream status in error.status, but the catch handler always responds with HTTP 400. This misrepresents a server-side/upstream failure as a client input error. While the front-end works around this by switching on the code field, the HTTP status is misleading for monitoring, retry logic, and non-UI consumers. The error.status is available and could be forwarded directly.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 287dade. Configure here.

const cloudId =
resolved?.providerId === ATLASSIAN_SERVICE_ACCOUNT_PROVIDER_ID && resolved.credentialId
? (await getAtlassianServiceAccountSecret(resolved.credentialId)).cloudId
: await getConfluenceCloudId(domain, accessToken)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Redundant credential resolution and secret decryption in selector

Low Severity

For Atlassian service-account credentials, resolveOAuthAccountId is called twice (once inside refreshAccessTokenIfNeeded at line 47, and again directly at line 65), and the encrypted secret is decrypted twice (once inside getAtlassianServiceAccountToken via refreshAccessTokenIfNeeded, and again at line 68 via getAtlassianServiceAccountSecret). This triples the DB and crypto work for every Confluence spaces selector request with a service account credential.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 287dade. Configure here.

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.

4 participants