diff --git a/docs-v2/reference/api/connection/get.mdx b/docs-v2/reference/api/connection/get.mdx
index b287a76720d..a0331801f21 100644
--- a/docs-v2/reference/api/connection/get.mdx
+++ b/docs-v2/reference/api/connection/get.mdx
@@ -3,43 +3,9 @@ title: 'Get connection & credentials'
openapi: 'GET /connection/{connectionId}'
---
-
- ```json Example Response
-{
- "id": 18393, // Nango internal connection id
- "created_at": "2023-03-08T09:43:03.725Z", // Creation timestamp
- "updated_at": "2023-03-08T09:43:03.725Z", // Last updated timestamp (e.g. last token refresh)
- "provider_config_key": "github", //
- "connection_id": "1", //
- "credentials": {
- "type": "OAUTH2", // OAUTH2 or OAUTH1
- "access_token": "gho_tsXLG73f....", // The current access token (refreshed if needed)
- "refresh_token": "gho_fjofu84u9....", // Refresh token (Only returned if the REFRESH_TOKEN boolean parameter is set to true and the refresh token is available)
- "expires_at": "2024-03-08T09:43:03.725Z", // Expiration date of access token (only if refresh token is present, otherwise missing)
- "raw": { // Raw token response from the OAuth provider: Contents vary!
- "access_token": "gho_tsXLG73f....",
- "refresh_token": "gho_fjofu84u9....", // Refresh token (Only returned if the REFRESH_TOKEN boolean parameter is set to true and the refresh token is available)
- "token_type": "bearer",
- "scope": "public_repo,user"
- }
- },
- "connection_config": { // Additional API Configuration, see OAuth guide
- "subdomain": "myshop",
- "realmId": "XXXXX",
- "instance_id": "YYYYYYY"
- },
- "account_id": 0, // ID of your Nango account (Nango Cloud only)
- "metadata": { // Custom metadata stored by you
- "myProperty": "yes",
- "filter": "closed=true"
- }
-}
- ```
-
-
-The response content depends on the API authentication type (OAuth 2, OAuth 1, API key, Basic auth).
+The response content depends on the API authentication type (e.g: OAuth 2, OAuth 1, API key, etc.).
If you do not want to deal with collecting & injecting credentials in requests for multiple authentication types, use the Proxy([step-by-step guide](/guides/proxy-requests-to-an-api)).
diff --git a/docs-v2/reference/sdks/node.mdx b/docs-v2/reference/sdks/node.mdx
index 24486ba266b..ff0cf12c1c7 100644
--- a/docs-v2/reference/sdks/node.mdx
+++ b/docs-v2/reference/sdks/node.mdx
@@ -463,7 +463,6 @@ We recommend not caching tokens for longer than 5 minutes to ensure they are fre
"realmId": "XXXXX",
"instance_id": "YYYYYYY"
},
- "account_id": 0,
"metadata": {
"myProperty": "yes",
"filter": "closed=true"
@@ -939,7 +938,7 @@ await nango.startSync('', ['SYNC_NAME1', 'SYNC_NAME2'], '
- The connection ID. If ommitted, the sync will trigger for all relevant connections.
+ The connection ID. If omitted, the sync will trigger for all relevant connections.
@@ -965,7 +964,7 @@ await nango.startSync('', ['SYNC_NAME1', 'SYNC_NAME2'], '
- The connection ID. If ommitted, the sync will pause for all relevant connections.
+ The connection ID. If omitted, the sync will pause for all relevant connections.
@@ -1108,7 +1107,7 @@ await nango.triggerAction('', '', ''
The name of the action to trigger.
-
+
The necessary input for your action's `runAction` function.
@@ -1171,7 +1170,7 @@ await nango.delete(config); // DELETE request
Array of additional status codes to retry a request in addition to the 5xx, 429, ECONNRESET, ETIMEDOUT, and ECONNABORTED
- The API base URL. Can be ommitted if the base URL is configured for this API in the [providers.yaml](https://nango.dev/providers.yaml).
+ The API base URL. Can be omitted if the base URL is configured for this API in the [providers.yaml](https://nango.dev/providers.yaml).
Override the decompress option when making requests. Optional, defaults to false
diff --git a/docs-v2/spec.yaml b/docs-v2/spec.yaml
index 32a20c1cf9a..f7dd6590520 100644
--- a/docs-v2/spec.yaml
+++ b/docs-v2/spec.yaml
@@ -463,21 +463,14 @@ paths:
errors:
type: array
items:
- type: object
- properties:
- type:
- type: string
- example: "'auth' or 'sync'"
- log_id:
- type: string
- example: 'VrnbtykXJFckCm3HP93t'
+ $ref: '#/components/schemas/ConnectionError'
description: |
List of connection errors. Ex:
- Connection credentials are invalid (type=auth)
- Last sync for the connection has failed (type=sync)
end_user:
nullable: true
- $ref: '#/components/schemas/ConnectSessionInput/properties/end_user'
+ $ref: '#/components/schemas/ConnectionEndUser'
post:
description: Adds a connection for which you already have credentials.
@@ -581,6 +574,10 @@ paths:
responses:
'200':
description: Successfully returned a connection
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ConnectionFull'
'400':
description: Invalid request
content:
@@ -2180,6 +2177,8 @@ components:
type: string
organization:
type: object
+ required:
+ - id
properties:
id:
type: string
@@ -2239,3 +2238,423 @@ components:
type: string
description: When the token expires
format: date-time
+
+ AllAuthCredentials:
+ anyOf:
+ - $ref: '#/components/schemas/OAuth1Credentials'
+ - $ref: '#/components/schemas/OAuth2Credentials'
+ - $ref: '#/components/schemas/BasicApiCredentials'
+ - $ref: '#/components/schemas/ApiKeyCredentials'
+ - $ref: '#/components/schemas/AppCredentials'
+ - $ref: '#/components/schemas/JwtCredentials'
+ - $ref: '#/components/schemas/OAuth2ClientCredentials'
+ - $ref: '#/components/schemas/AppStoreCredentials'
+ - $ref: '#/components/schemas/UnauthCredentials'
+ - $ref: '#/components/schemas/CustomCredentials'
+ - $ref: '#/components/schemas/TbaCredentials'
+ - $ref: '#/components/schemas/TableauCredentials'
+ - $ref: '#/components/schemas/BillCredentials'
+ - $ref: '#/components/schemas/TwoStepCredentials'
+ - $ref: '#/components/schemas/SignatureCredentials'
+
+ ## ---------------------- Credentials
+ ApiKeyCredentials:
+ type: object
+ title: Api Key
+ additionalProperties: false
+ properties:
+ apiKey:
+ type: string
+ type:
+ type: string
+ enum: [API_KEY]
+ required:
+ - type
+ - apiKey
+ AppCredentials:
+ type: object
+ title: GitHub App
+ additionalProperties: false
+ properties:
+ access_token:
+ type: string
+ expires_at:
+ format: date-time
+ type: string
+ raw:
+ type: object
+ type:
+ type: string
+ enum: [APP]
+ required:
+ - type
+ - access_token
+ - raw
+ AppStoreCredentials:
+ type: object
+ title: App Store
+ additionalProperties: false
+ properties:
+ access_token:
+ type: string
+ expires_at:
+ type: string
+ format: date-time
+ private_key:
+ type: string
+ raw:
+ type: object
+ type:
+ type: string
+ enum: [APP_STORE]
+ required:
+ - access_token
+ - raw
+ - private_key
+ BasicApiCredentials:
+ type: object
+ title: Basic Auth
+ additionalProperties: false
+ properties:
+ password:
+ type: string
+ type:
+ enum: [BASIC]
+ type: string
+ username:
+ type: string
+ required:
+ - type
+ - username
+ - password
+ BillCredentials:
+ type: object
+ title: Bill
+ additionalProperties: false
+ properties:
+ dev_key:
+ type: string
+ expires_at:
+ type: string
+ format: date-time
+ organization_id:
+ type: string
+ password:
+ type: string
+ raw:
+ type: object
+ session_id:
+ type: string
+ type:
+ type: string
+ enum: [BILL]
+ user_id:
+ type: string
+ username:
+ type: string
+ required:
+ - dev_key
+ - organization_id
+ - password
+ - raw
+ - type
+ - username
+ CustomCredentials:
+ type: object
+ title: Custom
+ additionalProperties: false
+ properties:
+ raw:
+ type: object
+ type:
+ type: string
+ enum: [CUSTOM]
+ required:
+ - raw
+ - type
+ JwtCredentials:
+ type: object
+ title: JWT
+ additionalProperties: false
+ properties:
+ expires_at:
+ format: date-time
+ type: string
+ issuerId:
+ type: string
+ privateKey:
+ anyOf:
+ - additionalProperties: false
+ properties:
+ id:
+ type: string
+ secret:
+ type: string
+ required:
+ - id
+ - secret
+ type: object
+ - type: string
+ privateKeyId:
+ type: string
+ token:
+ type: string
+ type:
+ type: string
+ enum: [JWT]
+ required:
+ - type
+ - privateKey
+ OAuth1Credentials:
+ type: object
+ title: OAuth1
+ additionalProperties: false
+ properties:
+ oauth_token:
+ type: string
+ oauth_token_secret:
+ type: string
+ raw:
+ type: object
+ type:
+ type: string
+ enum: [OAUTH1]
+ required:
+ - oauth_token
+ - oauth_token_secret
+ - raw
+ - type
+ OAuth2ClientCredentials:
+ type: object
+ title: OAuth2 Client
+ additionalProperties: false
+ properties:
+ client_id:
+ type: string
+ client_secret:
+ type: string
+ expires_at:
+ type: string
+ format: date-time
+ raw:
+ type: object
+ token:
+ type: string
+ type:
+ type: string
+ enum: [OAUTH2_CC]
+ required:
+ - client_id
+ - client_secret
+ - raw
+ - token
+ - type
+ OAuth2Credentials:
+ type: object
+ title: OAuth2
+ additionalProperties: false
+ properties:
+ access_token:
+ type: string
+ config_override:
+ additionalProperties: false
+ properties:
+ client_id:
+ type: string
+ client_secret:
+ type: string
+ type: object
+ expires_at:
+ format: date-time
+ type: string
+ raw:
+ type: object
+ refresh_token:
+ type: string
+ type:
+ enum: [OAUTH2]
+ type: string
+ required:
+ - access_token
+ - raw
+ - type
+ SignatureCredentials:
+ type: object
+ title: Signature
+ additionalProperties: false
+ properties:
+ expires_at:
+ type: string
+ format: date-time
+ password:
+ type: string
+ token:
+ type: string
+ type:
+ type: string
+ enum: [SIGNATURE]
+ username:
+ type: string
+ required:
+ - type
+ - username
+ - password
+ TableauCredentials:
+ type: object
+ title: Tableau
+ additionalProperties: false
+ properties:
+ content_url:
+ type: string
+ expires_at:
+ type: string
+ format: date-time
+ pat_name:
+ type: string
+ pat_secret:
+ type: string
+ raw:
+ type: object
+ token:
+ type: string
+ type:
+ type: string
+ enum: [TABLEAU]
+ required:
+ - pat_name
+ - pat_secret
+ - raw
+ - type
+ TbaCredentials:
+ type: object
+ title: TBA
+ additionalProperties: false
+ properties:
+ config_override:
+ additionalProperties: false
+ properties:
+ client_id:
+ type: string
+ client_secret:
+ type: string
+ type: object
+ token_id:
+ type: string
+ token_secret:
+ type: string
+ type:
+ type: string
+ enum: [TBA]
+ required:
+ - type
+ - token_id
+ - token_secret
+ - config_override
+ TwoStepCredentials:
+ type: object
+ title: Two Step
+ additionalProperties: false
+ properties:
+ expires_at:
+ format: date-time
+ type: string
+ raw:
+ type: object
+ token:
+ type: string
+ type:
+ type: string
+ enum: [TWO_STEP]
+ required:
+ - raw
+ - type
+ UnauthCredentials:
+ type: object
+ title: Unauthenticated
+ additionalProperties: false
+
+ ## ---------------------- Connection
+ ConnectionEndUser:
+ type: object
+ required:
+ - id
+ properties:
+ id:
+ type: string
+ description: Uniquely identifies the end user.
+ email:
+ nullable: true
+ type: string
+ display_name:
+ nullable: true
+ type: string
+ organization:
+ type: object
+ nullable: true
+ required:
+ - id
+ properties:
+ id:
+ type: string
+ description: Uniquely identifies the organization the end user belongs to
+ display_name:
+ type: string
+ nullable: true
+ ConnectionError:
+ type: object
+ required:
+ - type
+ - log_id
+ properties:
+ type:
+ type: string
+ enum: [auth, sync]
+ example: auth
+ log_id:
+ type: string
+ example: VrnbtykXJFckCm3HP93t
+ ConnectionFull:
+ type: object
+ required:
+ - id
+ - connection_id
+ - provider_config_key
+ - provider
+ - errors
+ - end_user
+ - metadata
+ - connection_config
+ - created_at
+ - updated_at
+ - last_fetched_at
+ - credentials
+ properties:
+ id:
+ type: integer
+ connection_id:
+ type: string
+ provider_config_key:
+ type: string
+ provider:
+ type: string
+ errors:
+ type: array
+ items:
+ $ref: '#/components/schemas/ConnectionError'
+ end_user:
+ nullable: true
+ $ref: '#/components/schemas/ConnectionEndUser'
+ metadata:
+ type: object
+ additionalProperties: true
+ connection_config:
+ type: object
+ additionalProperties: true
+ created_at:
+ type: string
+ updated_at:
+ type: string
+ last_fetched_at:
+ type: string
+ credentials:
+ $ref: '#/components/schemas/AllAuthCredentials'
diff --git a/packages/cli/lib/services/__snapshots__/model.service.unit.test.ts.snap b/packages/cli/lib/services/__snapshots__/model.service.unit.test.ts.snap
index b1098c9f0e2..e822cd4111a 100644
--- a/packages/cli/lib/services/__snapshots__/model.service.unit.test.ts.snap
+++ b/packages/cli/lib/services/__snapshots__/model.service.unit.test.ts.snap
@@ -45,7 +45,7 @@ import { Nango } from '@nangohq/node';
import type { AxiosInstance, AxiosInterceptorManager, AxiosRequestConfig, AxiosResponse } from 'axios';
import { AxiosError } from 'axios';
import type { SyncConfig } from '../models/Sync.js';
-import type { DBTeam, GetPublicIntegration, RunnerFlags } from '@nangohq/types';
+import type { ApiEndUser, DBTeam, GetPublicIntegration, RunnerFlags } from '@nangohq/types';
export declare const oldLevelToNewLevel: {
readonly debug: "debug";
readonly info: "info";
@@ -245,18 +245,21 @@ interface MetadataChangeResponse {
connection_id: string | string[];
}
interface Connection {
- id?: number;
- created_at: Date;
- updated_at: Date;
+ id: number;
provider_config_key: string;
connection_id: string;
connection_config: Record;
- environment_id: number;
- metadata?: Metadata | null;
- credentials_iv?: string | null;
- credentials_tag?: string | null;
+ created_at: string;
+ updated_at: string;
+ last_fetched_at: string;
+ metadata: Record | null;
+ provider: string;
+ errors: {
+ type: string;
+ log_id: string;
+ }[];
+ end_user: ApiEndUser | null;
credentials: AuthCredentials;
- end_user_id: number | null;
}
export declare class ActionError> extends Error {
type: string;
diff --git a/packages/node-client/lib/index.ts b/packages/node-client/lib/index.ts
index 1d4ebb04da0..6572c4b0ba3 100644
--- a/packages/node-client/lib/index.ts
+++ b/packages/node-client/lib/index.ts
@@ -25,10 +25,10 @@ import type {
TwoStepCredentials,
GetPublicConnections,
SignatureCredentials,
- PostPublicConnectSessionsReconnect
+ PostPublicConnectSessionsReconnect,
+ GetPublicConnection
} from '@nangohq/types';
import type {
- Connection,
CreateConnectionOAuth1,
CreateConnectionOAuth2,
Integration,
@@ -308,7 +308,12 @@ export class Nango {
* @param refreshToken - Optional. When set to true, this returns the refresh token as part of the response
* @returns A promise that resolves with a connection object
*/
- public async getConnection(providerConfigKey: string, connectionId: string, forceRefresh?: boolean, refreshToken?: boolean): Promise {
+ public async getConnection(
+ providerConfigKey: string,
+ connectionId: string,
+ forceRefresh?: boolean,
+ refreshToken?: boolean
+ ): Promise {
const response = await this.getConnectionDetails(providerConfigKey, connectionId, forceRefresh, refreshToken);
return response.data;
}
@@ -960,7 +965,7 @@ export class Nango {
forceRefresh: boolean = false,
refreshToken: boolean = false,
additionalHeader: Record = {}
- ): Promise> {
+ ): Promise> {
const url = `${this.serverUrl}/connection/${connectionId}`;
const headers = {
diff --git a/packages/node-client/lib/types.ts b/packages/node-client/lib/types.ts
index 9a9dc681abc..54a2f94a30b 100644
--- a/packages/node-client/lib/types.ts
+++ b/packages/node-client/lib/types.ts
@@ -38,6 +38,7 @@ import type {
GetPublicListIntegrationsLegacy,
GetPublicIntegration,
GetPublicConnections,
+ GetPublicConnection,
PostConnectSessions,
PostPublicConnectSessionsReconnect
} from '@nangohq/types';
@@ -83,6 +84,7 @@ export type {
GetPublicListIntegrationsLegacy,
GetPublicIntegration,
GetPublicConnections,
+ GetPublicConnection,
PostConnectSessions,
PostPublicConnectSessionsReconnect
};
@@ -156,21 +158,6 @@ export interface MetadataChangeResponse {
connection_id: string | string[];
}
-export interface Connection {
- id?: number;
- end_user_id: number | null;
- created_at: Date;
- updated_at: Date;
- provider_config_key: string;
- connection_id: string;
- connection_config: Record;
- environment_id: number;
- metadata?: Metadata | null;
- credentials_iv?: string | null;
- credentials_tag?: string | null;
- credentials: AllAuthCredentials;
-}
-
export interface IntegrationWithCreds extends Integration {
client_id: string;
client_secret: string;
diff --git a/packages/server/lib/controllers/connection.controller.ts b/packages/server/lib/controllers/connection.controller.ts
index 86c26f23f89..092f818d140 100644
--- a/packages/server/lib/controllers/connection.controller.ts
+++ b/packages/server/lib/controllers/connection.controller.ts
@@ -4,15 +4,9 @@ import db from '@nangohq/database';
import type { TbaCredentials, ApiKeyCredentials, BasicApiCredentials, ConnectionConfig, OAuth1Credentials, OAuth2ClientCredentials } from '@nangohq/types';
import { configService, connectionService, errorManager, NangoError, accountService, SlackService, getProvider } from '@nangohq/shared';
import { NANGO_ADMIN_UUID } from './account.controller.js';
-import { metrics } from '@nangohq/utils';
import { logContextGetter } from '@nangohq/logs';
import type { RequestLocals } from '../utils/express.js';
-import {
- connectionCreated as connectionCreatedHook,
- connectionCreationStartCapCheck as connectionCreationStartCapCheckHook,
- connectionRefreshSuccess as connectionRefreshSuccessHook,
- connectionRefreshFailed as connectionRefreshFailedHook
-} from '../hooks/hooks.js';
+import { connectionCreated as connectionCreatedHook, connectionCreationStartCapCheck as connectionCreationStartCapCheckHook } from '../hooks/hooks.js';
import { getOrchestrator } from '../utils/utils.js';
import { preConnectionDeletion } from '../hooks/connection/on/connection-deleted.js';
@@ -21,83 +15,6 @@ export type { ConnectionList };
const orchestrator = getOrchestrator();
class ConnectionController {
- /**
- * CLI/SDK/API
- */
-
- async getConnectionCreds(req: Request, res: Response>, next: NextFunction) {
- try {
- const { environment, account } = res.locals;
- const connectionId = req.params['connectionId'] as string;
- const providerConfigKey = req.query['provider_config_key'] as string;
- const returnRefreshToken = req.query['refresh_token'] === 'true';
- const instantRefresh = req.query['force_refresh'] === 'true';
- const isSync = (req.get('Nango-Is-Sync') as string) === 'true';
-
- if (!providerConfigKey) {
- res.status(400).send({ error: 'Missing providerConfigKey' });
- return;
- }
-
- if (!isSync) {
- metrics.increment(metrics.Types.GET_CONNECTION, 1, { accountId: account.id });
- }
-
- const integration = await configService.getProviderConfig(providerConfigKey, environment.id);
- if (!integration) {
- res.status(404).send({
- error: {
- code: 'unknown_provider_config',
- message:
- 'Provider config not found for the given provider config key. Please make sure the provider config exists in the Nango dashboard.'
- }
- });
- return;
- }
-
- const connectionRes = await connectionService.getConnection(connectionId, providerConfigKey, environment.id);
- if (connectionRes.error || !connectionRes.response) {
- errorManager.errResFromNangoErr(res, connectionRes.error);
- return;
- }
-
- const credentialResponse = await connectionService.refreshOrTestCredentials({
- account,
- environment,
- connection: connectionRes.response,
- integration,
- logContextGetter,
- instantRefresh,
- onRefreshSuccess: connectionRefreshSuccessHook,
- onRefreshFailed: connectionRefreshFailedHook
- });
-
- if (credentialResponse.isErr()) {
- errorManager.errResFromNangoErr(res, credentialResponse.error);
- return;
- }
-
- const { value: connection } = credentialResponse;
-
- if (connection && connection.credentials && connection.credentials.type === 'OAUTH2' && !returnRefreshToken) {
- if (connection.credentials.refresh_token) {
- delete connection.credentials.refresh_token;
- }
-
- if (connection.credentials.raw && connection.credentials.raw['refresh_token']) {
- const rawCreds = { ...connection.credentials.raw }; // Properties from 'raw' are not mutable so we need to create a new object.
- // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
- delete rawCreds['refresh_token'];
- connection.credentials.raw = rawCreds;
- }
- }
-
- res.status(200).send(connection);
- } catch (err) {
- next(err);
- }
- }
-
async deleteAdminConnection(req: Request, res: Response>, next: NextFunction) {
try {
const { environment, account: team } = res.locals;
diff --git a/packages/server/lib/controllers/connection/connectionId/getConnection.integration.test.ts b/packages/server/lib/controllers/connection/connectionId/getConnection.integration.test.ts
new file mode 100644
index 00000000000..e2613575263
--- /dev/null
+++ b/packages/server/lib/controllers/connection/connectionId/getConnection.integration.test.ts
@@ -0,0 +1,155 @@
+import { afterAll, beforeAll, describe, it, expect } from 'vitest';
+import { runServer, shouldBeProtected, isSuccess, isError } from '../../../utils/tests.js';
+import { seeders } from '@nangohq/shared';
+
+let api: Awaited>;
+
+const endpoint = '/connection/:connectionId';
+
+describe(`GET ${endpoint}`, () => {
+ beforeAll(async () => {
+ api = await runServer();
+ });
+ afterAll(() => {
+ api.server.close();
+ });
+
+ it('should be protected', async () => {
+ const res = await api.fetch(endpoint, {
+ method: 'GET',
+ params: { connectionId: 'test' },
+ query: { provider_config_key: 'github' }
+ });
+
+ shouldBeProtected(res);
+ });
+
+ it('should 404 on unknown provider', async () => {
+ const { env } = await seeders.seedAccountEnvAndUser();
+
+ const res = await api.fetch(endpoint, {
+ method: 'GET',
+ token: env.secret_key,
+ params: { connectionId: 'test' },
+ query: { provider_config_key: 'github' }
+ });
+
+ isError(res.json);
+ expect(res.json).toStrictEqual({
+ error: { code: 'unknown_provider_config', message: 'Provider does not exists' }
+ });
+ });
+
+ it('should 404 on unknown connectionId', async () => {
+ const { env } = await seeders.seedAccountEnvAndUser();
+ await seeders.createConfigSeed(env, 'github', 'github');
+
+ const res = await api.fetch(endpoint, {
+ method: 'GET',
+ token: env.secret_key,
+ params: { connectionId: 'test' },
+ query: { provider_config_key: 'github' }
+ });
+
+ isError(res.json);
+ expect(res.json).toStrictEqual({
+ error: { code: 'not_found', message: 'Failed to find connection' }
+ });
+ });
+
+ it('should get a connection', async () => {
+ const { env, account } = await seeders.seedAccountEnvAndUser();
+ await seeders.createConfigSeed(env, 'algolia', 'algolia');
+ const endUser = await seeders.createEndUser({ environment: env, account });
+ const conn = await seeders.createConnectionSeed(env, 'algolia', endUser, {
+ rawCredentials: { type: 'API_KEY', apiKey: 'test_api_key' },
+ connectionConfig: { APP_ID: 'TEST' }
+ });
+
+ const res = await api.fetch(endpoint, {
+ method: 'GET',
+ token: env.secret_key,
+ params: { connectionId: conn.connection_id },
+ query: { provider_config_key: 'algolia' }
+ });
+
+ isSuccess(res.json);
+ expect(res.json).toStrictEqual({
+ connection_id: conn.connection_id,
+ created_at: expect.toBeIsoDateTimezone(),
+ credentials: {
+ apiKey: 'test_api_key',
+ type: 'API_KEY'
+ },
+ connection_config: { APP_ID: 'TEST' },
+ end_user: {
+ displayName: null,
+ email: endUser.email,
+ id: endUser.endUserId,
+ organization: {
+ displayName: null,
+ id: endUser.organization!.organizationId
+ }
+ },
+ errors: [],
+ id: expect.any(Number),
+ last_fetched_at: expect.toBeIsoDateTimezone(),
+ metadata: null,
+ provider: 'algolia',
+ provider_config_key: 'algolia',
+ updated_at: expect.toBeIsoDateTimezone()
+ });
+ });
+
+ it('should get a connection despite another connection with same name on a different provider', async () => {
+ const { env, account } = await seeders.seedAccountEnvAndUser();
+
+ await seeders.createConfigSeed(env, 'algolia', 'algolia');
+ const endUser = await seeders.createEndUser({ environment: env, account });
+ const conn = await seeders.createConnectionSeed(env, 'algolia', endUser, {
+ rawCredentials: { type: 'API_KEY', apiKey: 'test_api_key' },
+ connectionConfig: { APP_ID: 'TEST' }
+ });
+
+ await seeders.createConfigSeed(env, 'google', 'google');
+ await seeders.createConnectionSeed(env, 'google', endUser, {
+ connectionId: conn.connection_id,
+ rawCredentials: { type: 'API_KEY', apiKey: 'test_api_key' },
+ connectionConfig: { APP_ID: 'TEST' }
+ });
+
+ const res = await api.fetch(endpoint, {
+ method: 'GET',
+ token: env.secret_key,
+ params: { connectionId: conn.connection_id },
+ query: { provider_config_key: 'algolia' }
+ });
+
+ isSuccess(res.json);
+ expect(res.json).toStrictEqual({
+ connection_id: conn.connection_id,
+ created_at: expect.toBeIsoDateTimezone(),
+ credentials: {
+ apiKey: 'test_api_key',
+ type: 'API_KEY'
+ },
+ connection_config: { APP_ID: 'TEST' },
+ end_user: {
+ displayName: null,
+ email: endUser.email,
+ id: endUser.endUserId,
+ organization: {
+ displayName: null,
+ id: endUser.organization!.organizationId
+ }
+ },
+ errors: [],
+ id: expect.any(Number),
+ last_fetched_at: expect.toBeIsoDateTimezone(),
+ metadata: null,
+ provider: 'algolia',
+ provider_config_key: 'algolia',
+ updated_at: expect.toBeIsoDateTimezone()
+ });
+ });
+});
diff --git a/packages/server/lib/controllers/connection/connectionId/getConnection.ts b/packages/server/lib/controllers/connection/connectionId/getConnection.ts
new file mode 100644
index 00000000000..c9a5d63af75
--- /dev/null
+++ b/packages/server/lib/controllers/connection/connectionId/getConnection.ts
@@ -0,0 +1,111 @@
+import { z } from 'zod';
+import { asyncWrapper } from '../../../utils/asyncWrapper.js';
+import { metrics, zodErrorToHTTP } from '@nangohq/utils';
+import type { GetPublicConnection } from '@nangohq/types';
+import { connectionService, configService } from '@nangohq/shared';
+import { connectionRefreshFailed as connectionRefreshFailedHook, connectionRefreshSuccess as connectionRefreshSuccessHook } from '../../../hooks/hooks.js';
+import { logContextGetter } from '@nangohq/logs';
+import { connectionIdSchema, providerConfigKeySchema, stringBool } from '../../../helpers/validation.js';
+import { connectionFullToPublicApi } from '../../../formatters/connection.js';
+
+const queryStringValidation = z
+ .object({
+ provider_config_key: providerConfigKeySchema,
+ refresh_token: stringBool.optional(),
+ force_refresh: stringBool.optional()
+ })
+ .strict();
+
+const paramValidation = z
+ .object({
+ connectionId: connectionIdSchema
+ })
+ .strict();
+
+export const getPublicConnection = asyncWrapper(async (req, res) => {
+ const queryParamValues = queryStringValidation.safeParse(req.query);
+ if (!queryParamValues.success) {
+ res.status(400).send({ error: { code: 'invalid_query_params', errors: zodErrorToHTTP(queryParamValues.error) } });
+ return;
+ }
+
+ const paramValue = paramValidation.safeParse(req.params);
+ if (!paramValue.success) {
+ res.status(400).send({ error: { code: 'invalid_uri_params', errors: zodErrorToHTTP(paramValue.error) } });
+ return;
+ }
+
+ const { environment, account } = res.locals;
+
+ const queryParams: GetPublicConnection['Querystring'] = queryParamValues.data;
+ const params: GetPublicConnection['Params'] = paramValue.data;
+
+ const { provider_config_key: providerConfigKey, force_refresh: instantRefresh, refresh_token: returnRefreshToken } = queryParams;
+ const { connectionId } = params;
+
+ const isSync = req.headers['Nango-Is-Sync'] === 'true';
+
+ if (!isSync) {
+ metrics.increment(metrics.Types.GET_CONNECTION, 1, { accountId: account.id });
+ }
+
+ const integration = await configService.getProviderConfig(providerConfigKey, environment.id);
+ if (!integration) {
+ res.status(400).send({ error: { code: 'unknown_provider_config', message: 'Provider does not exists' } });
+ return;
+ }
+
+ const connectionRes = await connectionService.getConnection(connectionId, providerConfigKey, environment.id);
+ if (connectionRes.error || !connectionRes.response) {
+ res.status(404).send({ error: { code: 'not_found', message: 'Failed to find connection' } });
+ return;
+ }
+
+ const credentialResponse = await connectionService.refreshOrTestCredentials({
+ account,
+ environment,
+ connection: connectionRes.response,
+ integration,
+ logContextGetter,
+ instantRefresh: instantRefresh ?? false,
+ onRefreshSuccess: connectionRefreshSuccessHook,
+ onRefreshFailed: connectionRefreshFailedHook
+ });
+ if (credentialResponse.isErr()) {
+ res.status(500).send({ error: { code: 'server_error', message: 'Failed to refresh or test credentials' } });
+ return;
+ }
+
+ const { value: connection } = credentialResponse;
+
+ if (connection && connection.credentials && connection.credentials.type === 'OAUTH2' && !returnRefreshToken) {
+ if (connection.credentials.refresh_token) {
+ delete connection.credentials.refresh_token;
+ }
+
+ if (connection.credentials.raw && connection.credentials.raw['refresh_token']) {
+ const rawCreds = { ...connection.credentials.raw }; // Properties from 'raw' are not mutable so we need to create a new object.
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
+
+ const { refresh_token, ...rest } = rawCreds;
+ connection.credentials.raw = rest;
+ }
+ }
+
+ // We get connection one last time to get endUser, errors
+ // This is very unoptimized unfortunately
+ const finalConnections = await connectionService.listConnections({ environmentId: environment.id, connectionId, integrationIds: [providerConfigKey] });
+ if (finalConnections.length !== 1 || !finalConnections[0]) {
+ res.status(500).send({ error: { code: 'server_error', message: 'Failed to get connection' } });
+ return;
+ }
+
+ res.status(200).send(
+ connectionFullToPublicApi({
+ data: { ...finalConnections[0].connection, credentials: connection.credentials },
+ activeLog: finalConnections[0].active_logs,
+ endUser: finalConnections[0].end_user,
+ provider: finalConnections[0].provider
+ })
+ );
+});
diff --git a/packages/server/lib/controllers/connection/getConnections.ts b/packages/server/lib/controllers/connection/getConnections.ts
index 0b4892b5804..03e41cccad0 100644
--- a/packages/server/lib/controllers/connection/getConnections.ts
+++ b/packages/server/lib/controllers/connection/getConnections.ts
@@ -2,7 +2,7 @@ import { asyncWrapper } from '../../utils/asyncWrapper.js';
import { zodErrorToHTTP } from '@nangohq/utils';
import type { GetPublicConnections } from '@nangohq/types';
import { AnalyticsTypes, analytics, connectionService } from '@nangohq/shared';
-import { connectionToPublicApi } from '../../formatters/connection.js';
+import { connectionSimpleToPublicApi } from '../../formatters/connection.js';
import { z } from 'zod';
import { bodySchema } from '../connect/postSessions.js';
@@ -40,7 +40,7 @@ export const getPublicConnections = asyncWrapper(async (re
res.status(200).send({
connections: connections.map((data) => {
// TODO: return end_user
- return connectionToPublicApi({
+ return connectionSimpleToPublicApi({
data: data.connection,
activeLog: data.active_logs,
provider: data.provider,
diff --git a/packages/server/lib/controllers/v1/connections/connectionId/getConnection.ts b/packages/server/lib/controllers/v1/connections/connectionId/getConnection.ts
index 4e7bb7faec2..055fb05bd36 100644
--- a/packages/server/lib/controllers/v1/connections/connectionId/getConnection.ts
+++ b/packages/server/lib/controllers/v1/connections/connectionId/getConnection.ts
@@ -47,8 +47,8 @@ export const getConnection = asyncWrapper(async (req, res) => {
const { environment, account } = res.locals;
- const queryParams = queryParamValues.data;
- const params = paramValue.data;
+ const queryParams: GetConnection['Querystring'] = queryParamValues.data;
+ const params: GetConnection['Params'] = paramValue.data;
const { provider_config_key: providerConfigKey } = queryParams;
const { connectionId } = params;
diff --git a/packages/server/lib/controllers/v1/connections/getConnections.ts b/packages/server/lib/controllers/v1/connections/getConnections.ts
index 9bff957c22b..746bba1acbc 100644
--- a/packages/server/lib/controllers/v1/connections/getConnections.ts
+++ b/packages/server/lib/controllers/v1/connections/getConnections.ts
@@ -2,7 +2,7 @@ import { z } from 'zod';
import { asyncWrapper } from '../../../utils/asyncWrapper.js';
import { zodErrorToHTTP } from '@nangohq/utils';
import type { GetConnections } from '@nangohq/types';
-import { envSchema, providerConfigKeySchema } from '../../../helpers/validation.js';
+import { envSchema, providerConfigKeySchema, stringBool } from '../../../helpers/validation.js';
import { connectionService } from '@nangohq/shared';
import { connectionSimpleToApi } from '../../../formatters/connection.js';
@@ -15,12 +15,7 @@ const queryStringValidation = z
.pipe(z.array(providerConfigKeySchema))
.optional(),
search: z.string().max(255).optional(),
- withError: z
- .enum(['true', 'false', ''])
- .optional()
- .default('false')
- .transform((value) => value === 'true')
- .optional(),
+ withError: stringBool.optional(),
env: envSchema,
page: z.coerce.number().min(0).max(50).optional()
})
diff --git a/packages/server/lib/formatters/connection.ts b/packages/server/lib/formatters/connection.ts
index e420953c0bc..d2590c77148 100644
--- a/packages/server/lib/formatters/connection.ts
+++ b/packages/server/lib/formatters/connection.ts
@@ -1,4 +1,12 @@
-import type { ApiConnectionFull, ApiConnectionSimple, ApiPublicConnection, DBConnection, DBEndUser } from '@nangohq/types';
+import type {
+ AllAuthCredentials,
+ ApiConnectionFull,
+ ApiConnectionSimple,
+ ApiPublicConnection,
+ ApiPublicConnectionFull,
+ DBConnection,
+ DBEndUser
+} from '@nangohq/types';
export function connectionSimpleToApi({
data,
@@ -39,7 +47,7 @@ export function connectionFullToApi(connection: DBConnection): ApiConnectionFull
};
}
-export function connectionToPublicApi({
+export function connectionSimpleToPublicApi({
data,
provider,
activeLog,
@@ -68,3 +76,37 @@ export function connectionToPublicApi({
created: String(data.created_at)
};
}
+
+export function connectionFullToPublicApi({
+ data,
+ provider,
+ activeLog,
+ endUser
+}: {
+ data: DBConnection;
+ provider: string;
+ activeLog: [{ type: string; log_id: string }];
+ endUser: DBEndUser | null;
+}): ApiPublicConnectionFull {
+ return {
+ id: data.id,
+ connection_id: data.connection_id,
+ provider_config_key: data.provider_config_key,
+ provider,
+ errors: activeLog,
+ end_user: endUser
+ ? {
+ id: endUser.end_user_id,
+ displayName: endUser.display_name || null,
+ email: endUser.email,
+ organization: endUser.organization_id ? { id: endUser.organization_id, displayName: endUser.organization_display_name || null } : null
+ }
+ : null,
+ metadata: data.metadata || null,
+ connection_config: data.connection_config || {},
+ created_at: String(data.created_at),
+ updated_at: String(data.updated_at),
+ last_fetched_at: String(data.last_fetched_at),
+ credentials: data.credentials as AllAuthCredentials
+ };
+}
diff --git a/packages/server/lib/helpers/validation.ts b/packages/server/lib/helpers/validation.ts
index 91e2426fbcd..c17d17c5b62 100644
--- a/packages/server/lib/helpers/validation.ts
+++ b/packages/server/lib/helpers/validation.ts
@@ -31,3 +31,9 @@ export const connectionCredential = z.union([
z.object({ public_key: z.string().uuid(), hmac: z.string().optional() }),
z.object({ connect_session_token: connectSessionTokenSchema })
]);
+
+export const stringBool = z
+ .enum(['true', 'false', ''])
+ .optional()
+ .default('false')
+ .transform((value) => value === 'true');
diff --git a/packages/server/lib/routes.ts b/packages/server/lib/routes.ts
index 8bc4e922b1a..333b11c20dc 100644
--- a/packages/server/lib/routes.ts
+++ b/packages/server/lib/routes.ts
@@ -108,6 +108,7 @@ import { postPublicApiKeyAuthorization } from './controllers/auth/postApiKey.js'
import { postPublicBasicAuthorization } from './controllers/auth/postBasic.js';
import { postPublicAppStoreAuthorization } from './controllers/auth/postAppStore.js';
import { postRollout } from './controllers/fleet/postRollout.js';
+import { getPublicConnection } from './controllers/connection/connectionId/getConnection.js';
import { postWebhook } from './controllers/webhook/environmentUuid/postWebhook.js';
export const router = express.Router();
@@ -218,7 +219,7 @@ publicAPI.route('/config/:providerConfigKey').delete(apiAuth, deletePublicIntegr
publicAPI.route('/integrations').get(connectSessionOrApiAuth, getPublicListIntegrations);
publicAPI.route('/integrations/:uniqueKey').get(apiAuth, getPublicIntegration);
-publicAPI.route('/connection/:connectionId').get(apiAuth, connectionController.getConnectionCreds.bind(connectionController));
+publicAPI.route('/connection/:connectionId').get(apiAuth, getPublicConnection);
publicAPI.route('/connection').get(apiAuth, getPublicConnections);
publicAPI.route('/connection/:connectionId').delete(apiAuth, deletePublicConnection);
publicAPI.route('/connection/:connectionId/metadata').post(apiAuth, connectionController.setMetadataLegacy.bind(connectionController));
diff --git a/packages/shared/lib/clients/orchestrator.ts b/packages/shared/lib/clients/orchestrator.ts
index 88726470dd6..ad15935bc2c 100644
--- a/packages/shared/lib/clients/orchestrator.ts
+++ b/packages/shared/lib/clients/orchestrator.ts
@@ -167,7 +167,7 @@ export class Orchestrator {
action: actionName,
connection: connection.connection_id,
integration: connection.provider_config_key,
- truncated_response: JSON.stringify(res.value, null, 2)?.slice(0, 100)
+ truncated_response: JSON.stringify(res.value)?.slice(0, 100)
});
await telemetry.log(
@@ -175,7 +175,7 @@ export class Orchestrator {
content,
LogActionEnum.ACTION,
{
- input: JSON.stringify(input, null, 2),
+ input: JSON.stringify(input),
environmentId: String(connection.environment_id),
connectionId: connection.connection_id,
providerConfigKey: connection.provider_config_key,
@@ -220,7 +220,7 @@ export class Orchestrator {
LogActionEnum.ACTION,
{
error: stringifyError(err),
- input: JSON.stringify(input, null, 2),
+ input: JSON.stringify(input),
environmentId: String(connection.environment_id),
connectionId: connection.connection_id,
providerConfigKey: connection.provider_config_key,
diff --git a/packages/shared/lib/models/Proxy.ts b/packages/shared/lib/models/Proxy.ts
index d09bb3a33ae..4e5e88544ac 100644
--- a/packages/shared/lib/models/Proxy.ts
+++ b/packages/shared/lib/models/Proxy.ts
@@ -61,14 +61,14 @@ export interface ApplicationConstructedProxyConfiguration extends BaseProxyConfi
| TwoStepCredentials
| SignatureCredentials;
provider: Provider;
- connection: Connection;
+ connection: Pick;
}
export type ResponseType = 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' | 'stream';
export interface InternalProxyConfiguration {
providerName: string;
- connection: Connection;
+ connection: Pick;
existingActivityLogId?: string | null | undefined;
}
diff --git a/packages/shared/lib/sdk/sync.ts b/packages/shared/lib/sdk/sync.ts
index 79fca94b913..4079f400b86 100644
--- a/packages/shared/lib/sdk/sync.ts
+++ b/packages/shared/lib/sdk/sync.ts
@@ -21,7 +21,7 @@ import type { SyncConfig } from '../models/Sync.js';
import type { ValidateDataError } from './dataValidation.js';
import { validateData } from './dataValidation.js';
import { NangoError } from '../utils/error.js';
-import type { DBTeam, GetPublicIntegration, MessageRowInsert, RunnerFlags } from '@nangohq/types';
+import type { ApiEndUser, DBTeam, GetPublicIntegration, MessageRowInsert, RunnerFlags } from '@nangohq/types';
import { getProvider } from '../services/providers.js';
import { redactHeaders, redactURL } from '../utils/http.js';
@@ -312,18 +312,18 @@ interface MetadataChangeResponse {
}
interface Connection {
- id?: number;
- created_at: Date;
- updated_at: Date;
+ id: number;
provider_config_key: string;
connection_id: string;
connection_config: Record;
- environment_id: number;
- metadata?: Metadata | null;
- credentials_iv?: string | null;
- credentials_tag?: string | null;
+ created_at: string;
+ updated_at: string;
+ last_fetched_at: string;
+ metadata: Record | null;
+ provider: string;
+ errors: { type: string; log_id: string }[];
+ end_user: ApiEndUser | null;
credentials: AuthCredentials;
- end_user_id: number | null;
}
export class ActionError> extends Error {
diff --git a/packages/shared/lib/seeders/connection.seeder.ts b/packages/shared/lib/seeders/connection.seeder.ts
index 5e8cc45e16e..c91d812d80c 100644
--- a/packages/shared/lib/seeders/connection.seeder.ts
+++ b/packages/shared/lib/seeders/connection.seeder.ts
@@ -24,14 +24,23 @@ export const createConnectionSeeds = async (env: DBEnvironment): Promise => {
- const name = Math.random().toString(36).substring(7);
+export const createConnectionSeed = async (
+ env: DBEnvironment,
+ provider: string,
+ endUser?: EndUser,
+ rest?: {
+ connectionId?: string;
+ rawCredentials?: AuthCredentials;
+ connectionConfig?: any;
+ }
+): Promise => {
+ const name = rest?.connectionId ? rest.connectionId : Math.random().toString(36).substring(7);
const result = await connectionService.upsertConnection({
connectionId: name,
providerConfigKey: provider,
provider: provider,
- parsedRawCredentials: {} as AuthCredentials,
- connectionConfig: {},
+ parsedRawCredentials: rest?.rawCredentials || ({} as AuthCredentials),
+ connectionConfig: rest?.connectionConfig || {},
environmentId: env.id,
accountId: 0
});
diff --git a/packages/shared/lib/services/connection.service.ts b/packages/shared/lib/services/connection.service.ts
index 8284f7c3425..9dce601fc0b 100644
--- a/packages/shared/lib/services/connection.service.ts
+++ b/packages/shared/lib/services/connection.service.ts
@@ -1,7 +1,7 @@
import jwt from 'jsonwebtoken';
import { XMLBuilder, XMLParser } from 'fast-xml-parser';
import type { Knex } from '@nangohq/database';
-import db, { schema, dbNamespace } from '@nangohq/database';
+import db, { dbNamespace } from '@nangohq/database';
import analytics, { AnalyticsTypes } from '../utils/analytics.js';
import type { Config as ProviderConfig, AuthCredentials, OAuth1Credentials, Config } from '../models/index.js';
import { LogActionEnum } from '../models/Telemetry.js';
@@ -384,49 +384,15 @@ class ConnectionService {
}
public async getConnection(connectionId: string, providerConfigKey: string, environment_id: number): Promise> {
- if (!environment_id) {
- const error = new NangoError('missing_environment');
-
- return { success: false, error, response: null };
- }
-
- if (!connectionId) {
- const error = new NangoError('missing_connection');
-
- await telemetry.log(LogTypes.GET_CONNECTION_FAILURE, error.message, LogActionEnum.AUTH, {
- environmentId: String(environment_id),
- connectionId,
- providerConfigKey,
- level: 'error'
- });
-
- return { success: false, error, response: null };
- }
-
- if (!providerConfigKey) {
- const error = new NangoError('missing_provider_config');
-
- await telemetry.log(LogTypes.GET_CONNECTION_FAILURE, error.message, LogActionEnum.AUTH, {
- environmentId: String(environment_id),
- connectionId,
- providerConfigKey,
- level: 'error'
- });
-
- return { success: false, error, response: null };
- }
-
- const result: StoredConnection[] | null = (await schema()
- .select('*')
- .from(`_nango_connections`)
- .where({ connection_id: connectionId, provider_config_key: providerConfigKey, environment_id, deleted: false })) as unknown as StoredConnection[];
-
- const storedConnection = result == null || result.length == 0 ? null : result[0] || null;
-
- if (!storedConnection) {
- const environmentName = await environmentService.getEnvironmentName(environment_id);
+ const rawConnection = await db.knex
+ .from(`_nango_connections`)
+ .select('*')
+ .where({ connection_id: connectionId, provider_config_key: providerConfigKey, environment_id, deleted: false })
+ .limit(1)
+ .first();
- const error = new NangoError('unknown_connection', { connectionId, providerConfigKey, environmentName });
+ if (!rawConnection) {
+ const error = new NangoError('unknown_connection', { connectionId, providerConfigKey });
await telemetry.log(LogTypes.GET_CONNECTION_FAILURE, error.message, LogActionEnum.AUTH, {
environmentId: String(environment_id),
@@ -438,67 +404,14 @@ class ConnectionService {
return { success: false, error, response: null };
}
- const connection = encryptionManager.decryptConnection(storedConnection);
+ const connection = encryptionManager.decryptConnection(rawConnection)!;
// Parse the token expiration date.
- if (connection != null) {
- const credentials = connection.credentials as
- | OAuth1Credentials
- | OAuth2Credentials
- | AppCredentials
- | OAuth2ClientCredentials
- | TableauCredentials
- | JwtCredentials
- | TwoStepCredentials
- | BillCredentials
- | SignatureCredentials;
- if (credentials.type && credentials.type === 'OAUTH2') {
- const creds = credentials;
- creds.expires_at = creds.expires_at != null ? parseTokenExpirationDate(creds.expires_at) : undefined;
- connection.credentials = creds;
- }
-
- if (credentials.type && credentials.type === 'APP') {
- const creds = credentials;
- creds.expires_at = creds.expires_at != null ? parseTokenExpirationDate(creds.expires_at) : undefined;
- connection.credentials = creds;
- }
-
- if (credentials.type && credentials.type === 'OAUTH2_CC') {
- const creds = credentials;
- creds.expires_at = creds.expires_at != null ? parseTokenExpirationDate(creds.expires_at) : undefined;
- connection.credentials = creds;
- }
-
- if (credentials.type && credentials.type === 'TABLEAU') {
- const creds = credentials;
- creds.expires_at = creds.expires_at != null ? parseTokenExpirationDate(creds.expires_at) : undefined;
- connection.credentials = creds;
- }
-
- if (credentials.type && credentials.type === 'JWT') {
- const creds = credentials;
- creds.expires_at = creds.expires_at != null ? parseTokenExpirationDate(creds.expires_at) : undefined;
- connection.credentials = creds;
- }
-
- if (credentials.type && credentials.type === 'BILL') {
- const creds = credentials;
- creds.expires_at = creds.expires_at != null ? parseTokenExpirationDate(creds.expires_at) : undefined;
- connection.credentials = creds;
- }
-
- if (credentials.type && credentials.type === 'SIGNATURE') {
- const creds = credentials;
- creds.expires_at = creds.expires_at != null ? parseTokenExpirationDate(creds.expires_at) : undefined;
- connection.credentials = creds;
- }
-
- if (credentials.type && credentials.type === 'TWO_STEP') {
- const creds = credentials;
- creds.expires_at = creds.expires_at != null ? parseTokenExpirationDate(creds.expires_at) : undefined;
- connection.credentials = creds;
- }
+ const credentials = connection.credentials;
+ if (credentials.type && 'expires_at' in credentials) {
+ const creds = credentials;
+ creds.expires_at = creds.expires_at != null ? parseTokenExpirationDate(creds.expires_at) : undefined;
+ connection.credentials = creds;
}
return { success: true, error: null, response: connection };
diff --git a/packages/shared/lib/services/proxy.service.unit.test.ts b/packages/shared/lib/services/proxy.service.unit.test.ts
index 893a7ae39e6..875d8390b3b 100644
--- a/packages/shared/lib/services/proxy.service.unit.test.ts
+++ b/packages/shared/lib/services/proxy.service.unit.test.ts
@@ -474,9 +474,7 @@ describe('Proxy service Construct URL Tests', () => {
}
},
token: { apiKey: 'sweet-secret-token' },
- connection: {
- environment_id: 1
- }
+ connection: {}
});
const url = proxyService.constructUrl(config);
@@ -659,14 +657,9 @@ describe('Proxy service configure', () => {
const internalConfig: InternalProxyConfiguration = {
providerName: 'provider-1',
connection: {
- environment_id: 1,
- end_user_id: null,
connection_id: 'connection-1',
- provider_config_key: 'provider-config-key-1',
credentials: {} as OAuth2Credentials,
- connection_config: {},
- created_at: new Date(),
- updated_at: new Date()
+ connection_config: {}
},
existingActivityLogId: '1'
};
@@ -694,14 +687,9 @@ describe('Proxy service configure', () => {
const internalConfig: InternalProxyConfiguration = {
providerName: 'provider-1',
connection: {
- environment_id: 1,
- end_user_id: null,
connection_id: 'connection-1',
- provider_config_key: 'provider-config-key-1',
credentials: {} as OAuth2Credentials,
- connection_config: {},
- created_at: new Date(),
- updated_at: new Date()
+ connection_config: {}
},
existingActivityLogId: '1'
};
@@ -730,14 +718,9 @@ describe('Proxy service configure', () => {
const internalConfig: InternalProxyConfiguration = {
providerName: 'provider-1',
connection: {
- environment_id: 1,
- end_user_id: null,
connection_id: 'connection-1',
- provider_config_key: 'provider-config-key-1',
credentials: {} as OAuth2Credentials,
- connection_config: {},
- created_at: new Date(),
- updated_at: new Date()
+ connection_config: {}
},
existingActivityLogId: '1'
};
@@ -766,14 +749,9 @@ describe('Proxy service configure', () => {
const internalConfig: InternalProxyConfiguration = {
providerName: 'unknown',
connection: {
- environment_id: 1,
- end_user_id: null,
connection_id: 'connection-1',
- provider_config_key: 'provider-config-key-1',
credentials: {} as OAuth2Credentials,
- connection_config: {},
- created_at: new Date(),
- updated_at: new Date()
+ connection_config: {}
},
existingActivityLogId: '1'
};
@@ -808,14 +786,9 @@ describe('Proxy service configure', () => {
const internalConfig: InternalProxyConfiguration = {
providerName: 'github',
connection: {
- environment_id: 1,
- end_user_id: null,
connection_id: 'connection-1',
- provider_config_key: 'provider-config-key-1',
credentials: {} as OAuth2Credentials,
- connection_config: {},
- created_at: new Date(),
- updated_at: new Date()
+ connection_config: {}
},
existingActivityLogId: '1'
};
@@ -844,9 +817,7 @@ describe('Proxy service configure', () => {
baseUrlOverride: 'https://api.github.com.override',
decompress: false,
connection: {
- environment_id: 1,
connection_id: 'connection-1',
- provider_config_key: 'provider-config-key-1',
credentials: {},
connection_config: {}
},
diff --git a/packages/shared/lib/utils/utils.ts b/packages/shared/lib/utils/utils.ts
index 16c2faec2ea..37394facb44 100644
--- a/packages/shared/lib/utils/utils.ts
+++ b/packages/shared/lib/utils/utils.ts
@@ -224,7 +224,7 @@ export function extractValueByPath(obj: Record, path: string): any
return get(obj, path);
}
-export function connectionCopyWithParsedConnectionConfig(connection: Connection) {
+export function connectionCopyWithParsedConnectionConfig(connection: Pick) {
const connectionCopy = Object.assign({}, connection);
const rawConfig: Record = connectionCopy.connection_config;
diff --git a/packages/types/lib/api.endpoints.ts b/packages/types/lib/api.endpoints.ts
index f6b22551fde..b1940d96eeb 100644
--- a/packages/types/lib/api.endpoints.ts
+++ b/packages/types/lib/api.endpoints.ts
@@ -52,6 +52,7 @@ import type {
GetConnection,
GetConnections,
GetConnectionsCount,
+ GetPublicConnection,
GetPublicConnections,
PostConnectionRefresh
} from './connection/api/get';
@@ -80,6 +81,7 @@ export type PublicApiEndpoints =
| PostConnectSessions
| PostPublicConnectSessionsReconnect
| GetPublicConnections
+ | GetPublicConnection
| GetConnectSession
| DeleteConnectSession
| PostDeployInternal
diff --git a/packages/types/lib/connection/api/get.ts b/packages/types/lib/connection/api/get.ts
index 931a7214a10..77e0c10fde0 100644
--- a/packages/types/lib/connection/api/get.ts
+++ b/packages/types/lib/connection/api/get.ts
@@ -3,6 +3,7 @@ import type { DBConnection } from '../db.js';
import type { ActiveLog } from '../../notification/active-logs/db.js';
import type { Merge } from 'type-fest';
import type { ApiEndUser } from '../../endUser/index.js';
+import type { AllAuthCredentials } from '../../auth/api.js';
export type ApiConnectionSimple = Pick, 'id' | 'connection_id' | 'provider_config_key' | 'created_at' | 'updated_at'> & {
provider: string;
@@ -77,6 +78,32 @@ export type GetConnection = Endpoint<{
};
};
}>;
+
+export type ApiPublicConnectionFull = Pick & {
+ created_at: string;
+ updated_at: string;
+ last_fetched_at: string;
+ metadata: Record | null;
+ provider: string;
+ errors: { type: string; log_id: string }[];
+ end_user: ApiEndUser | null;
+ credentials: AllAuthCredentials;
+};
+export type GetPublicConnection = Endpoint<{
+ Method: 'GET';
+ Params: {
+ connectionId: string;
+ };
+ Querystring: {
+ provider_config_key: string;
+ refresh_token?: boolean | undefined;
+ force_refresh?: boolean | undefined;
+ };
+ Path: '/connection/:connectionId';
+ Error: ApiError<'unknown_provider_config'>;
+ Success: ApiPublicConnectionFull;
+}>;
+
export type PostConnectionRefresh = Endpoint<{
Method: 'POST';
Params: {