11import * as z from 'zod' ;
22
33import db from '@nangohq/database' ;
4- import { logContextGetter } from '@nangohq/logs' ;
4+ import { defaultOperationExpiration , logContextGetter } from '@nangohq/logs' ;
55import {
66 EndUserMapper ,
77 buildTagsFromEndUser ,
@@ -27,7 +27,8 @@ import {
2727 connectionTagsSchema ,
2828 endUserSchema
2929} from '../../helpers/validation.js' ;
30- import { connectionCreated , connectionCreationStartCapCheck , connectionRefreshSuccess } from '../../hooks/hooks.js' ;
30+ import { validateConnection } from '../../hooks/connection/on/validate-connection.js' ;
31+ import { connectionCreated , connectionCreationStartCapCheck , connectionRefreshSuccess , testConnectionCredentials } from '../../hooks/hooks.js' ;
3132import { asyncWrapper } from '../../utils/asyncWrapper.js' ;
3233
3334import type { AuthOperationType , ConnectionConfig , ConnectionUpsertResponse , EndUser , PostPublicConnection , ProviderGithubApp } from '@nangohq/types' ;
@@ -141,6 +142,20 @@ export const postPublicConnection = asyncWrapper<PostPublicConnection>(async (re
141142 return ;
142143 }
143144
145+ const logCtx = await logContextGetter . create (
146+ {
147+ operation : { type : 'auth' , action : 'create_connection' } ,
148+ meta : { authType : 'connection_api' } ,
149+ expiresAt : defaultOperationExpiration . auth ( )
150+ } ,
151+ { account, environment }
152+ ) ;
153+ await logCtx . enrichOperation ( {
154+ integrationId : integration . id ! ,
155+ integrationName : integration . unique_key ,
156+ providerName
157+ } ) ;
158+
144159 let updatedConnection : ConnectionUpsertResponse | undefined ;
145160
146161 const connCreatedHook = ( res : ConnectionUpsertResponse ) => {
@@ -187,13 +202,29 @@ export const postPublicConnection = asyncWrapper<PostPublicConnection>(async (re
187202 }
188203 case 'API_KEY' :
189204 case 'BASIC' : {
205+ // the testconnection only works with API_KEY, BASIC and TBA from this list
206+ const connectionConfig = body . connection_config || { } ;
207+ const connectionResponse = await testConnectionCredentials ( {
208+ config : integration ,
209+ connectionConfig,
210+ connectionId,
211+ credentials : body . credentials ,
212+ provider,
213+ logCtx
214+ } ) ;
215+ if ( connectionResponse . isErr ( ) ) {
216+ await logCtx . failed ( ) ;
217+ res . status ( 400 ) . send ( { error : { code : 'connection_test_failed' , message : connectionResponse . error . message } } ) ;
218+ return ;
219+ }
220+
190221 const [ imported ] = await connectionService . importApiAuthConnection ( {
191222 connectionId,
192223 providerConfigKey : body . provider_config_key ,
193224 metadata : body . metadata || { } ,
194225 environment,
195226 credentials : body . credentials ,
196- connectionConfig : body . connection_config || { } ,
227+ connectionConfig,
197228 connectionCreatedHook : connCreatedHook ,
198229 tags : mergedTags
199230 } ) ;
@@ -215,6 +246,7 @@ export const postPublicConnection = asyncWrapper<PostPublicConnection>(async (re
215246 connectionConfig
216247 } ) ;
217248 if ( credentialsRes . isErr ( ) ) {
249+ await logCtx . failed ( ) ;
218250 res . status ( 500 ) . send ( { error : { code : 'server_error' , message : credentialsRes . error . message } } ) ;
219251 return ;
220252 }
@@ -248,7 +280,8 @@ export const postPublicConnection = asyncWrapper<PostPublicConnection>(async (re
248280 connectionConfig
249281 } ) ;
250282 if ( credentialsRes . isErr ( ) ) {
251- res . status ( 500 ) . send ( { error : { code : 'server_error' , message : credentialsRes . error . message } } ) ;
283+ await logCtx . failed ( ) ;
284+ res . status ( 400 ) . send ( { error : { code : 'server_error' , message : credentialsRes . error . message } } ) ;
252285 return ;
253286 }
254287
@@ -270,13 +303,29 @@ export const postPublicConnection = asyncWrapper<PostPublicConnection>(async (re
270303 }
271304 case 'TBA' : {
272305 if ( ! body . connection_config || ! body . connection_config [ 'accountId' ] ) {
306+ await logCtx . failed ( ) ;
273307 res . status ( 400 ) . send ( {
274308 error : { code : 'invalid_body' , message : 'Missing accountId in connection_config. This is required to create a TBA connection.' }
275309 } ) ;
276310
277311 return ;
278312 }
279313
314+ const connectionConfig = body . connection_config || { } ;
315+ const connectionResponse = await testConnectionCredentials ( {
316+ config : integration ,
317+ connectionConfig,
318+ connectionId,
319+ credentials : body . credentials ,
320+ provider,
321+ logCtx
322+ } ) ;
323+ if ( connectionResponse . isErr ( ) ) {
324+ await logCtx . failed ( ) ;
325+ res . status ( 400 ) . send ( { error : { code : 'connection_test_failed' , message : connectionResponse . error . message } } ) ;
326+ return ;
327+ }
328+
280329 const [ imported ] = await connectionService . upsertAuthConnection ( {
281330 connectionId,
282331 providerConfigKey : body . provider_config_key ,
@@ -316,6 +365,7 @@ export const postPublicConnection = asyncWrapper<PostPublicConnection>(async (re
316365 }
317366 default :
318367 // Missing Bill, Signature, JWT, TwoStep, AppStore
368+ await logCtx . failed ( ) ;
319369 res . status ( 400 ) . send ( { error : { code : 'invalid_body' , message : `Unsupported auth type ${ provider . auth_mode } ` } } ) ;
320370 return ;
321371 }
@@ -326,10 +376,37 @@ export const postPublicConnection = asyncWrapper<PostPublicConnection>(async (re
326376 }
327377
328378 if ( ! updatedConnection ) {
379+ await logCtx . failed ( ) ;
329380 res . status ( 500 ) . send ( { error : { code : 'server_error' , message : `Failed to create connection` } } ) ;
330381 return ;
331382 }
332383
384+ const customValidationResponse = await validateConnection ( {
385+ connection : updatedConnection . connection ,
386+ config : integration ,
387+ account,
388+ logCtx
389+ } ) ;
390+
391+ if ( customValidationResponse . isErr ( ) ) {
392+ if ( updatedConnection . operation === 'creation' ) {
393+ // since this is a new invalid connection, delete it with no trace of it
394+ await connectionService . hardDelete ( updatedConnection . connection . id ) ;
395+ }
396+
397+ const payload = customValidationResponse . error ?. payload ;
398+ const message = typeof payload [ 'message' ] === 'string' ? payload [ 'message' ] : 'Connection failed validation' ;
399+
400+ await logCtx . failed ( ) ;
401+ res . status ( 400 ) . send ( {
402+ error : {
403+ code : 'connection_validation_failed' ,
404+ message
405+ }
406+ } ) ;
407+ return ;
408+ }
409+
333410 let endUser : EndUser | undefined ;
334411 if ( body . end_user ) {
335412 await db . knex . transaction ( async ( trx ) => {
@@ -340,6 +417,7 @@ export const postPublicConnection = asyncWrapper<PostPublicConnection>(async (re
340417 endUser : EndUserMapper . apiToEndUser ( body . end_user ! )
341418 } ) ;
342419 if ( endUserRes . isErr ( ) ) {
420+ await logCtx . failed ( ) ;
343421 res . status ( 500 ) . send ( { error : { code : 'server_error' , message : 'Failed to update end user' } } ) ;
344422 return ;
345423 }
@@ -356,6 +434,13 @@ export const postPublicConnection = asyncWrapper<PostPublicConnection>(async (re
356434
357435 const connection = encryptionManager . decryptConnection ( updatedConnection . connection ) ;
358436
437+ await logCtx . enrichOperation ( {
438+ connectionId : updatedConnection . connection . id ,
439+ connectionName : updatedConnection . connection . connection_id
440+ } ) ;
441+ void logCtx . info ( 'Connection creation was successful' ) ;
442+ await logCtx . success ( ) ;
443+
359444 res . status ( 201 ) . send (
360445 connectionFullToPublicApi ( { data : connection , provider : providerName , activeLog : [ ] , endUser : endUser ? EndUserMapper . to ( endUser ) : null } )
361446 ) ;
0 commit comments