Compare commits
27 Commits
aut-1180
...
dependabot
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a6990dae5a | ||
![]() |
0800642a2a | ||
![]() |
48b2b006c0 | ||
![]() |
af4c1f08ec | ||
![]() |
87b26b6342 | ||
![]() |
706fb8d82f | ||
![]() |
6540d0ea53 | ||
![]() |
5995038e21 | ||
![]() |
337ba6ea87 | ||
![]() |
04c6183752 | ||
![]() |
0b63922f46 | ||
![]() |
81c39d7d93 | ||
![]() |
164d31dfbc | ||
![]() |
1a833aad52 | ||
![]() |
77246c1fde | ||
![]() |
56c08a3587 | ||
![]() |
ce6214dc0f | ||
![]() |
22002d50ac | ||
![]() |
ab4e94695d | ||
![]() |
47a01cec7e | ||
![]() |
0cf9bc1a32 | ||
![]() |
6552ebcd3c | ||
![]() |
5af1d94fc0 | ||
![]() |
a4ec7b3047 | ||
![]() |
66f5003d91 | ||
![]() |
0c754e4b4b | ||
![]() |
e008087c4a |
@@ -11,6 +11,7 @@
|
|||||||
"start:worker": "node src/worker.js",
|
"start:worker": "node src/worker.js",
|
||||||
"pretest": "APP_ENV=test node ./test/setup/prepare-test-env.js",
|
"pretest": "APP_ENV=test node ./test/setup/prepare-test-env.js",
|
||||||
"test": "APP_ENV=test vitest run",
|
"test": "APP_ENV=test vitest run",
|
||||||
|
"test:watch": "APP_ENV=test vitest watch",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"db:create": "node ./bin/database/create.js",
|
"db:create": "node ./bin/database/create.js",
|
||||||
"db:seed:user": "node ./bin/database/seed-user.js",
|
"db:seed:user": "node ./bin/database/seed-user.js",
|
||||||
|
@@ -33,6 +33,7 @@ export default defineAction({
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
required: true,
|
required: true,
|
||||||
variables: true,
|
variables: true,
|
||||||
|
valueType: 'parse',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@@ -11,5 +11,5 @@ export default async (request, response) => {
|
|||||||
|
|
||||||
await accessToken.revoke();
|
await accessToken.revoke();
|
||||||
|
|
||||||
response.status(204).send();
|
response.status(204).end();
|
||||||
};
|
};
|
||||||
|
@@ -0,0 +1,25 @@
|
|||||||
|
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(appAuthClientParams(request));
|
||||||
|
|
||||||
|
renderObject(response, appAuthClient, { status: 201 });
|
||||||
|
};
|
||||||
|
|
||||||
|
const appAuthClientParams = (request) => {
|
||||||
|
const { active, appKey, name, formattedAuthDefaults } = request.body;
|
||||||
|
|
||||||
|
return {
|
||||||
|
active,
|
||||||
|
appKey,
|
||||||
|
name,
|
||||||
|
formattedAuthDefaults,
|
||||||
|
};
|
||||||
|
};
|
@@ -0,0 +1,94 @@
|
|||||||
|
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 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 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 bad request response for missing required fields', async () => {
|
||||||
|
await createAppConfig({
|
||||||
|
key: 'gitlab',
|
||||||
|
});
|
||||||
|
|
||||||
|
const appAuthClient = {
|
||||||
|
appKey: 'gitlab',
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await request(app)
|
||||||
|
.post('/api/v1/admin/apps/gitlab/auth-clients')
|
||||||
|
.set('Authorization', token)
|
||||||
|
.send(appAuthClient)
|
||||||
|
.expect(422);
|
||||||
|
|
||||||
|
expect(response.body.meta.type).toEqual('ModelValidation');
|
||||||
|
expect(response.body.errors).toMatchObject({
|
||||||
|
name: ["must have required property 'name'"],
|
||||||
|
formattedAuthDefaults: [
|
||||||
|
"must have required property 'formattedAuthDefaults'",
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,21 @@
|
|||||||
|
import { renderObject } from '../../../../../helpers/renderer.js';
|
||||||
|
import AppConfig from '../../../../../models/app-config.js';
|
||||||
|
|
||||||
|
export default async (request, response) => {
|
||||||
|
const createdAppConfig = await AppConfig.query().insertAndFetch(
|
||||||
|
appConfigParams(request)
|
||||||
|
);
|
||||||
|
|
||||||
|
renderObject(response, createdAppConfig, { status: 201 });
|
||||||
|
};
|
||||||
|
|
||||||
|
const appConfigParams = (request) => {
|
||||||
|
const { allowCustomConnection, shared, disabled } = request.body;
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: request.params.appKey,
|
||||||
|
allowCustomConnection,
|
||||||
|
shared,
|
||||||
|
disabled,
|
||||||
|
};
|
||||||
|
};
|
@@ -0,0 +1,67 @@
|
|||||||
|
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 createAppConfigMock from '../../../../../../test/mocks/rest/api/v1/admin/apps/create-config.js';
|
||||||
|
import { createAppConfig } from '../../../../../../test/factories/app-config.js';
|
||||||
|
import * as license from '../../../../../helpers/license.ee.js';
|
||||||
|
|
||||||
|
describe('POST /api/v1/admin/apps/:appKey/config', () => {
|
||||||
|
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 created app config', async () => {
|
||||||
|
const appConfig = {
|
||||||
|
allowCustomConnection: true,
|
||||||
|
shared: true,
|
||||||
|
disabled: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await request(app)
|
||||||
|
.post('/api/v1/admin/apps/gitlab/config')
|
||||||
|
.set('Authorization', token)
|
||||||
|
.send(appConfig)
|
||||||
|
.expect(201);
|
||||||
|
|
||||||
|
const expectedPayload = createAppConfigMock({
|
||||||
|
...appConfig,
|
||||||
|
key: 'gitlab',
|
||||||
|
});
|
||||||
|
expect(response.body).toMatchObject(expectedPayload);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return HTTP 422 for already existing app config', async () => {
|
||||||
|
const appConfig = {
|
||||||
|
key: 'gitlab',
|
||||||
|
allowCustomConnection: true,
|
||||||
|
shared: true,
|
||||||
|
disabled: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
await createAppConfig(appConfig);
|
||||||
|
|
||||||
|
const response = await request(app)
|
||||||
|
.post('/api/v1/admin/apps/gitlab/config')
|
||||||
|
.set('Authorization', token)
|
||||||
|
.send({
|
||||||
|
disabled: false,
|
||||||
|
})
|
||||||
|
.expect(422);
|
||||||
|
|
||||||
|
expect(response.body.meta.type).toEqual('UniqueViolationError');
|
||||||
|
expect(response.body.errors).toMatchObject({
|
||||||
|
key: ["'key' must be unique."],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -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);
|
||||||
|
};
|
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,9 @@
|
|||||||
|
export default async (request, response) => {
|
||||||
|
const step = await request.currentUser.authorizedSteps
|
||||||
|
.findById(request.params.stepId)
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
await step.delete();
|
||||||
|
|
||||||
|
response.status(204).end();
|
||||||
|
};
|
@@ -0,0 +1,134 @@
|
|||||||
|
import { describe, it, beforeEach } from 'vitest';
|
||||||
|
import request from 'supertest';
|
||||||
|
import Crypto from 'crypto';
|
||||||
|
import app from '../../../../app.js';
|
||||||
|
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
|
||||||
|
import { createUser } from '../../../../../test/factories/user';
|
||||||
|
import { createConnection } from '../../../../../test/factories/connection';
|
||||||
|
import { createFlow } from '../../../../../test/factories/flow';
|
||||||
|
import { createStep } from '../../../../../test/factories/step';
|
||||||
|
import { createPermission } from '../../../../../test/factories/permission';
|
||||||
|
|
||||||
|
describe('DELETE /api/v1/steps/:stepId', () => {
|
||||||
|
let currentUser, currentUserRole, token;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
currentUser = await createUser();
|
||||||
|
currentUserRole = await currentUser.$relatedQuery('role');
|
||||||
|
|
||||||
|
token = await createAuthTokenByUserId(currentUser.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove the step of the current user and return no content', async () => {
|
||||||
|
const currentUserFlow = await createFlow({ userId: currentUser.id });
|
||||||
|
const currentUserConnection = await createConnection();
|
||||||
|
|
||||||
|
await createStep({
|
||||||
|
flowId: currentUserFlow.id,
|
||||||
|
connectionId: currentUserConnection.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const actionStep = await createStep({
|
||||||
|
flowId: currentUserFlow.id,
|
||||||
|
connectionId: currentUserConnection.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
await createPermission({
|
||||||
|
action: 'read',
|
||||||
|
subject: 'Flow',
|
||||||
|
roleId: currentUserRole.id,
|
||||||
|
conditions: ['isCreator'],
|
||||||
|
});
|
||||||
|
|
||||||
|
await createPermission({
|
||||||
|
action: 'update',
|
||||||
|
subject: 'Flow',
|
||||||
|
roleId: currentUserRole.id,
|
||||||
|
conditions: ['isCreator'],
|
||||||
|
});
|
||||||
|
|
||||||
|
await request(app)
|
||||||
|
.delete(`/api/v1/steps/${actionStep.id}`)
|
||||||
|
.set('Authorization', token)
|
||||||
|
.expect(204);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove the step of the another user and return no content', async () => {
|
||||||
|
const anotherUser = await createUser();
|
||||||
|
const anotherUserFlow = await createFlow({ userId: anotherUser.id });
|
||||||
|
const anotherUserConnection = await createConnection();
|
||||||
|
|
||||||
|
await createStep({
|
||||||
|
flowId: anotherUserFlow.id,
|
||||||
|
connectionId: anotherUserConnection.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const actionStep = await createStep({
|
||||||
|
flowId: anotherUserFlow.id,
|
||||||
|
connectionId: anotherUserConnection.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
await createPermission({
|
||||||
|
action: 'read',
|
||||||
|
subject: 'Flow',
|
||||||
|
roleId: currentUserRole.id,
|
||||||
|
conditions: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
await createPermission({
|
||||||
|
action: 'update',
|
||||||
|
subject: 'Flow',
|
||||||
|
roleId: currentUserRole.id,
|
||||||
|
conditions: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
await request(app)
|
||||||
|
.delete(`/api/v1/steps/${actionStep.id}`)
|
||||||
|
.set('Authorization', token)
|
||||||
|
.expect(204);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return not found response for not existing step UUID', async () => {
|
||||||
|
await createPermission({
|
||||||
|
action: 'update',
|
||||||
|
subject: 'Flow',
|
||||||
|
roleId: currentUserRole.id,
|
||||||
|
conditions: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
await createPermission({
|
||||||
|
action: 'read',
|
||||||
|
subject: 'Flow',
|
||||||
|
roleId: currentUserRole.id,
|
||||||
|
conditions: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const notExistingStepUUID = Crypto.randomUUID();
|
||||||
|
|
||||||
|
await request(app)
|
||||||
|
.delete(`/api/v1/steps/${notExistingStepUUID}`)
|
||||||
|
.set('Authorization', token)
|
||||||
|
.expect(404);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return bad request response for invalid step UUID', async () => {
|
||||||
|
await createPermission({
|
||||||
|
action: 'update',
|
||||||
|
subject: 'Flow',
|
||||||
|
roleId: currentUserRole.id,
|
||||||
|
conditions: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
await createPermission({
|
||||||
|
action: 'read',
|
||||||
|
subject: 'Flow',
|
||||||
|
roleId: currentUserRole.id,
|
||||||
|
conditions: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
await request(app)
|
||||||
|
.delete('/api/v1/steps/invalidStepUUID')
|
||||||
|
.set('Authorization', token)
|
||||||
|
.expect(400);
|
||||||
|
});
|
||||||
|
});
|
@@ -1,12 +0,0 @@
|
|||||||
export async function up(knex) {
|
|
||||||
await knex('config').insert({
|
|
||||||
key: 'userManagement.preventUsersFromUpdatingTheirProfile',
|
|
||||||
value: {
|
|
||||||
data: false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function down(knex) {
|
|
||||||
await knex('config').where({ key: 'userManagement.preventUsersFromUpdatingTheirProfile' }).delete();
|
|
||||||
};
|
|
@@ -1,5 +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 createConnection from './mutations/create-connection.js';
|
||||||
import createFlow from './mutations/create-flow.js';
|
import createFlow from './mutations/create-flow.js';
|
||||||
import createRole from './mutations/create-role.ee.js';
|
import createRole from './mutations/create-role.ee.js';
|
||||||
@@ -9,7 +7,6 @@ import deleteConnection from './mutations/delete-connection.js';
|
|||||||
import deleteCurrentUser from './mutations/delete-current-user.ee.js';
|
import deleteCurrentUser from './mutations/delete-current-user.ee.js';
|
||||||
import deleteFlow from './mutations/delete-flow.js';
|
import deleteFlow from './mutations/delete-flow.js';
|
||||||
import deleteRole from './mutations/delete-role.ee.js';
|
import deleteRole from './mutations/delete-role.ee.js';
|
||||||
import deleteStep from './mutations/delete-step.js';
|
|
||||||
import duplicateFlow from './mutations/duplicate-flow.js';
|
import duplicateFlow from './mutations/duplicate-flow.js';
|
||||||
import executeFlow from './mutations/execute-flow.js';
|
import executeFlow from './mutations/execute-flow.js';
|
||||||
import generateAuthUrl from './mutations/generate-auth-url.js';
|
import generateAuthUrl from './mutations/generate-auth-url.js';
|
||||||
@@ -27,11 +24,12 @@ import updateStep from './mutations/update-step.js';
|
|||||||
import updateUser from './mutations/update-user.ee.js';
|
import updateUser from './mutations/update-user.ee.js';
|
||||||
import upsertSamlAuthProvider from './mutations/upsert-saml-auth-provider.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 upsertSamlAuthProvidersRoleMappings from './mutations/upsert-saml-auth-providers-role-mappings.ee.js';
|
||||||
|
|
||||||
|
// Converted mutations
|
||||||
|
import deleteStep from './mutations/delete-step.js';
|
||||||
import verifyConnection from './mutations/verify-connection.js';
|
import verifyConnection from './mutations/verify-connection.js';
|
||||||
|
|
||||||
const mutationResolvers = {
|
const mutationResolvers = {
|
||||||
createAppAuthClient,
|
|
||||||
createAppConfig,
|
|
||||||
createConnection,
|
createConnection,
|
||||||
createFlow,
|
createFlow,
|
||||||
createRole,
|
createRole,
|
||||||
|
@@ -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;
|
|
@@ -1,18 +0,0 @@
|
|||||||
import App from '../../models/app.js';
|
|
||||||
import AppConfig from '../../models/app-config.js';
|
|
||||||
|
|
||||||
const createAppConfig = async (_parent, params, context) => {
|
|
||||||
context.currentUser.can('update', 'App');
|
|
||||||
|
|
||||||
const key = params.input.key;
|
|
||||||
|
|
||||||
const app = await App.findOneByKey(key);
|
|
||||||
|
|
||||||
if (!app) throw new Error('The app cannot be found!');
|
|
||||||
|
|
||||||
const appConfig = await AppConfig.query().insert(params.input);
|
|
||||||
|
|
||||||
return appConfig;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default createAppConfig;
|
|
@@ -2,8 +2,6 @@ type Query {
|
|||||||
placeholderQuery(name: String): Boolean
|
placeholderQuery(name: String): Boolean
|
||||||
}
|
}
|
||||||
type Mutation {
|
type Mutation {
|
||||||
createAppConfig(input: CreateAppConfigInput): AppConfig
|
|
||||||
createAppAuthClient(input: CreateAppAuthClientInput): AppAuthClient
|
|
||||||
createConnection(input: CreateConnectionInput): Connection
|
createConnection(input: CreateConnectionInput): Connection
|
||||||
createFlow(input: CreateFlowInput): Flow
|
createFlow(input: CreateFlowInput): Flow
|
||||||
createRole(input: CreateRoleInput): Role
|
createRole(input: CreateRoleInput): Role
|
||||||
@@ -550,13 +548,6 @@ type Subject {
|
|||||||
key: String
|
key: String
|
||||||
}
|
}
|
||||||
|
|
||||||
input CreateAppConfigInput {
|
|
||||||
key: String
|
|
||||||
allowCustomConnection: Boolean
|
|
||||||
shared: Boolean
|
|
||||||
disabled: Boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
input UpdateAppConfigInput {
|
input UpdateAppConfigInput {
|
||||||
id: String
|
id: String
|
||||||
allowCustomConnection: Boolean
|
allowCustomConnection: Boolean
|
||||||
@@ -571,13 +562,6 @@ type AppAuthClient {
|
|||||||
active: Boolean
|
active: Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
input CreateAppAuthClientInput {
|
|
||||||
appConfigId: String
|
|
||||||
name: String
|
|
||||||
formattedAuthDefaults: JSONObject
|
|
||||||
active: Boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
input UpdateAppAuthClientInput {
|
input UpdateAppAuthClientInput {
|
||||||
id: String
|
id: String
|
||||||
name: String
|
name: String
|
||||||
|
@@ -43,6 +43,10 @@ const authorizationList = {
|
|||||||
action: 'update',
|
action: 'update',
|
||||||
subject: 'Connection',
|
subject: 'Connection',
|
||||||
},
|
},
|
||||||
|
'POST /api/v1/connections/:connectionId/verify': {
|
||||||
|
action: 'create',
|
||||||
|
subject: 'Connection',
|
||||||
|
},
|
||||||
'GET /api/v1/apps/:appKey/flows': {
|
'GET /api/v1/apps/:appKey/flows': {
|
||||||
action: 'read',
|
action: 'read',
|
||||||
subject: 'Flow',
|
subject: 'Flow',
|
||||||
@@ -63,6 +67,10 @@ const authorizationList = {
|
|||||||
action: 'read',
|
action: 'read',
|
||||||
subject: 'Execution',
|
subject: 'Execution',
|
||||||
},
|
},
|
||||||
|
'DELETE /api/v1/steps/:stepId': {
|
||||||
|
action: 'update',
|
||||||
|
subject: 'Flow',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const authorizeUser = async (request, response, next) => {
|
export const authorizeUser = async (request, response, next) => {
|
||||||
|
@@ -6,10 +6,21 @@ function getParameterEntries(parameters) {
|
|||||||
return Object.entries(parameters);
|
return Object.entries(parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeParameterEntries(parameterEntries, executionSteps) {
|
function getFieldByKey(key, fields = []) {
|
||||||
|
return fields.find((field) => field.key === key);
|
||||||
|
};
|
||||||
|
|
||||||
|
function getParameterValueType(parameterKey, fields) {
|
||||||
|
const field = getFieldByKey(parameterKey, fields);
|
||||||
|
const defaultValueType = 'string';
|
||||||
|
|
||||||
|
return field?.valueType || defaultValueType;
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeParameterEntries(parameterEntries, fields, executionSteps) {
|
||||||
const defaultComputedParameters = {};
|
const defaultComputedParameters = {};
|
||||||
return parameterEntries.reduce((result, [key, value]) => {
|
return parameterEntries.reduce((result, [key, value]) => {
|
||||||
const parameterComputedValue = computeParameter(value, executionSteps);
|
const parameterComputedValue = computeParameter(key, value, fields, executionSteps);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...result,
|
...result,
|
||||||
@@ -18,14 +29,21 @@ function computeParameterEntries(parameterEntries, executionSteps) {
|
|||||||
}, defaultComputedParameters);
|
}, defaultComputedParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeParameter(value, executionSteps) {
|
function shouldAutoParse(key, fields) {
|
||||||
|
const parameterValueType = getParameterValueType(key, fields);
|
||||||
|
const shouldAutoParse = parameterValueType === 'parse';
|
||||||
|
|
||||||
|
return shouldAutoParse;
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeParameter(key, value, fields, executionSteps) {
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
const computedStringParameter = computeStringParameter(value, executionSteps);
|
const computedStringParameter = computeStringParameter(key, value, fields, executionSteps);
|
||||||
return computedStringParameter;
|
return computedStringParameter;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
const computedArrayParameter = computeArrayParameter(value, executionSteps);
|
const computedArrayParameter = computeArrayParameter(key, value, fields, executionSteps);
|
||||||
return computedArrayParameter;
|
return computedArrayParameter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +125,7 @@ function autoParseComputedVariable(computedVariable) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeStringParameter(stringValue, executionSteps) {
|
function computeStringParameter(key, stringValue, fields, executionSteps) {
|
||||||
const parts = splitByVariable(stringValue);
|
const parts = splitByVariable(stringValue);
|
||||||
|
|
||||||
const computedValue = parts
|
const computedValue = parts
|
||||||
@@ -122,17 +140,26 @@ function computeStringParameter(stringValue, executionSteps) {
|
|||||||
})
|
})
|
||||||
.join('');
|
.join('');
|
||||||
|
|
||||||
|
const autoParse = shouldAutoParse(key, fields);
|
||||||
|
if (autoParse) {
|
||||||
const autoParsedValue = autoParseComputedVariable(computedValue);
|
const autoParsedValue = autoParseComputedVariable(computedValue);
|
||||||
|
|
||||||
return autoParsedValue;
|
return autoParsedValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeArrayParameter(arrayValue, executionSteps) {
|
return computedValue;
|
||||||
return arrayValue.map((item) => computeParameters(item, executionSteps));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function computeParameters(parameters, executionSteps) {
|
function computeArrayParameter(key, arrayValue, fields = [], executionSteps) {
|
||||||
|
return arrayValue.map((item) => {
|
||||||
|
const itemFields = fields.find((field) => field.key === key)?.fields;
|
||||||
|
|
||||||
|
return computeParameters(item, itemFields, executionSteps);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function computeParameters(parameters, fields, executionSteps) {
|
||||||
const parameterEntries = getParameterEntries(parameters);
|
const parameterEntries = getParameterEntries(parameters);
|
||||||
|
|
||||||
return computeParameterEntries(parameterEntries, executionSteps);
|
return computeParameterEntries(parameterEntries, fields, executionSteps);
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import { beforeEach, describe, it, expect } from 'vitest';
|
import { beforeEach, describe, it, expect } from 'vitest';
|
||||||
import { createExecutionStep } from '../../test/factories/execution-step.js';
|
import { createExecutionStep } from '../../test/factories/execution-step.js';
|
||||||
|
import { createDropdownArgument, createDynamicArgument, createStringArgument } from '../../test/factories/app.js';
|
||||||
import computeParameters from './compute-parameters.js';
|
import computeParameters from './compute-parameters.js';
|
||||||
|
|
||||||
const computeVariable = (stepId, path) => `{{step.${stepId}.${path}}}`;
|
const computeVariable = (stepId, path) => `{{step.${stepId}.${path}}}`;
|
||||||
@@ -68,7 +69,7 @@ describe('Compute parameters helper', () => {
|
|||||||
key2: `"${computeVariable('non-existent-step-id', 'non-existent-key')}" is the value for non-existent-key`,
|
key2: `"${computeVariable('non-existent-step-id', 'non-existent-key')}" is the value for non-existent-key`,
|
||||||
};
|
};
|
||||||
|
|
||||||
const computedParameters = computeParameters(parameters, executionSteps);
|
const computedParameters = computeParameters(parameters, [], executionSteps);
|
||||||
const expectedParameters = {
|
const expectedParameters = {
|
||||||
key1: '',
|
key1: '',
|
||||||
key2: '"" is the value for non-existent-key',
|
key2: '"" is the value for non-existent-key',
|
||||||
@@ -81,20 +82,29 @@ describe('Compute parameters helper', () => {
|
|||||||
it('should resolve empty object', () => {
|
it('should resolve empty object', () => {
|
||||||
const parameters = {};
|
const parameters = {};
|
||||||
|
|
||||||
const computedParameters = computeParameters(parameters, executionSteps);
|
const computedParameters = computeParameters(parameters, [], executionSteps);
|
||||||
|
|
||||||
expect(computedParameters).toStrictEqual(parameters);
|
expect(computedParameters).toStrictEqual(parameters);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with string parameters', () => {
|
describe('with string parameters', () => {
|
||||||
|
let stepArguments;
|
||||||
|
beforeEach(() => {
|
||||||
|
stepArguments = [
|
||||||
|
createStringArgument({
|
||||||
|
key: 'key1',
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
it('should resolve as-is without variables', () => {
|
it('should resolve as-is without variables', () => {
|
||||||
const parameters = {
|
const parameters = {
|
||||||
key1: 'plain text',
|
key1: 'plain text',
|
||||||
key2: 'plain text',
|
key2: 'plain text',
|
||||||
};
|
};
|
||||||
|
|
||||||
const computedParameters = computeParameters(parameters, executionSteps);
|
const computedParameters = computeParameters(parameters, stepArguments, executionSteps);
|
||||||
|
|
||||||
expect(computedParameters).toStrictEqual(parameters);
|
expect(computedParameters).toStrictEqual(parameters);
|
||||||
});
|
});
|
||||||
@@ -106,7 +116,7 @@ describe('Compute parameters helper', () => {
|
|||||||
key3: ' plain text',
|
key3: ' plain text',
|
||||||
};
|
};
|
||||||
|
|
||||||
const computedParameters = computeParameters(parameters, executionSteps);
|
const computedParameters = computeParameters(parameters, stepArguments, executionSteps);
|
||||||
|
|
||||||
expect(computedParameters).toStrictEqual(parameters);
|
expect(computedParameters).toStrictEqual(parameters);
|
||||||
});
|
});
|
||||||
@@ -116,72 +126,25 @@ describe('Compute parameters helper', () => {
|
|||||||
key1: `static text ${computeVariable(executionStepOne.stepId, 'step1Key1')}`,
|
key1: `static text ${computeVariable(executionStepOne.stepId, 'step1Key1')}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
const computedParameters = computeParameters(parameters, executionSteps);
|
const computedParameters = computeParameters(parameters, stepArguments, executionSteps);
|
||||||
const expectedParameters = {
|
const expectedParameters = {
|
||||||
key1: `static text plain text value for step1Key1`,
|
key1: `static text plain text value for step1Key1`,
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(computedParameters).toStrictEqual(expectedParameters);
|
expect(computedParameters).toStrictEqual(expectedParameters);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('with variables containing JSON', () => {
|
||||||
|
describe('without explicit valueType defined', () => {
|
||||||
|
let stepArguments;
|
||||||
|
beforeEach(() => {
|
||||||
|
stepArguments = [
|
||||||
|
createStringArgument({
|
||||||
|
key: 'key1',
|
||||||
|
}),
|
||||||
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with number parameters', () => {
|
|
||||||
it('should resolve number larger than MAX_SAFE_INTEGER correctly', () => {
|
|
||||||
const parameters = {
|
|
||||||
// eslint-disable-next-line no-loss-of-precision
|
|
||||||
key1: 119007199254740999,
|
|
||||||
};
|
|
||||||
|
|
||||||
const computedParameters = computeParameters(parameters, executionSteps);
|
|
||||||
|
|
||||||
expect(computedParameters).toStrictEqual(parameters);
|
|
||||||
expect(computedParameters.key1 > Number.MAX_SAFE_INTEGER).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should resolve stringified number as-is', () => {
|
|
||||||
const parameters = {
|
|
||||||
key1: '119007199254740999',
|
|
||||||
};
|
|
||||||
|
|
||||||
const computedParameters = computeParameters(parameters, executionSteps);
|
|
||||||
|
|
||||||
expect(computedParameters).toStrictEqual(parameters);
|
|
||||||
expect(parseInt(parameters.key1))
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should compute variables with int values correctly', () => {
|
|
||||||
const parameters = {
|
|
||||||
key1: `another static text ${computeVariable(executionStepThree.stepId, 'step3Key2')}`,
|
|
||||||
key2: `${computeVariable(executionStepThree.stepId, 'step3Key3')}`
|
|
||||||
};
|
|
||||||
|
|
||||||
const computedParameters = computeParameters(parameters, executionSteps);
|
|
||||||
const expectedParameters = {
|
|
||||||
key1: `another static text 123123`,
|
|
||||||
key2: `123123`
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(computedParameters).toStrictEqual(expectedParameters);
|
|
||||||
});
|
|
||||||
|
|
||||||
it.todo('should compute variables with bigint values correctly', () => {
|
|
||||||
const parameters = {
|
|
||||||
key1: `another static text ${computeVariable(executionStepTwo.stepId, 'step2Key2')}`,
|
|
||||||
key2: `${computeVariable(executionStepTwo.stepId, 'step2Key3')}`
|
|
||||||
};
|
|
||||||
|
|
||||||
const computedParameters = computeParameters(parameters, executionSteps);
|
|
||||||
const expectedParameters = {
|
|
||||||
// The expected `key2` is computed wrongly.
|
|
||||||
key1: `another static text 6502380617126796383`,
|
|
||||||
key2: `6502380617126796383`
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(computedParameters).toStrictEqual(expectedParameters);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with JSON parameters', () => {
|
|
||||||
it('should resolve text + JSON value as-is', () => {
|
it('should resolve text + JSON value as-is', () => {
|
||||||
const parameters = {
|
const parameters = {
|
||||||
key1: 'prepended text {"key": "value"} ',
|
key1: 'prepended text {"key": "value"} ',
|
||||||
@@ -197,7 +160,123 @@ describe('Compute parameters helper', () => {
|
|||||||
key1: '{"key1": "plain text", "key2": "119007199254740999"}',
|
key1: '{"key1": "plain text", "key2": "119007199254740999"}',
|
||||||
};
|
};
|
||||||
|
|
||||||
const computedParameters = computeParameters(parameters, executionSteps);
|
const computedParameters = computeParameters(parameters, stepArguments, executionSteps);
|
||||||
|
const expectedParameters = {
|
||||||
|
key1: '{"key1": "plain text", "key2": "119007199254740999"}',
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(computedParameters).toStrictEqual(expectedParameters);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle arrays at root level', () => {
|
||||||
|
const parameters = {
|
||||||
|
key1: '["value1", "value2"]',
|
||||||
|
};
|
||||||
|
|
||||||
|
const computedParameters = computeParameters(parameters, stepArguments, executionSteps);
|
||||||
|
const expectedParameters = {
|
||||||
|
key1: '["value1", "value2"]',
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(computedParameters).toStrictEqual(expectedParameters);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle arrays in nested level', () => {
|
||||||
|
const parameters = {
|
||||||
|
key1: '{"items": ["value1", "value2"]}',
|
||||||
|
};
|
||||||
|
|
||||||
|
const computedParameters = computeParameters(parameters, stepArguments, executionSteps);
|
||||||
|
const expectedParameters = {
|
||||||
|
key1: '{"items": ["value1", "value2"]}',
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(computedParameters).toStrictEqual(expectedParameters);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should compute mix variables correctly', () => {
|
||||||
|
const parameters = {
|
||||||
|
key1: `another static text ${computeVariable(executionStepThree.stepId, 'step3Key4.step3Key4ChildKey4')}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const computedParameters = computeParameters(parameters, stepArguments, executionSteps);
|
||||||
|
const expectedParameters = {
|
||||||
|
key1: `another static text ["value1","value2"]`,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(computedParameters).toStrictEqual(expectedParameters);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should compute variables correctly', () => {
|
||||||
|
const parameters = {
|
||||||
|
key1: `${computeVariable(executionStepThree.stepId, 'step3Key4.step3Key4ChildKey4')}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const computedParameters = computeParameters(parameters, stepArguments, executionSteps);
|
||||||
|
const expectedParameters = {
|
||||||
|
key1: '["value1","value2"]',
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(computedParameters).toStrictEqual(expectedParameters);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not parse non-primitives in nested arrays', () => {
|
||||||
|
const stepArguments = [
|
||||||
|
createDynamicArgument({
|
||||||
|
key: 'inputs',
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
const parameters = {
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
key: 'person',
|
||||||
|
value: '{ "name": "John Doe", "age": 32 }',
|
||||||
|
}
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const computedParameters = computeParameters(parameters, stepArguments, executionSteps);
|
||||||
|
const expectedParameters = {
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
key: 'person',
|
||||||
|
value: '{ "name": "John Doe", "age": 32 }',
|
||||||
|
}
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(computedParameters).toStrictEqual(expectedParameters);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(`with valueType as 'parse'`, () => {
|
||||||
|
let stepArguments;
|
||||||
|
beforeEach(() => {
|
||||||
|
stepArguments = [
|
||||||
|
createStringArgument({
|
||||||
|
key: 'key1',
|
||||||
|
valueType: 'parse',
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should resolve text + JSON value as-is', () => {
|
||||||
|
const parameters = {
|
||||||
|
key1: 'prepended text {"key": "value"} ',
|
||||||
|
};
|
||||||
|
|
||||||
|
const computedParameters = computeParameters(parameters, stepArguments, executionSteps);
|
||||||
|
|
||||||
|
expect(computedParameters).toStrictEqual(parameters);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should resolve stringified JSON parsed', () => {
|
||||||
|
const parameters = {
|
||||||
|
key1: '{"key1": "plain text", "key2": "119007199254740999"}',
|
||||||
|
};
|
||||||
|
|
||||||
|
const computedParameters = computeParameters(parameters, stepArguments, executionSteps);
|
||||||
const expectedParameters = {
|
const expectedParameters = {
|
||||||
key1: {
|
key1: {
|
||||||
key1: 'plain text',
|
key1: 'plain text',
|
||||||
@@ -213,7 +292,7 @@ describe('Compute parameters helper', () => {
|
|||||||
key1: '["value1", "value2"]',
|
key1: '["value1", "value2"]',
|
||||||
};
|
};
|
||||||
|
|
||||||
const computedParameters = computeParameters(parameters, executionSteps);
|
const computedParameters = computeParameters(parameters, stepArguments, executionSteps);
|
||||||
const expectedParameters = {
|
const expectedParameters = {
|
||||||
key1: ['value1', 'value2'],
|
key1: ['value1', 'value2'],
|
||||||
};
|
};
|
||||||
@@ -226,7 +305,7 @@ describe('Compute parameters helper', () => {
|
|||||||
key1: '{"items": ["value1", "value2"]}',
|
key1: '{"items": ["value1", "value2"]}',
|
||||||
};
|
};
|
||||||
|
|
||||||
const computedParameters = computeParameters(parameters, executionSteps);
|
const computedParameters = computeParameters(parameters, stepArguments, executionSteps);
|
||||||
const expectedParameters = {
|
const expectedParameters = {
|
||||||
key1: {
|
key1: {
|
||||||
items: ['value1', 'value2'],
|
items: ['value1', 'value2'],
|
||||||
@@ -236,12 +315,12 @@ describe('Compute parameters helper', () => {
|
|||||||
expect(computedParameters).toStrictEqual(expectedParameters);
|
expect(computedParameters).toStrictEqual(expectedParameters);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should compute mix variables correctly', () => {
|
it('should compute and parse mix variables correctly', () => {
|
||||||
const parameters = {
|
const parameters = {
|
||||||
key1: `another static text ${computeVariable(executionStepThree.stepId, 'step3Key4.step3Key4ChildKey4')}`,
|
key1: `another static text ${computeVariable(executionStepThree.stepId, 'step3Key4.step3Key4ChildKey4')}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
const computedParameters = computeParameters(parameters, executionSteps);
|
const computedParameters = computeParameters(parameters, stepArguments, executionSteps);
|
||||||
const expectedParameters = {
|
const expectedParameters = {
|
||||||
key1: `another static text ["value1","value2"]`,
|
key1: `another static text ["value1","value2"]`,
|
||||||
};
|
};
|
||||||
@@ -249,17 +328,160 @@ describe('Compute parameters helper', () => {
|
|||||||
expect(computedParameters).toStrictEqual(expectedParameters);
|
expect(computedParameters).toStrictEqual(expectedParameters);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should compute variables correctly', () => {
|
it('should compute and parse variables correctly', () => {
|
||||||
const parameters = {
|
const parameters = {
|
||||||
key1: `${computeVariable(executionStepThree.stepId, 'step3Key4.step3Key4ChildKey4')}`,
|
key1: `${computeVariable(executionStepThree.stepId, 'step3Key4.step3Key4ChildKey4')}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
const computedParameters = computeParameters(parameters, executionSteps);
|
const computedParameters = computeParameters(parameters, stepArguments, executionSteps);
|
||||||
const expectedParameters = {
|
const expectedParameters = {
|
||||||
key1: ["value1", "value2"],
|
key1: ["value1", "value2"],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(computedParameters).toStrictEqual(expectedParameters);
|
expect(computedParameters).toStrictEqual(expectedParameters);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should compute and parse variables in nested arrays correctly', () => {
|
||||||
|
const stepArguments = [
|
||||||
|
createDynamicArgument({
|
||||||
|
key: 'inputs',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: 'Key',
|
||||||
|
key: 'key',
|
||||||
|
required: true,
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Value',
|
||||||
|
key: 'value',
|
||||||
|
required: true,
|
||||||
|
variables: true,
|
||||||
|
valueType: 'parse',
|
||||||
|
}
|
||||||
|
],
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
const parameters = {
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
key: 'person',
|
||||||
|
value: '{ "name": "John Doe", "age": 32 }',
|
||||||
|
}
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const computedParameters = computeParameters(parameters, stepArguments, executionSteps);
|
||||||
|
const expectedParameters = {
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
key: 'person',
|
||||||
|
value: {
|
||||||
|
name: 'John Doe',
|
||||||
|
age: 32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(computedParameters).toStrictEqual(expectedParameters);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with number parameters', () => {
|
||||||
|
let stepArguments;
|
||||||
|
beforeEach(() => {
|
||||||
|
stepArguments = [
|
||||||
|
createStringArgument({
|
||||||
|
key: 'key1',
|
||||||
|
}),
|
||||||
|
createStringArgument({
|
||||||
|
key: 'key2',
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should resolve number larger than MAX_SAFE_INTEGER correctly', () => {
|
||||||
|
const parameters = {
|
||||||
|
// eslint-disable-next-line no-loss-of-precision
|
||||||
|
key1: 119007199254740999,
|
||||||
|
};
|
||||||
|
|
||||||
|
const computedParameters = computeParameters(parameters, stepArguments, executionSteps);
|
||||||
|
|
||||||
|
expect(computedParameters).toStrictEqual(parameters);
|
||||||
|
expect(computedParameters.key1 > Number.MAX_SAFE_INTEGER).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should resolve stringified number as-is', () => {
|
||||||
|
const parameters = {
|
||||||
|
key1: '119007199254740999',
|
||||||
|
};
|
||||||
|
|
||||||
|
const computedParameters = computeParameters(parameters, stepArguments, executionSteps);
|
||||||
|
|
||||||
|
expect(computedParameters).toStrictEqual(parameters);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should compute variables with int values correctly', () => {
|
||||||
|
const parameters = {
|
||||||
|
key1: `another static text ${computeVariable(executionStepThree.stepId, 'step3Key2')}`,
|
||||||
|
key2: `${computeVariable(executionStepThree.stepId, 'step3Key3')}`
|
||||||
|
};
|
||||||
|
|
||||||
|
const computedParameters = computeParameters(parameters, stepArguments, executionSteps);
|
||||||
|
const expectedParameters = {
|
||||||
|
key1: `another static text 123123`,
|
||||||
|
key2: `123123`
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(computedParameters).toStrictEqual(expectedParameters);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.todo('should compute variables with bigint values correctly', () => {
|
||||||
|
const parameters = {
|
||||||
|
key1: `another static text ${computeVariable(executionStepTwo.stepId, 'step2Key2')}`,
|
||||||
|
key2: `${computeVariable(executionStepTwo.stepId, 'step2Key3')}`
|
||||||
|
};
|
||||||
|
|
||||||
|
const computedParameters = computeParameters(parameters, stepArguments, executionSteps);
|
||||||
|
const expectedParameters = {
|
||||||
|
// The expected `key2` is computed wrongly.
|
||||||
|
key1: `another static text 6502380617126796383`,
|
||||||
|
key2: `6502380617126796383`
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(computedParameters).toStrictEqual(expectedParameters);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with boolean parameters', () => {
|
||||||
|
let stepArguments;
|
||||||
|
beforeEach(() => {
|
||||||
|
stepArguments = [
|
||||||
|
createDropdownArgument({
|
||||||
|
key: 'key1',
|
||||||
|
}),
|
||||||
|
createDropdownArgument({
|
||||||
|
key: 'key2',
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should resolve boolean as-is', () => {
|
||||||
|
const parameters = {
|
||||||
|
key1: true,
|
||||||
|
key2: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const computedParameters = computeParameters(parameters, stepArguments, executionSteps);
|
||||||
|
|
||||||
|
expect(computedParameters).toStrictEqual(parameters);
|
||||||
|
expect(computedParameters.key1).toBe(true);
|
||||||
|
expect(computedParameters.key2).toBe(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,8 +1,13 @@
|
|||||||
import logger from './logger.js';
|
import logger from './logger.js';
|
||||||
import objection from 'objection';
|
import objection from 'objection';
|
||||||
import * as Sentry from './sentry.ee.js';
|
import * as Sentry from './sentry.ee.js';
|
||||||
const { NotFoundError, DataError } = objection;
|
const { NotFoundError, DataError, ValidationError, UniqueViolationError } =
|
||||||
|
objection;
|
||||||
import HttpError from '../errors/http.js';
|
import HttpError from '../errors/http.js';
|
||||||
|
import {
|
||||||
|
renderObjectionError,
|
||||||
|
renderUniqueViolationError,
|
||||||
|
} from './renderer.js';
|
||||||
|
|
||||||
// Do not remove `next` argument as the function signature will not fit for an error handler middleware
|
// Do not remove `next` argument as the function signature will not fit for an error handler middleware
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
@@ -15,6 +20,14 @@ const errorHandler = (error, request, response, next) => {
|
|||||||
response.status(404).end();
|
response.status(404).end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (error instanceof ValidationError) {
|
||||||
|
renderObjectionError(response, error, 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error instanceof UniqueViolationError) {
|
||||||
|
renderUniqueViolationError(response, error);
|
||||||
|
}
|
||||||
|
|
||||||
if (error instanceof DataError) {
|
if (error instanceof DataError) {
|
||||||
response.status(400).end();
|
response.status(400).end();
|
||||||
}
|
}
|
||||||
|
@@ -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) => {
|
const renderError = (response, errors, status, type) => {
|
||||||
@@ -62,4 +64,31 @@ const renderError = (response, errors, status, type) => {
|
|||||||
return response.status(errorStatus).send(payload);
|
return response.status(errorStatus).send(payload);
|
||||||
};
|
};
|
||||||
|
|
||||||
export { renderObject, renderError };
|
const renderUniqueViolationError = (response, error) => {
|
||||||
|
const errors = error.columns.map((column) => ({
|
||||||
|
[column]: [`'${column}' must be unique.`],
|
||||||
|
}));
|
||||||
|
|
||||||
|
return renderError(response, errors, 422, 'UniqueViolationError');
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderObjectionError = (response, error, status) => {
|
||||||
|
const { statusCode, type, data = {} } = error;
|
||||||
|
|
||||||
|
const computedStatusCode = status || statusCode;
|
||||||
|
|
||||||
|
const computedErrors = Object.entries(data).map(
|
||||||
|
([fieldName, fieldErrors]) => ({
|
||||||
|
[fieldName]: fieldErrors.map(({ message }) => message),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return renderError(response, computedErrors, computedStatusCode, type);
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
renderObject,
|
||||||
|
renderError,
|
||||||
|
renderObjectionError,
|
||||||
|
renderUniqueViolationError,
|
||||||
|
};
|
||||||
|
@@ -15,6 +15,8 @@ class AppConfig extends Base {
|
|||||||
allowCustomConnection: { type: 'boolean', default: false },
|
allowCustomConnection: { type: 'boolean', default: false },
|
||||||
shared: { type: 'boolean', default: false },
|
shared: { type: 'boolean', default: false },
|
||||||
disabled: { type: 'boolean', default: false },
|
disabled: { type: 'boolean', default: false },
|
||||||
|
createdAt: { type: 'string' },
|
||||||
|
updatedAt: { type: 'string' },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -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) {
|
async verifyWebhook(request) {
|
||||||
if (!this.key) return true;
|
if (!this.key) return true;
|
||||||
|
|
||||||
|
@@ -198,6 +198,32 @@ class Step extends Base {
|
|||||||
return existingArguments;
|
return existingArguments;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getSetupAndDynamicFields() {
|
||||||
|
const setupFields = await this.getSetupFields();
|
||||||
|
const setupAndDynamicFields = [];
|
||||||
|
|
||||||
|
for (const setupField of setupFields) {
|
||||||
|
setupAndDynamicFields.push(setupField);
|
||||||
|
|
||||||
|
const additionalFields = setupField.additionalFields;
|
||||||
|
if (additionalFields) {
|
||||||
|
const keyArgument = additionalFields.arguments.find(
|
||||||
|
(argument) => argument.name === 'key'
|
||||||
|
);
|
||||||
|
const dynamicFieldsKey = keyArgument.value;
|
||||||
|
|
||||||
|
const dynamicFields = await this.createDynamicFields(
|
||||||
|
dynamicFieldsKey,
|
||||||
|
this.parameters
|
||||||
|
);
|
||||||
|
|
||||||
|
setupAndDynamicFields.push(...dynamicFields);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return setupAndDynamicFields;
|
||||||
|
}
|
||||||
|
|
||||||
async createDynamicFields(dynamicFieldsKey, parameters) {
|
async createDynamicFields(dynamicFieldsKey, parameters) {
|
||||||
const connection = await this.$relatedQuery('connection');
|
const connection = await this.$relatedQuery('connection');
|
||||||
const flow = await this.$relatedQuery('flow');
|
const flow = await this.$relatedQuery('flow');
|
||||||
@@ -240,8 +266,11 @@ class Step extends Base {
|
|||||||
})
|
})
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
|
const setupAndDynamicFields = await this.getSetupAndDynamicFields();
|
||||||
|
|
||||||
const computedParameters = computeParameters(
|
const computedParameters = computeParameters(
|
||||||
$.step.parameters,
|
$.step.parameters,
|
||||||
|
setupAndDynamicFields,
|
||||||
priorExecutionSteps
|
priorExecutionSteps
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -262,6 +291,25 @@ class Step extends Base {
|
|||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async delete() {
|
||||||
|
await this.$relatedQuery('executionSteps').delete();
|
||||||
|
await this.$query().delete();
|
||||||
|
|
||||||
|
const flow = await this.$relatedQuery('flow');
|
||||||
|
|
||||||
|
const nextSteps = await flow
|
||||||
|
.$relatedQuery('steps')
|
||||||
|
.where('position', '>', this.position);
|
||||||
|
|
||||||
|
const nextStepQueries = nextSteps.map(async (nextStep) => {
|
||||||
|
await nextStep.$query().patch({
|
||||||
|
position: nextStep.position - 1,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(nextStepQueries);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Step;
|
export default Step;
|
||||||
|
@@ -3,11 +3,22 @@ import asyncHandler from 'express-async-handler';
|
|||||||
import { authenticateUser } from '../../../../helpers/authentication.js';
|
import { authenticateUser } from '../../../../helpers/authentication.js';
|
||||||
import { authorizeAdmin } from '../../../../helpers/authorization.js';
|
import { authorizeAdmin } from '../../../../helpers/authorization.js';
|
||||||
import { checkIsEnterprise } from '../../../../helpers/check-is-enterprise.js';
|
import { checkIsEnterprise } from '../../../../helpers/check-is-enterprise.js';
|
||||||
|
import createConfigAction from '../../../../controllers/api/v1/admin/apps/create-config.ee.js';
|
||||||
import getAuthClientsAction from '../../../../controllers/api/v1/admin/apps/get-auth-clients.ee.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 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();
|
const router = Router();
|
||||||
|
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/:appKey/config',
|
||||||
|
authenticateUser,
|
||||||
|
authorizeAdmin,
|
||||||
|
checkIsEnterprise,
|
||||||
|
asyncHandler(createConfigAction)
|
||||||
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/:appKey/auth-clients',
|
'/:appKey/auth-clients',
|
||||||
authenticateUser,
|
authenticateUser,
|
||||||
@@ -16,6 +27,14 @@ router.get(
|
|||||||
asyncHandler(getAuthClientsAction)
|
asyncHandler(getAuthClientsAction)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/:appKey/auth-clients',
|
||||||
|
authenticateUser,
|
||||||
|
authorizeAdmin,
|
||||||
|
checkIsEnterprise,
|
||||||
|
asyncHandler(createAuthClientAction)
|
||||||
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/:appKey/auth-clients/:appAuthClientId',
|
'/:appKey/auth-clients/:appAuthClientId',
|
||||||
authenticateUser,
|
authenticateUser,
|
||||||
|
@@ -3,7 +3,8 @@ import asyncHandler from 'express-async-handler';
|
|||||||
import { authenticateUser } from '../../../helpers/authentication.js';
|
import { authenticateUser } from '../../../helpers/authentication.js';
|
||||||
import { authorizeUser } from '../../../helpers/authorization.js';
|
import { authorizeUser } from '../../../helpers/authorization.js';
|
||||||
import getFlowsAction from '../../../controllers/api/v1/connections/get-flows.js';
|
import getFlowsAction from '../../../controllers/api/v1/connections/get-flows.js';
|
||||||
import createTestAction from '../../../controllers/api/v1/connections/create-test.js';
|
import testConnectionAction from '../../../controllers/api/v1/connections/test-connection.js';
|
||||||
|
import verifyConnectionAction from '../../../controllers/api/v1/connections/verify-connection.js';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
@@ -18,7 +19,14 @@ router.post(
|
|||||||
'/:connectionId/test',
|
'/:connectionId/test',
|
||||||
authenticateUser,
|
authenticateUser,
|
||||||
authorizeUser,
|
authorizeUser,
|
||||||
asyncHandler(createTestAction)
|
asyncHandler(testConnectionAction)
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/:connectionId/verify',
|
||||||
|
authenticateUser,
|
||||||
|
authorizeUser,
|
||||||
|
asyncHandler(verifyConnectionAction)
|
||||||
);
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@@ -6,6 +6,7 @@ import getConnectionAction from '../../../controllers/api/v1/steps/get-connectio
|
|||||||
import getPreviousStepsAction from '../../../controllers/api/v1/steps/get-previous-steps.js';
|
import getPreviousStepsAction from '../../../controllers/api/v1/steps/get-previous-steps.js';
|
||||||
import createDynamicFieldsAction from '../../../controllers/api/v1/steps/create-dynamic-fields.js';
|
import createDynamicFieldsAction from '../../../controllers/api/v1/steps/create-dynamic-fields.js';
|
||||||
import createDynamicDataAction from '../../../controllers/api/v1/steps/create-dynamic-data.js';
|
import createDynamicDataAction from '../../../controllers/api/v1/steps/create-dynamic-data.js';
|
||||||
|
import deleteStepAction from '../../../controllers/api/v1/steps/delete-step.js';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
@@ -37,4 +38,11 @@ router.post(
|
|||||||
asyncHandler(createDynamicDataAction)
|
asyncHandler(createDynamicDataAction)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
router.delete(
|
||||||
|
'/:stepId',
|
||||||
|
authenticateUser,
|
||||||
|
authorizeUser,
|
||||||
|
asyncHandler(deleteStepAction)
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@@ -31,8 +31,11 @@ export const processAction = async (options) => {
|
|||||||
execution_id: $.execution.id,
|
execution_id: $.execution.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const stepSetupAndDynamicFields = await step.getSetupAndDynamicFields();
|
||||||
|
|
||||||
const computedParameters = computeParameters(
|
const computedParameters = computeParameters(
|
||||||
$.step.parameters,
|
$.step.parameters,
|
||||||
|
stepSetupAndDynamicFields,
|
||||||
priorExecutionSteps
|
priorExecutionSteps
|
||||||
);
|
);
|
||||||
|
|
||||||
|
72
packages/backend/test/factories/app.js
Normal file
72
packages/backend/test/factories/app.js
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import { faker } from '@faker-js/faker';
|
||||||
|
|
||||||
|
export const createArgument = (params = {}) => {
|
||||||
|
const labelAndKey = faker.lorem.word();
|
||||||
|
|
||||||
|
const argument = {
|
||||||
|
label: labelAndKey,
|
||||||
|
key: labelAndKey,
|
||||||
|
required: false,
|
||||||
|
variables: true,
|
||||||
|
...params,
|
||||||
|
};
|
||||||
|
|
||||||
|
return argument;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createStringArgument = (params = {}) => {
|
||||||
|
const stringArgument = createArgument({
|
||||||
|
...params,
|
||||||
|
type: 'string',
|
||||||
|
});
|
||||||
|
|
||||||
|
return stringArgument;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createDropdownArgument = (params = {}) => {
|
||||||
|
const dropdownArgument = createArgument({
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: 'Yes',
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'No',
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
...params,
|
||||||
|
type: 'dropdown',
|
||||||
|
});
|
||||||
|
|
||||||
|
return dropdownArgument;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createDynamicArgument = (params = {}) => {
|
||||||
|
const dynamicArgument = createArgument({
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
key: '',
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: 'Key',
|
||||||
|
key: 'key',
|
||||||
|
required: true,
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Value',
|
||||||
|
key: 'value',
|
||||||
|
required: true,
|
||||||
|
variables: true,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
...params,
|
||||||
|
type: 'dynamic',
|
||||||
|
});
|
||||||
|
|
||||||
|
return dynamicArgument;
|
||||||
|
};
|
@@ -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;
|
@@ -0,0 +1,19 @@
|
|||||||
|
const createAppConfigMock = (appConfig) => {
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
key: appConfig.key,
|
||||||
|
allowCustomConnection: appConfig.allowCustomConnection,
|
||||||
|
shared: appConfig.shared,
|
||||||
|
disabled: appConfig.disabled,
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
count: 1,
|
||||||
|
currentPage: null,
|
||||||
|
isArray: false,
|
||||||
|
totalPages: null,
|
||||||
|
type: 'AppConfig',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default createAppConfigMock;
|
@@ -8,7 +8,9 @@ global.beforeAll(async () => {
|
|||||||
logger.silent = true;
|
logger.silent = true;
|
||||||
|
|
||||||
// Remove default roles and permissions before running the test suite
|
// Remove default roles and permissions before running the test suite
|
||||||
await knex.raw('TRUNCATE TABLE config, roles, permissions CASCADE');
|
await knex.raw('TRUNCATE TABLE config CASCADE');
|
||||||
|
await knex.raw('TRUNCATE TABLE roles CASCADE');
|
||||||
|
await knex.raw('TRUNCATE TABLE permissions CASCADE');
|
||||||
});
|
});
|
||||||
|
|
||||||
global.beforeEach(async () => {
|
global.beforeEach(async () => {
|
||||||
|
@@ -11,7 +11,6 @@ export class FlowEditorPage extends AuthenticatedPage {
|
|||||||
constructor(page) {
|
constructor(page) {
|
||||||
super(page);
|
super(page);
|
||||||
|
|
||||||
this.page = page;
|
|
||||||
this.appAutocomplete = this.page.getByTestId('choose-app-autocomplete');
|
this.appAutocomplete = this.page.getByTestId('choose-app-autocomplete');
|
||||||
this.eventAutocomplete = this.page.getByTestId('choose-event-autocomplete');
|
this.eventAutocomplete = this.page.getByTestId('choose-event-autocomplete');
|
||||||
this.continueButton = this.page.getByTestId('flow-substep-continue-button');
|
this.continueButton = this.page.getByTestId('flow-substep-continue-button');
|
||||||
@@ -77,7 +76,8 @@ export class FlowEditorPage extends AuthenticatedPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async testAndContinue() {
|
async testAndContinue() {
|
||||||
await this.continueButton.last().click();
|
await expect(this.continueButton).toHaveCount(1);
|
||||||
|
await this.continueButton.click();
|
||||||
await expect(this.testOutput).toBeVisible();
|
await expect(this.testOutput).toBeVisible();
|
||||||
await this.continueButton.click();
|
await this.continueButton.click();
|
||||||
}
|
}
|
||||||
|
@@ -20,14 +20,16 @@ test.describe('Webhook flow', () => {
|
|||||||
|
|
||||||
await flowEditorPage.chooseAppAndEvent('Webhook', 'Respond with');
|
await flowEditorPage.chooseAppAndEvent('Webhook', 'Respond with');
|
||||||
|
|
||||||
await expect(flowEditorPage.continueButton.last()).not.toBeEnabled();
|
await expect(flowEditorPage.continueButton).toHaveCount(1);
|
||||||
|
await expect(flowEditorPage.continueButton).not.toBeEnabled();
|
||||||
|
|
||||||
await page
|
await page
|
||||||
.getByTestId('parameters.statusCode-power-input')
|
.getByTestId('parameters.statusCode-power-input')
|
||||||
.locator('[contenteditable]')
|
.locator('[contenteditable]')
|
||||||
.fill('200');
|
.fill('200');
|
||||||
await flowEditorPage.clickAway();
|
await flowEditorPage.clickAway();
|
||||||
await expect(flowEditorPage.continueButton.last()).not.toBeEnabled();
|
await expect(flowEditorPage.continueButton).toHaveCount(1);
|
||||||
|
await expect(flowEditorPage.continueButton).not.toBeEnabled();
|
||||||
|
|
||||||
await page
|
await page
|
||||||
.getByTestId('parameters.body-power-input')
|
.getByTestId('parameters.body-power-input')
|
||||||
@@ -54,15 +56,16 @@ test.describe('Webhook flow', () => {
|
|||||||
const asyncWebhookUrl = await flowEditorPage.createWebhookTrigger(false);
|
const asyncWebhookUrl = await flowEditorPage.createWebhookTrigger(false);
|
||||||
|
|
||||||
await flowEditorPage.chooseAppAndEvent('Webhook', 'Respond with');
|
await flowEditorPage.chooseAppAndEvent('Webhook', 'Respond with');
|
||||||
|
await expect(flowEditorPage.continueButton).toHaveCount(1);
|
||||||
await expect(flowEditorPage.continueButton.last()).not.toBeEnabled();
|
await expect(flowEditorPage.continueButton).not.toBeEnabled();
|
||||||
|
|
||||||
await page
|
await page
|
||||||
.getByTestId('parameters.statusCode-power-input')
|
.getByTestId('parameters.statusCode-power-input')
|
||||||
.locator('[contenteditable]')
|
.locator('[contenteditable]')
|
||||||
.fill('200');
|
.fill('200');
|
||||||
await flowEditorPage.clickAway();
|
await flowEditorPage.clickAway();
|
||||||
await expect(flowEditorPage.continueButton.last()).not.toBeEnabled();
|
await expect(flowEditorPage.continueButton).toHaveCount(1);
|
||||||
|
await expect(flowEditorPage.continueButton).not.toBeEnabled();
|
||||||
|
|
||||||
await page
|
await page
|
||||||
.getByTestId('parameters.body-power-input')
|
.getByTestId('parameters.body-power-input')
|
||||||
|
@@ -7,7 +7,6 @@ import DialogContentText from '@mui/material/DialogContentText';
|
|||||||
import DialogTitle from '@mui/material/DialogTitle';
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
|
||||||
|
|
||||||
import { AppPropType } from 'propTypes/propTypes';
|
import { AppPropType } from 'propTypes/propTypes';
|
||||||
import AppAuthClientsDialog from 'components/AppAuthClientsDialog/index.ee';
|
import AppAuthClientsDialog from 'components/AppAuthClientsDialog/index.ee';
|
||||||
@@ -18,6 +17,7 @@ import useFormatMessage from 'hooks/useFormatMessage';
|
|||||||
import { generateExternalLink } from 'helpers/translationValues';
|
import { generateExternalLink } from 'helpers/translationValues';
|
||||||
import { Form } from './style';
|
import { Form } from './style';
|
||||||
import useAppAuth from 'hooks/useAppAuth';
|
import useAppAuth from 'hooks/useAppAuth';
|
||||||
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
function AddAppConnection(props) {
|
function AddAppConnection(props) {
|
||||||
const { application, connectionId, onClose } = props;
|
const { application, connectionId, onClose } = props;
|
||||||
|
@@ -1,12 +1,11 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { useCallback, useMemo } from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
import { useMutation } from '@apollo/client';
|
|
||||||
|
|
||||||
import { AppPropType } from 'propTypes/propTypes';
|
import { AppPropType } from 'propTypes/propTypes';
|
||||||
import { CREATE_APP_CONFIG } from 'graphql/mutations/create-app-config';
|
import useAdminCreateAppConfig from 'hooks/useAdminCreateAppConfig';
|
||||||
import { CREATE_APP_AUTH_CLIENT } from 'graphql/mutations/create-app-auth-client';
|
|
||||||
import useAppConfig from 'hooks/useAppConfig.ee';
|
import useAppConfig from 'hooks/useAppConfig.ee';
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
|
import useAdminCreateAppAuthClient from 'hooks/useAdminCreateAppAuthClient.ee';
|
||||||
import AdminApplicationAuthClientDialog from 'components/AdminApplicationAuthClientDialog';
|
import AdminApplicationAuthClientDialog from 'components/AdminApplicationAuthClientDialog';
|
||||||
import useAppAuth from 'hooks/useAppAuth';
|
import useAppAuth from 'hooks/useAppAuth';
|
||||||
|
|
||||||
@@ -18,51 +17,38 @@ function AdminApplicationCreateAuthClient(props) {
|
|||||||
const { data: appConfig, isLoading: isAppConfigLoading } =
|
const { data: appConfig, isLoading: isAppConfigLoading } =
|
||||||
useAppConfig(appKey);
|
useAppConfig(appKey);
|
||||||
|
|
||||||
const [
|
const {
|
||||||
createAppConfig,
|
mutateAsync: createAppConfig,
|
||||||
{ loading: loadingCreateAppConfig, error: createAppConfigError },
|
isPending: isCreateAppConfigPending,
|
||||||
] = useMutation(CREATE_APP_CONFIG, {
|
error: createAppConfigError
|
||||||
refetchQueries: ['GetAppConfig'],
|
} = useAdminCreateAppConfig(props.appKey);
|
||||||
context: { autoSnackbar: false },
|
|
||||||
});
|
|
||||||
|
|
||||||
const [
|
const {
|
||||||
createAppAuthClient,
|
mutateAsync: createAppAuthClient,
|
||||||
{ loading: loadingCreateAppAuthClient, error: createAppAuthClientError },
|
isPending: isCreateAppAuthClientPending,
|
||||||
] = useMutation(CREATE_APP_AUTH_CLIENT, {
|
error: createAppAuthClientError,
|
||||||
refetchQueries: ['GetAppAuthClients'],
|
} = useAdminCreateAppAuthClient(appKey);
|
||||||
context: { autoSnackbar: false },
|
|
||||||
});
|
|
||||||
|
|
||||||
const submitHandler = async (values) => {
|
const submitHandler = async (values) => {
|
||||||
let appConfigId = appConfig?.data?.id;
|
let appConfigId = appConfig?.data?.id;
|
||||||
|
|
||||||
if (!appConfigId) {
|
if (!appConfigId) {
|
||||||
const { data: appConfigData } = await createAppConfig({
|
const { data: appConfigData } = await createAppConfig({
|
||||||
variables: {
|
allowCustomConnection: true,
|
||||||
input: {
|
|
||||||
key: appKey,
|
|
||||||
allowCustomConnection: false,
|
|
||||||
shared: false,
|
shared: false,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
appConfigId = appConfigData.createAppConfig.id;
|
appConfigId = appConfigData.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { name, active, ...formattedAuthDefaults } = values;
|
const { name, active, ...formattedAuthDefaults } = values;
|
||||||
|
|
||||||
await createAppAuthClient({
|
await createAppAuthClient({
|
||||||
variables: {
|
appKey,
|
||||||
input: {
|
|
||||||
appConfigId,
|
|
||||||
name,
|
name,
|
||||||
active,
|
active,
|
||||||
formattedAuthDefaults,
|
formattedAuthDefaults,
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onClose();
|
onClose();
|
||||||
@@ -103,7 +89,7 @@ function AdminApplicationCreateAuthClient(props) {
|
|||||||
loading={isAppConfigLoading}
|
loading={isAppConfigLoading}
|
||||||
submitHandler={submitHandler}
|
submitHandler={submitHandler}
|
||||||
authFields={auth?.data?.fields}
|
authFields={auth?.data?.fields}
|
||||||
submitting={loadingCreateAppConfig || loadingCreateAppAuthClient}
|
submitting={isCreateAppConfigPending || isCreateAppAuthClientPending}
|
||||||
defaultValues={defaultValues}
|
defaultValues={defaultValues}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@@ -8,11 +8,11 @@ import Stack from '@mui/material/Stack';
|
|||||||
import LoadingButton from '@mui/lab/LoadingButton';
|
import LoadingButton from '@mui/lab/LoadingButton';
|
||||||
import { useMutation } from '@apollo/client';
|
import { useMutation } from '@apollo/client';
|
||||||
|
|
||||||
import { CREATE_APP_CONFIG } from 'graphql/mutations/create-app-config';
|
|
||||||
import { UPDATE_APP_CONFIG } from 'graphql/mutations/update-app-config';
|
import { UPDATE_APP_CONFIG } from 'graphql/mutations/update-app-config';
|
||||||
import Form from 'components/Form';
|
import Form from 'components/Form';
|
||||||
import { Switch } from './style';
|
import { Switch } from './style';
|
||||||
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
|
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
|
||||||
|
import useAdminCreateAppConfig from 'hooks/useAdminCreateAppConfig';
|
||||||
|
|
||||||
function AdminApplicationSettings(props) {
|
function AdminApplicationSettings(props) {
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
@@ -20,12 +20,10 @@ function AdminApplicationSettings(props) {
|
|||||||
|
|
||||||
const { data: appConfig, isLoading: loading } = useAppConfig(props.appKey);
|
const { data: appConfig, isLoading: loading } = useAppConfig(props.appKey);
|
||||||
|
|
||||||
const [createAppConfig, { loading: loadingCreateAppConfig }] = useMutation(
|
const {
|
||||||
CREATE_APP_CONFIG,
|
mutateAsync: createAppConfig,
|
||||||
{
|
isPending: isCreateAppConfigPending,
|
||||||
refetchQueries: ['GetAppConfig'],
|
} = useAdminCreateAppConfig(props.appKey);
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const [updateAppConfig, { loading: loadingUpdateAppConfig }] = useMutation(
|
const [updateAppConfig, { loading: loadingUpdateAppConfig }] = useMutation(
|
||||||
UPDATE_APP_CONFIG,
|
UPDATE_APP_CONFIG,
|
||||||
@@ -37,11 +35,7 @@ function AdminApplicationSettings(props) {
|
|||||||
const handleSubmit = async (values) => {
|
const handleSubmit = async (values) => {
|
||||||
try {
|
try {
|
||||||
if (!appConfig?.data) {
|
if (!appConfig?.data) {
|
||||||
await createAppConfig({
|
await createAppConfig(values);
|
||||||
variables: {
|
|
||||||
input: { key: props.appKey, ...values },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
await updateAppConfig({
|
await updateAppConfig({
|
||||||
variables: {
|
variables: {
|
||||||
@@ -108,7 +102,7 @@ function AdminApplicationSettings(props) {
|
|||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
sx={{ boxShadow: 2, mt: 5 }}
|
sx={{ boxShadow: 2, mt: 5 }}
|
||||||
loading={loadingCreateAppConfig || loadingUpdateAppConfig}
|
loading={isCreateAppConfigPending || loadingUpdateAppConfig}
|
||||||
disabled={!isDirty || loading}
|
disabled={!isDirty || loading}
|
||||||
>
|
>
|
||||||
{formatMessage('adminAppsSettings.save')}
|
{formatMessage('adminAppsSettings.save')}
|
||||||
|
@@ -16,7 +16,7 @@ function AdminApplicationUpdateAuthClient(props) {
|
|||||||
const { clientId } = useParams();
|
const { clientId } = useParams();
|
||||||
|
|
||||||
const { data: adminAppAuthClient, isLoading: isAdminAuthClientLoading } =
|
const { data: adminAppAuthClient, isLoading: isAdminAuthClientLoading } =
|
||||||
useAdminAppAuthClient(clientId);
|
useAdminAppAuthClient(application.key, clientId);
|
||||||
|
|
||||||
const { data: auth } = useAppAuth(application.key);
|
const { data: auth } = useAppAuth(application.key);
|
||||||
|
|
||||||
|
@@ -13,6 +13,7 @@ import CircularProgress from '@mui/material/CircularProgress';
|
|||||||
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
import * as yup from 'yup';
|
import * as yup from 'yup';
|
||||||
|
import { isEqual } from 'lodash';
|
||||||
|
|
||||||
import { EditorContext } from 'contexts/Editor';
|
import { EditorContext } from 'contexts/Editor';
|
||||||
import { StepExecutionsProvider } from 'contexts/StepExecutions';
|
import { StepExecutionsProvider } from 'contexts/StepExecutions';
|
||||||
@@ -177,7 +178,9 @@ function FlowStep(props) {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleSubmit = (val) => {
|
const handleSubmit = (val) => {
|
||||||
|
if (!isEqual(step, val)) {
|
||||||
handleChange({ step: val });
|
handleChange({ step: val });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const stepValidationSchema = React.useMemo(
|
const stepValidationSchema = React.useMemo(
|
||||||
|
@@ -12,7 +12,6 @@ import TextField from 'components/TextField';
|
|||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
import useCreateAccessToken from 'hooks/useCreateAccessToken';
|
import useCreateAccessToken from 'hooks/useCreateAccessToken';
|
||||||
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
|
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
|
||||||
import usePreventUsersFromUpdatingTheirProfile from 'hooks/usePreventUsersFromUpdatingTheirProfile';
|
|
||||||
|
|
||||||
function LoginForm() {
|
function LoginForm() {
|
||||||
const isCloud = useCloud();
|
const isCloud = useCloud();
|
||||||
@@ -20,7 +19,6 @@ function LoginForm() {
|
|||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
const enqueueSnackbar = useEnqueueSnackbar();
|
const enqueueSnackbar = useEnqueueSnackbar();
|
||||||
const authentication = useAuthentication();
|
const authentication = useAuthentication();
|
||||||
const preventUsersFromUpdatingTheirProfile = usePreventUsersFromUpdatingTheirProfile();
|
|
||||||
const { mutateAsync: createAccessToken, isPending: loading } =
|
const { mutateAsync: createAccessToken, isPending: loading } =
|
||||||
useCreateAccessToken();
|
useCreateAccessToken();
|
||||||
|
|
||||||
@@ -86,7 +84,7 @@ function LoginForm() {
|
|||||||
sx={{ mb: 1 }}
|
sx={{ mb: 1 }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{isCloud && !preventUsersFromUpdatingTheirProfile && (
|
{isCloud && (
|
||||||
<Link
|
<Link
|
||||||
component={RouterLink}
|
component={RouterLink}
|
||||||
to={URLS.FORGOT_PASSWORD}
|
to={URLS.FORGOT_PASSWORD}
|
||||||
|
@@ -40,10 +40,6 @@ function Switch(props) {
|
|||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
className={className}
|
className={className}
|
||||||
{...FormControlLabelProps}
|
{...FormControlLabelProps}
|
||||||
componentsProps={{
|
|
||||||
typography: {
|
|
||||||
display: 'flex',
|
|
||||||
}}}
|
|
||||||
control={
|
control={
|
||||||
<MuiSwitch
|
<MuiSwitch
|
||||||
{...switchProps}
|
{...switchProps}
|
||||||
|
@@ -62,11 +62,11 @@ function TestSubstep(props) {
|
|||||||
|
|
||||||
React.useEffect(
|
React.useEffect(
|
||||||
function resetTestDataOnSubstepToggle() {
|
function resetTestDataOnSubstepToggle() {
|
||||||
if (!expanded) {
|
if (!expanded && !loading) {
|
||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[expanded, reset],
|
[expanded, reset, loading],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSubmit = React.useCallback(async () => {
|
const handleSubmit = React.useCallback(async () => {
|
||||||
@@ -118,7 +118,11 @@ function TestSubstep(props) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{hasNoOutput && (
|
{hasNoOutput && (
|
||||||
<Alert data-test="flow-test-substep-no-output" severity="warning" sx={{ mb: 1, width: '100%' }}>
|
<Alert
|
||||||
|
data-test="flow-test-substep-no-output"
|
||||||
|
severity="warning"
|
||||||
|
sx={{ mb: 1, width: '100%' }}
|
||||||
|
>
|
||||||
<AlertTitle sx={{ fontWeight: 700 }}>
|
<AlertTitle sx={{ fontWeight: 700 }}>
|
||||||
{formatMessage('flowEditor.noTestDataTitle')}
|
{formatMessage('flowEditor.noTestDataTitle')}
|
||||||
</AlertTitle>
|
</AlertTitle>
|
||||||
|
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
@@ -1,12 +0,0 @@
|
|||||||
import { gql } from '@apollo/client';
|
|
||||||
export const CREATE_APP_CONFIG = gql`
|
|
||||||
mutation CreateAppConfig($input: CreateAppConfigInput) {
|
|
||||||
createAppConfig(input: $input) {
|
|
||||||
id
|
|
||||||
key
|
|
||||||
allowCustomConnection
|
|
||||||
shared
|
|
||||||
disabled
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
@@ -2,17 +2,17 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
|
|
||||||
import api from 'helpers/api';
|
import api from 'helpers/api';
|
||||||
|
|
||||||
export default function useAdminAppAuthClient(id) {
|
export default function useAdminAppAuthClient(appKey, id) {
|
||||||
const query = useQuery({
|
const query = useQuery({
|
||||||
queryKey: ['admin', 'appAuthClients', id],
|
queryKey: ['admin', 'apps', appKey, 'authClients', id],
|
||||||
queryFn: async ({ signal }) => {
|
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,
|
signal,
|
||||||
});
|
});
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
enabled: !!id,
|
enabled: !!appKey && !!id,
|
||||||
});
|
});
|
||||||
|
|
||||||
return query;
|
return query;
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import api from 'helpers/api';
|
import api from 'helpers/api';
|
||||||
|
|
||||||
export default function useAdminAppAuthClient(appKey) {
|
export default function useAdminAppAuthClients(appKey) {
|
||||||
const query = useQuery({
|
const query = useQuery({
|
||||||
queryKey: ['admin', 'apps', appKey, 'auth-clients'],
|
queryKey: ['admin', 'apps', appKey, 'authClients'],
|
||||||
queryFn: async ({ signal }) => {
|
queryFn: async ({ signal }) => {
|
||||||
const { data } = await api.get(`/v1/admin/apps/${appKey}/auth-clients`, {
|
const { data } = await api.get(`/v1/admin/apps/${appKey}/auth-clients`, {
|
||||||
signal,
|
signal,
|
||||||
|
21
packages/web/src/hooks/useAdminCreateAppAuthClient.ee.js
Normal file
21
packages/web/src/hooks/useAdminCreateAppAuthClient.ee.js
Normal file
@@ -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;
|
||||||
|
}
|
21
packages/web/src/hooks/useAdminCreateAppConfig.js
Normal file
21
packages/web/src/hooks/useAdminCreateAppConfig.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import api from 'helpers/api';
|
||||||
|
|
||||||
|
export default function useAdminCreateAppConfig(appKey) {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const query = useMutation({
|
||||||
|
mutationFn: async (payload) => {
|
||||||
|
const { data } = await api.post(`/v1/admin/apps/${appKey}/config`, payload);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ['apps', appKey, 'config'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
@@ -1,13 +0,0 @@
|
|||||||
import useAutomatischConfig from 'hooks/useAutomatischConfig';
|
|
||||||
|
|
||||||
export default function usePreventUsersFromUpdatingTheirProfile() {
|
|
||||||
const { data, isSuccess } =
|
|
||||||
useAutomatischConfig();
|
|
||||||
const automatischConfig = data?.data;
|
|
||||||
|
|
||||||
const preventUsersFromUpdatingTheirProfile = isSuccess ? automatischConfig['userManagement.preventUsersFromUpdatingTheirProfile'] : false;
|
|
||||||
|
|
||||||
console.log('preventUsersFromUpdatingTheirProfile', preventUsersFromUpdatingTheirProfile, automatischConfig)
|
|
||||||
|
|
||||||
return preventUsersFromUpdatingTheirProfile;
|
|
||||||
}
|
|
@@ -259,26 +259,20 @@
|
|||||||
"userInterfacePage.primaryLightColorFieldLabel": "Primary light color",
|
"userInterfacePage.primaryLightColorFieldLabel": "Primary light color",
|
||||||
"userInterfacePage.svgDataFieldLabel": "Logo SVG code",
|
"userInterfacePage.svgDataFieldLabel": "Logo SVG code",
|
||||||
"userInterfacePage.submit": "Update",
|
"userInterfacePage.submit": "Update",
|
||||||
"authenticationPage.title": "Authentication",
|
"authenticationPage.title": "Single Sign-On with SAML",
|
||||||
"authenticationConfig.title": "User management",
|
"authenticationForm.active": "Active",
|
||||||
"authenticationConfig.userManagementPreventUsersFromUpdatingTheirProfile": "Prevent users from updating their profile",
|
"authenticationForm.name": "Name",
|
||||||
"authenticationConfig.userManagementPreventUsersFromUpdatingTheirProfileTooltip": "This will prevent users from updating their full name, email and password. This is useful when you want to manage users from your own identity provider.",
|
"authenticationForm.certificate": "Certificate",
|
||||||
"authenticationConfig.save": "Save",
|
"authenticationForm.signatureAlgorithm": "Signature algorithm",
|
||||||
"authenticationConfig.successfullySaved": "The configuration has been saved.",
|
"authenticationForm.issuer": "Issuer",
|
||||||
"samlAuthenticationPage.title": "Single Sign-On with SAML",
|
"authenticationForm.entryPoint": "Entry point",
|
||||||
"samlAuthenticationForm.active": "Active",
|
"authenticationForm.firstnameAttributeName": "Firstname attribute name",
|
||||||
"samlAuthenticationForm.name": "Name",
|
"authenticationForm.surnameAttributeName": "Surname attribute name",
|
||||||
"samlAuthenticationForm.certificate": "Certificate",
|
"authenticationForm.emailAttributeName": "Email attribute name",
|
||||||
"samlAuthenticationForm.signatureAlgorithm": "Signature algorithm",
|
"authenticationForm.roleAttributeName": "Role attribute name",
|
||||||
"samlAuthenticationForm.issuer": "Issuer",
|
"authenticationForm.defaultRole": "Default role",
|
||||||
"samlAuthenticationForm.entryPoint": "Entry point",
|
"authenticationForm.successfullySaved": "The provider has been saved.",
|
||||||
"samlAuthenticationForm.firstnameAttributeName": "Firstname attribute name",
|
"authenticationForm.save": "Save",
|
||||||
"samlAuthenticationForm.surnameAttributeName": "Surname attribute name",
|
|
||||||
"samlAuthenticationForm.emailAttributeName": "Email attribute name",
|
|
||||||
"samlAuthenticationForm.roleAttributeName": "Role attribute name",
|
|
||||||
"samlAuthenticationForm.defaultRole": "Default role",
|
|
||||||
"samlAuthenticationForm.successfullySaved": "The provider has been saved.",
|
|
||||||
"samlAuthenticationForm.save": "Save",
|
|
||||||
"roleMappingsForm.title": "Role mappings",
|
"roleMappingsForm.title": "Role mappings",
|
||||||
"roleMappingsForm.remoteRoleName": "Remote role name",
|
"roleMappingsForm.remoteRoleName": "Remote role name",
|
||||||
"roleMappingsForm.role": "Role",
|
"roleMappingsForm.role": "Role",
|
||||||
|
@@ -1,98 +0,0 @@
|
|||||||
import { useMutation } from '@apollo/client';
|
|
||||||
import LoadingButton from '@mui/lab/LoadingButton';
|
|
||||||
import Stack from '@mui/material/Stack';
|
|
||||||
import Switch from 'components/Switch';
|
|
||||||
import Typography from '@mui/material/Typography';
|
|
||||||
import Tooltip from '@mui/material/Tooltip';
|
|
||||||
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
|
|
||||||
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
|
|
||||||
|
|
||||||
import Form from 'components/Form';
|
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
|
||||||
import useAutomatischConfig from 'hooks/useAutomatischConfig';
|
|
||||||
import { UPDATE_CONFIG } from 'graphql/mutations/update-config.ee';
|
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
|
||||||
|
|
||||||
function AuthenticationConfig() {
|
|
||||||
const formatMessage = useFormatMessage();
|
|
||||||
const enqueueSnackbar = useEnqueueSnackbar();
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
const { data, isLoading: isAutomatischConfigLoading } =
|
|
||||||
useAutomatischConfig();
|
|
||||||
const automatischConfig = data?.data;
|
|
||||||
|
|
||||||
const [
|
|
||||||
updateConfig,
|
|
||||||
{ loading: updateConfigLoading },
|
|
||||||
] = useMutation(UPDATE_CONFIG);
|
|
||||||
|
|
||||||
const handleSubmit = async (values) => {
|
|
||||||
try {
|
|
||||||
await updateConfig({
|
|
||||||
variables: {
|
|
||||||
input: {
|
|
||||||
'userManagement.preventUsersFromUpdatingTheirProfile': values.userManagement.preventUsersFromUpdatingTheirProfile,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await queryClient.invalidateQueries({
|
|
||||||
queryKey: ['automatisch', 'config'],
|
|
||||||
});
|
|
||||||
|
|
||||||
enqueueSnackbar(formatMessage('authenticationConfig.successfullySaved'), {
|
|
||||||
variant: 'success',
|
|
||||||
SnackbarProps: {
|
|
||||||
'data-test': 'snackbar-update-role-mappings-success',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error('Failed while saving!');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isAutomatischConfigLoading) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Typography variant="h4">
|
|
||||||
{formatMessage('authenticationConfig.title')}
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<Form defaultValues={automatischConfig} onSubmit={handleSubmit}>
|
|
||||||
<Stack direction="column" spacing={2}>
|
|
||||||
<Switch
|
|
||||||
name="userManagement.preventUsersFromUpdatingTheirProfile"
|
|
||||||
label={<>
|
|
||||||
{formatMessage('authenticationConfig.userManagementPreventUsersFromUpdatingTheirProfile')}
|
|
||||||
|
|
||||||
<Tooltip
|
|
||||||
title={formatMessage('authenticationConfig.userManagementPreventUsersFromUpdatingTheirProfileTooltip')}
|
|
||||||
sx={{ ml: 1 }}
|
|
||||||
>
|
|
||||||
<InfoOutlinedIcon />
|
|
||||||
</Tooltip>
|
|
||||||
</>}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<LoadingButton
|
|
||||||
type="submit"
|
|
||||||
variant="contained"
|
|
||||||
color="primary"
|
|
||||||
sx={{ boxShadow: 2 }}
|
|
||||||
loading={updateConfigLoading}
|
|
||||||
>
|
|
||||||
{formatMessage('authenticationConfig.save')}
|
|
||||||
</LoadingButton>
|
|
||||||
</Stack>
|
|
||||||
</Form>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
AuthenticationConfig.propTypes = {
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AuthenticationConfig;
|
|
@@ -1,6 +1,7 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useMutation } from '@apollo/client';
|
import { useMutation } from '@apollo/client';
|
||||||
import LoadingButton from '@mui/lab/LoadingButton';
|
import LoadingButton from '@mui/lab/LoadingButton';
|
||||||
|
import Divider from '@mui/material/Divider';
|
||||||
import Stack from '@mui/material/Stack';
|
import Stack from '@mui/material/Stack';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
|
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
|
||||||
@@ -84,6 +85,7 @@ function RoleMappings({ provider, providerLoading }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<Divider sx={{ pt: 2 }} />
|
||||||
<Typography variant="h3">
|
<Typography variant="h3">
|
||||||
{formatMessage('roleMappingsForm.title')}
|
{formatMessage('roleMappingsForm.title')}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
@@ -75,7 +75,7 @@ function SamlConfiguration({ provider, providerLoading }) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
enqueueSnackbar(formatMessage('samlAuthenticationForm.successfullySaved'), {
|
enqueueSnackbar(formatMessage('authenticationForm.successfullySaved'), {
|
||||||
variant: 'success',
|
variant: 'success',
|
||||||
SnackbarProps: {
|
SnackbarProps: {
|
||||||
'data-test': 'snackbar-save-saml-provider-success',
|
'data-test': 'snackbar-save-saml-provider-success',
|
||||||
@@ -98,18 +98,18 @@ function SamlConfiguration({ provider, providerLoading }) {
|
|||||||
<Stack direction="column" gap={2}>
|
<Stack direction="column" gap={2}>
|
||||||
<Switch
|
<Switch
|
||||||
name="active"
|
name="active"
|
||||||
label={formatMessage('samlAuthenticationForm.active')}
|
label={formatMessage('authenticationForm.active')}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
required={true}
|
required={true}
|
||||||
name="name"
|
name="name"
|
||||||
label={formatMessage('samlAuthenticationForm.name')}
|
label={formatMessage('authenticationForm.name')}
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
required={true}
|
required={true}
|
||||||
name="certificate"
|
name="certificate"
|
||||||
label={formatMessage('samlAuthenticationForm.certificate')}
|
label={formatMessage('authenticationForm.certificate')}
|
||||||
fullWidth
|
fullWidth
|
||||||
multiline
|
multiline
|
||||||
/>
|
/>
|
||||||
@@ -126,44 +126,44 @@ function SamlConfiguration({ provider, providerLoading }) {
|
|||||||
renderInput={(params) => (
|
renderInput={(params) => (
|
||||||
<MuiTextField
|
<MuiTextField
|
||||||
{...params}
|
{...params}
|
||||||
label={formatMessage('samlAuthenticationForm.signatureAlgorithm')}
|
label={formatMessage('authenticationForm.signatureAlgorithm')}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
required={true}
|
required={true}
|
||||||
name="issuer"
|
name="issuer"
|
||||||
label={formatMessage('samlAuthenticationForm.issuer')}
|
label={formatMessage('authenticationForm.issuer')}
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
required={true}
|
required={true}
|
||||||
name="entryPoint"
|
name="entryPoint"
|
||||||
label={formatMessage('samlAuthenticationForm.entryPoint')}
|
label={formatMessage('authenticationForm.entryPoint')}
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
required={true}
|
required={true}
|
||||||
name="firstnameAttributeName"
|
name="firstnameAttributeName"
|
||||||
label={formatMessage('samlAuthenticationForm.firstnameAttributeName')}
|
label={formatMessage('authenticationForm.firstnameAttributeName')}
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
required={true}
|
required={true}
|
||||||
name="surnameAttributeName"
|
name="surnameAttributeName"
|
||||||
label={formatMessage('samlAuthenticationForm.surnameAttributeName')}
|
label={formatMessage('authenticationForm.surnameAttributeName')}
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
required={true}
|
required={true}
|
||||||
name="emailAttributeName"
|
name="emailAttributeName"
|
||||||
label={formatMessage('samlAuthenticationForm.emailAttributeName')}
|
label={formatMessage('authenticationForm.emailAttributeName')}
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
required={true}
|
required={true}
|
||||||
name="roleAttributeName"
|
name="roleAttributeName"
|
||||||
label={formatMessage('samlAuthenticationForm.roleAttributeName')}
|
label={formatMessage('authenticationForm.roleAttributeName')}
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
<ControlledAutocomplete
|
<ControlledAutocomplete
|
||||||
@@ -175,7 +175,7 @@ function SamlConfiguration({ provider, providerLoading }) {
|
|||||||
renderInput={(params) => (
|
renderInput={(params) => (
|
||||||
<MuiTextField
|
<MuiTextField
|
||||||
{...params}
|
{...params}
|
||||||
label={formatMessage('samlAuthenticationForm.defaultRole')}
|
label={formatMessage('authenticationForm.defaultRole')}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
loading={isRolesLoading}
|
loading={isRolesLoading}
|
||||||
@@ -187,7 +187,7 @@ function SamlConfiguration({ provider, providerLoading }) {
|
|||||||
sx={{ boxShadow: 2 }}
|
sx={{ boxShadow: 2 }}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
>
|
>
|
||||||
{formatMessage('samlAuthenticationForm.save')}
|
{formatMessage('authenticationForm.save')}
|
||||||
</LoadingButton>
|
</LoadingButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Form>
|
</Form>
|
||||||
|
@@ -2,14 +2,11 @@ import Grid from '@mui/material/Grid';
|
|||||||
import Stack from '@mui/material/Stack';
|
import Stack from '@mui/material/Stack';
|
||||||
import PageTitle from 'components/PageTitle';
|
import PageTitle from 'components/PageTitle';
|
||||||
import Container from 'components/Container';
|
import Container from 'components/Container';
|
||||||
import Divider from '@mui/material/Divider';
|
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
import useSamlAuthProvider from 'hooks/useSamlAuthProvider';
|
import useSamlAuthProvider from 'hooks/useSamlAuthProvider';
|
||||||
import AuthenticationConfig from './AuthenticationConfig';
|
|
||||||
import SamlConfiguration from './SamlConfiguration';
|
import SamlConfiguration from './SamlConfiguration';
|
||||||
import RoleMappings from './RoleMappings';
|
import RoleMappings from './RoleMappings';
|
||||||
import useAdminSamlAuthProviders from 'hooks/useAdminSamlAuthProviders.ee';
|
import useAdminSamlAuthProviders from 'hooks/useAdminSamlAuthProviders.ee';
|
||||||
|
|
||||||
function AuthenticationPage() {
|
function AuthenticationPage() {
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
|
|
||||||
@@ -19,37 +16,20 @@ function AuthenticationPage() {
|
|||||||
const { data, isLoading: isProviderLoading } = useSamlAuthProvider({
|
const { data, isLoading: isProviderLoading } = useSamlAuthProvider({
|
||||||
samlAuthProviderId,
|
samlAuthProviderId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const provider = data?.data;
|
const provider = data?.data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container sx={{ py: 3, display: 'flex', justifyContent: 'center' }}>
|
<Container sx={{ py: 3, display: 'flex', justifyContent: 'center' }}>
|
||||||
<Grid container item xs={12} sm={10} md={9}>
|
<Grid container item xs={12} sm={10} md={9}>
|
||||||
<Grid container item xs={12}>
|
<Grid container item xs={12} sx={{ mb: [2, 5] }}>
|
||||||
<PageTitle>{formatMessage('authenticationPage.title')}</PageTitle>
|
<PageTitle>{formatMessage('authenticationPage.title')}</PageTitle>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid item xs={12} sx={{ pt: 5, pb: 5 }}>
|
|
||||||
<Stack spacing={5}>
|
|
||||||
<AuthenticationConfig />
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
</Stack>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid container item xs={12}>
|
|
||||||
<PageTitle variant="h4">{formatMessage('samlAuthenticationPage.title')}</PageTitle>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid item xs={12} sx={{ pt: 5, pb: 5 }}>
|
<Grid item xs={12} sx={{ pt: 5, pb: 5 }}>
|
||||||
<Stack spacing={5}>
|
<Stack spacing={5}>
|
||||||
<SamlConfiguration
|
<SamlConfiguration
|
||||||
provider={provider}
|
provider={provider}
|
||||||
providerLoading={isProviderLoading}
|
providerLoading={isProviderLoading}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
<RoleMappings
|
<RoleMappings
|
||||||
provider={provider}
|
provider={provider}
|
||||||
providerLoading={isProviderLoading}
|
providerLoading={isProviderLoading}
|
||||||
|
@@ -10,7 +10,6 @@ export default function Login() {
|
|||||||
<Container maxWidth="sm">
|
<Container maxWidth="sm">
|
||||||
<Stack direction="column" gap={2}>
|
<Stack direction="column" gap={2}>
|
||||||
<LoginForm />
|
<LoginForm />
|
||||||
|
|
||||||
<SsoProviders />
|
<SsoProviders />
|
||||||
</Stack>
|
</Stack>
|
||||||
</Container>
|
</Container>
|
||||||
|
@@ -29,14 +29,12 @@ import adminSettingsRoutes from './adminSettingsRoutes';
|
|||||||
import Notifications from 'pages/Notifications';
|
import Notifications from 'pages/Notifications';
|
||||||
import useAutomatischConfig from 'hooks/useAutomatischConfig';
|
import useAutomatischConfig from 'hooks/useAutomatischConfig';
|
||||||
import useAuthentication from 'hooks/useAuthentication';
|
import useAuthentication from 'hooks/useAuthentication';
|
||||||
import usePreventUsersFromUpdatingTheirProfile from 'hooks/usePreventUsersFromUpdatingTheirProfile';
|
|
||||||
import useAutomatischInfo from 'hooks/useAutomatischInfo';
|
import useAutomatischInfo from 'hooks/useAutomatischInfo';
|
||||||
import Installation from 'pages/Installation';
|
import Installation from 'pages/Installation';
|
||||||
|
|
||||||
function Routes() {
|
function Routes() {
|
||||||
const { data: automatischInfo, isSuccess } = useAutomatischInfo();
|
const { data: automatischInfo, isSuccess } = useAutomatischInfo();
|
||||||
const { data: configData } = useAutomatischConfig();
|
const { data: configData } = useAutomatischConfig();
|
||||||
const preventUsersFromUpdatingTheirProfile = usePreventUsersFromUpdatingTheirProfile();
|
|
||||||
const { isAuthenticated } = useAuthentication();
|
const { isAuthenticated } = useAuthentication();
|
||||||
const config = configData?.data;
|
const config = configData?.data;
|
||||||
|
|
||||||
@@ -136,14 +134,14 @@ function Routes() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{preventUsersFromUpdatingTheirProfile === false && <Route
|
<Route
|
||||||
path={URLS.FORGOT_PASSWORD}
|
path={URLS.FORGOT_PASSWORD}
|
||||||
element={
|
element={
|
||||||
<PublicLayout>
|
<PublicLayout>
|
||||||
<ForgotPassword />
|
<ForgotPassword />
|
||||||
</PublicLayout>
|
</PublicLayout>
|
||||||
}
|
}
|
||||||
/>}
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path={URLS.RESET_PASSWORD}
|
path={URLS.RESET_PASSWORD}
|
||||||
|
@@ -95,11 +95,11 @@ export const defaultTheme = createTheme({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
h4: {
|
h4: {
|
||||||
fontSize: referenceTheme.typography.pxToRem(28),
|
fontSize: referenceTheme.typography.pxToRem(32),
|
||||||
lineHeight: 1.3,
|
lineHeight: 1.3,
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
[referenceTheme.breakpoints.down('sm')]: {
|
[referenceTheme.breakpoints.down('sm')]: {
|
||||||
fontSize: referenceTheme.typography.pxToRem(22),
|
fontSize: referenceTheme.typography.pxToRem(16),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
h5: {
|
h5: {
|
||||||
|
16
yarn.lock
16
yarn.lock
@@ -5986,7 +5986,7 @@ brace-expansion@^2.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
balanced-match "^1.0.0"
|
balanced-match "^1.0.0"
|
||||||
|
|
||||||
braces@^3.0.1, braces@~3.0.2:
|
braces@^3.0.3, braces@~3.0.2:
|
||||||
version "3.0.3"
|
version "3.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789"
|
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789"
|
||||||
integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
|
integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
|
||||||
@@ -11630,12 +11630,12 @@ micro@^10.0.1:
|
|||||||
raw-body "2.4.1"
|
raw-body "2.4.1"
|
||||||
|
|
||||||
micromatch@^4.0.2, micromatch@^4.0.4:
|
micromatch@^4.0.2, micromatch@^4.0.4:
|
||||||
version "4.0.4"
|
version "4.0.8"
|
||||||
resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz"
|
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202"
|
||||||
integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==
|
integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==
|
||||||
dependencies:
|
dependencies:
|
||||||
braces "^3.0.1"
|
braces "^3.0.3"
|
||||||
picomatch "^2.2.3"
|
picomatch "^2.3.1"
|
||||||
|
|
||||||
mime-db@1.51.0, "mime-db@>= 1.43.0 < 2":
|
mime-db@1.51.0, "mime-db@>= 1.43.0 < 2":
|
||||||
version "1.51.0"
|
version "1.51.0"
|
||||||
@@ -13073,9 +13073,9 @@ picocolors@^1.0.0:
|
|||||||
resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz"
|
resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz"
|
||||||
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
|
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
|
||||||
|
|
||||||
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3:
|
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3, picomatch@^2.3.1:
|
||||||
version "2.3.1"
|
version "2.3.1"
|
||||||
resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz"
|
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
|
||||||
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
||||||
|
|
||||||
pify@^2.3.0:
|
pify@^2.3.0:
|
||||||
|
Reference in New Issue
Block a user