From dd0a1328e841e7eccbe706b83ee35dba707126e7 Mon Sep 17 00:00:00 2001 From: "kasia.oczkowska" Date: Wed, 17 Jul 2024 15:04:15 +0100 Subject: [PATCH 01/12] feat: refactor reset password mutation with the REST API endpoint --- .../backend/src/graphql/mutation-resolvers.js | 2 - .../graphql/mutations/reset-password.ee.js | 23 --------- packages/backend/src/graphql/schema.graphql | 6 --- .../backend/src/helpers/authentication.js | 1 - .../components/ResetPasswordForm/index.ee.jsx | 48 +++++++++++-------- .../graphql/mutations/reset-password.ee.js | 6 --- packages/web/src/hooks/useResetPassword.js | 15 ++++++ packages/web/src/locales/en.json | 1 + 8 files changed, 45 insertions(+), 57 deletions(-) delete mode 100644 packages/backend/src/graphql/mutations/reset-password.ee.js delete mode 100644 packages/web/src/graphql/mutations/reset-password.ee.js create mode 100644 packages/web/src/hooks/useResetPassword.js diff --git a/packages/backend/src/graphql/mutation-resolvers.js b/packages/backend/src/graphql/mutation-resolvers.js index cacd3936..a695f6e9 100644 --- a/packages/backend/src/graphql/mutation-resolvers.js +++ b/packages/backend/src/graphql/mutation-resolvers.js @@ -32,7 +32,6 @@ 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, @@ -54,7 +53,6 @@ const mutationResolvers = { login, registerUser, resetConnection, - resetPassword, updateAppAuthClient, updateAppConfig, updateConfig, 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..98d254c4 100644 --- a/packages/backend/src/graphql/schema.graphql +++ b/packages/backend/src/graphql/schema.graphql @@ -21,7 +21,6 @@ type Mutation { 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 @@ -404,11 +403,6 @@ input UpdateCurrentUserInput { fullName: String } -input ResetPasswordInput { - token: String! - password: String! -} - input LoginInput { email: String! password: String! diff --git a/packages/backend/src/helpers/authentication.js b/packages/backend/src/helpers/authentication.js index f57c41fa..b119915a 100644 --- a/packages/backend/src/helpers/authentication.js +++ b/packages/backend/src/helpers/authentication.js @@ -55,7 +55,6 @@ export const authenticationRules = { '*': isAuthenticatedRule, login: allow, registerUser: allow, - resetPassword: allow, }, }; 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/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/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..14a289f9 100644 --- a/packages/web/src/locales/en.json +++ b/packages/web/src/locales/en.json @@ -165,6 +165,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", From 4f7ce9874ff2a70fadc95ddee7fc9185ac444313 Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Wed, 17 Jul 2024 15:55:43 +0000 Subject: [PATCH 02/12] feat(formatter/date-time): add get current timestamp action --- .../src/apps/formatter/actions/date-time/index.js | 13 ++++++++++++- .../date-time/transformers/get-current-timestamp.js | 5 +++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 packages/backend/src/apps/formatter/actions/date-time/transformers/get-current-timestamp.js 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; From b7df1759500ede4c4df4160f6b6caafd9db1b26f Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Wed, 17 Jul 2024 16:07:23 +0000 Subject: [PATCH 03/12] feat(formatter/text): add create uuid action --- .../backend/src/apps/formatter/actions/text/index.js | 11 +++++++---- .../actions/text/transformers/create-uuid.js | 7 +++++++ 2 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 packages/backend/src/apps/formatter/actions/text/transformers/create-uuid.js 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; From 0f9d732667b03d2a746a87ac9183f9bf0a275d82 Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Mon, 15 Jul 2024 13:18:49 +0000 Subject: [PATCH 04/12] feat: add cryptography app with createHmac action --- .../cryptography/actions/create-hmac/index.js | 50 +++++++++++++++++++ .../src/apps/cryptography/actions/index.js | 3 ++ .../src/apps/cryptography/assets/favicon.svg | 3 ++ .../backend/src/apps/cryptography/index.js | 14 ++++++ packages/docs/pages/.vitepress/config.js | 9 ++++ .../docs/pages/apps/cryptography/actions.md | 12 +++++ .../pages/apps/cryptography/connection.md | 3 ++ .../pages/public/favicons/cryptography.svg | 3 ++ 8 files changed, 97 insertions(+) create mode 100644 packages/backend/src/apps/cryptography/actions/create-hmac/index.js create mode 100644 packages/backend/src/apps/cryptography/actions/index.js create mode 100644 packages/backend/src/apps/cryptography/assets/favicon.svg create mode 100644 packages/backend/src/apps/cryptography/index.js create mode 100644 packages/docs/pages/apps/cryptography/actions.md create mode 100644 packages/docs/pages/apps/cryptography/connection.md create mode 100644 packages/docs/pages/public/favicons/cryptography.svg 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..5d4d71f4 --- /dev/null +++ b/packages/backend/src/apps/cryptography/actions/create-hmac/index.js @@ -0,0 +1,50 @@ +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 data.', + 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: 'Secret key', + key: 'secretKey', + type: 'string', + required: true, + description: 'The secret key used to create the HMAC.', + variables: true, + }, + { + label: 'Message data', + key: 'data', + type: 'string', + required: true, + description: 'The input data or message to be hashed. This is the data that will be processed to generate the HMAC.', + variables: true, + }, + ], + + async run($) { + const hash = createHmac($.step.parameters.algorithm, $.step.parameters.secretKey) + .update($.step.parameters.data) + .digest('hex'); + + $.setActionItem({ + raw: { + hash + }, + }); + }, +}); 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..55f2ca82 --- /dev/null +++ b/packages/backend/src/apps/cryptography/actions/index.js @@ -0,0 +1,3 @@ +import createHmac from './create-hmac/index.js'; + +export default [createHmac]; 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/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..9b28d7c4 --- /dev/null +++ b/packages/docs/pages/apps/cryptography/actions.md @@ -0,0 +1,12 @@ +--- +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 data. +--- + + + + 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 @@ + + + From 949a2543f59a9741c355b22beee68644d834c2e1 Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Tue, 16 Jul 2024 11:24:52 +0000 Subject: [PATCH 05/12] feat(cryptography): add create signature action --- .../cryptography/actions/create-hmac/index.js | 22 ++++---- .../create-rsa-sha256-signature/index.js | 51 +++++++++++++++++++ .../src/apps/cryptography/actions/index.js | 3 +- .../docs/pages/apps/cryptography/actions.md | 4 +- 4 files changed, 67 insertions(+), 13 deletions(-) create mode 100644 packages/backend/src/apps/cryptography/actions/create-rsa-sha256-signature/index.js diff --git a/packages/backend/src/apps/cryptography/actions/create-hmac/index.js b/packages/backend/src/apps/cryptography/actions/create-hmac/index.js index 5d4d71f4..0058f27c 100644 --- a/packages/backend/src/apps/cryptography/actions/create-hmac/index.js +++ b/packages/backend/src/apps/cryptography/actions/create-hmac/index.js @@ -4,7 +4,7 @@ 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 data.', + description: 'Create a Hash-based Message Authentication Code (HMAC) using the specified algorithm, secret key, and message.', arguments: [ { label: 'Algorithm', @@ -14,10 +14,18 @@ export default defineAction({ value: 'sha256', description: 'Specifies the cryptographic hash function to use for HMAC generation.', options: [ - { label: 'SHA-256 ', value: 'sha256' }, + { 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', @@ -26,19 +34,11 @@ export default defineAction({ description: 'The secret key used to create the HMAC.', variables: true, }, - { - label: 'Message data', - key: 'data', - type: 'string', - required: true, - description: 'The input data or message to be hashed. This is the data that will be processed to generate the HMAC.', - variables: true, - }, ], async run($) { const hash = createHmac($.step.parameters.algorithm, $.step.parameters.secretKey) - .update($.step.parameters.data) + .update($.step.parameters.message) .digest('hex'); $.setActionItem({ 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..e7847ba5 --- /dev/null +++ b/packages/backend/src/apps/cryptography/actions/create-rsa-sha256-signature/index.js @@ -0,0 +1,51 @@ +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, + } + ], + + async run($) { + const signer = crypto.createSign($.step.parameters.algorithm); + signer.update($.step.parameters.message); + signer.end(); + const signature = signer.sign($.step.parameters.privateKey, 'hex'); + + $.setActionItem({ + raw: { + signature + }, + }); + }, +}); diff --git a/packages/backend/src/apps/cryptography/actions/index.js b/packages/backend/src/apps/cryptography/actions/index.js index 55f2ca82..ab2da71e 100644 --- a/packages/backend/src/apps/cryptography/actions/index.js +++ b/packages/backend/src/apps/cryptography/actions/index.js @@ -1,3 +1,4 @@ import createHmac from './create-hmac/index.js'; +import createRsaSha256Signature from './create-rsa-sha256-signature/index.js'; -export default [createHmac]; +export default [createHmac, createRsaSha256Signature]; diff --git a/packages/docs/pages/apps/cryptography/actions.md b/packages/docs/pages/apps/cryptography/actions.md index 9b28d7c4..a7aa2a77 100644 --- a/packages/docs/pages/apps/cryptography/actions.md +++ b/packages/docs/pages/apps/cryptography/actions.md @@ -2,7 +2,9 @@ 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 data. + 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. ---