diff --git a/packages/backend/src/apps/cryptography/actions/create-hmac/index.js b/packages/backend/src/apps/cryptography/actions/create-hmac/index.js
new file mode 100644
index 00000000..e14f0d67
--- /dev/null
+++ b/packages/backend/src/apps/cryptography/actions/create-hmac/index.js
@@ -0,0 +1,64 @@
+import { createHmac } from 'node:crypto';
+import defineAction from '../../../../helpers/define-action.js';
+
+export default defineAction({
+ name: 'Create HMAC',
+ key: 'createHmac',
+ description: 'Create a Hash-based Message Authentication Code (HMAC) using the specified algorithm, secret key, and message.',
+ arguments: [
+ {
+ label: 'Algorithm',
+ key: 'algorithm',
+ type: 'dropdown',
+ required: true,
+ value: 'sha256',
+ description: 'Specifies the cryptographic hash function to use for HMAC generation.',
+ options: [
+ { label: 'SHA-256', value: 'sha256' },
+ ],
+ variables: true,
+ },
+ {
+ label: 'Message',
+ key: 'message',
+ type: 'string',
+ required: true,
+ description: 'The input message to be hashed. This is the value that will be processed to generate the HMAC.',
+ variables: true,
+ },
+ {
+ label: 'Secret Key',
+ key: 'secretKey',
+ type: 'string',
+ required: true,
+ description: 'The secret key used to create the HMAC.',
+ variables: true,
+ },
+ {
+ label: 'Output Encoding',
+ key: 'outputEncoding',
+ type: 'dropdown',
+ required: true,
+ value: 'hex',
+ description: 'Specifies the encoding format for the HMAC digest output.',
+ options: [
+ { label: 'base64', value: 'base64' },
+ { label: 'base64url', value: 'base64url' },
+ { label: 'hex', value: 'hex' },
+ ],
+ variables: true,
+ },
+ ],
+
+ async run($) {
+ const hash = createHmac($.step.parameters.algorithm, $.step.parameters.secretKey)
+ .update($.step.parameters.message)
+ .digest($.step.parameters.outputEncoding);
+
+ $.setActionItem({
+ raw: {
+ hash
+ },
+ });
+ },
+});
diff --git a/packages/backend/src/apps/cryptography/actions/create-rsa-sha256-signature/index.js b/packages/backend/src/apps/cryptography/actions/create-rsa-sha256-signature/index.js
new file mode 100644
index 00000000..446309e8
--- /dev/null
+++ b/packages/backend/src/apps/cryptography/actions/create-rsa-sha256-signature/index.js
@@ -0,0 +1,65 @@
+import crypto from 'node:crypto';
+import defineAction from '../../../../helpers/define-action.js';
+
+export default defineAction({
+ name: 'Create Signature',
+ key: 'createSignature',
+ description: 'Create a digital signature using the specified algorithm, secret key, and message.',
+ arguments: [
+ {
+ label: 'Algorithm',
+ key: 'algorithm',
+ type: 'dropdown',
+ required: true,
+ value: 'RSA-SHA256',
+ description: 'Specifies the cryptographic hash function to use for HMAC generation.',
+ options: [
+ { label: 'RSA-SHA256', value: 'RSA-SHA256' },
+ ],
+ variables: true,
+ },
+ {
+ label: 'Message',
+ key: 'message',
+ type: 'string',
+ required: true,
+ description: 'The input message to be signed.',
+ variables: true,
+ },
+ {
+ label: 'Private Key',
+ key: 'privateKey',
+ type: 'string',
+ required: true,
+ description: 'The RSA private key in PEM format used for signing.',
+ variables: true,
+ },
+ {
+ label: 'Output Encoding',
+ key: 'outputEncoding',
+ type: 'dropdown',
+ required: true,
+ value: 'hex',
+ description: 'Specifies the encoding format for the digital signature output. This determines how the generated signature will be represented as a string.',
+ options: [
+ { label: 'base64', value: 'base64' },
+ { label: 'base64url', value: 'base64url' },
+ { label: 'hex', value: 'hex' },
+ ],
+ variables: true,
+ },
+ ],
+
+ async run($) {
+ const signer = crypto.createSign($.step.parameters.algorithm);
+ signer.update($.step.parameters.message);
+ signer.end();
+ const signature = signer.sign($.step.parameters.privateKey, $.step.parameters.outputEncoding);
+
+ $.setActionItem({
+ raw: {
+ signature
+ },
+ });
+ },
+});
diff --git a/packages/backend/src/apps/cryptography/actions/index.js b/packages/backend/src/apps/cryptography/actions/index.js
new file mode 100644
index 00000000..ab2da71e
--- /dev/null
+++ b/packages/backend/src/apps/cryptography/actions/index.js
@@ -0,0 +1,4 @@
+import createHmac from './create-hmac/index.js';
+import createRsaSha256Signature from './create-rsa-sha256-signature/index.js';
+
+export default [createHmac, createRsaSha256Signature];
diff --git a/packages/backend/src/apps/cryptography/assets/favicon.svg b/packages/backend/src/apps/cryptography/assets/favicon.svg
new file mode 100644
index 00000000..da529327
--- /dev/null
+++ b/packages/backend/src/apps/cryptography/assets/favicon.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/backend/src/apps/cryptography/index.js b/packages/backend/src/apps/cryptography/index.js
new file mode 100644
index 00000000..23e57b2e
--- /dev/null
+++ b/packages/backend/src/apps/cryptography/index.js
@@ -0,0 +1,14 @@
+import defineApp from '../../helpers/define-app.js';
+import actions from './actions/index.js';
+
+export default defineApp({
+ name: 'Cryptography',
+ key: 'cryptography',
+ iconUrl: '{BASE_URL}/apps/cryptography/assets/favicon.svg',
+ authDocUrl: '{DOCS_URL}/apps/cryptography/connection',
+ supportsConnections: false,
+ baseUrl: '',
+ apiBaseUrl: '',
+ primaryColor: '001F52',
+ actions,
+});
diff --git a/packages/backend/src/apps/formatter/actions/date-time/index.js b/packages/backend/src/apps/formatter/actions/date-time/index.js
index 830421d7..88fbecbe 100644
--- a/packages/backend/src/apps/formatter/actions/date-time/index.js
+++ b/packages/backend/src/apps/formatter/actions/date-time/index.js
@@ -1,8 +1,10 @@
import defineAction from '../../../../helpers/define-action.js';
import formatDateTime from './transformers/format-date-time.js';
+import getCurrentTimestamp from './transformers/get-current-timestamp.js';
const transformers = {
formatDateTime,
+ getCurrentTimestamp,
};
export default defineAction({
@@ -16,7 +18,16 @@ export default defineAction({
type: 'dropdown',
required: true,
variables: true,
- options: [{ label: 'Format Date / Time', value: 'formatDateTime' }],
+ options: [
+ {
+ label: 'Get current timestamp',
+ value: 'getCurrentTimestamp',
+ },
+ {
+ label: 'Format Date / Time',
+ value: 'formatDateTime',
+ },
+ ],
additionalFields: {
type: 'query',
name: 'getDynamicFields',
diff --git a/packages/backend/src/apps/formatter/actions/date-time/transformers/get-current-timestamp.js b/packages/backend/src/apps/formatter/actions/date-time/transformers/get-current-timestamp.js
new file mode 100644
index 00000000..a0d7f0c2
--- /dev/null
+++ b/packages/backend/src/apps/formatter/actions/date-time/transformers/get-current-timestamp.js
@@ -0,0 +1,5 @@
+const getCurrentTimestamp = () => {
+ return Date.now();
+};
+
+export default getCurrentTimestamp;
diff --git a/packages/backend/src/apps/formatter/actions/text/index.js b/packages/backend/src/apps/formatter/actions/text/index.js
index b2c7ee60..e33160ad 100644
--- a/packages/backend/src/apps/formatter/actions/text/index.js
+++ b/packages/backend/src/apps/formatter/actions/text/index.js
@@ -15,6 +15,7 @@ import encodeUri from './transformers/encode-uri.js';
import trimWhitespace from './transformers/trim-whitespace.js';
import useDefaultValue from './transformers/use-default-value.js';
import parseStringifiedJson from './transformers/parse-stringified-json.js';
+import createUuid from './transformers/create-uuid.js';
const transformers = {
base64ToString,
@@ -32,6 +33,7 @@ const transformers = {
trimWhitespace,
useDefaultValue,
parseStringifiedJson,
+ createUuid,
};
export default defineAction({
@@ -49,22 +51,23 @@ export default defineAction({
options: [
{ label: 'Base64 to String', value: 'base64ToString' },
{ label: 'Capitalize', value: 'capitalize' },
+ { label: 'Convert HTML to Markdown', value: 'htmlToMarkdown' },
+ { label: 'Convert Markdown to HTML', value: 'markdownToHtml' },
+ { label: 'Create UUID', value: 'createUuid' },
+ { label: 'Encode URI', value: 'encodeUri' },
{
label: 'Encode URI Component',
value: 'encodeUriComponent',
},
- { label: 'Convert HTML to Markdown', value: 'htmlToMarkdown' },
- { label: 'Convert Markdown to HTML', value: 'markdownToHtml' },
{ label: 'Extract Email Address', value: 'extractEmailAddress' },
{ label: 'Extract Number', value: 'extractNumber' },
{ label: 'Lowercase', value: 'lowercase' },
+ { label: 'Parse stringified JSON', value: 'parseStringifiedJson' },
{ label: 'Pluralize', value: 'pluralize' },
{ label: 'Replace', value: 'replace' },
{ label: 'String to Base64', value: 'stringToBase64' },
- { label: 'Encode URI', value: 'encodeUri' },
{ label: 'Trim Whitespace', value: 'trimWhitespace' },
{ label: 'Use Default Value', value: 'useDefaultValue' },
- { label: 'Parse stringified JSON', value: 'parseStringifiedJson' },
],
additionalFields: {
type: 'query',
diff --git a/packages/backend/src/apps/formatter/actions/text/transformers/create-uuid.js b/packages/backend/src/apps/formatter/actions/text/transformers/create-uuid.js
new file mode 100644
index 00000000..20d1ecc3
--- /dev/null
+++ b/packages/backend/src/apps/formatter/actions/text/transformers/create-uuid.js
@@ -0,0 +1,7 @@
+import { v4 as uuidv4 } from 'uuid';
+
+const createUuidV4 = () => {
+ return uuidv4();
+};
+
+export default createUuidV4;
diff --git a/packages/backend/src/graphql/mutation-resolvers.js b/packages/backend/src/graphql/mutation-resolvers.js
index cacd3936..4d60ae53 100644
--- a/packages/backend/src/graphql/mutation-resolvers.js
+++ b/packages/backend/src/graphql/mutation-resolvers.js
@@ -29,11 +29,6 @@ import upsertSamlAuthProvider from './mutations/upsert-saml-auth-provider.ee.js'
import upsertSamlAuthProvidersRoleMappings from './mutations/upsert-saml-auth-providers-role-mappings.ee.js';
import verifyConnection from './mutations/verify-connection.js';
-// Converted mutations
-import deleteUser from './mutations/delete-user.ee.js';
-import login from './mutations/login.js';
-import resetPassword from './mutations/reset-password.ee.js';
-
const mutationResolvers = {
createAppAuthClient,
createAppConfig,
@@ -47,14 +42,11 @@ const mutationResolvers = {
deleteFlow,
deleteRole,
deleteStep,
- deleteUser,
duplicateFlow,
executeFlow,
generateAuthUrl,
- login,
registerUser,
resetConnection,
- resetPassword,
updateAppAuthClient,
updateAppConfig,
updateConfig,
diff --git a/packages/backend/src/graphql/mutations/delete-user.ee.js b/packages/backend/src/graphql/mutations/delete-user.ee.js
deleted file mode 100644
index 7b66dd05..00000000
--- a/packages/backend/src/graphql/mutations/delete-user.ee.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import { Duration } from 'luxon';
-import User from '../../models/user.js';
-import deleteUserQueue from '../../queues/delete-user.ee.js';
-
-const deleteUser = async (_parent, params, context) => {
- context.currentUser.can('delete', 'User');
-
- const id = params.input.id;
-
- await User.query().deleteById(id);
-
- const jobName = `Delete user - ${id}`;
- const jobPayload = { id };
- const millisecondsFor30Days = Duration.fromObject({ days: 30 }).toMillis();
- const jobOptions = {
- delay: millisecondsFor30Days,
- };
-
- await deleteUserQueue.add(jobName, jobPayload, jobOptions);
-
- return true;
-};
-
-export default deleteUser;
diff --git a/packages/backend/src/graphql/mutations/login.js b/packages/backend/src/graphql/mutations/login.js
deleted file mode 100644
index b0504570..00000000
--- a/packages/backend/src/graphql/mutations/login.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import User from '../../models/user.js';
-import createAuthTokenByUserId from '../../helpers/create-auth-token-by-user-id.js';
-
-const login = async (_parent, params) => {
- const user = await User.query().findOne({
- email: params.input.email.toLowerCase(),
- });
-
- if (user && (await user.login(params.input.password))) {
- const token = await createAuthTokenByUserId(user.id);
- return { token, user };
- }
-
- throw new Error('User could not be found.');
-};
-
-export default login;
diff --git a/packages/backend/src/graphql/mutations/reset-password.ee.js b/packages/backend/src/graphql/mutations/reset-password.ee.js
deleted file mode 100644
index 309b006a..00000000
--- a/packages/backend/src/graphql/mutations/reset-password.ee.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import User from '../../models/user.js';
-
-const resetPassword = async (_parent, params) => {
- const { token, password } = params.input;
-
- if (!token) {
- throw new Error('Reset password token is required!');
- }
-
- const user = await User.query().findOne({ reset_password_token: token });
-
- if (!user || !user.isResetPasswordTokenValid()) {
- throw new Error(
- 'Reset password link is not valid or expired. Try generating a new link.'
- );
- }
-
- await user.resetPassword(password);
-
- return true;
-};
-
-export default resetPassword;
diff --git a/packages/backend/src/graphql/schema.graphql b/packages/backend/src/graphql/schema.graphql
index e64228fd..a398cb41 100644
--- a/packages/backend/src/graphql/schema.graphql
+++ b/packages/backend/src/graphql/schema.graphql
@@ -14,14 +14,11 @@ type Mutation {
deleteFlow(input: DeleteFlowInput): Boolean
deleteRole(input: DeleteRoleInput): Boolean
deleteStep(input: DeleteStepInput): Step
- deleteUser(input: DeleteUserInput): Boolean
duplicateFlow(input: DuplicateFlowInput): Flow
executeFlow(input: ExecuteFlowInput): executeFlowType
generateAuthUrl(input: GenerateAuthUrlInput): AuthLink
- login(input: LoginInput): Auth
registerUser(input: RegisterUserInput): User
resetConnection(input: ResetConnectionInput): Connection
- resetPassword(input: ResetPasswordInput): Boolean
updateAppAuthClient(input: UpdateAppAuthClientInput): AppAuthClient
updateAppConfig(input: UpdateAppConfigInput): AppConfig
updateConfig(input: JSONObject): JSONObject
@@ -153,11 +150,6 @@ enum ArgumentEnumType {
string
}
-type Auth {
- user: User
- token: String
-}
-
type AuthenticationStep {
type: String
name: String
@@ -388,10 +380,6 @@ input UpdateUserInput {
role: UserRoleInput
}
-input DeleteUserInput {
- id: String!
-}
-
input RegisterUserInput {
fullName: String!
email: String!
@@ -404,16 +392,6 @@ input UpdateCurrentUserInput {
fullName: String
}
-input ResetPasswordInput {
- token: String!
- password: String!
-}
-
-input LoginInput {
- email: String!
- password: String!
-}
-
input PermissionInput {
action: String!
subject: String!
diff --git a/packages/backend/src/helpers/authentication.js b/packages/backend/src/helpers/authentication.js
index f57c41fa..9f01d21d 100644
--- a/packages/backend/src/helpers/authentication.js
+++ b/packages/backend/src/helpers/authentication.js
@@ -53,9 +53,7 @@ const isAuthenticatedRule = rule()(isAuthenticated);
export const authenticationRules = {
Mutation: {
'*': isAuthenticatedRule,
- login: allow,
registerUser: allow,
- resetPassword: allow,
},
};
diff --git a/packages/docs/pages/.vitepress/config.js b/packages/docs/pages/.vitepress/config.js
index 219a1d51..216b6514 100644
--- a/packages/docs/pages/.vitepress/config.js
+++ b/packages/docs/pages/.vitepress/config.js
@@ -59,6 +59,15 @@ export default defineConfig({
{ text: 'Connection', link: '/apps/carbone/connection' },
],
},
+ {
+ text: 'Cryptography',
+ collapsible: true,
+ collapsed: true,
+ items: [
+ { text: 'Actions', link: '/apps/cryptography/actions' },
+ { text: 'Connection', link: '/apps/cryptography/connection' },
+ ],
+ },
{
text: 'Datastore',
collapsible: true,
diff --git a/packages/docs/pages/apps/cryptography/actions.md b/packages/docs/pages/apps/cryptography/actions.md
new file mode 100644
index 00000000..a7aa2a77
--- /dev/null
+++ b/packages/docs/pages/apps/cryptography/actions.md
@@ -0,0 +1,14 @@
+---
+favicon: /favicons/cryptography.svg
+items:
+ - name: Create HMAC
+ desc: Create a Hash-based Message Authentication Code (HMAC) using the specified algorithm, secret key, and message.
+ - name: Create Signature
+ desc: Create a digital signature using the specified algorithm, secret key, and message.
+---
+
+
+
+
diff --git a/packages/docs/pages/apps/cryptography/connection.md b/packages/docs/pages/apps/cryptography/connection.md
new file mode 100644
index 00000000..5cf28566
--- /dev/null
+++ b/packages/docs/pages/apps/cryptography/connection.md
@@ -0,0 +1,3 @@
+# Cryptography
+
+Cryptography is a built-in app shipped with Automatisch, allowing you to perform cryptographic operations without needing to connect to any external services.
diff --git a/packages/docs/pages/public/favicons/cryptography.svg b/packages/docs/pages/public/favicons/cryptography.svg
new file mode 100644
index 00000000..da529327
--- /dev/null
+++ b/packages/docs/pages/public/favicons/cryptography.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/e2e-tests/.eslintignore b/packages/e2e-tests/.eslintignore
new file mode 100644
index 00000000..e2699178
--- /dev/null
+++ b/packages/e2e-tests/.eslintignore
@@ -0,0 +1,6 @@
+node_modules
+build
+
+.eslintrc.js
+
+playwright-report/*
\ No newline at end of file
diff --git a/packages/e2e-tests/.eslintrc.json b/packages/e2e-tests/.eslintrc.json
new file mode 100644
index 00000000..943e6da1
--- /dev/null
+++ b/packages/e2e-tests/.eslintrc.json
@@ -0,0 +1,25 @@
+{
+ "root": true,
+ "env": {
+ "node": true,
+ "es6": true
+ },
+ "extends": [
+ "eslint:recommended",
+ "prettier"
+ ],
+ "parserOptions": {
+ "ecmaVersion": "latest",
+ "sourceType": "module"
+ },
+ "rules": {
+ "semi": [
+ 2,
+ "always"
+ ],
+ "indent": [
+ "error",
+ 2
+ ]
+ }
+}
\ No newline at end of file
diff --git a/packages/e2e-tests/fixtures/admin/delete-user-modal.js b/packages/e2e-tests/fixtures/admin/delete-user-modal.js
index 6082d0e3..8a4f33ed 100644
--- a/packages/e2e-tests/fixtures/admin/delete-user-modal.js
+++ b/packages/e2e-tests/fixtures/admin/delete-user-modal.js
@@ -14,6 +14,6 @@ export class DeleteUserModal {
async close () {
await this.page.click('body', {
position: { x: 10, y: 10 }
- })
+ });
}
}
\ No newline at end of file
diff --git a/packages/e2e-tests/fixtures/admin/edit-role-page.js b/packages/e2e-tests/fixtures/admin/edit-role-page.js
index 9dd21dfe..9c9fec6b 100644
--- a/packages/e2e-tests/fixtures/admin/edit-role-page.js
+++ b/packages/e2e-tests/fixtures/admin/edit-role-page.js
@@ -1,4 +1,4 @@
-const { AdminCreateRolePage } = require('./create-role-page')
+const { AdminCreateRolePage } = require('./create-role-page');
export class AdminEditRolePage extends AdminCreateRolePage {
constructor (page) {
diff --git a/packages/e2e-tests/fixtures/admin/edit-user-page.js b/packages/e2e-tests/fixtures/admin/edit-user-page.js
index 9308294b..4bc3a1b6 100644
--- a/packages/e2e-tests/fixtures/admin/edit-user-page.js
+++ b/packages/e2e-tests/fixtures/admin/edit-user-page.js
@@ -23,6 +23,7 @@ export class AdminEditUserPage extends AuthenticatedPage {
*/
async waitForLoad(fullName) {
return await this.page.waitForFunction((fullName) => {
+ // eslint-disable-next-line no-undef
const el = document.querySelector("[data-test='full-name-input']");
return el && el.value === fullName;
}, fullName);
diff --git a/packages/e2e-tests/fixtures/admin/index.js b/packages/e2e-tests/fixtures/admin/index.js
index 8c25fd7c..fe746243 100644
--- a/packages/e2e-tests/fixtures/admin/index.js
+++ b/packages/e2e-tests/fixtures/admin/index.js
@@ -25,5 +25,5 @@ export const adminFixtures = {
adminCreateRolePage: async ({ page}, use) => {
await use(new AdminCreateRolePage(page));
},
-}
+};
diff --git a/packages/e2e-tests/fixtures/admin/users-page.js b/packages/e2e-tests/fixtures/admin/users-page.js
index 4c96fd75..af6dbac3 100644
--- a/packages/e2e-tests/fixtures/admin/users-page.js
+++ b/packages/e2e-tests/fixtures/admin/users-page.js
@@ -87,6 +87,7 @@ export class AdminUsersPage extends AuthenticatedPage {
await this.firstPageButton.click();
}
+ // eslint-disable-next-line no-constant-condition
while (true) {
if (await this.usersLoader.isVisible()) {
await this.usersLoader.waitFor({
@@ -108,6 +109,7 @@ export class AdminUsersPage extends AuthenticatedPage {
async getTotalRows() {
return await this.page.evaluate(() => {
+ // eslint-disable-next-line no-undef
const node = document.querySelector('[data-total-count]');
if (node) {
const count = Number(node.dataset.totalCount);
@@ -121,6 +123,7 @@ export class AdminUsersPage extends AuthenticatedPage {
async getRowsPerPage() {
return await this.page.evaluate(() => {
+ // eslint-disable-next-line no-undef
const node = document.querySelector('[data-rows-per-page]');
if (node) {
const count = Number(node.dataset.rowsPerPage);
diff --git a/packages/e2e-tests/fixtures/applications-modal.js b/packages/e2e-tests/fixtures/applications-modal.js
index 8833087f..f9bab969 100644
--- a/packages/e2e-tests/fixtures/applications-modal.js
+++ b/packages/e2e-tests/fixtures/applications-modal.js
@@ -25,7 +25,7 @@ export class ApplicationsModal extends BasePage {
if (this.applications[link] === undefined) {
throw {
message: `Unknown link "${link}" passed to ApplicationsModal.selectLink`
- }
+ };
}
await this.searchInput.fill(link);
await this.appListItem.first().click();
diff --git a/packages/e2e-tests/fixtures/apps/github/github-page.js b/packages/e2e-tests/fixtures/apps/github/github-page.js
index f1a05d5f..d826c0d4 100644
--- a/packages/e2e-tests/fixtures/apps/github/github-page.js
+++ b/packages/e2e-tests/fixtures/apps/github/github-page.js
@@ -1,10 +1,11 @@
const { BasePage } = require('../../base-page');
const { AddGithubConnectionModal } = require('./add-github-connection-modal');
+const { expect } = require('@playwright/test');
export class GithubPage extends BasePage {
constructor (page) {
- super(page)
+ super(page);
this.addConnectionButton = page.getByTestId('add-connection-button');
this.connectionsTab = page.getByTestId('connections-tab');
this.flowsTab = page.getByTestId('flows-tab');
@@ -38,7 +39,7 @@ export class GithubPage extends BasePage {
await this.flowsTab.click();
await expect(this.flowsTab).toBeVisible();
}
- return await this.flowRows.count() > 0
+ return await this.flowRows.count() > 0;
}
async hasConnections () {
diff --git a/packages/e2e-tests/fixtures/apps/github/github-popup.js b/packages/e2e-tests/fixtures/apps/github/github-popup.js
index 0bdb8579..f1c8e2fe 100644
--- a/packages/e2e-tests/fixtures/apps/github/github-popup.js
+++ b/packages/e2e-tests/fixtures/apps/github/github-popup.js
@@ -1,4 +1,5 @@
const { BasePage } = require('../../base-page');
+const { expect } = require('@playwright/test');
export class GithubPopup extends BasePage {
@@ -11,7 +12,7 @@ export class GithubPopup extends BasePage {
}
getPathname () {
- const url = this.page.url()
+ const url = this.page.url();
try {
return new URL(url).pathname;
} catch (e) {
@@ -34,17 +35,17 @@ export class GithubPopup extends BasePage {
loginInput.click();
await loginInput.fill(process.env.GITHUB_USERNAME);
const passwordInput = this.page.getByLabel('Password');
- passwordInput.click()
+ passwordInput.click();
await passwordInput.fill(process.env.GITHUB_PASSWORD);
await this.page.getByRole('button', { name: 'Sign in' }).click();
// await this.page.waitForTimeout(2000);
if (this.page.isClosed()) {
- return
+ return;
}
// await this.page.waitForLoadState('networkidle', 30000);
this.page.waitForEvent('load');
if (this.page.isClosed()) {
- return
+ return;
}
await this.page.waitForURL(function (url) {
const u = new URL(url);
@@ -55,7 +56,7 @@ export class GithubPopup extends BasePage {
}
async handleAuthorize () {
- if (this.page.isClosed()) { return }
+ if (this.page.isClosed()) { return; }
const authorizeButton = this.page.getByRole(
'button',
{ name: 'Authorize' }
@@ -69,7 +70,7 @@ export class GithubPopup extends BasePage {
) && (
u.searchParams.get('client_id') === null
);
- })
+ });
const passwordInput = this.page.getByLabel('Password');
if (await passwordInput.isVisible()) {
await passwordInput.fill(process.env.GITHUB_PASSWORD);
@@ -87,6 +88,6 @@ export class GithubPopup extends BasePage {
};
}
}
- await this.page.waitForEvent('close')
+ await this.page.waitForEvent('close');
}
}
\ No newline at end of file
diff --git a/packages/e2e-tests/fixtures/authenticated-page.js b/packages/e2e-tests/fixtures/authenticated-page.js
index d0d653be..ceda6f6f 100644
--- a/packages/e2e-tests/fixtures/authenticated-page.js
+++ b/packages/e2e-tests/fixtures/authenticated-page.js
@@ -1,7 +1,4 @@
-const path = require('node:path');
-const { expect } = require('@playwright/test');
const { BasePage } = require('./base-page');
-const { LoginPage } = require('./login-page');
export class AuthenticatedPage extends BasePage {
/**
diff --git a/packages/e2e-tests/fixtures/connections-page.js b/packages/e2e-tests/fixtures/connections-page.js
index ad2c7fc8..a0ef8c42 100644
--- a/packages/e2e-tests/fixtures/connections-page.js
+++ b/packages/e2e-tests/fixtures/connections-page.js
@@ -1,4 +1,3 @@
-const path = require('node:path');
const { AuthenticatedPage } = require('./authenticated-page');
export class ConnectionsPage extends AuthenticatedPage {
diff --git a/packages/e2e-tests/fixtures/executions-page.js b/packages/e2e-tests/fixtures/executions-page.js
index 4a00782a..41b673e3 100644
--- a/packages/e2e-tests/fixtures/executions-page.js
+++ b/packages/e2e-tests/fixtures/executions-page.js
@@ -1,4 +1,3 @@
-const path = require('node:path');
const { AuthenticatedPage } = require('./authenticated-page');
export class ExecutionsPage extends AuthenticatedPage {
diff --git a/packages/e2e-tests/fixtures/flow-editor-page.js b/packages/e2e-tests/fixtures/flow-editor-page.js
index d0383525..b7de2d98 100644
--- a/packages/e2e-tests/fixtures/flow-editor-page.js
+++ b/packages/e2e-tests/fixtures/flow-editor-page.js
@@ -1,4 +1,3 @@
-const path = require('node:path');
const { AuthenticatedPage } = require('./authenticated-page');
export class FlowEditorPage extends AuthenticatedPage {
diff --git a/packages/e2e-tests/fixtures/index.js b/packages/e2e-tests/fixtures/index.js
index d9fab044..f9376786 100644
--- a/packages/e2e-tests/fixtures/index.js
+++ b/packages/e2e-tests/fixtures/index.js
@@ -8,6 +8,7 @@ const { LoginPage } = require('./login-page');
const { AcceptInvitation } = require('./accept-invitation-page');
const { adminFixtures } = require('./admin');
const { AdminSetupPage } = require('./admin-setup-page');
+const { AdminCreateUserPage } = require('./admin/create-user-page');
exports.test = test.extend({
page: async ({ page }, use) => {
@@ -58,6 +59,11 @@ exports.publicTest = test.extend({
const adminSetupPage = new AdminSetupPage(page);
await use(adminSetupPage);
},
+
+ adminCreateUserPage: async ({page}, use) => {
+ const adminCreateUserPage = new AdminCreateUserPage(page);
+ await use(adminCreateUserPage);
+ }
});
expect.extend({
diff --git a/packages/e2e-tests/fixtures/postgres-client-config.js b/packages/e2e-tests/fixtures/postgres-client-config.js
index 076b6b8f..8ebc831d 100644
--- a/packages/e2e-tests/fixtures/postgres-client-config.js
+++ b/packages/e2e-tests/fixtures/postgres-client-config.js
@@ -1,11 +1,11 @@
const { Client } = require('pg');
const client = new Client({
- host: process.env.POSTGRES_HOST,
- user: process.env.POSTGRES_USERNAME,
- port: process.env.POSTGRES_PORT,
- password: process.env.POSTGRES_PASSWORD,
- database: process.env.POSTGRES_DATABASE
+ host: process.env.POSTGRES_HOST,
+ user: process.env.POSTGRES_USERNAME,
+ port: process.env.POSTGRES_PORT,
+ password: process.env.POSTGRES_PASSWORD,
+ database: process.env.POSTGRES_DATABASE
});
exports.client = client;
diff --git a/packages/e2e-tests/fixtures/user-interface-page.js b/packages/e2e-tests/fixtures/user-interface-page.js
index 119b92b1..6342af3a 100644
--- a/packages/e2e-tests/fixtures/user-interface-page.js
+++ b/packages/e2e-tests/fixtures/user-interface-page.js
@@ -1,4 +1,3 @@
-const path = require('node:path');
const { AuthenticatedPage } = require('./authenticated-page');
export class UserInterfacePage extends AuthenticatedPage {
diff --git a/packages/e2e-tests/package.json b/packages/e2e-tests/package.json
index 1e731307..4bca4344 100644
--- a/packages/e2e-tests/package.json
+++ b/packages/e2e-tests/package.json
@@ -7,7 +7,8 @@
"scripts": {
"start-mock-license-server": "node ./license-server-with-mock.js",
"test": "playwright test",
- "test:fast": "yarn test -j 90% --quiet --reporter null --ignore-snapshots -x"
+ "test:fast": "yarn test -j 90% --quiet --reporter null --ignore-snapshots -x",
+ "lint": "eslint ."
},
"contributors": [
{
diff --git a/packages/e2e-tests/tests/admin/manage-roles.spec.js b/packages/e2e-tests/tests/admin/manage-roles.spec.js
index 7a4317d6..fa9eace1 100644
--- a/packages/e2e-tests/tests/admin/manage-roles.spec.js
+++ b/packages/e2e-tests/tests/admin/manage-roles.spec.js
@@ -17,7 +17,6 @@ test.describe('Role management page', () => {
adminCreateRolePage,
adminEditRolePage,
adminRolesPage,
- page,
}) => {
await test.step('Create a new role', async () => {
await adminRolesPage.navigateTo();
@@ -126,12 +125,14 @@ test.describe('Role management page', () => {
await adminCreateRolePage.isMounted();
const initScrollTop = await page.evaluate(() => {
+ // eslint-disable-next-line no-undef
return document.documentElement.scrollTop;
});
await page.mouse.move(400, 100);
await page.mouse.click(400, 100);
await page.mouse.wheel(200, 0);
const updatedScrollTop = await page.evaluate(() => {
+ // eslint-disable-next-line no-undef
return document.documentElement.scrollTop;
});
await expect(initScrollTop).not.toBe(updatedScrollTop);
@@ -144,11 +145,13 @@ test.describe('Role management page', () => {
await adminEditRolePage.isMounted();
const initScrollTop = await page.evaluate(() => {
+ // eslint-disable-next-line no-undef
return document.documentElement.scrollTop;
});
await page.mouse.move(400, 100);
await page.mouse.wheel(200, 0);
const updatedScrollTop = await page.evaluate(() => {
+ // eslint-disable-next-line no-undef
return document.documentElement.scrollTop;
});
await expect(initScrollTop).not.toBe(updatedScrollTop);
@@ -165,7 +168,6 @@ test.describe('Role management page', () => {
adminUsersPage,
adminCreateUserPage,
adminEditUserPage,
- page,
}) => {
await adminRolesPage.navigateTo();
await test.step('Create a new role', async () => {
@@ -270,7 +272,6 @@ test.describe('Role management page', () => {
adminRolesPage,
adminUsersPage,
adminCreateUserPage,
- page,
}) => {
await adminRolesPage.navigateTo();
await test.step('Create a new role', async () => {
@@ -429,6 +430,7 @@ test('Accessibility of role management page', async ({
await page.goto(url);
await page.waitForTimeout(750);
const isUnmounted = await page.evaluate(() => {
+ // eslint-disable-next-line no-undef
const root = document.querySelector('#root');
if (root) {
diff --git a/packages/e2e-tests/tests/admin/manage-users.spec.js b/packages/e2e-tests/tests/admin/manage-users.spec.js
index fc3c324b..80ebfc07 100644
--- a/packages/e2e-tests/tests/admin/manage-users.spec.js
+++ b/packages/e2e-tests/tests/admin/manage-users.spec.js
@@ -98,7 +98,7 @@ test.describe('User management page', () => {
await expect(userRow).not.toBeVisible(false);
}
);
- });
+ });
test(
'Creating a user which has been deleted',
diff --git a/packages/e2e-tests/tests/admin/role-conditions.spec.js b/packages/e2e-tests/tests/admin/role-conditions.spec.js
index 6f69ad58..1b738406 100644
--- a/packages/e2e-tests/tests/admin/role-conditions.spec.js
+++ b/packages/e2e-tests/tests/admin/role-conditions.spec.js
@@ -51,7 +51,7 @@ test(
const subjects = ['Connection', 'Execution', 'Flow'];
for (let subject of subjects) {
- const row = adminCreateRolePage.getSubjectRow(subject)
+ const row = adminCreateRolePage.getSubjectRow(subject);
const modal = adminCreateRolePage.getRoleConditionsModal(subject);
await adminCreateRolePage.clickPermissionSettings(row);
await expect(modal.modal).toBeVisible();
diff --git a/packages/e2e-tests/tests/executions/display-execution.spec.js b/packages/e2e-tests/tests/executions/display-execution.spec.js
index 8e1b4995..8de79dd7 100644
--- a/packages/e2e-tests/tests/executions/display-execution.spec.js
+++ b/packages/e2e-tests/tests/executions/display-execution.spec.js
@@ -2,7 +2,7 @@ const { test, expect } = require('../../fixtures/index');
// no execution data exists in an empty account
test.describe.skip('Executions page', () => {
- test.beforeEach(async ({ page, executionsPage }) => {
+ test.beforeEach(async ({ page }) => {
await page.getByTestId('executions-page-drawer-link').click();
await page.getByTestId('execution-row').first().click();
diff --git a/packages/e2e-tests/tests/flow-editor/create-flow.spec.js b/packages/e2e-tests/tests/flow-editor/create-flow.spec.js
index 363cad57..43d82861 100644
--- a/packages/e2e-tests/tests/flow-editor/create-flow.spec.js
+++ b/packages/e2e-tests/tests/flow-editor/create-flow.spec.js
@@ -6,7 +6,7 @@ test('Ensure creating a new flow works', async ({ page }) => {
await expect(page).toHaveURL(
/\/editor\/[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}/
);
-})
+});
test(
'Create a new flow with a Scheduler step then an Ntfy step',
diff --git a/packages/e2e-tests/tests/user-invitation/invitation.spec.js b/packages/e2e-tests/tests/user-invitation/invitation.spec.js
index 63a2f13e..33bc4378 100644
--- a/packages/e2e-tests/tests/user-invitation/invitation.spec.js
+++ b/packages/e2e-tests/tests/user-invitation/invitation.spec.js
@@ -1,17 +1,8 @@
-const { AdminCreateUserPage } = require('../../fixtures/admin/create-user-page');
const { publicTest, expect } = require('../../fixtures/index');
const { client } = require('../../fixtures/postgres-client-config');
const { DateTime } = require('luxon');
publicTest.describe('Accept invitation page', () => {
- publicTest.beforeAll(async () => {
- await client.connect();
- });
-
- publicTest.afterAll(async () => {
- await client.end();
- });
-
publicTest('should not be able to set the password if token is empty', async ({ acceptInvitationPage }) => {
await acceptInvitationPage.open('');
await acceptInvitationPage.excpectSubmitButtonToBeDisabled();
@@ -19,44 +10,83 @@ publicTest.describe('Accept invitation page', () => {
await acceptInvitationPage.excpectSubmitButtonToBeDisabled();
});
- publicTest('should not be able to set the password if token is expired', async ({ acceptInvitationPage, page }) => {
- const expiredTokenDate = DateTime.now().minus({days: 3}).toISO();
- const expiredToken = (Math.random() + 1).toString(36).substring(2);
-
- const adminCreateUserPage = new AdminCreateUserPage(page);
- adminCreateUserPage.seed(Math.ceil(Math.random() * Number.MAX_SAFE_INTEGER));
- const user = adminCreateUserPage.generateUser();
-
- const queryRole = {
- text: 'SELECT * FROM roles WHERE name = $1',
- values: ['Admin']
- };
-
- try {
- const queryRoleIdResult = await client.query(queryRole);
- expect(queryRoleIdResult.rowCount).toEqual(1);
-
- const insertUser = {
- text: 'INSERT INTO users (email, full_name, role_id, status, invitation_token, invitation_token_sent_at) VALUES ($1, $2, $3, $4, $5, $6)',
- values: [user.email, user.fullName, queryRoleIdResult.rows[0].id, 'invited', expiredToken, expiredTokenDate],
- };
-
- const insertUserResult = await client.query(insertUser);
- expect(insertUserResult.rowCount).toBe(1);
- expect(insertUserResult.command).toBe('INSERT');
- } catch (err) {
- console.error(err.message);
- throw err;
- }
-
- await acceptInvitationPage.open(expiredToken);
- await acceptInvitationPage.acceptInvitation('something');
- await acceptInvitationPage.expectAlertToBeVisible();
- });
-
publicTest('should not be able to set the password if token is not in db', async ({ acceptInvitationPage }) => {
await acceptInvitationPage.open('abc');
await acceptInvitationPage.acceptInvitation('something');
await acceptInvitationPage.expectAlertToBeVisible();
});
+
+ publicTest.describe('Accept invitation page - users', () => {
+ const expiredTokenDate = DateTime.now().minus({days: 3}).toISO();
+ const token = (Math.random() + 1).toString(36).substring(2);
+
+ publicTest.beforeAll(async () => {
+ await client.connect();
+ });
+
+ publicTest.afterAll(async () => {
+ await client.end();
+ });
+
+ publicTest('should not be able to set the password if token is expired', async ({ acceptInvitationPage, adminCreateUserPage }) => {
+ adminCreateUserPage.seed(Math.ceil(Math.random() * Number.MAX_SAFE_INTEGER));
+ const user = adminCreateUserPage.generateUser();
+
+ const queryRole = {
+ text: 'SELECT * FROM roles WHERE name = $1',
+ values: ['Admin']
+ };
+
+ try {
+ const queryRoleIdResult = await client.query(queryRole);
+ expect(queryRoleIdResult.rowCount).toEqual(1);
+
+ const insertUser = {
+ text: 'INSERT INTO users (email, full_name, role_id, status, invitation_token, invitation_token_sent_at) VALUES ($1, $2, $3, $4, $5, $6)',
+ values: [user.email, user.fullName, queryRoleIdResult.rows[0].id, 'invited', token, expiredTokenDate],
+ };
+
+ const insertUserResult = await client.query(insertUser);
+ expect(insertUserResult.rowCount).toBe(1);
+ expect(insertUserResult.command).toBe('INSERT');
+ } catch (err) {
+ console.error(err.message);
+ throw err;
+ }
+ await acceptInvitationPage.open(token);
+ await acceptInvitationPage.acceptInvitation('something');
+ await acceptInvitationPage.expectAlertToBeVisible();
+ });
+
+ publicTest('should not be able to accept invitation if user was soft deleted', async ({ acceptInvitationPage, adminCreateUserPage }) => {
+ const dateNow = DateTime.now().toISO();
+ const user = adminCreateUserPage.generateUser();
+
+ const queryRole = {
+ text: 'SELECT * FROM roles WHERE name = $1',
+ values: ['Admin']
+ };
+
+ try {
+ const queryRoleIdResult = await client.query(queryRole);
+ expect(queryRoleIdResult.rowCount).toEqual(1);
+
+ const insertUser = {
+ text: 'INSERT INTO users (email, full_name, deleted_at, role_id, status, invitation_token, invitation_token_sent_at) VALUES ($1, $2, $3, $4, $5, $6, $7)',
+ values: [user.email, user.fullName, dateNow, queryRoleIdResult.rows[0].id, 'invited', token, dateNow],
+ };
+
+ const insertUserResult = await client.query(insertUser);
+ expect(insertUserResult.rowCount).toBe(1);
+ expect(insertUserResult.command).toBe('INSERT');
+ } catch (err) {
+ console.error(err.message);
+ throw err;
+ }
+
+ await acceptInvitationPage.open(token);
+ await acceptInvitationPage.acceptInvitation('something');
+ await acceptInvitationPage.expectAlertToBeVisible();
+ });
+ });
});
diff --git a/packages/web/src/components/AppConnectionRow/index.jsx b/packages/web/src/components/AppConnectionRow/index.jsx
index 5e6dc0ac..3b76256c 100644
--- a/packages/web/src/components/AppConnectionRow/index.jsx
+++ b/packages/web/src/components/AppConnectionRow/index.jsx
@@ -10,7 +10,7 @@ import CardActionArea from '@mui/material/CardActionArea';
import CircularProgress from '@mui/material/CircularProgress';
import Stack from '@mui/material/Stack';
import { DateTime } from 'luxon';
-
+import { useQueryClient } from '@tanstack/react-query';
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
import ConnectionContextMenu from 'components/AppConnectionContextMenu';
import { DELETE_CONNECTION } from 'graphql/mutations/delete-connection';
@@ -35,6 +35,7 @@ function AppConnectionRow(props) {
const [verificationVisible, setVerificationVisible] = React.useState(false);
const contextButtonRef = React.useRef(null);
const [anchorEl, setAnchorEl] = React.useState(null);
+ const queryClient = useQueryClient();
const [deleteConnection] = useMutation(DELETE_CONNECTION);
@@ -75,6 +76,9 @@ function AppConnectionRow(props) {
},
});
+ await queryClient.invalidateQueries({
+ queryKey: ['apps', key, 'connections'],
+ });
enqueueSnackbar(formatMessage('connection.deletedMessage'), {
variant: 'success',
SnackbarProps: {
@@ -86,7 +90,7 @@ function AppConnectionRow(props) {
testConnection({ variables: { id } });
}
},
- [deleteConnection, id, testConnection, formatMessage, enqueueSnackbar],
+ [deleteConnection, id, queryClient, key, enqueueSnackbar, formatMessage, testConnection],
);
const relativeCreatedAt = DateTime.fromMillis(
diff --git a/packages/web/src/components/DeleteUserButton/index.ee.jsx b/packages/web/src/components/DeleteUserButton/index.ee.jsx
index 7a28c7e2..de4b918f 100644
--- a/packages/web/src/components/DeleteUserButton/index.ee.jsx
+++ b/packages/web/src/components/DeleteUserButton/index.ee.jsx
@@ -1,5 +1,4 @@
import PropTypes from 'prop-types';
-import { useMutation } from '@apollo/client';
import DeleteIcon from '@mui/icons-material/Delete';
import IconButton from '@mui/material/IconButton';
import { useQueryClient } from '@tanstack/react-query';
@@ -7,16 +6,14 @@ import { useQueryClient } from '@tanstack/react-query';
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
import * as React from 'react';
import ConfirmationDialog from 'components/ConfirmationDialog';
-import { DELETE_USER } from 'graphql/mutations/delete-user.ee';
import useFormatMessage from 'hooks/useFormatMessage';
+import useAdminUserDelete from 'hooks/useAdminUserDelete';
function DeleteUserButton(props) {
const { userId } = props;
const [showConfirmation, setShowConfirmation] = React.useState(false);
- const [deleteUser] = useMutation(DELETE_USER, {
- variables: { input: { id: userId } },
- refetchQueries: ['GetUsers'],
- });
+ const { mutateAsync: deleteUser } = useAdminUserDelete(userId);
+
const formatMessage = useFormatMessage();
const enqueueSnackbar = useEnqueueSnackbar();
const queryClient = useQueryClient();
@@ -33,7 +30,12 @@ function DeleteUserButton(props) {
},
});
} catch (error) {
- throw new Error('Failed while deleting!');
+ enqueueSnackbar(
+ error?.message || formatMessage('deleteUserButton.deleteError'),
+ {
+ variant: 'error',
+ },
+ );
}
}, [deleteUser]);
diff --git a/packages/web/src/components/LoginForm/index.jsx b/packages/web/src/components/LoginForm/index.jsx
index 6cf95fbc..3611417e 100644
--- a/packages/web/src/components/LoginForm/index.jsx
+++ b/packages/web/src/components/LoginForm/index.jsx
@@ -1,6 +1,5 @@
import * as React from 'react';
import { useNavigate, Link as RouterLink } from 'react-router-dom';
-import { useMutation } from '@apollo/client';
import Paper from '@mui/material/Paper';
import Link from '@mui/material/Link';
import Typography from '@mui/material/Typography';
@@ -8,17 +7,20 @@ import LoadingButton from '@mui/lab/LoadingButton';
import useAuthentication from 'hooks/useAuthentication';
import useCloud from 'hooks/useCloud';
import * as URLS from 'config/urls';
-import { LOGIN } from 'graphql/mutations/login';
import Form from 'components/Form';
import TextField from 'components/TextField';
import useFormatMessage from 'hooks/useFormatMessage';
+import useCreateAccessToken from 'hooks/useCreateAccessToken';
+import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
function LoginForm() {
const isCloud = useCloud();
const navigate = useNavigate();
const formatMessage = useFormatMessage();
+ const enqueueSnackbar = useEnqueueSnackbar();
const authentication = useAuthentication();
- const [login, { loading }] = useMutation(LOGIN);
+ const { mutateAsync: createAccessToken, isPending: loading } =
+ useCreateAccessToken();
React.useEffect(() => {
if (authentication.isAuthenticated) {
@@ -27,13 +29,19 @@ function LoginForm() {
}, [authentication.isAuthenticated]);
const handleSubmit = async (values) => {
- const { data } = await login({
- variables: {
- input: values,
- },
- });
- const { token } = data.login;
- authentication.updateToken(token);
+ try {
+ const { email, password } = values;
+ const { data } = await createAccessToken({
+ email,
+ password,
+ });
+ const { token } = data;
+ authentication.updateToken(token);
+ } catch (error) {
+ enqueueSnackbar(error?.message || formatMessage('loginForm.error'), {
+ variant: 'error',
+ });
+ }
};
return (
diff --git a/packages/web/src/components/ResetPasswordForm/index.ee.jsx b/packages/web/src/components/ResetPasswordForm/index.ee.jsx
index 996311ab..4d4cdaaf 100644
--- a/packages/web/src/components/ResetPasswordForm/index.ee.jsx
+++ b/packages/web/src/components/ResetPasswordForm/index.ee.jsx
@@ -1,4 +1,3 @@
-import { useMutation } from '@apollo/client';
import { yupResolver } from '@hookform/resolvers/yup';
import LoadingButton from '@mui/lab/LoadingButton';
import Paper from '@mui/material/Paper';
@@ -7,11 +6,12 @@ import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
import * as React from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import * as yup from 'yup';
+
import Form from 'components/Form';
import TextField from 'components/TextField';
import * as URLS from 'config/urls';
-import { RESET_PASSWORD } from 'graphql/mutations/reset-password.ee';
import useFormatMessage from 'hooks/useFormatMessage';
+import useResetPassword from 'hooks/useResetPassword';
const validationSchema = yup.object().shape({
password: yup.string().required('resetPasswordForm.mandatoryInput'),
@@ -26,25 +26,35 @@ export default function ResetPasswordForm() {
const formatMessage = useFormatMessage();
const navigate = useNavigate();
const [searchParams] = useSearchParams();
- const [resetPassword, { data, loading }] = useMutation(RESET_PASSWORD);
+ const {
+ mutateAsync: resetPassword,
+ isPending,
+ isSuccess,
+ } = useResetPassword();
const token = searchParams.get('token');
const handleSubmit = async (values) => {
- await resetPassword({
- variables: {
- input: {
- password: values.password,
- token,
+ const { password } = values;
+ try {
+ await resetPassword({
+ password,
+ token,
+ });
+ enqueueSnackbar(formatMessage('resetPasswordForm.passwordUpdated'), {
+ variant: 'success',
+ SnackbarProps: {
+ 'data-test': 'snackbar-reset-password-success',
},
- },
- });
- enqueueSnackbar(formatMessage('resetPasswordForm.passwordUpdated'), {
- variant: 'success',
- SnackbarProps: {
- 'data-test': 'snackbar-reset-password-success',
- },
- });
- navigate(URLS.LOGIN);
+ });
+ navigate(URLS.LOGIN);
+ } catch (error) {
+ enqueueSnackbar(
+ error?.message || formatMessage('resetPasswordForm.error'),
+ {
+ variant: 'error',
+ },
+ );
+ }
};
return (
@@ -113,8 +123,8 @@ export default function ResetPasswordForm() {
variant="contained"
color="primary"
sx={{ boxShadow: 2, my: 3 }}
- loading={loading}
- disabled={data || !token}
+ loading={isPending}
+ disabled={isSuccess || !token}
fullWidth
>
{formatMessage('resetPasswordForm.submit')}
diff --git a/packages/web/src/components/SignUpForm/index.ee.jsx b/packages/web/src/components/SignUpForm/index.ee.jsx
index d155729a..3ae6d2bc 100644
--- a/packages/web/src/components/SignUpForm/index.ee.jsx
+++ b/packages/web/src/components/SignUpForm/index.ee.jsx
@@ -11,8 +11,10 @@ import * as URLS from 'config/urls';
import { REGISTER_USER } from 'graphql/mutations/register-user.ee';
import Form from 'components/Form';
import TextField from 'components/TextField';
-import { LOGIN } from 'graphql/mutations/login';
import useFormatMessage from 'hooks/useFormatMessage';
+import useCreateAccessToken from 'hooks/useCreateAccessToken';
+import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
+
const validationSchema = yup.object().shape({
fullName: yup.string().trim().required('signupForm.mandatoryInput'),
email: yup
@@ -26,39 +28,57 @@ const validationSchema = yup.object().shape({
.required('signupForm.mandatoryInput')
.oneOf([yup.ref('password')], 'signupForm.passwordsMustMatch'),
});
+
const initialValues = {
fullName: '',
email: '',
password: '',
confirmPassword: '',
};
+
function SignUpForm() {
const navigate = useNavigate();
const authentication = useAuthentication();
const formatMessage = useFormatMessage();
+ const enqueueSnackbar = useEnqueueSnackbar();
const [registerUser, { loading: registerUserLoading }] =
useMutation(REGISTER_USER);
- const [login, { loading: loginLoading }] = useMutation(LOGIN);
+ const { mutateAsync: createAccessToken, isPending: loginLoading } =
+ useCreateAccessToken();
+
React.useEffect(() => {
if (authentication.isAuthenticated) {
navigate(URLS.DASHBOARD);
}
}, [authentication.isAuthenticated]);
+
const handleSubmit = async (values) => {
const { fullName, email, password } = values;
+
await registerUser({
variables: {
- input: { fullName, email, password },
+ input: {
+ fullName,
+ email,
+ password,
+ },
},
});
- const { data } = await login({
- variables: {
- input: { email, password },
- },
- });
- const { token } = data.login;
- authentication.updateToken(token);
+
+ try {
+ const { data } = await createAccessToken({
+ email,
+ password,
+ });
+ const { token } = data;
+ authentication.updateToken(token);
+ } catch (error) {
+ enqueueSnackbar(error?.message || formatMessage('signupForm.error'), {
+ variant: 'error',
+ });
+ }
};
+
return (
);
}
+
export default SignUpForm;
diff --git a/packages/web/src/graphql/mutations/delete-user.ee.js b/packages/web/src/graphql/mutations/delete-user.ee.js
deleted file mode 100644
index c64916c3..00000000
--- a/packages/web/src/graphql/mutations/delete-user.ee.js
+++ /dev/null
@@ -1,6 +0,0 @@
-import { gql } from '@apollo/client';
-export const DELETE_USER = gql`
- mutation DeleteUser($input: DeleteUserInput) {
- deleteUser(input: $input)
- }
-`;
diff --git a/packages/web/src/graphql/mutations/login.js b/packages/web/src/graphql/mutations/login.js
deleted file mode 100644
index c0f632a5..00000000
--- a/packages/web/src/graphql/mutations/login.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import { gql } from '@apollo/client';
-export const LOGIN = gql`
- mutation Login($input: LoginInput) {
- login(input: $input) {
- token
- user {
- id
- email
- }
- }
- }
-`;
diff --git a/packages/web/src/graphql/mutations/reset-password.ee.js b/packages/web/src/graphql/mutations/reset-password.ee.js
deleted file mode 100644
index 42360bca..00000000
--- a/packages/web/src/graphql/mutations/reset-password.ee.js
+++ /dev/null
@@ -1,6 +0,0 @@
-import { gql } from '@apollo/client';
-export const RESET_PASSWORD = gql`
- mutation ResetPassword($input: ResetPasswordInput) {
- resetPassword(input: $input)
- }
-`;
diff --git a/packages/web/src/hooks/useAdminUserDelete.js b/packages/web/src/hooks/useAdminUserDelete.js
new file mode 100644
index 00000000..94deb2ac
--- /dev/null
+++ b/packages/web/src/hooks/useAdminUserDelete.js
@@ -0,0 +1,15 @@
+import { useMutation } from '@tanstack/react-query';
+
+import api from 'helpers/api';
+
+export default function useAdminUserDelete(userId) {
+ const mutation = useMutation({
+ mutationFn: async () => {
+ const { data } = await api.delete(`/v1/admin/users/${userId}`);
+
+ return data;
+ },
+ });
+
+ return mutation;
+}
diff --git a/packages/web/src/hooks/useCreateAccessToken.js b/packages/web/src/hooks/useCreateAccessToken.js
new file mode 100644
index 00000000..28b5e33d
--- /dev/null
+++ b/packages/web/src/hooks/useCreateAccessToken.js
@@ -0,0 +1,15 @@
+import { useMutation } from '@tanstack/react-query';
+
+import api from 'helpers/api';
+
+export default function useCreateAccessToken() {
+ const query = useMutation({
+ mutationFn: async ({ email, password }) => {
+ const { data } = await api.post('/v1/access-tokens', { email, password });
+
+ return data;
+ },
+ });
+
+ return query;
+}
diff --git a/packages/web/src/hooks/useResetPassword.js b/packages/web/src/hooks/useResetPassword.js
new file mode 100644
index 00000000..48e8b5c2
--- /dev/null
+++ b/packages/web/src/hooks/useResetPassword.js
@@ -0,0 +1,15 @@
+import { useMutation } from '@tanstack/react-query';
+
+import api from 'helpers/api';
+
+export default function useResetPassword() {
+ const mutation = useMutation({
+ mutationFn: async (payload) => {
+ const { data } = await api.post('/v1/users/reset-password', payload);
+
+ return data;
+ },
+ });
+
+ return mutation;
+}
diff --git a/packages/web/src/locales/en.json b/packages/web/src/locales/en.json
index d2485902..e65d0ecd 100644
--- a/packages/web/src/locales/en.json
+++ b/packages/web/src/locales/en.json
@@ -144,6 +144,7 @@
"signupForm.validateEmail": "Email must be valid.",
"signupForm.passwordsMustMatch": "Passwords must match.",
"signupForm.mandatoryInput": "{inputName} is required.",
+ "signupForm.error": "Something went wrong. Please try again.",
"loginForm.title": "Login",
"loginForm.emailFieldLabel": "Email",
"loginForm.passwordFieldLabel": "Password",
@@ -152,6 +153,7 @@
"loginForm.noAccount": "Don't have an Automatisch account yet?",
"loginForm.signUp": "Sign up",
"loginPage.divider": "OR",
+ "loginForm.error": "Something went wrong. Please try again.",
"ssoProviders.loginWithProvider": "Login with {providerName}",
"forgotPasswordForm.title": "Forgot password",
"forgotPasswordForm.submit": "Send reset instructions",
@@ -165,6 +167,7 @@
"resetPasswordForm.passwordFieldLabel": "Password",
"resetPasswordForm.confirmPasswordFieldLabel": "Confirm password",
"resetPasswordForm.passwordUpdated": "The password has been updated. Now, you can login.",
+ "resetPasswordForm.error": "Something went wrong. Please try again.",
"acceptInvitationForm.passwordsMustMatch": "Passwords must match.",
"acceptInvitationForm.mandatoryInput": "{inputName} is required.",
"acceptInvitationForm.title": "Accept invitation",
@@ -210,6 +213,7 @@
"deleteUserButton.cancel": "Cancel",
"deleteUserButton.confirm": "Delete",
"deleteUserButton.successfullyDeleted": "The user has been deleted.",
+ "deleteUserButton.deleteError": "Failed while deleting!",
"createUserPage.title": "Create user",
"userForm.fullName": "Full name",
"userForm.email": "Email",
diff --git a/packages/web/src/routes.jsx b/packages/web/src/routes.jsx
index 065dfeda..3c832d86 100644
--- a/packages/web/src/routes.jsx
+++ b/packages/web/src/routes.jsx
@@ -32,11 +32,11 @@ import useAuthentication from 'hooks/useAuthentication';
import Installation from 'pages/Installation';
function Routes() {
- const { data: configData } = useAutomatischConfig();
+ const { data: configData, isSuccess } = useAutomatischConfig();
const { isAuthenticated } = useAuthentication();
const config = configData?.data;
- const installed = configData?.data?.['installation.completed'] === true;
+ const installed = isSuccess ? config?.['installation.completed'] === true : true;
const navigate = useNavigate();
useEffect(() => {