feat(platform): integration package upload with OAuth2 support#426
feat(platform): integration package upload with OAuth2 support#426larryro wants to merge 30 commits into
Conversation
📝 WalkthroughWalkthroughThis pull request implements custom REST API integration uploads through a two-step wizard interface. Users can upload ZIP packages containing config.json and connector.js files for custom integrations. The changes include new upload/preview UI components, OAuth2 credential flows with token refresh, file parsing and validation utilities, and substantial backend refactoring. The integration system is extended with support for multiple authentication methods, OAuth2 configuration storage, icon storage, and improved test connection flows. Example integration packages for Shopify, Circuly, and Protel are added. Email provider modules are refactored with cleaner naming conventions (removing Logic suffixes). The schema is updated to support oauth2Config, supportedAuthMethods, and iconStorageId fields. Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 28
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
services/platform/convex/predefined_integrations/circuly.ts (1)
56-105: 🧹 Nitpick | 🔵 TrivialConsider consistent error handling between testConnection and execute.
The
testConnectionfunction provides specific error messages for 401 and 403 status codes, butexecuteonly has a generic error handler. For better debugging, consider adding the same specific status code handling inexecute.♻️ Suggested improvement for execute error handling
if (response.status !== 200) { + if (response.status === 401) { + throw new Error('Circuly authentication failed. Please verify your username and password.'); + } + if (response.status === 403) { + throw new Error('Circuly access denied. Please verify your account has API access.'); + } throw new Error('Circuly API error (' + response.status + '): ' + response.text()); }services/platform/convex/email_providers/actions.ts (1)
212-217:⚠️ Potential issue | 🟠 MajorMissing authentication guard in
testExistingProvideraction.This action is missing the explicit auth check that all other actions in this file have. Per the defense-in-depth pattern established in this codebase, public actions should enforce authentication at the boundary before delegating to handlers.
🔒 Proposed fix to add auth guard
export const testExistingProvider = action({ args: { providerId: v.id('emailProviders'), }, handler: async (ctx, args): Promise<TestResult> => { + const authUser = await authComponent.getAuthUser(ctx); + if (!authUser) { + throw new Error('Unauthenticated'); + } + return await testExistingProviderHandler(ctx, args.providerId, {Based on learnings: "For integrations actions, enforce explicit authentication at the action boundary using
const authUser = await authComponent.getAuthUser(ctx); if (!authUser) throw new Error('Unauthenticated');"services/platform/convex/node_only/integration_sandbox/internal_actions.ts (1)
99-136: 🧹 Nitpick | 🔵 TrivialConsider clearer error propagation in the two-pass HTTP pattern.
The error swallowing in lines 107-111 allows HTTP requests to proceed even if the first pass throws. However, if the first pass throws and there are HTTP requests, the second pass runs but the original error is lost. If the second pass also fails, only that error is surfaced.
This might be intentional to allow HTTP-dependent code to complete, but consider documenting this behavior or capturing the first error for debugging.
🤖 Fix all issues with AI agents
In `@examples/integrations/circuly/connector.js`:
- Around line 82-89: The code builds the query string by concatenating user
input directly into queryParts and then into fullUrl (baseUrl + endpoint + '?' +
queryParts.join('&')), which can produce malformed or unsafe URLs; update the
construction in the function that uses params and queryParts to properly
URL-encode each key and value (e.g., use encodeURIComponent for params.sort,
params.id, params.customer_id, params.status and params.desc, or replace the
manual queryParts logic with a URLSearchParams instance) so that special
characters are escaped before joining into fullUrl.
- Line 90: Remove the production debug log that prints the full request URL (the
console.log('Making Circuly request to: ' + fullUrl) line); either delete it or
guard it behind a debug flag/config (e.g., process.env.DEBUG_CIRCULY) or use a
proper logger at debug level that redacts query params so sensitive values are
not written to logs, and update the code path that performs the Circuly request
to use the guarded logger or omit the URL entirely when not in debug mode.
In `@examples/integrations/protel/config.json`:
- Around line 12-14: The config's "options" currently sets "encrypt": false and
"trustServerCertificate": true which disables TLS and certificate validation;
update the defaults to secure values by setting "encrypt" to true and
"trustServerCertificate" to false, and if you must allow the insecure combo for
local/on-prem use add a clear descriptive comment in the JSON description
explaining the specific allowed scenarios and risks; look for the "options"
object and the "encrypt" and "trustServerCertificate" keys in the config.json to
make these changes.
- Around line 217-221: The operations get_next_reservation_id (and
get_next_guest_id) use a MAX(...)+1 pattern which causes race conditions; either
update their "description" text to explicitly document this limitation and warn
callers, or change the implementation to generate IDs atomically (preferably by
performing the INSERT and returning the new ID from the DB, e.g., using an
INSERT ... RETURNING/OUTPUT clause or a DB sequence/auto-increment) and update
the operation's "query" accordingly so callers receive the inserted ID instead
of relying on MAX(buchnr)+1.
In `@examples/integrations/shopify/connector.js`:
- Around line 30-34: The domain normalization currently trims protocol/slashes
but can leave paths/queries (producing values like "store.myshopify.com/admin"),
so update the logic in the connector.js normalization block that sets
cleanDomain and shopDomain to extract only the hostname (e.g., using the URL
constructor or a hostname parse) before checking '.myshopify.com'; replace the
two-step replace logic with a single hostname extraction and then set shopDomain
= hostname.includes('.myshopify.com') ? hostname : hostname + '.myshopify.com',
and apply the same fix to the corresponding logic around lines 94-98 to ensure
hostname-only values everywhere.
- Around line 144-151: The buildEndpoint function currently treats a missing
params.resourceId on "get_*" operations as a list request; update
buildEndpoint(operation, params) to validate that when action === 'get' a
params.resourceId exists and, if not, throw a clear Error (e.g., "Missing
resourceId for get_<resource> operation") instead of returning the list
endpoint; keep the existing behavior for other actions (like 'count') unchanged
and reference the action, params.resourceId and resource variables when
constructing the error message to aid debugging.
- Around line 154-167: The buildQueryParams function currently concatenates raw
param values into the query string which can break on reserved characters;
update buildQueryParams to URL-encode all non-numeric parameter values (e.g.,
params.page_info, params.since_id, params.created_at_min, params.updated_at_min,
params.status, params.fields) using encodeURIComponent (leave numeric limit
as-is) before pushing them into queryParts so every pushed segment is safe for
inclusion in the URL.
In
`@services/platform/app/features/settings/integrations/components/integration-details.tsx`:
- Around line 176-189: The two computed strings operationsSummary and
sqlOperationsSummary are currently created with useCallback but should be
memoized values; replace useCallback(...) with useMemo(...) using the same
dependency arrays (restOperations and sqlOperations respectively) to return the
computed string, and update all call sites that do operationsSummary() or
sqlOperationsSummary() to use the value directly (operationsSummary,
sqlOperationsSummary). Ensure you preserve the existing mapping/formatting logic
(headers, queries, joins) when moving the implementation into useMemo.
- Around line 218-241: The <ul> elements in IntegrationDetails component include
a redundant role="list" attribute; remove the role="list" attribute from the
<ul> elements (the one rendering restOperations.map and the other at the second
occurrence mentioned) in
services/platform/app/features/settings/integrations/components/integration-details.tsx
so the markup relies on the native semantic list role instead of explicitly
setting role="list".
- Around line 81-83: Remove the duplicated isRecord implementation and import
the shared isRecord utility from the existing type-guards module, replacing the
local function; update any local references to use the imported isRecord (the
function name stays the same) and delete the local isRecord declaration in
integration-details.tsx so there’s a single source of truth.
In
`@services/platform/app/features/settings/integrations/components/integration-manage-dialog.tsx`:
- Around line 228-266: The preview URL created with URL.createObjectURL(file)
isn't revoked and may leak memory; after creating previewUrl in the upload flow
inside the handler, ensure you call URL.revokeObjectURL(previewUrl) once the
real icon is set (after updateIcon and setOptimisticIconUrl) and also revoke it
on failure in the catch block; additionally add a cleanup in the component
(e.g., a useEffect cleanup) that revokes any outstanding optimistic URL stored
in state (the value set by setOptimisticIconUrl) on unmount or when a new
preview is created so the previous object URL is always revoked.
- Around line 874-898: The dynamic status block that renders testResult in
integration-manage-dialog.tsx (the div with role="status" that shows
testResult.message and the close button tied to setTestResult) needs an ARIA
live region so screen readers announce updates; add aria-live="polite" (or
appropriate politeness) to that div (and the other similar status block where
testResult is rendered) or replace the container with a semantic <output>
element that includes aria-live, ensuring the element still contains the
existing role, message span, and close button behavior.
- Around line 138-163: The OAuth2 form fields (oauth2Fields state) can retain
values when switching integrations because the effect only runs when
integration.oauth2Config is truthy; update the effect tied to
integration.oauth2Config to also handle the falsy case by clearing oauth2Fields
(call setOAuth2Fields to reset authorizationUrl, tokenUrl, clientId,
clientSecret to empty strings) whenever integration.oauth2Config is undefined or
when the integration changes (e.g., include integration.id or the whole
integration object in the dependency array); ensure clientSecret is explicitly
cleared to avoid leaking previous credentials.
In
`@services/platform/app/features/settings/integrations/components/integration-upload/integration-upload-dialog.tsx`:
- Around line 61-77: Currently the icon upload block in
integration-upload-dialog.tsx silently ignores non-OK uploadResponse and
proceeds without an icon; update the flow in the upload logic that uses
generateUploadUrl(), uploadResponse, and iconFile so that when uploadResponse.ok
is false you surface the failure (e.g., throw a descriptive error or set an
error state) instead of continuing — ensure the error is handled by the
surrounding submit path to stop the integration creation and show a user-facing
message indicating the icon upload failed.
In
`@services/platform/app/features/settings/integrations/components/integration-upload/steps/preview-step.tsx`:
- Around line 120-123: Remove the redundant aria role from the unordered list in
the PreviewStep UI: locate the <ul> in preview-step.tsx that has
className="bg-muted max-h-48 space-y-1 overflow-y-auto rounded-md p-3 text-sm"
and remove the role="list" attribute (the semantic <ul> already implies the list
role and the a11y lint flags it as redundant).
In
`@services/platform/app/features/settings/integrations/components/integration-upload/steps/upload-step.tsx`:
- Around line 55-57: The accept prop currently includes ".ts" which should be
removed because TypeScript packages aren't supported; update the component where
the file input is rendered (the accept prop in the upload-step component that
sets onFilesSelected={handleFilesSelected}) to only include supported extensions
(e.g., ".zip,.json,.js") and ensure any UI text or tests referencing ".ts" are
updated accordingly.
In `@services/platform/convex/email_providers/generate_oauth2_auth_url.ts`:
- Around line 131-135: The function generateOAuth2AuthUrl declares an unused
ctx: ActionCtx parameter; remove ctx from the function signature and any
corresponding type annotations so the signature becomes (args:
GenerateOAuth2AuthUrlArgs, deps: GenerateOAuth2AuthUrlDependencies):
Promise<string>, then update all callers (e.g., the invocation in actions.ts) to
stop passing ctx and adjust call sites to pass only args and deps; ensure any
exported type declarations or overloads that referenced the old signature are
updated to match and run typechecks to catch remaining references.
In `@services/platform/convex/email_providers/test_existing_provider.ts`:
- Around line 301-319: The code passes a dummy smtpConfig to deps.testConnection
when providerData.sendMethod === 'api'; instead make smtpConfig optional on
TestExistingProviderDependencies.testConnection and call deps.testConnection
without smtpConfig in the API path (i.e., remove smtpConfig from the call that
produces imapOnlyResult), or alternatively update testConnection's
implementation to accept undefined smtpConfig and treat it as "skip SMTP";
change the testConnection signature/type accordingly and adjust any callers to
handle optional smtpConfig so imapOnlyResult and result logic remain correct.
In `@services/platform/convex/integrations/decrypt_and_refresh_oauth2.ts`:
- Around line 93-95: The token expiry is calculated using Date.now() after the
async refresh, which can yield a slightly inconsistent expiry; capture a single
timestamp before the network call (e.g., const now =
Math.floor(Date.now()/1000)) and use that captured value when computing
tokenExpiry instead of calling Date.now() inline, updating the calculation
around the tokenExpiry assignment that uses tokens.expires_in in
decrypt_and_refresh_oauth2.ts.
In `@services/platform/convex/integrations/generate_oauth2_auth_url.ts`:
- Around line 63-65: The current generateOAuth2AuthUrl function sets
Google-specific parameters unconditionally (params.set('access_type','offline')
and params.set('prompt','consent'))—change this so provider-specific behavior is
applied based on oauth2Config (e.g., oauth2Config.provider or a providerType
field): only set access_type=offline and prompt=consent when provider is Google;
for Microsoft, ensure you add the offline_access scope instead of access_type;
for providers like GitHub do not add these parameters (or respect a config flag
like oauth2Config.requestRefreshToken) so refresh-token requests are only made
where supported. Update generateOAuth2AuthUrl to branch on oauth2Config.provider
and modify params and scopes accordingly rather than unconditionally setting
Google parameters.
In `@services/platform/convex/integrations/mutations.ts`:
- Around line 32-38: The current flow deletes the old icon via
ctx.storage.delete(integration.iconStorageId) before updating the DB, risking
data loss if ctx.db.patch(args.integrationId, { iconStorageId:
args.iconStorageId }) fails; change the order to first call ctx.db.patch(...) to
set the new iconStorageId for args.integrationId and only after a successful
patch (and if integration.iconStorageId is set and different from
args.iconStorageId) call ctx.storage.delete(integration.iconStorageId) so the
old asset is removed atomically and not lost on patch failure.
In `@services/platform/convex/integrations/oauth2_callback.ts`:
- Around line 103-110: The handler currently logs the raw incoming state on
parse failure (see parseState usage and the console.error call) which may expose
sensitive IDs; change the error logging in the OAuth2 callback to omit the full
state value (e.g., log a generic message like "[Integration OAuth2 Callback]
Invalid state format" or log a hashed/truncated/masked token instead) and keep
the existing redirect built via buildRedirectUrl and returned with
Response.redirect; ensure no other code paths emit the raw state to logs.
- Around line 53-66: The current parseState function assumes colon-separated
parts and will break if integrationId or organizationId contain ':'; update the
implementation of parseState (and any callers) to use a robust, reversible
encoding instead — e.g., have state be a base64-encoded JSON object and change
parseState to base64-decode and JSON.parse into ParsedState (keeping the
toId<'integrations'>(integrationId) conversion and validation), or alternatively
escape/unescape colons consistently; pick one approach and update
documentation/comments to state the chosen format so the assumption is explicit.
In `@services/platform/convex/integrations/run_health_check.ts`:
- Around line 65-67: The health check currently returns early when connectorCode
is falsy, making REST integrations with missing connector code pass silently;
update the check in run_health_check.ts (the block using connectorCode, likely
inside runHealthCheck) to throw a descriptive Error (e.g., "Missing
connectorCode for integration" or similar) instead of returning so the health
check fails fast and invalid packages are rejected; ensure the thrown error
includes the integration identifier/context if available.
- Around line 31-33: The early return when args.type === 'sql' in
run_health_check.ts skips SQL validation even though the docstring expects a
SELECT 1; remove or replace that return so SQL credentials are actually
verified: either invoke the existing SQL health-check helper (e.g., call the
module/function that performs a SELECT 1 or testSqlConnection) when args.type
=== 'sql', or if skipping is intentional update the docstring/caller
expectations to state SQL checks are omitted; ensure you reference and use the
args.type check and the SQL test helper consistently so SQL paths execute
validation instead of returning early.
In `@services/platform/convex/integrations/test_connection.ts`:
- Around line 71-82: The code writes both overrides.apiKeyAuth.key and
overrides.oauth2Auth.accessToken into the same secrets['accessToken'] key,
causing oauth2Auth to silently overwrite apiKeyAuth; update the logic in
test_connection.ts around the overrides handling to detect when both
overrides.apiKeyAuth?.key and overrides.oauth2Auth?.accessToken are provided and
either (a) throw a clear validation error indicating the conflicting auth
methods, or (b) log the chosen precedence and avoid silent overwrite (e.g., keep
apiKeyAuth if you want it to win or keep oauth2Auth if preferred), and ensure
secrets['accessToken'] is only set once accordingly so callers see deterministic
behavior; reference the overrides object, apiKeyAuth, oauth2Auth, and
secrets['accessToken'] when making the change.
- Around line 188-203: The assignment of user/password from overrides?.basicAuth
lacks null/empty safety; update the logic in the test_connection flow that
handles overrides.basicAuth so you validate overrides.basicAuth.username and
overrides.basicAuth.password before using them (e.g., ensure non-empty strings),
and if either is missing fall back to the saved integration.basicAuth (using
integration.basicAuth.username and decrypting
integration.basicAuth.passwordEncrypted via
ctx.runAction(internal.lib.crypto.internal_actions.decryptString)) or throw a
clear error; adjust the branches around the user/password variables and the
existing integration.basicAuth usage to handle partial overrides safely.
In `@services/platform/convex/predefined_integrations/shopify.ts`:
- Around line 33-38: The domain normalization currently leaves
path/query/fragment parts (so cleanDomain may include "/admin"), causing
shopDomain and url to be invalid; update the logic around cleanDomain/shopDomain
in services/platform/convex/predefined_integrations/shopify.ts to first strip
any protocol and then truncate at the first '/', '?' or '#' (e.g., take
substring before / ? or #), then remove any trailing slashes and only then apply
the '.myshopify.com' suffix when cleanDomain does not already include
'.myshopify.com'; ensure the constructed url uses that sanitized shopDomain
variable.
| if (overrides.apiKeyAuth?.key) { | ||
| secrets['accessToken'] = overrides.apiKeyAuth.key; | ||
| } | ||
| if (overrides.basicAuth) { | ||
| if (overrides.basicAuth.username) | ||
| secrets['username'] = overrides.basicAuth.username; | ||
| if (overrides.basicAuth.password) | ||
| secrets['password'] = overrides.basicAuth.password; | ||
| } | ||
| if (overrides.oauth2Auth?.accessToken) { | ||
| secrets['accessToken'] = overrides.oauth2Auth.accessToken; | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Potential secret key collision between apiKeyAuth and oauth2Auth.
Both apiKeyAuth.key (Line 72) and oauth2Auth.accessToken (Line 81) write to secrets['accessToken']. If both are somehow provided, oauth2Auth will silently overwrite apiKeyAuth. This may be intentional (only one auth method used at a time), but consider documenting this precedence or adding a guard.
🤖 Prompt for AI Agents
In `@services/platform/convex/integrations/test_connection.ts` around lines 71 -
82, The code writes both overrides.apiKeyAuth.key and
overrides.oauth2Auth.accessToken into the same secrets['accessToken'] key,
causing oauth2Auth to silently overwrite apiKeyAuth; update the logic in
test_connection.ts around the overrides handling to detect when both
overrides.apiKeyAuth?.key and overrides.oauth2Auth?.accessToken are provided and
either (a) throw a clear validation error indicating the conflicting auth
methods, or (b) log the chosen precedence and avoid silent overwrite (e.g., keep
apiKeyAuth if you want it to win or keep oauth2Auth if preferred), and ensure
secrets['accessToken'] is only set once accordingly so callers see deterministic
behavior; reference the overrides object, apiKeyAuth, oauth2Auth, and
secrets['accessToken'] when making the change.
There was a problem hiding this comment.
This is a false positive. The authMethod field on the integration schema is a single-value union ('api_key' | 'bearer_token' | 'basic_auth' | 'oauth2'), meaning an integration has exactly one auth method at a time. The caller (testConnection) passes overrides straight from TestConnectionArgs, which come from the UI wizard where only one auth method is configured.
Both apiKeyAuth and oauth2Auth intentionally map to secrets['accessToken'] because that's the key connectors expect regardless of how the token was obtained. This mirrors the persisted-credential path in buildTestSecrets (build_test_secrets.ts), which uses integration.authMethod to branch into the same secrets['accessToken'] assignment.
Adding a guard here would be defensive against a state the data model and UI prevent. No change needed.
If you agree with this reasoning and no further modifications are needed, please resolve this conversation.
There was a problem hiding this comment.
@larryro, you're absolutely right—thank you for the detailed explanation! The single authMethod constraint at the schema level and the normalized secrets['accessToken'] interface pattern make perfect sense. The intentional mapping to the same key across auth methods is the correct design, and your reference to the buildTestSecrets parallel confirms the consistency.
My original concern was a false positive. No changes needed here. I'll resolve this conversation.
🧠 Learnings used
Learnt from: larryro
Repo: tale-project/tale PR: 397
File: services/platform/convex/integrations/actions.ts:51-74
Timestamp: 2026-02-07T12:23:53.075Z
Learning: Repo: tale-project/tale
File: services/platform/convex/integrations/actions.ts
Learning: For integrations actions (create, update, testConnection), add an explicit top-level auth guard using authComponent.getAuthUser(ctx) and throw "Unauthenticated" before delegating to logic functions, even if those functions also perform auth. This defense-in-depth pattern should be kept consistent across actions in this module.
Learnt from: larryro
Repo: tale-project/tale PR: 397
File: services/platform/convex/integrations/actions.ts:23-49
Timestamp: 2026-02-07T12:27:08.634Z
Learning: Repo: tale-project/tale (PR `#397`)
Context: services/platform/convex/integrations/actions.ts
Learning: For integrations actions, enforce explicit authentication at the action boundary using `const authUser = await authComponent.getAuthUser(ctx); if (!authUser) throw new Error('Unauthenticated');` rather than relying on downstream RLS via queries like `api.integrations.queries.list`, which return an empty array for unauthenticated callers and do not throw. This ensures consistent, defense-in-depth auth.
Learnt from: larryro
Repo: tale-project/tale PR: 76
File: services/platform/convex/agent_tools/sub_agents/helpers/format_integrations.ts:73-111
Timestamp: 2026-01-05T01:44:28.744Z
Learning: In Convex integration-related code (e.g., services/platform/convex/agent_tools/sub_agents/helpers/format_integrations.ts, services/platform/convex/agent_tools/integrations/execute_batch_integration_internal.ts), `as any` casts are necessary when accessing optional fields like `sqlOperations` and `connector` on `Doc<'integrations'>` because Convex's generated types don't capture these optional fields at the type level. This limitation is tracked in issue `#79`.
Learnt from: larryro
Repo: tale-project/tale PR: 397
File: services/platform/convex/node_only/imap/internal_actions.ts:22-23
Timestamp: 2026-02-07T12:32:11.051Z
Learning: Repo: tale-project/tale (PR `#397`)
File: services/platform/convex/node_only/imap/internal_actions.ts
Learning: For IMAP retrieval (retrieveImapEmails internalAction), enforce a runtime invariant that credentials include either password or accessToken and throw a descriptive error otherwise, even if upstream callers validate. This defense-in-depth pattern should be preserved for IMAP actions.
Learnt from: larryro
Repo: tale-project/tale PR: 397
File: services/platform/convex/team_members/queries.ts:24-31
Timestamp: 2026-02-07T12:42:05.726Z
Learning: Repo: tale-project/tale (PR `#397`)
Context: services/platform/convex/* queries using authComponent.getAuthUser(ctx)
Learning: In Convex query handlers, failures from authComponent.getAuthUser(ctx) are intentionally swallowed (empty catch) and treated as an expected unauthenticated state. Do not log warnings/errors for these cases to avoid noise; return empty data or null instead. Maintain this convention for consistency across queries (e.g., threads/queries.ts, list_threads.ts).
Learnt from: larryro
Repo: tale-project/tale PR: 207
File: services/platform/convex/member.ts:639-653
Timestamp: 2026-01-16T14:11:26.202Z
Learning: In services/platform/convex/member.ts, BetterAuth-related types (BetterAuthTeam, BetterAuthTeamMember) are intentionally defined locally rather than centralized in a shared types module to avoid cross-module dependencies. This architectural pattern should be preserved.
…n testing Add a multi-step upload wizard for integration packages (ZIP with config.json and optional connector.js), supporting both REST API and SQL types. Consolidate per-provider test connection logic into a unified test_connection module, remove _logic file suffix convention from email_providers and integrations, and add example integration packages for Circuly, Protel, and Shopify.
…upport Implement complete OAuth2 authorization code flow for integrations: - Auth URL generation, redirect callback, token exchange, and encrypted storage - Automatic token refresh with 5-minute expiry buffer - OAuth2 client credential management UI in manage dialog - OAuth2 success/error banner notifications Add support for multiple authentication methods per integration: - New `supportedAuthMethods` field allows declaring multiple auth options - Auth method selector dropdown in manage dialog when multiple methods available - Switching methods clears credentials and resets test state - Config validation ensures authMethod is included in supportedAuthMethods
…ample Extend integration schema with operationType and requiresApproval fields for SQL operations. Add oauth2Config support to createIntegrationInternal for OAuth2 authorization flows. Include Outlook integration example.
…vent stale credential leakage
…only runs JavaScript
…fined integration
173efb2 to
35a3ce0
Compare
…and OAuth2 refresh Add approval cards (integration, workflow, human input) to test chat panel using merged chat items. Improve approval card UI with expandable parameters, text overflow handling, and separate status update before execution. Include parameter schemas in operation summaries and validate required parameters before creating approvals or executing. Restructure OAuth2 token refresh with early return, specific error diagnostics, and response validation. Update integration upload to accept icon files and pass parameter schemas. Fetch all approval statuses per thread instead of only pending.
Link users to the example integrations repo from the upload dialog and ignore the examples/ directory in knip.
…est setup jsdom doesn't implement Blob.text() or Blob.arrayBuffer(), causing parse-integration-package tests to fail with "config.json is not valid JSON". Added FileReader-based polyfills in the UI test setup.
Summary
Closes #318
config.json+connector.js), supporting both REST API and SQL integration typestestConnectionhandler with built-in secret resolutionsupportedAuthMethods), letting users choose between API key, basic auth, and OAuth2useQuerycalls to TanStack Query cache (useSuspenseQuery/useCachedPaginatedQuery) for improved client-side caching and reduced Convex subscriptionsexecutionIdto eliminate OCC contention on concurrent executionsTest plan
npm run lint --workspace=@tale/platformandnpx tsc --noEmit🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes