From 1a833aad52813251e36e2fc4159b4b138b9b4ef8 Mon Sep 17 00:00:00 2001 From: Faruk AYDIN Date: Tue, 27 Aug 2024 14:48:12 +0300 Subject: [PATCH] feat: Implement rest API endpoint to verify connection --- .../api/v1/connections/verify-connection.js | 14 ++++ .../v1/connections/verify-connection.test.js | 82 +++++++++++++++++++ .../backend/src/graphql/mutation-resolvers.js | 2 +- packages/backend/src/helpers/authorization.js | 4 + packages/backend/src/models/connection.js | 11 +++ .../backend/src/routes/api/v1/connections.js | 8 ++ 6 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 packages/backend/src/controllers/api/v1/connections/verify-connection.js create mode 100644 packages/backend/src/controllers/api/v1/connections/verify-connection.test.js diff --git a/packages/backend/src/controllers/api/v1/connections/verify-connection.js b/packages/backend/src/controllers/api/v1/connections/verify-connection.js new file mode 100644 index 00000000..2004f199 --- /dev/null +++ b/packages/backend/src/controllers/api/v1/connections/verify-connection.js @@ -0,0 +1,14 @@ +import { renderObject } from '../../../../helpers/renderer.js'; + +export default async (request, response) => { + let connection = await request.currentUser + .$relatedQuery('connections') + .findOne({ + id: request.params.connectionId, + }) + .throwIfNotFound(); + + connection = await connection.verifyAndUpdateConnection(); + + renderObject(response, connection); +}; diff --git a/packages/backend/src/controllers/api/v1/connections/verify-connection.test.js b/packages/backend/src/controllers/api/v1/connections/verify-connection.test.js new file mode 100644 index 00000000..6187d2db --- /dev/null +++ b/packages/backend/src/controllers/api/v1/connections/verify-connection.test.js @@ -0,0 +1,82 @@ +import { vi, describe, it, expect, beforeEach } from 'vitest'; +import request from 'supertest'; +import Crypto from 'crypto'; +import app from '../../../../app.js'; +import App from '../../../../models/app.js'; +import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js'; +import { createUser } from '../../../../../test/factories/user.js'; +import { createConnection } from '../../../../../test/factories/connection.js'; +import { createPermission } from '../../../../../test/factories/permission.js'; + +describe('POST /api/v1/connections/:connectionId/verify', () => { + let currentUser, currentUserRole, token; + + beforeEach(async () => { + currentUser = await createUser(); + currentUserRole = await currentUser.$relatedQuery('role'); + + token = await createAuthTokenByUserId(currentUser.id); + }); + + it('should update the connection as verified for current user', async () => { + const currentUserConnection = await createConnection({ + userId: currentUser.id, + key: 'deepl', + verified: true, + }); + + await createPermission({ + action: 'create', + subject: 'Connection', + roleId: currentUserRole.id, + conditions: ['isCreator'], + }); + + vi.spyOn(App, 'findOneByKey').mockImplementation((key) => { + if (key !== currentUserConnection.key) return; + + return { + auth: { + verifyCredentials: vi.fn().mockResolvedValue(), + }, + }; + }); + + const response = await request(app) + .post(`/api/v1/connections/${currentUserConnection.id}/verify`) + .set('Authorization', token) + .expect(200); + + expect(response.body.data.verified).toEqual(true); + }); + + it('should return not found response for not existing connection UUID', async () => { + const notExistingConnectionUUID = Crypto.randomUUID(); + + await createPermission({ + action: 'create', + subject: 'Connection', + roleId: currentUserRole.id, + conditions: ['isCreator'], + }); + + await request(app) + .post(`/api/v1/connections/${notExistingConnectionUUID}/verify`) + .set('Authorization', token) + .expect(404); + }); + + it('should return bad request response for invalid UUID', async () => { + await createPermission({ + action: 'create', + subject: 'Connection', + roleId: currentUserRole.id, + conditions: ['isCreator'], + }); + + await request(app) + .post('/api/v1/connections/invalidConnectionUUID/verify') + .set('Authorization', token) + .expect(400); + }); +}); diff --git a/packages/backend/src/graphql/mutation-resolvers.js b/packages/backend/src/graphql/mutation-resolvers.js index 8ff998a7..21152759 100644 --- a/packages/backend/src/graphql/mutation-resolvers.js +++ b/packages/backend/src/graphql/mutation-resolvers.js @@ -26,10 +26,10 @@ import updateStep from './mutations/update-step.js'; import updateUser from './mutations/update-user.ee.js'; import upsertSamlAuthProvider from './mutations/upsert-saml-auth-provider.ee.js'; import upsertSamlAuthProvidersRoleMappings from './mutations/upsert-saml-auth-providers-role-mappings.ee.js'; -import verifyConnection from './mutations/verify-connection.js'; // Converted mutations import deleteStep from './mutations/delete-step.js'; +import verifyConnection from './mutations/verify-connection.js'; const mutationResolvers = { createAppAuthClient, diff --git a/packages/backend/src/helpers/authorization.js b/packages/backend/src/helpers/authorization.js index 41561224..eeee88ef 100644 --- a/packages/backend/src/helpers/authorization.js +++ b/packages/backend/src/helpers/authorization.js @@ -43,6 +43,10 @@ const authorizationList = { action: 'update', subject: 'Connection', }, + 'POST /api/v1/connections/:connectionId/verify': { + action: 'create', + subject: 'Connection', + }, 'GET /api/v1/apps/:appKey/flows': { action: 'read', subject: 'Flow', diff --git a/packages/backend/src/models/connection.js b/packages/backend/src/models/connection.js index d4e3f9f8..a9cfefb2 100644 --- a/packages/backend/src/models/connection.js +++ b/packages/backend/src/models/connection.js @@ -171,6 +171,17 @@ class Connection extends Base { }); } + async verifyAndUpdateConnection() { + const app = await this.getApp(); + const $ = await globalVariable({ connection: this, app }); + await app.auth.verifyCredentials($); + + return await this.$query().patchAndFetch({ + verified: true, + draft: false, + }); + } + async verifyWebhook(request) { if (!this.key) return true; diff --git a/packages/backend/src/routes/api/v1/connections.js b/packages/backend/src/routes/api/v1/connections.js index 29f41a4f..eb9565b4 100644 --- a/packages/backend/src/routes/api/v1/connections.js +++ b/packages/backend/src/routes/api/v1/connections.js @@ -4,6 +4,7 @@ import { authenticateUser } from '../../../helpers/authentication.js'; import { authorizeUser } from '../../../helpers/authorization.js'; import getFlowsAction from '../../../controllers/api/v1/connections/get-flows.js'; import testConnectionAction from '../../../controllers/api/v1/connections/test-connection.js'; +import verifyConnectionAction from '../../../controllers/api/v1/connections/verify-connection.js'; const router = Router(); @@ -21,4 +22,11 @@ router.post( asyncHandler(testConnectionAction) ); +router.post( + '/:connectionId/verify', + authenticateUser, + authorizeUser, + asyncHandler(verifyConnectionAction) +); + export default router;