diff --git a/packages/backend/src/controllers/api/v1/admin/apps/create-auth-client.ee.js b/packages/backend/src/controllers/api/v1/admin/apps/create-auth-client.ee.js new file mode 100644 index 00000000..87c80f1b --- /dev/null +++ b/packages/backend/src/controllers/api/v1/admin/apps/create-auth-client.ee.js @@ -0,0 +1,14 @@ +import { renderObject } from '../../../../../helpers/renderer.js'; +import AppConfig from '../../../../../models/app-config.js'; + +export default async (request, response) => { + const appConfig = await AppConfig.query() + .findOne({ key: request.params.appKey }) + .throwIfNotFound(); + + const appAuthClient = await appConfig + .$relatedQuery('appAuthClients') + .insert(request.body); + + renderObject(response, appAuthClient, { status: 201 }); +}; diff --git a/packages/backend/src/controllers/api/v1/admin/apps/create-auth-client.ee.test.js b/packages/backend/src/controllers/api/v1/admin/apps/create-auth-client.ee.test.js new file mode 100644 index 00000000..c7368081 --- /dev/null +++ b/packages/backend/src/controllers/api/v1/admin/apps/create-auth-client.ee.test.js @@ -0,0 +1,86 @@ +import { vi, describe, it, expect, beforeEach } from 'vitest'; +import request from 'supertest'; + +import app from '../../../../../app.js'; +import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js'; +import { createUser } from '../../../../../../test/factories/user.js'; +import { createRole } from '../../../../../../test/factories/role.js'; +import createAppAuthClientMock from '../../../../../../test/mocks/rest/api/v1/admin/apps/create-auth-client.js'; +import { createAppConfig } from '../../../../../../test/factories/app-config.js'; +import * as license from '../../../../../helpers/license.ee.js'; + +describe('POST /api/v1/admin/apps/:appKey/auth-clients', () => { + let currentUser, adminRole, token; + + beforeEach(async () => { + vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true); + + adminRole = await createRole({ key: 'admin' }); + currentUser = await createUser({ roleId: adminRole.id }); + + token = await createAuthTokenByUserId(currentUser.id); + }); + + it('should return not found response for not existing app config', async () => { + const appAuthClient = { + active: true, + appKey: 'gitlab', + name: 'First auth client', + formattedAuthDefaults: { + clientid: 'sample client ID', + clientSecret: 'sample client secret', + instanceUrl: 'https://gitlab.com', + oAuthRedirectUrl: 'http://localhost:3001/app/gitlab/connection/add', + } + }; + + await request(app) + .post('/api/v1/admin/apps/gitlab/auth-clients') + .set('Authorization', token) + .send(appAuthClient) + .expect(404); + }); + + it('should return created response for valid app config', async () => { + await createAppConfig({ + key: 'gitlab' + }); + + const appAuthClient = { + active: true, + appKey: 'gitlab', + name: 'First auth client', + formattedAuthDefaults: { + clientid: 'sample client ID', + clientSecret: 'sample client secret', + instanceUrl: 'https://gitlab.com', + oAuthRedirectUrl: 'http://localhost:3001/app/gitlab/connection/add', + } + }; + + const response = await request(app) + .post('/api/v1/admin/apps/gitlab/auth-clients') + .set('Authorization', token) + .send(appAuthClient) + .expect(201); + + const expectedPayload = createAppAuthClientMock(appAuthClient); + expect(response.body).toMatchObject(expectedPayload); + }); + + it('should return bad request response for missing required fields', async () => { + await createAppConfig({ + key: 'gitlab' + }); + + const appAuthClient = { + appKey: 'gitlab', + }; + + await request(app) + .post('/api/v1/admin/apps/gitlab/auth-clients') + .set('Authorization', token) + .send(appAuthClient) + .expect(400); + }); +}); diff --git a/packages/backend/src/graphql/mutation-resolvers.js b/packages/backend/src/graphql/mutation-resolvers.js index 21152759..c5d61f9f 100644 --- a/packages/backend/src/graphql/mutation-resolvers.js +++ b/packages/backend/src/graphql/mutation-resolvers.js @@ -1,4 +1,3 @@ -import createAppAuthClient from './mutations/create-app-auth-client.ee.js'; import createAppConfig from './mutations/create-app-config.ee.js'; import createConnection from './mutations/create-connection.js'; import createFlow from './mutations/create-flow.js'; @@ -32,7 +31,6 @@ import deleteStep from './mutations/delete-step.js'; import verifyConnection from './mutations/verify-connection.js'; const mutationResolvers = { - createAppAuthClient, createAppConfig, createConnection, createFlow, diff --git a/packages/backend/src/graphql/mutations/create-app-auth-client.ee.js b/packages/backend/src/graphql/mutations/create-app-auth-client.ee.js deleted file mode 100644 index 6af40984..00000000 --- a/packages/backend/src/graphql/mutations/create-app-auth-client.ee.js +++ /dev/null @@ -1,17 +0,0 @@ -import AppConfig from '../../models/app-config.js'; - -const createAppAuthClient = async (_parent, params, context) => { - context.currentUser.can('update', 'App'); - - const appConfig = await AppConfig.query() - .findById(params.input.appConfigId) - .throwIfNotFound(); - - const appAuthClient = await appConfig - .$relatedQuery('appAuthClients') - .insert(params.input); - - return appAuthClient; -}; - -export default createAppAuthClient; diff --git a/packages/backend/src/graphql/schema.graphql b/packages/backend/src/graphql/schema.graphql index a398cb41..6d0496d7 100644 --- a/packages/backend/src/graphql/schema.graphql +++ b/packages/backend/src/graphql/schema.graphql @@ -3,7 +3,6 @@ type Query { } type Mutation { createAppConfig(input: CreateAppConfigInput): AppConfig - createAppAuthClient(input: CreateAppAuthClientInput): AppAuthClient createConnection(input: CreateConnectionInput): Connection createFlow(input: CreateFlowInput): Flow createRole(input: CreateRoleInput): Role @@ -571,13 +570,6 @@ type AppAuthClient { active: Boolean } -input CreateAppAuthClientInput { - appConfigId: String - name: String - formattedAuthDefaults: JSONObject - active: Boolean -} - input UpdateAppAuthClientInput { id: String name: String diff --git a/packages/backend/src/helpers/renderer.js b/packages/backend/src/helpers/renderer.js index c4329a00..8bcf6373 100644 --- a/packages/backend/src/helpers/renderer.js +++ b/packages/backend/src/helpers/renderer.js @@ -41,7 +41,9 @@ const renderObject = (response, object, options) => { }, }; - return response.json(computedPayload); + const status = options?.status || 200; + + return response.status(status).json(computedPayload); }; const renderError = (response, errors, status, type) => { diff --git a/packages/backend/src/routes/api/v1/admin/apps.ee.js b/packages/backend/src/routes/api/v1/admin/apps.ee.js index ef3d97c5..2a87f8ac 100644 --- a/packages/backend/src/routes/api/v1/admin/apps.ee.js +++ b/packages/backend/src/routes/api/v1/admin/apps.ee.js @@ -5,6 +5,7 @@ import { authorizeAdmin } from '../../../../helpers/authorization.js'; import { checkIsEnterprise } from '../../../../helpers/check-is-enterprise.js'; import getAuthClientsAction from '../../../../controllers/api/v1/admin/apps/get-auth-clients.ee.js'; import getAuthClientAction from '../../../../controllers/api/v1/admin/apps/get-auth-client.ee.js'; +import createAuthClientAction from '../../../../controllers/api/v1/admin/apps/create-auth-client.ee.js'; const router = Router(); @@ -16,6 +17,14 @@ router.get( asyncHandler(getAuthClientsAction) ); +router.post( + '/:appKey/auth-clients', + authenticateUser, + authorizeAdmin, + checkIsEnterprise, + asyncHandler(createAuthClientAction) +); + router.get( '/:appKey/auth-clients/:appAuthClientId', authenticateUser, diff --git a/packages/backend/test/mocks/rest/api/v1/admin/apps/create-auth-client.js b/packages/backend/test/mocks/rest/api/v1/admin/apps/create-auth-client.js new file mode 100644 index 00000000..f91c8500 --- /dev/null +++ b/packages/backend/test/mocks/rest/api/v1/admin/apps/create-auth-client.js @@ -0,0 +1,17 @@ +const createAppAuthClientMock = (appAuthClient) => { + return { + data: { + name: appAuthClient.name, + active: appAuthClient.active, + }, + meta: { + count: 1, + currentPage: null, + isArray: false, + totalPages: null, + type: 'AppAuthClient', + }, + }; +}; + +export default createAppAuthClientMock; diff --git a/packages/web/src/components/AdminApplicationCreateAuthClient/index.jsx b/packages/web/src/components/AdminApplicationCreateAuthClient/index.jsx index 4271605a..9b9844eb 100644 --- a/packages/web/src/components/AdminApplicationCreateAuthClient/index.jsx +++ b/packages/web/src/components/AdminApplicationCreateAuthClient/index.jsx @@ -4,9 +4,9 @@ import { useMutation } from '@apollo/client'; import { AppPropType } from 'propTypes/propTypes'; import { CREATE_APP_CONFIG } from 'graphql/mutations/create-app-config'; -import { CREATE_APP_AUTH_CLIENT } from 'graphql/mutations/create-app-auth-client'; import useAppConfig from 'hooks/useAppConfig.ee'; import useFormatMessage from 'hooks/useFormatMessage'; +import useAdminCreateAppAuthClient from 'hooks/useAdminCreateAppAuthClient.ee'; import AdminApplicationAuthClientDialog from 'components/AdminApplicationAuthClientDialog'; import useAppAuth from 'hooks/useAppAuth'; @@ -26,13 +26,11 @@ function AdminApplicationCreateAuthClient(props) { context: { autoSnackbar: false }, }); - const [ - createAppAuthClient, - { loading: loadingCreateAppAuthClient, error: createAppAuthClientError }, - ] = useMutation(CREATE_APP_AUTH_CLIENT, { - refetchQueries: ['GetAppAuthClients'], - context: { autoSnackbar: false }, - }); + const { + mutateAsync: createAppAuthClient, + isPending: isCreateAppAuithClientPending, + error: createAppAuthClientError, + } = useAdminCreateAppAuthClient(appKey); const submitHandler = async (values) => { let appConfigId = appConfig?.data?.id; @@ -55,14 +53,10 @@ function AdminApplicationCreateAuthClient(props) { const { name, active, ...formattedAuthDefaults } = values; await createAppAuthClient({ - variables: { - input: { - appConfigId, - name, - active, - formattedAuthDefaults, - }, - }, + appKey, + name, + active, + formattedAuthDefaults, }); onClose(); @@ -103,7 +97,7 @@ function AdminApplicationCreateAuthClient(props) { loading={isAppConfigLoading} submitHandler={submitHandler} authFields={auth?.data?.fields} - submitting={loadingCreateAppConfig || loadingCreateAppAuthClient} + submitting={loadingCreateAppConfig || isCreateAppAuithClientPending} defaultValues={defaultValues} /> ); diff --git a/packages/web/src/components/AdminApplicationUpdateAuthClient/index.jsx b/packages/web/src/components/AdminApplicationUpdateAuthClient/index.jsx index 7e15dec3..f30a1261 100644 --- a/packages/web/src/components/AdminApplicationUpdateAuthClient/index.jsx +++ b/packages/web/src/components/AdminApplicationUpdateAuthClient/index.jsx @@ -16,7 +16,7 @@ function AdminApplicationUpdateAuthClient(props) { const { clientId } = useParams(); const { data: adminAppAuthClient, isLoading: isAdminAuthClientLoading } = - useAdminAppAuthClient(clientId); + useAdminAppAuthClient(application.key, clientId); const { data: auth } = useAppAuth(application.key); diff --git a/packages/web/src/graphql/mutations/create-app-auth-client.js b/packages/web/src/graphql/mutations/create-app-auth-client.js deleted file mode 100644 index edcbc1fd..00000000 --- a/packages/web/src/graphql/mutations/create-app-auth-client.js +++ /dev/null @@ -1,11 +0,0 @@ -import { gql } from '@apollo/client'; -export const CREATE_APP_AUTH_CLIENT = gql` - mutation CreateAppAuthClient($input: CreateAppAuthClientInput) { - createAppAuthClient(input: $input) { - id - appConfigId - name - active - } - } -`; diff --git a/packages/web/src/hooks/useAdminAppAuthClient.ee.js b/packages/web/src/hooks/useAdminAppAuthClient.ee.js index 3ad618b9..694ba03b 100644 --- a/packages/web/src/hooks/useAdminAppAuthClient.ee.js +++ b/packages/web/src/hooks/useAdminAppAuthClient.ee.js @@ -2,17 +2,17 @@ import { useQuery } from '@tanstack/react-query'; import api from 'helpers/api'; -export default function useAdminAppAuthClient(id) { +export default function useAdminAppAuthClient(appKey, id) { const query = useQuery({ - queryKey: ['admin', 'appAuthClients', id], + queryKey: ['admin', 'apps', appKey, 'authClients', id], queryFn: async ({ signal }) => { - const { data } = await api.get(`/v1/admin/app-auth-clients/${id}`, { + const { data } = await api.get(`/v1/admin/apps/${appKey}/auth-clients/${id}`, { signal, }); return data; }, - enabled: !!id, + enabled: !!appKey && !!id, }); return query; diff --git a/packages/web/src/hooks/useAdminAppAuthClients.js b/packages/web/src/hooks/useAdminAppAuthClients.js index 22de022f..a4bc4bc8 100644 --- a/packages/web/src/hooks/useAdminAppAuthClients.js +++ b/packages/web/src/hooks/useAdminAppAuthClients.js @@ -1,9 +1,9 @@ import { useQuery } from '@tanstack/react-query'; import api from 'helpers/api'; -export default function useAdminAppAuthClient(appKey) { +export default function useAdminAppAuthClients(appKey) { const query = useQuery({ - queryKey: ['admin', 'apps', appKey, 'auth-clients'], + queryKey: ['admin', 'apps', appKey, 'authClients'], queryFn: async ({ signal }) => { const { data } = await api.get(`/v1/admin/apps/${appKey}/auth-clients`, { signal, diff --git a/packages/web/src/hooks/useAdminCreateAppAuthClient.ee.js b/packages/web/src/hooks/useAdminCreateAppAuthClient.ee.js new file mode 100644 index 00000000..37a39045 --- /dev/null +++ b/packages/web/src/hooks/useAdminCreateAppAuthClient.ee.js @@ -0,0 +1,21 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import api from 'helpers/api'; + +export default function useAdminCreateAppAuthClient(appKey) { + const queryClient = useQueryClient(); + + const query = useMutation({ + mutationFn: async (payload) => { + const { data } = await api.post(`/v1/admin/apps/${appKey}/auth-clients`, payload); + + return data; + }, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: ['admin', 'apps', appKey, 'authClients'], + }); + } + }); + + return query; +}