diff --git a/packages/backend/src/controllers/api/v1/admin/apps/create-config.ee.js b/packages/backend/src/controllers/api/v1/admin/apps/create-config.ee.js index 7d5021aa..edf0ff9a 100644 --- a/packages/backend/src/controllers/api/v1/admin/apps/create-config.ee.js +++ b/packages/backend/src/controllers/api/v1/admin/apps/create-config.ee.js @@ -10,11 +10,11 @@ export default async (request, response) => { }; const appConfigParams = (request) => { - const { allowCustomConnection, shared, disabled } = request.body; + const { customConnectionAllowed, shared, disabled } = request.body; return { key: request.params.appKey, - allowCustomConnection, + customConnectionAllowed, shared, disabled, }; diff --git a/packages/backend/src/controllers/api/v1/admin/apps/create-config.ee.test.js b/packages/backend/src/controllers/api/v1/admin/apps/create-config.ee.test.js index ad9b7600..f92df586 100644 --- a/packages/backend/src/controllers/api/v1/admin/apps/create-config.ee.test.js +++ b/packages/backend/src/controllers/api/v1/admin/apps/create-config.ee.test.js @@ -23,7 +23,7 @@ describe('POST /api/v1/admin/apps/:appKey/config', () => { it('should return created app config', async () => { const appConfig = { - allowCustomConnection: true, + customConnectionAllowed: true, shared: true, disabled: false, }; @@ -44,7 +44,7 @@ describe('POST /api/v1/admin/apps/:appKey/config', () => { it('should return HTTP 422 for already existing app config', async () => { const appConfig = { key: 'gitlab', - allowCustomConnection: true, + customConnectionAllowed: true, shared: true, disabled: false, }; diff --git a/packages/backend/src/controllers/api/v1/admin/apps/update-config.ee.js b/packages/backend/src/controllers/api/v1/admin/apps/update-config.ee.js index 0467d573..8475a264 100644 --- a/packages/backend/src/controllers/api/v1/admin/apps/update-config.ee.js +++ b/packages/backend/src/controllers/api/v1/admin/apps/update-config.ee.js @@ -8,16 +8,19 @@ export default async (request, response) => { }) .throwIfNotFound(); - await appConfig.$query().patchAndFetch(appConfigParams(request)); + await appConfig.$query().patchAndFetch({ + ...appConfigParams(request), + key: request.params.appKey, + }); renderObject(response, appConfig); }; const appConfigParams = (request) => { - const { allowCustomConnection, shared, disabled } = request.body; + const { customConnectionAllowed, shared, disabled } = request.body; return { - allowCustomConnection, + customConnectionAllowed, shared, disabled, }; diff --git a/packages/backend/src/controllers/api/v1/admin/apps/update-config.ee.test.js b/packages/backend/src/controllers/api/v1/admin/apps/update-config.ee.test.js index 20d7f8ae..c5436ad5 100644 --- a/packages/backend/src/controllers/api/v1/admin/apps/update-config.ee.test.js +++ b/packages/backend/src/controllers/api/v1/admin/apps/update-config.ee.test.js @@ -24,7 +24,7 @@ describe('PATCH /api/v1/admin/apps/:appKey/config', () => { it('should return updated app config', async () => { const appConfig = { key: 'gitlab', - allowCustomConnection: true, + customConnectionAllowed: true, shared: true, disabled: false, }; @@ -34,7 +34,7 @@ describe('PATCH /api/v1/admin/apps/:appKey/config', () => { const newAppConfigValues = { shared: false, disabled: true, - allowCustomConnection: false, + customConnectionAllowed: false, }; const response = await request(app) @@ -55,7 +55,7 @@ describe('PATCH /api/v1/admin/apps/:appKey/config', () => { const appConfig = { shared: false, disabled: true, - allowCustomConnection: false, + customConnectionAllowed: false, }; await request(app) @@ -68,7 +68,7 @@ describe('PATCH /api/v1/admin/apps/:appKey/config', () => { it('should return HTTP 422 for invalid app config data', async () => { const appConfig = { key: 'gitlab', - allowCustomConnection: true, + customConnectionAllowed: true, shared: true, disabled: false, }; diff --git a/packages/backend/src/controllers/api/v1/apps/create-connection.test.js b/packages/backend/src/controllers/api/v1/apps/create-connection.test.js index c6e3634f..4a12aa99 100644 --- a/packages/backend/src/controllers/api/v1/apps/create-connection.test.js +++ b/packages/backend/src/controllers/api/v1/apps/create-connection.test.js @@ -155,7 +155,7 @@ describe('POST /api/v1/apps/:appKey/connections', () => { await createAppConfig({ key: 'gitlab', disabled: false, - allowCustomConnection: true, + customConnectionAllowed: true, }); }); @@ -218,7 +218,7 @@ describe('POST /api/v1/apps/:appKey/connections', () => { await createAppConfig({ key: 'gitlab', disabled: false, - allowCustomConnection: false, + customConnectionAllowed: false, }); }); diff --git a/packages/backend/src/controllers/api/v1/apps/get-config.ee.test.js b/packages/backend/src/controllers/api/v1/apps/get-config.ee.test.js index d10c2bd7..a39b315b 100644 --- a/packages/backend/src/controllers/api/v1/apps/get-config.ee.test.js +++ b/packages/backend/src/controllers/api/v1/apps/get-config.ee.test.js @@ -17,7 +17,7 @@ describe('GET /api/v1/apps/:appKey/config', () => { appConfig = await createAppConfig({ key: 'deepl', - allowCustomConnection: true, + customConnectionAllowed: true, shared: true, disabled: false, }); diff --git a/packages/backend/src/db/migrations/20241002121145_add_connection_allowed_to_app_configs.js b/packages/backend/src/db/migrations/20241002121145_add_connection_allowed_to_app_configs.js new file mode 100644 index 00000000..3b12b083 --- /dev/null +++ b/packages/backend/src/db/migrations/20241002121145_add_connection_allowed_to_app_configs.js @@ -0,0 +1,37 @@ +export async function up(knex) { + await knex.schema.alterTable('app_configs', (table) => { + table.boolean('connection_allowed').defaultTo(false); + }); + + const appConfigs = await knex('app_configs').select('*'); + + for (const appConfig of appConfigs) { + const appAuthClients = await knex('app_auth_clients').where( + 'app_key', + appConfig.key + ); + + const hasSomeActiveAppAuthClients = !!appAuthClients?.some( + (appAuthClient) => appAuthClient.active + ); + const shared = appConfig.shared; + const active = appConfig.disabled === false; + + const connectionAllowedConditions = [ + hasSomeActiveAppAuthClients, + shared, + active, + ]; + const connectionAllowed = connectionAllowedConditions.every(Boolean); + + await knex('app_configs') + .where('id', appConfig.id) + .update({ connection_allowed: connectionAllowed }); + } +} + +export async function down(knex) { + await knex.schema.alterTable('app_configs', (table) => { + table.dropColumn('connection_allowed'); + }); +} diff --git a/packages/backend/src/db/migrations/20241009094438_rename_allow_custom_connection_as_custom_connection_allowed_in_app_configs.js b/packages/backend/src/db/migrations/20241009094438_rename_allow_custom_connection_as_custom_connection_allowed_in_app_configs.js new file mode 100644 index 00000000..b8fab01a --- /dev/null +++ b/packages/backend/src/db/migrations/20241009094438_rename_allow_custom_connection_as_custom_connection_allowed_in_app_configs.js @@ -0,0 +1,11 @@ +export async function up(knex) { + return knex.schema.alterTable('app_configs', (table) => { + table.renameColumn('allow_custom_connection', 'custom_connection_allowed'); + }); +} + +export async function down(knex) { + return knex.schema.alterTable('app_configs', (table) => { + table.renameColumn('custom_connection_allowed', 'allow_custom_connection'); + }); +} diff --git a/packages/backend/src/db/migrations/20241024130418_make_key_primary_for_app_configs.js b/packages/backend/src/db/migrations/20241024130418_make_key_primary_for_app_configs.js new file mode 100644 index 00000000..cae7f315 --- /dev/null +++ b/packages/backend/src/db/migrations/20241024130418_make_key_primary_for_app_configs.js @@ -0,0 +1,13 @@ +export async function up(knex) { + return knex.schema.alterTable('app_configs', function (table) { + table.dropPrimary(); + table.primary('key'); + }); +} + +export async function down(knex) { + return knex.schema.alterTable('app_configs', function (table) { + table.dropPrimary(); + table.primary('id'); + }); +} diff --git a/packages/backend/src/db/migrations/20241024131158_remove_id_column_from_app_configs.js b/packages/backend/src/db/migrations/20241024131158_remove_id_column_from_app_configs.js new file mode 100644 index 00000000..9059828f --- /dev/null +++ b/packages/backend/src/db/migrations/20241024131158_remove_id_column_from_app_configs.js @@ -0,0 +1,11 @@ +export async function up(knex) { + return knex.schema.alterTable('app_configs', function (table) { + table.dropColumn('id'); + }); +} + +export async function down(knex) { + return knex.schema.alterTable('app_configs', function (table) { + table.uuid('id').defaultTo(knex.raw('gen_random_uuid()')); + }); +} diff --git a/packages/backend/src/models/__snapshots__/app-config.test.js.snap b/packages/backend/src/models/__snapshots__/app-config.test.js.snap new file mode 100644 index 00000000..aea9fa56 --- /dev/null +++ b/packages/backend/src/models/__snapshots__/app-config.test.js.snap @@ -0,0 +1,41 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`AppConfig model > jsonSchema should have correct validations 1`] = ` +{ + "properties": { + "connectionAllowed": { + "default": false, + "type": "boolean", + }, + "createdAt": { + "type": "string", + }, + "customConnectionAllowed": { + "default": false, + "type": "boolean", + }, + "disabled": { + "default": false, + "type": "boolean", + }, + "id": { + "format": "uuid", + "type": "string", + }, + "key": { + "type": "string", + }, + "shared": { + "default": false, + "type": "boolean", + }, + "updatedAt": { + "type": "string", + }, + }, + "required": [ + "key", + ], + "type": "object", +} +`; diff --git a/packages/backend/src/models/app-auth-client.js b/packages/backend/src/models/app-auth-client.js index e4e9753e..90a9bda3 100644 --- a/packages/backend/src/models/app-auth-client.js +++ b/packages/backend/src/models/app-auth-client.js @@ -2,6 +2,7 @@ import AES from 'crypto-js/aes.js'; import enc from 'crypto-js/enc-utf8.js'; import appConfig from '../config/app.js'; import Base from './base.js'; +import AppConfig from './app-config.js'; class AppAuthClient extends Base { static tableName = 'app_auth_clients'; @@ -21,6 +22,17 @@ class AppAuthClient extends Base { }, }; + static relationMappings = () => ({ + appConfig: { + relation: Base.BelongsToOneRelation, + modelClass: AppConfig, + join: { + from: 'app_auth_clients.app_key', + to: 'app_configs.key', + }, + }, + }); + encryptData() { if (!this.eligibleForEncryption()) return; @@ -48,6 +60,17 @@ class AppAuthClient extends Base { return this.authDefaults ? true : false; } + async triggerAppConfigUpdate() { + const appConfig = await this.$relatedQuery('appConfig'); + + // This is a workaround to update connection allowed column for AppConfig + await appConfig?.$query().patch({ + key: appConfig.key, + shared: appConfig.shared, + disabled: appConfig.disabled, + }); + } + // TODO: Make another abstraction like beforeSave instead of using // beforeInsert and beforeUpdate separately for the same operation. async $beforeInsert(queryContext) { @@ -55,11 +78,23 @@ class AppAuthClient extends Base { this.encryptData(); } + async $afterInsert(queryContext) { + await super.$afterInsert(queryContext); + + await this.triggerAppConfigUpdate(); + } + async $beforeUpdate(opt, queryContext) { await super.$beforeUpdate(opt, queryContext); this.encryptData(); } + async $afterUpdate(opt, queryContext) { + await super.$afterUpdate(opt, queryContext); + + await this.triggerAppConfigUpdate(); + } + async $afterFind() { this.decryptData(); } diff --git a/packages/backend/src/models/app-auth-client.test.js b/packages/backend/src/models/app-auth-client.test.js index d95ade3d..94b5c9f8 100644 --- a/packages/backend/src/models/app-auth-client.test.js +++ b/packages/backend/src/models/app-auth-client.test.js @@ -2,9 +2,12 @@ import { describe, it, expect, vi } from 'vitest'; import AES from 'crypto-js/aes.js'; import enc from 'crypto-js/enc-utf8.js'; +import AppConfig from './app-config.js'; import AppAuthClient from './app-auth-client.js'; +import Base from './base.js'; import appConfig from '../config/app.js'; import { createAppAuthClient } from '../../test/factories/app-auth-client.js'; +import { createAppConfig } from '../../test/factories/app-config.js'; describe('AppAuthClient model', () => { it('tableName should return correct name', () => { @@ -15,6 +18,23 @@ describe('AppAuthClient model', () => { expect(AppAuthClient.jsonSchema).toMatchSnapshot(); }); + it('relationMappings should return correct associations', () => { + const relationMappings = AppAuthClient.relationMappings(); + + const expectedRelations = { + appConfig: { + relation: Base.BelongsToOneRelation, + modelClass: AppConfig, + join: { + from: 'app_auth_clients.app_key', + to: 'app_configs.key', + }, + }, + }; + + expect(relationMappings).toStrictEqual(expectedRelations); + }); + describe('encryptData', () => { it('should return undefined if eligibleForEncryption is not true', async () => { vi.spyOn( @@ -140,6 +160,63 @@ describe('AppAuthClient model', () => { }); }); + describe('triggerAppConfigUpdate', () => { + it('should trigger an update in related app config', async () => { + await createAppConfig({ key: 'gitlab' }); + + const appAuthClient = await createAppAuthClient({ + appKey: 'gitlab', + }); + + const appConfigBeforeUpdateSpy = vi.spyOn( + AppConfig.prototype, + '$beforeUpdate' + ); + + await appAuthClient.triggerAppConfigUpdate(); + + expect(appConfigBeforeUpdateSpy).toHaveBeenCalledOnce(); + }); + + it('should update related AppConfig after creating an instance', async () => { + const appConfig = await createAppConfig({ + key: 'gitlab', + disabled: false, + shared: true, + }); + + await createAppAuthClient({ + appKey: 'gitlab', + active: true, + }); + + const refetchedAppConfig = await appConfig.$query(); + + expect(refetchedAppConfig.connectionAllowed).toBe(true); + }); + + it('should update related AppConfig after updating an instance', async () => { + const appConfig = await createAppConfig({ + key: 'gitlab', + disabled: false, + shared: true, + }); + + const appAuthClient = await createAppAuthClient({ + appKey: 'gitlab', + active: false, + }); + + let refetchedAppConfig = await appConfig.$query(); + expect(refetchedAppConfig.connectionAllowed).toBe(false); + + await appAuthClient.$query().patchAndFetch({ active: true }); + + refetchedAppConfig = await appConfig.$query(); + expect(refetchedAppConfig.connectionAllowed).toBe(true); + }); + }); + it('$beforeInsert should call AppAuthClient.encryptData', async () => { const appAuthClientBeforeInsertSpy = vi.spyOn( AppAuthClient.prototype, @@ -151,6 +228,17 @@ describe('AppAuthClient model', () => { expect(appAuthClientBeforeInsertSpy).toHaveBeenCalledOnce(); }); + it('$afterInsert should call AppAuthClient.triggerAppConfigUpdate', async () => { + const appAuthClientAfterInsertSpy = vi.spyOn( + AppAuthClient.prototype, + 'triggerAppConfigUpdate' + ); + + await createAppAuthClient(); + + expect(appAuthClientAfterInsertSpy).toHaveBeenCalledOnce(); + }); + it('$beforeUpdate should call AppAuthClient.encryptData', async () => { const appAuthClient = await createAppAuthClient(); @@ -164,6 +252,19 @@ describe('AppAuthClient model', () => { expect(appAuthClientBeforeUpdateSpy).toHaveBeenCalledOnce(); }); + it('$afterUpdate should call AppAuthClient.triggerAppConfigUpdate', async () => { + const appAuthClient = await createAppAuthClient(); + + const appAuthClientAfterUpdateSpy = vi.spyOn( + AppAuthClient.prototype, + 'triggerAppConfigUpdate' + ); + + await appAuthClient.$query().patchAndFetch({ name: 'sample' }); + + expect(appAuthClientAfterUpdateSpy).toHaveBeenCalledOnce(); + }); + it('$afterFind should call AppAuthClient.decryptData', async () => { const appAuthClient = await createAppAuthClient(); diff --git a/packages/backend/src/models/app-config.js b/packages/backend/src/models/app-config.js index 885cbbb1..1a9176b9 100644 --- a/packages/backend/src/models/app-config.js +++ b/packages/backend/src/models/app-config.js @@ -5,6 +5,10 @@ import Base from './base.js'; class AppConfig extends Base { static tableName = 'app_configs'; + static get idColumn() { + return 'key'; + } + static jsonSchema = { type: 'object', required: ['key'], @@ -12,7 +16,8 @@ class AppConfig extends Base { properties: { id: { type: 'string', format: 'uuid' }, key: { type: 'string' }, - allowCustomConnection: { type: 'boolean', default: false }, + connectionAllowed: { type: 'boolean', default: false }, + customConnectionAllowed: { type: 'boolean', default: false }, shared: { type: 'boolean', default: false }, disabled: { type: 'boolean', default: false }, createdAt: { type: 'string' }, @@ -31,31 +36,44 @@ class AppConfig extends Base { }, }); - static get virtualAttributes() { - return ['canConnect', 'canCustomConnect']; - } - - get canCustomConnect() { - return !this.disabled && this.allowCustomConnection; - } - - get canConnect() { - const hasSomeActiveAppAuthClients = !!this.appAuthClients?.some( - (appAuthClient) => appAuthClient.active - ); - const shared = this.shared; - const active = this.disabled === false; - - const conditions = [hasSomeActiveAppAuthClients, shared, active]; - - return conditions.every(Boolean); - } - async getApp() { if (!this.key) return null; return await App.findOneByKey(this.key); } + + async computeAndAssignConnectionAllowedProperty() { + this.connectionAllowed = await this.computeConnectionAllowedProperty(); + } + + async computeConnectionAllowedProperty() { + const appAuthClients = await this.$relatedQuery('appAuthClients'); + + const hasSomeActiveAppAuthClients = + appAuthClients?.some((appAuthClient) => appAuthClient.active) || false; + + const conditions = [ + hasSomeActiveAppAuthClients, + this.shared, + !this.disabled, + ]; + + const connectionAllowed = conditions.every(Boolean); + + return connectionAllowed; + } + + async $beforeInsert(queryContext) { + await super.$beforeInsert(queryContext); + + await this.computeAndAssignConnectionAllowedProperty(); + } + + async $beforeUpdate(opt, queryContext) { + await super.$beforeUpdate(opt, queryContext); + + await this.computeAndAssignConnectionAllowedProperty(); + } } export default AppConfig; diff --git a/packages/backend/src/models/app-config.test.js b/packages/backend/src/models/app-config.test.js new file mode 100644 index 00000000..4945066c --- /dev/null +++ b/packages/backend/src/models/app-config.test.js @@ -0,0 +1,180 @@ +import { vi, describe, it, expect } from 'vitest'; + +import Base from './base.js'; +import AppConfig from './app-config.js'; +import App from './app.js'; +import AppAuthClient from './app-auth-client.js'; +import { createAppConfig } from '../../test/factories/app-config.js'; +import { createAppAuthClient } from '../../test/factories/app-auth-client.js'; + +describe('AppConfig model', () => { + it('tableName should return correct name', () => { + expect(AppConfig.tableName).toBe('app_configs'); + }); + + it('idColumn should return key field', () => { + expect(AppConfig.idColumn).toBe('key'); + }); + + it('jsonSchema should have correct validations', () => { + expect(AppConfig.jsonSchema).toMatchSnapshot(); + }); + + it('relationMappings should return correct associations', () => { + const relationMappings = AppConfig.relationMappings(); + + const expectedRelations = { + appAuthClients: { + relation: Base.HasManyRelation, + modelClass: AppAuthClient, + join: { + from: 'app_configs.key', + to: 'app_auth_clients.app_key', + }, + }, + }; + + expect(relationMappings).toStrictEqual(expectedRelations); + }); + + describe('getApp', () => { + it('getApp should return null if there is no key', async () => { + const appConfig = new AppConfig(); + const app = await appConfig.getApp(); + + expect(app).toBeNull(); + }); + + it('getApp should return app with provided key', async () => { + const appConfig = new AppConfig(); + appConfig.key = 'deepl'; + + const app = await appConfig.getApp(); + const expectedApp = await App.findOneByKey(appConfig.key); + + expect(app).toStrictEqual(expectedApp); + }); + }); + + describe('computeAndAssignConnectionAllowedProperty', () => { + it('should call computeConnectionAllowedProperty and assign the result', async () => { + const appConfig = await createAppConfig(); + + const computeConnectionAllowedPropertySpy = vi + .spyOn(appConfig, 'computeConnectionAllowedProperty') + .mockResolvedValue(true); + + await appConfig.computeAndAssignConnectionAllowedProperty(); + + expect(computeConnectionAllowedPropertySpy).toHaveBeenCalled(); + expect(appConfig.connectionAllowed).toBe(true); + }); + }); + + describe('computeConnectionAllowedProperty', () => { + it('should return true when app is enabled, shared and allows custom connection with an active app auth client', async () => { + await createAppAuthClient({ + appKey: 'deepl', + active: true, + }); + + await createAppAuthClient({ + appKey: 'deepl', + active: false, + }); + + const appConfig = await createAppConfig({ + disabled: false, + customConnectionAllowed: true, + shared: true, + key: 'deepl', + }); + + const connectionAllowed = + await appConfig.computeConnectionAllowedProperty(); + + expect(connectionAllowed).toBe(true); + }); + + it('should return false if there is no active app auth client', async () => { + await createAppAuthClient({ + appKey: 'deepl', + active: false, + }); + + const appConfig = await createAppConfig({ + disabled: false, + customConnectionAllowed: true, + shared: true, + key: 'deepl', + }); + + const connectionAllowed = + await appConfig.computeConnectionAllowedProperty(); + + expect(connectionAllowed).toBe(false); + }); + + it('should return false if there is no app auth clients', async () => { + const appConfig = await createAppConfig({ + disabled: false, + customConnectionAllowed: true, + shared: true, + key: 'deepl', + }); + + const connectionAllowed = + await appConfig.computeConnectionAllowedProperty(); + + expect(connectionAllowed).toBe(false); + }); + + it('should return false when app is disabled', async () => { + const appConfig = await createAppConfig({ + disabled: true, + customConnectionAllowed: true, + }); + + const connectionAllowed = + await appConfig.computeConnectionAllowedProperty(); + + expect(connectionAllowed).toBe(false); + }); + + it(`should return false when app doesn't allow custom connection`, async () => { + const appConfig = await createAppConfig({ + disabled: false, + customConnectionAllowed: false, + }); + + const connectionAllowed = + await appConfig.computeConnectionAllowedProperty(); + + expect(connectionAllowed).toBe(false); + }); + }); + + it('$beforeInsert should call computeAndAssignConnectionAllowedProperty', async () => { + const computeAndAssignConnectionAllowedPropertySpy = vi + .spyOn(AppConfig.prototype, 'computeAndAssignConnectionAllowedProperty') + .mockResolvedValue(true); + + await createAppConfig(); + + expect(computeAndAssignConnectionAllowedPropertySpy).toHaveBeenCalledOnce(); + }); + + it('$beforeUpdate should call computeAndAssignConnectionAllowedProperty', async () => { + const appConfig = await createAppConfig(); + + const computeAndAssignConnectionAllowedPropertySpy = vi + .spyOn(AppConfig.prototype, 'computeAndAssignConnectionAllowedProperty') + .mockResolvedValue(true); + + await appConfig.$query().patch({ + key: 'deepl', + }); + + expect(computeAndAssignConnectionAllowedPropertySpy).toHaveBeenCalledOnce(); + }); +}); diff --git a/packages/backend/src/models/connection.js b/packages/backend/src/models/connection.js index c743bbdc..325e1e07 100644 --- a/packages/backend/src/models/connection.js +++ b/packages/backend/src/models/connection.js @@ -89,7 +89,7 @@ class Connection extends Base { } if (this.appConfig) { - return !this.appConfig.disabled && this.appConfig.allowCustomConnection; + return !this.appConfig.disabled && this.appConfig.customConnectionAllowed; } return true; @@ -144,7 +144,7 @@ class Connection extends Base { ); } - if (!appConfig.allowCustomConnection && this.formattedData) { + if (!appConfig.customConnectionAllowed && this.formattedData) { throw new NotAuthorizedError( `New custom connections have been disabled for ${app.name}!` ); diff --git a/packages/backend/src/models/connection.test.js b/packages/backend/src/models/connection.test.js index e64d5fad..9818a3ec 100644 --- a/packages/backend/src/models/connection.test.js +++ b/packages/backend/src/models/connection.test.js @@ -121,7 +121,7 @@ describe('Connection model', () => { const appConfig = await createAppConfig({ key: 'gitlab', disabled: false, - allowCustomConnection: true, + customConnectionAllowed: true, }); const connection = await createConnection({ @@ -151,7 +151,7 @@ describe('Connection model', () => { await createAppConfig({ key: 'gitlab', disabled: true, - allowCustomConnection: false, + customConnectionAllowed: false, }); const connectionWithAppAuthClient = await connection @@ -373,7 +373,7 @@ describe('Connection model', () => { vi.spyOn(Connection.prototype, 'getAppConfig').mockResolvedValue({ disabled: false, - allowCustomConnection: false, + customConnectionAllowed: false, }); const connection = new Connection(); @@ -410,7 +410,7 @@ describe('Connection model', () => { await createAppConfig({ key: 'gitlab', disabled: false, - allowCustomConnection: true, + customConnectionAllowed: true, shared: true, }); diff --git a/packages/backend/src/serializers/app-config.js b/packages/backend/src/serializers/app-config.js index 0efd562b..97afcb76 100644 --- a/packages/backend/src/serializers/app-config.js +++ b/packages/backend/src/serializers/app-config.js @@ -2,11 +2,10 @@ const appConfigSerializer = (appConfig) => { return { id: appConfig.id, key: appConfig.key, - allowCustomConnection: appConfig.allowCustomConnection, + customConnectionAllowed: appConfig.customConnectionAllowed, shared: appConfig.shared, disabled: appConfig.disabled, - canConnect: appConfig.canConnect, - canCustomConnect: appConfig.canCustomConnect, + connectionAllowed: appConfig.connectionAllowed, createdAt: appConfig.createdAt.getTime(), updatedAt: appConfig.updatedAt.getTime(), }; diff --git a/packages/backend/src/serializers/app-config.test.js b/packages/backend/src/serializers/app-config.test.js index c0ede918..edf1d107 100644 --- a/packages/backend/src/serializers/app-config.test.js +++ b/packages/backend/src/serializers/app-config.test.js @@ -13,11 +13,10 @@ describe('appConfig serializer', () => { const expectedPayload = { id: appConfig.id, key: appConfig.key, - allowCustomConnection: appConfig.allowCustomConnection, + customConnectionAllowed: appConfig.customConnectionAllowed, shared: appConfig.shared, disabled: appConfig.disabled, - canConnect: appConfig.canConnect, - canCustomConnect: appConfig.canCustomConnect, + connectionAllowed: appConfig.connectionAllowed, createdAt: appConfig.createdAt.getTime(), updatedAt: appConfig.updatedAt.getTime(), }; diff --git a/packages/backend/test/factories/app-auth-client.js b/packages/backend/test/factories/app-auth-client.js index 0cadf197..831d4c14 100644 --- a/packages/backend/test/factories/app-auth-client.js +++ b/packages/backend/test/factories/app-auth-client.js @@ -10,7 +10,6 @@ const formattedAuthDefaults = { export const createAppAuthClient = async (params = {}) => { params.name = params?.name || faker.person.fullName(); - params.id = params?.id || faker.string.uuid(); params.appKey = params?.appKey || 'deepl'; params.active = params?.active ?? true; params.formattedAuthDefaults = diff --git a/packages/backend/test/mocks/rest/api/v1/admin/apps/create-config.js b/packages/backend/test/mocks/rest/api/v1/admin/apps/create-config.js index a1c1aad4..52e425ab 100644 --- a/packages/backend/test/mocks/rest/api/v1/admin/apps/create-config.js +++ b/packages/backend/test/mocks/rest/api/v1/admin/apps/create-config.js @@ -2,7 +2,7 @@ const createAppConfigMock = (appConfig) => { return { data: { key: appConfig.key, - allowCustomConnection: appConfig.allowCustomConnection, + customConnectionAllowed: appConfig.customConnectionAllowed, shared: appConfig.shared, disabled: appConfig.disabled, }, diff --git a/packages/backend/test/mocks/rest/api/v1/apps/get-config.js b/packages/backend/test/mocks/rest/api/v1/apps/get-config.js index 5faee118..60772644 100644 --- a/packages/backend/test/mocks/rest/api/v1/apps/get-config.js +++ b/packages/backend/test/mocks/rest/api/v1/apps/get-config.js @@ -3,11 +3,10 @@ const getAppConfigMock = (appConfig) => { data: { id: appConfig.id, key: appConfig.key, - allowCustomConnection: appConfig.allowCustomConnection, + customConnectionAllowed: appConfig.customConnectionAllowed, shared: appConfig.shared, disabled: appConfig.disabled, - canConnect: appConfig.canConnect, - canCustomConnect: appConfig.canCustomConnect, + connectionAllowed: appConfig.connectionAllowed, createdAt: appConfig.createdAt.getTime(), updatedAt: appConfig.updatedAt.getTime(), }, diff --git a/packages/e2e-tests/fixtures/admin/application-settings-page.js b/packages/e2e-tests/fixtures/admin/application-settings-page.js index 37dda591..57858ccb 100644 --- a/packages/e2e-tests/fixtures/admin/application-settings-page.js +++ b/packages/e2e-tests/fixtures/admin/application-settings-page.js @@ -8,11 +8,15 @@ export class AdminApplicationSettingsPage extends AuthenticatedPage { constructor(page) { super(page); - this.allowCustomConnectionsSwitch = this.page.locator('[name="allowCustomConnection"]'); + this.allowCustomConnectionsSwitch = this.page.locator( + '[name="customConnectionAllowed"]' + ); this.allowSharedConnectionsSwitch = this.page.locator('[name="shared"]'); this.disableConnectionsSwitch = this.page.locator('[name="disabled"]'); this.saveButton = this.page.getByTestId('submit-button'); - this.successSnackbar = this.page.getByTestId('snackbar-save-admin-apps-settings-success'); + this.successSnackbar = this.page.getByTestId( + 'snackbar-save-admin-apps-settings-success' + ); } async allowCustomConnections() { diff --git a/packages/web/src/components/AdminApplicationCreateAuthClient/index.jsx b/packages/web/src/components/AdminApplicationCreateAuthClient/index.jsx index dd385217..ccda0d0e 100644 --- a/packages/web/src/components/AdminApplicationCreateAuthClient/index.jsx +++ b/packages/web/src/components/AdminApplicationCreateAuthClient/index.jsx @@ -20,7 +20,7 @@ function AdminApplicationCreateAuthClient(props) { const { mutateAsync: createAppConfig, isPending: isCreateAppConfigPending, - error: createAppConfigError + error: createAppConfigError, } = useAdminCreateAppConfig(props.appKey); const { @@ -30,16 +30,15 @@ function AdminApplicationCreateAuthClient(props) { } = useAdminCreateAppAuthClient(appKey); const submitHandler = async (values) => { - let appConfigId = appConfig?.data?.id; + let appConfigKey = appConfig?.data?.key; - if (!appConfigId) { + if (!appConfigKey) { const { data: appConfigData } = await createAppConfig({ - allowCustomConnection: true, + customConnectionAllowed: true, shared: false, disabled: false, }); - - appConfigId = appConfigData.id; + appConfigKey = appConfigData.key; } const { name, active, ...formattedAuthDefaults } = values; diff --git a/packages/web/src/components/AdminApplicationSettings/index.jsx b/packages/web/src/components/AdminApplicationSettings/index.jsx index b24d2557..34ef8d0c 100644 --- a/packages/web/src/components/AdminApplicationSettings/index.jsx +++ b/packages/web/src/components/AdminApplicationSettings/index.jsx @@ -46,7 +46,8 @@ function AdminApplicationSettings(props) { const defaultValues = useMemo( () => ({ - allowCustomConnection: appConfig?.data?.allowCustomConnection || false, + customConnectionAllowed: + appConfig?.data?.customConnectionAllowed || false, shared: appConfig?.data?.shared || false, disabled: appConfig?.data?.disabled || false, }), @@ -61,8 +62,8 @@ function AdminApplicationSettings(props) { optionGenerator(connection)) || []; - if (!appConfig?.data || appConfig?.data?.canCustomConnect) { + if ( + !appConfig?.data || + (!appConfig.data?.disabled && appConfig.data?.customConnectionAllowed) + ) { options.push({ label: formatMessage('chooseConnectionSubstep.addNewConnection'), value: ADD_CONNECTION_VALUE, }); } - if (appConfig?.data?.canConnect) { + if (appConfig?.data?.connectionAllowed) { options.push({ label: formatMessage('chooseConnectionSubstep.addNewSharedConnection'), value: ADD_SHARED_CONNECTION_VALUE, diff --git a/packages/web/src/locales/en.json b/packages/web/src/locales/en.json index cc1ba1d3..b121f5e2 100644 --- a/packages/web/src/locales/en.json +++ b/packages/web/src/locales/en.json @@ -292,7 +292,7 @@ "adminApps.connections": "Connections", "adminApps.authClients": "Auth clients", "adminApps.settings": "Settings", - "adminAppsSettings.allowCustomConnection": "Allow custom connection", + "adminAppsSettings.customConnectionAllowed": "Allow custom connection", "adminAppsSettings.shared": "Shared", "adminAppsSettings.disabled": "Disabled", "adminAppsSettings.save": "Save", diff --git a/packages/web/src/pages/Application/index.jsx b/packages/web/src/pages/Application/index.jsx index e143c01b..74794784 100644 --- a/packages/web/src/pages/Application/index.jsx +++ b/packages/web/src/pages/Application/index.jsx @@ -77,14 +77,15 @@ export default function Application() { const connectionOptions = React.useMemo(() => { const shouldHaveCustomConnection = - appConfig?.data?.canConnect && appConfig?.data?.canCustomConnect; + appConfig?.data?.connectionAllowed && + appConfig?.data?.customConnectionAllowed; const options = [ { label: formatMessage('app.addConnection'), key: 'addConnection', 'data-test': 'add-connection-button', - to: URLS.APP_ADD_CONNECTION(appKey, appConfig?.data?.canConnect), + to: URLS.APP_ADD_CONNECTION(appKey, appConfig?.data?.connectionAllowed), disabled: !currentUserAbility.can('create', 'Connection'), }, ]; @@ -155,8 +156,9 @@ export default function Application() { disabled={ !allowed || (appConfig?.data && - !appConfig?.data?.canConnect && - !appConfig?.data?.canCustomConnect) || + !appConfig?.data?.disabled && + !appConfig?.data?.connectionAllowed && + !appConfig?.data?.customConnectionAllowed) || connectionOptions.every(({ disabled }) => disabled) } options={connectionOptions} diff --git a/packages/web/src/propTypes/propTypes.js b/packages/web/src/propTypes/propTypes.js index 29629d23..e6660c75 100644 --- a/packages/web/src/propTypes/propTypes.js +++ b/packages/web/src/propTypes/propTypes.js @@ -459,9 +459,8 @@ export const SamlAuthProviderRolePropType = PropTypes.shape({ export const AppConfigPropType = PropTypes.shape({ id: PropTypes.string, key: PropTypes.string, - allowCustomConnection: PropTypes.bool, - canConnect: PropTypes.bool, - canCustomConnect: PropTypes.bool, + customConnectionAllowed: PropTypes.bool, + connectionAllowed: PropTypes.bool, shared: PropTypes.bool, disabled: PropTypes.bool, }); @@ -469,7 +468,7 @@ export const AppConfigPropType = PropTypes.shape({ export const AppAuthClientPropType = PropTypes.shape({ id: PropTypes.string, name: PropTypes.string, - appConfigId: PropTypes.string, + appConfigKey: PropTypes.string, authDefaults: PropTypes.string, formattedAuthDefaults: PropTypes.object, active: PropTypes.bool,