From 57f13c9b9230f3c4c293ea6f3d8901990e6902c4 Mon Sep 17 00:00:00 2001
From: Paul Rastoin <45004772+prastoin@users.noreply.github.com>
Date: Tue, 19 May 2026 14:56:44 +0200
Subject: [PATCH] `[CONNECTED_ACCOUNT_BREAKING_CHANGE]` Encrypt
`ConnectedAccount` `connectionParameters` (#20673)
# Introduction
Prevent any cross user `connectedAccount` `connectionParamaters` leak
Also encrypt in db all `connectionParameters` password
Never return any password through `DTO` anymore
The settings now allow update mutation without providing the password in
edition mode
Verified all `connectionParameters.password` interaction
## Integration tests
- Added more coverage for both failing and successful paths
- Introduced a new env var that allow bypass the provider connection
test
## Legacy connected Account decryption support
Stop allowing non encrypted decryption on `accessToken` and
`refreshToken`, only allow legacy decryption on refactored
`connectionParameters`
## Upsert ownership
Completely got rid of the connected workspace schema context which is
legacy
Also now a user can only upsert a connected account for him only..
## New UI
If in edition the password is by default disabled
It needs to be selected as being edited to be enabled
## Next
- Refactor tool permissions flag not to include connected accounts
- Remove the legacy connected standard object
- Refactor and improve connected account resolver auth
---
.../src/metadata/generated/schema.graphql | 114 ++-
.../src/metadata/generated/schema.ts | 307 +++----
.../src/metadata/generated/types.ts | 768 ++++++++----------
.../src/generated-metadata/graphql.ts | 83 +-
.../accounts/types/ConnectedAccount.ts | 4 +-
.../accounts/types/ImapSmtpCaldavAccount.ts | 7 -
.../types/ImapSmtpCaldavAccountInput.ts | 7 +
.../SettingsAccountsConnectionForm.tsx | 85 +-
...gsAccountsEditImapSmtpCaldavConnection.tsx | 11 +-
.../SettingsAccountsPasswordController.tsx | 64 ++
.../accounts/constants/AccountProtocols.ts | 1 -
.../mutations/saveImapSmtpCaldavConnection.ts | 2 -
.../getConnectedImapSmtpCaldavAccount.ts | 3 -
.../graphql/queries/getMyConnectedAccounts.ts | 3 -
.../hooks/useImapSmtpCaldavConnectionForm.ts | 73 +-
.../accounts/utils/isProtocolConfigured.ts | 13 +
.../connectionImapSmtpCalDav.ts | 39 +-
...ttingsApplicationConnectionDetail.test.tsx | 2 +-
...ingsApplicationConnectionsSection.test.tsx | 2 +-
packages/twenty-server/.env.test | 1 +
...000010000-encrypt-connection-parameters.ts | 133 +++
.../instance-commands.constant.ts | 2 +
.../imap-smtp-caldav-connected-account.dto.ts | 32 +-
.../dtos/imap-smtp-caldav-connection.dto.ts | 72 --
.../dtos/imap-smtp-caldav-connection.input.ts | 37 +
.../imap-smtp-caldav-connection.module.ts | 7 +-
.../imap-smtp-caldav-connection.resolver.ts | 112 +--
.../connection-parameters-update.schema.ts | 8 +
.../schemas/connection-parameters.schema.ts | 9 +
...map-smtp-caldav-connection.service.spec.ts | 12 +-
...mtp-caldav-connection-validator.service.ts | 99 +--
.../imap-smtp-caldav-connection.service.ts | 114 +--
.../types/imap-smtp-caldav-connection.type.ts | 20 +-
...build-public-connection-parameters.util.ts | 42 +
.../twenty-config/config-variables.ts | 9 +
.../connected-account-metadata.service.ts | 14 +
.../dtos/connected-account-public.dto.ts | 17 +-
.../dtos/connected-account.dto.ts | 7 +-
.../entities/connected-account.entity.ts | 8 +
.../resolvers/connected-account.resolver.ts | 12 +-
...nected-account-token-encryption.service.ts | 104 ++-
.../drivers/caldav/caldav-driver.module.ts | 7 +-
.../services/caldav-get-events.service.ts | 21 +-
.../imap-api/imap-apis.module.ts | 2 +
.../imap-smtp-caldav-apis.service.spec.ts | 106 ++-
.../services/imap-smtp-caldav-apis.service.ts | 328 ++++----
.../imap-get-all-folders.service.spec.ts | 3 +-
.../services/imap-get-all-folders.service.ts | 2 +-
.../imap/messaging-imap-driver.module.ts | 2 +
.../imap/providers/imap-client.provider.ts | 37 +-
.../services/imap-get-messages.service.ts | 7 +-
.../smtp/messaging-smtp-driver.module.ts | 3 +-
.../smtp/providers/smtp-client.provider.ts | 23 +-
...tp-caldav-account.integration-spec.ts.snap | 67 ++
...ap-smtp-caldav-account.integration-spec.ts | 145 ++++
...ap-smtp-caldav-account.integration-spec.ts | 237 ++++++
.../utils/delete-connected-account.util.ts | 31 +
...connected-imap-smtp-caldav-account.util.ts | 46 ++
.../save-imap-smtp-caldav-account.util.ts | 72 ++
.../src/constants/AccountTypes.ts | 3 +
packages/twenty-shared/src/constants/index.ts | 2 +
61 files changed, 2263 insertions(+), 1340 deletions(-)
delete mode 100644 packages/twenty-front/src/modules/accounts/types/ImapSmtpCaldavAccount.ts
create mode 100644 packages/twenty-front/src/modules/accounts/types/ImapSmtpCaldavAccountInput.ts
create mode 100644 packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsPasswordController.tsx
delete mode 100644 packages/twenty-front/src/modules/settings/accounts/constants/AccountProtocols.ts
create mode 100644 packages/twenty-front/src/modules/settings/accounts/utils/isProtocolConfigured.ts
create mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/2-7/2-7-instance-command-slow-1798000010000-encrypt-connection-parameters.ts
delete mode 100644 packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connection.dto.ts
create mode 100644 packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connection.input.ts
create mode 100644 packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/schemas/connection-parameters-update.schema.ts
create mode 100644 packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/schemas/connection-parameters.schema.ts
create mode 100644 packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/utils/build-public-connection-parameters.util.ts
create mode 100644 packages/twenty-server/test/integration/metadata/suites/connected-account/__snapshots__/failing-save-imap-smtp-caldav-account.integration-spec.ts.snap
create mode 100644 packages/twenty-server/test/integration/metadata/suites/connected-account/failing-save-imap-smtp-caldav-account.integration-spec.ts
create mode 100644 packages/twenty-server/test/integration/metadata/suites/connected-account/successful-save-imap-smtp-caldav-account.integration-spec.ts
create mode 100644 packages/twenty-server/test/integration/metadata/suites/connected-account/utils/delete-connected-account.util.ts
create mode 100644 packages/twenty-server/test/integration/metadata/suites/connected-account/utils/get-connected-imap-smtp-caldav-account.util.ts
create mode 100644 packages/twenty-server/test/integration/metadata/suites/connected-account/utils/save-imap-smtp-caldav-account.util.ts
create mode 100644 packages/twenty-shared/src/constants/AccountTypes.ts
diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql
index 14e2f72332..76b7510f70 100644
--- a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql
+++ b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql
@@ -2417,18 +2417,49 @@ type PlaceDetailsResult {
location: Location
}
-type ConnectionParametersOutput {
+type PublicConnectionParametersOutput {
host: String!
port: Float!
username: String
- password: String!
secure: Boolean
}
-type ImapSmtpCaldavConnectionParameters {
- IMAP: ConnectionParametersOutput
- SMTP: ConnectionParametersOutput
- CALDAV: ConnectionParametersOutput
+type PublicImapSmtpCaldavConnectionParameters {
+ IMAP: PublicConnectionParametersOutput
+ SMTP: PublicConnectionParametersOutput
+ CALDAV: PublicConnectionParametersOutput
+}
+
+type ConnectedAccountPublicDTO {
+ id: UUID!
+ handle: String!
+ provider: String!
+ lastCredentialsRefreshedAt: DateTime
+ authFailedAt: DateTime
+ handleAliases: [String!]
+ scopes: [String!]
+ lastSignedInAt: DateTime
+ userWorkspaceId: UUID!
+ connectionProviderId: UUID
+ applicationId: UUID
+ name: String
+ visibility: String!
+ createdAt: DateTime!
+ updatedAt: DateTime!
+ connectionParameters: PublicImapSmtpCaldavConnectionParameters
+}
+
+type ImapSmtpCaldavPublicConnectionParams {
+ host: String!
+ port: Float!
+ username: String
+ secure: Boolean
+}
+
+type ImapSmtpCaldavPublicConnectionParameters {
+ IMAP: ImapSmtpCaldavPublicConnectionParams
+ SMTP: ImapSmtpCaldavPublicConnectionParams
+ CALDAV: ImapSmtpCaldavPublicConnectionParams
}
type ConnectedImapSmtpCaldavAccount {
@@ -2436,7 +2467,7 @@ type ConnectedImapSmtpCaldavAccount {
handle: String!
provider: String!
userWorkspaceId: UUID!
- connectionParameters: ImapSmtpCaldavConnectionParameters
+ connectionParameters: ImapSmtpCaldavPublicConnectionParameters
}
type ImapSmtpCaldavConnectionSuccess {
@@ -2551,57 +2582,6 @@ type DuplicatedDashboard {
updatedAt: String!
}
-type ConnectedAccountDTO {
- id: UUID!
- handle: String!
- provider: String!
- lastCredentialsRefreshedAt: DateTime
- authFailedAt: DateTime
- handleAliases: [String!]
- scopes: [String!]
- connectionParameters: ImapSmtpCaldavConnectionParameters
- lastSignedInAt: DateTime
- userWorkspaceId: UUID!
- connectionProviderId: UUID
- applicationId: UUID
- name: String
- visibility: String!
- createdAt: DateTime!
- updatedAt: DateTime!
-}
-
-type PublicConnectionParametersOutput {
- host: String!
- port: Float!
- username: String
- secure: Boolean
-}
-
-type PublicImapSmtpCaldavConnectionParameters {
- IMAP: PublicConnectionParametersOutput
- SMTP: PublicConnectionParametersOutput
- CALDAV: PublicConnectionParametersOutput
-}
-
-type ConnectedAccountPublicDTO {
- id: UUID!
- handle: String!
- provider: String!
- lastCredentialsRefreshedAt: DateTime
- authFailedAt: DateTime
- handleAliases: [String!]
- scopes: [String!]
- lastSignedInAt: DateTime
- userWorkspaceId: UUID!
- connectionProviderId: UUID
- applicationId: UUID
- name: String
- visibility: String!
- createdAt: DateTime!
- updatedAt: DateTime!
- connectionParameters: PublicImapSmtpCaldavConnectionParameters
-}
-
type SendEmailOutput {
success: Boolean!
error: String
@@ -3016,9 +2996,9 @@ type Query {
getViewGroup(id: String!): ViewGroup
myMessageFolders(messageChannelId: UUID): [MessageFolder!]!
myMessageChannels(connectedAccountId: UUID): [MessageChannel!]!
- myConnectedAccounts: [ConnectedAccountDTO!]!
+ myConnectedAccounts: [ConnectedAccountPublicDTO!]!
connectedAccountById(id: UUID!): ConnectedAccountPublicDTO
- connectedAccounts: [ConnectedAccountDTO!]!
+ connectedAccounts: [ConnectedAccountPublicDTO!]!
myCalendarChannels(connectedAccountId: UUID): [CalendarChannel!]!
webhooks: [Webhook!]!
webhook(id: UUID!): Webhook
@@ -3257,7 +3237,7 @@ type Mutation {
updateMessageChannel(input: UpdateMessageChannelInput!): MessageChannel!
createEmailGroupChannel(input: CreateEmailGroupChannelInput!): CreateEmailGroupChannelOutput!
deleteEmailGroupChannel(id: UUID!): MessageChannel!
- deleteConnectedAccount(id: UUID!): ConnectedAccountDTO!
+ deleteConnectedAccount(id: UUID!): ConnectedAccountPublicDTO!
updateCalendarChannel(input: UpdateCalendarChannelInput!): CalendarChannel!
createWebhook(input: CreateWebhookInput!): Webhook!
updateWebhook(input: UpdateWebhookInput!): Webhook!
@@ -3326,7 +3306,7 @@ type Mutation {
impersonate(userId: UUID!, workspaceId: UUID!): Impersonate!
sendEmail(input: SendEmailInput!): SendEmailOutput!
startChannelSync(connectedAccountId: UUID!): ChannelSyncSuccess!
- saveImapSmtpCaldavAccount(accountOwnerId: UUID!, handle: String!, connectionParameters: EmailAccountConnectionParameters!, id: UUID): ImapSmtpCaldavConnectionSuccess!
+ saveImapSmtpCaldavAccount(handle: String!, connectionParameters: EmailAccountConnectionParameters!, id: UUID): ImapSmtpCaldavConnectionSuccess!
updateLabPublicFeatureFlag(input: UpdateLabPublicFeatureFlagInput!): FeatureFlag!
createPublicDomain(domain: String!, applicationId: String): PublicDomain!
updatePublicDomain(domain: String!, applicationId: String): PublicDomain!
@@ -4389,16 +4369,16 @@ input SendEmailAttachmentInput {
}
input EmailAccountConnectionParameters {
- IMAP: ConnectionParameters
- SMTP: ConnectionParameters
- CALDAV: ConnectionParameters
+ IMAP: ConnectionParametersInput
+ SMTP: ConnectionParametersInput
+ CALDAV: ConnectionParametersInput
}
-input ConnectionParameters {
+input ConnectionParametersInput {
host: String!
port: Float!
username: String
- password: String!
+ password: String
secure: Boolean
}
diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.ts b/packages/twenty-client-sdk/src/metadata/generated/schema.ts
index 8da9e7cbc4..a15f96d5a7 100644
--- a/packages/twenty-client-sdk/src/metadata/generated/schema.ts
+++ b/packages/twenty-client-sdk/src/metadata/generated/schema.ts
@@ -2094,20 +2094,54 @@ export interface PlaceDetailsResult {
__typename: 'PlaceDetailsResult'
}
-export interface ConnectionParametersOutput {
+export interface PublicConnectionParametersOutput {
host: Scalars['String']
port: Scalars['Float']
username?: Scalars['String']
- password: Scalars['String']
secure?: Scalars['Boolean']
- __typename: 'ConnectionParametersOutput'
+ __typename: 'PublicConnectionParametersOutput'
}
-export interface ImapSmtpCaldavConnectionParameters {
- IMAP?: ConnectionParametersOutput
- SMTP?: ConnectionParametersOutput
- CALDAV?: ConnectionParametersOutput
- __typename: 'ImapSmtpCaldavConnectionParameters'
+export interface PublicImapSmtpCaldavConnectionParameters {
+ IMAP?: PublicConnectionParametersOutput
+ SMTP?: PublicConnectionParametersOutput
+ CALDAV?: PublicConnectionParametersOutput
+ __typename: 'PublicImapSmtpCaldavConnectionParameters'
+}
+
+export interface ConnectedAccountPublicDTO {
+ id: Scalars['UUID']
+ handle: Scalars['String']
+ provider: Scalars['String']
+ lastCredentialsRefreshedAt?: Scalars['DateTime']
+ authFailedAt?: Scalars['DateTime']
+ handleAliases?: Scalars['String'][]
+ scopes?: Scalars['String'][]
+ lastSignedInAt?: Scalars['DateTime']
+ userWorkspaceId: Scalars['UUID']
+ connectionProviderId?: Scalars['UUID']
+ applicationId?: Scalars['UUID']
+ name?: Scalars['String']
+ visibility: Scalars['String']
+ createdAt: Scalars['DateTime']
+ updatedAt: Scalars['DateTime']
+ connectionParameters?: PublicImapSmtpCaldavConnectionParameters
+ __typename: 'ConnectedAccountPublicDTO'
+}
+
+export interface ImapSmtpCaldavPublicConnectionParams {
+ host: Scalars['String']
+ port: Scalars['Float']
+ username?: Scalars['String']
+ secure?: Scalars['Boolean']
+ __typename: 'ImapSmtpCaldavPublicConnectionParams'
+}
+
+export interface ImapSmtpCaldavPublicConnectionParameters {
+ IMAP?: ImapSmtpCaldavPublicConnectionParams
+ SMTP?: ImapSmtpCaldavPublicConnectionParams
+ CALDAV?: ImapSmtpCaldavPublicConnectionParams
+ __typename: 'ImapSmtpCaldavPublicConnectionParameters'
}
export interface ConnectedImapSmtpCaldavAccount {
@@ -2115,7 +2149,7 @@ export interface ConnectedImapSmtpCaldavAccount {
handle: Scalars['String']
provider: Scalars['String']
userWorkspaceId: Scalars['UUID']
- connectionParameters?: ImapSmtpCaldavConnectionParameters
+ connectionParameters?: ImapSmtpCaldavPublicConnectionParameters
__typename: 'ConnectedImapSmtpCaldavAccount'
}
@@ -2243,61 +2277,6 @@ export interface DuplicatedDashboard {
__typename: 'DuplicatedDashboard'
}
-export interface ConnectedAccountDTO {
- id: Scalars['UUID']
- handle: Scalars['String']
- provider: Scalars['String']
- lastCredentialsRefreshedAt?: Scalars['DateTime']
- authFailedAt?: Scalars['DateTime']
- handleAliases?: Scalars['String'][]
- scopes?: Scalars['String'][]
- connectionParameters?: ImapSmtpCaldavConnectionParameters
- lastSignedInAt?: Scalars['DateTime']
- userWorkspaceId: Scalars['UUID']
- connectionProviderId?: Scalars['UUID']
- applicationId?: Scalars['UUID']
- name?: Scalars['String']
- visibility: Scalars['String']
- createdAt: Scalars['DateTime']
- updatedAt: Scalars['DateTime']
- __typename: 'ConnectedAccountDTO'
-}
-
-export interface PublicConnectionParametersOutput {
- host: Scalars['String']
- port: Scalars['Float']
- username?: Scalars['String']
- secure?: Scalars['Boolean']
- __typename: 'PublicConnectionParametersOutput'
-}
-
-export interface PublicImapSmtpCaldavConnectionParameters {
- IMAP?: PublicConnectionParametersOutput
- SMTP?: PublicConnectionParametersOutput
- CALDAV?: PublicConnectionParametersOutput
- __typename: 'PublicImapSmtpCaldavConnectionParameters'
-}
-
-export interface ConnectedAccountPublicDTO {
- id: Scalars['UUID']
- handle: Scalars['String']
- provider: Scalars['String']
- lastCredentialsRefreshedAt?: Scalars['DateTime']
- authFailedAt?: Scalars['DateTime']
- handleAliases?: Scalars['String'][]
- scopes?: Scalars['String'][]
- lastSignedInAt?: Scalars['DateTime']
- userWorkspaceId: Scalars['UUID']
- connectionProviderId?: Scalars['UUID']
- applicationId?: Scalars['UUID']
- name?: Scalars['String']
- visibility: Scalars['String']
- createdAt: Scalars['DateTime']
- updatedAt: Scalars['DateTime']
- connectionParameters?: PublicImapSmtpCaldavConnectionParameters
- __typename: 'ConnectedAccountPublicDTO'
-}
-
export interface SendEmailOutput {
success: Scalars['Boolean']
error?: Scalars['String']
@@ -2617,9 +2596,9 @@ export interface Query {
getViewGroup?: ViewGroup
myMessageFolders: MessageFolder[]
myMessageChannels: MessageChannel[]
- myConnectedAccounts: ConnectedAccountDTO[]
+ myConnectedAccounts: ConnectedAccountPublicDTO[]
connectedAccountById?: ConnectedAccountPublicDTO
- connectedAccounts: ConnectedAccountDTO[]
+ connectedAccounts: ConnectedAccountPublicDTO[]
myCalendarChannels: CalendarChannel[]
webhooks: Webhook[]
webhook?: Webhook
@@ -2791,7 +2770,7 @@ export interface Mutation {
updateMessageChannel: MessageChannel
createEmailGroupChannel: CreateEmailGroupChannelOutput
deleteEmailGroupChannel: MessageChannel
- deleteConnectedAccount: ConnectedAccountDTO
+ deleteConnectedAccount: ConnectedAccountPublicDTO
updateCalendarChannel: CalendarChannel
createWebhook: Webhook
updateWebhook: Webhook
@@ -5114,20 +5093,57 @@ export interface PlaceDetailsResultGenqlSelection{
__scalar?: boolean | number
}
-export interface ConnectionParametersOutputGenqlSelection{
+export interface PublicConnectionParametersOutputGenqlSelection{
host?: boolean | number
port?: boolean | number
username?: boolean | number
- password?: boolean | number
secure?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
-export interface ImapSmtpCaldavConnectionParametersGenqlSelection{
- IMAP?: ConnectionParametersOutputGenqlSelection
- SMTP?: ConnectionParametersOutputGenqlSelection
- CALDAV?: ConnectionParametersOutputGenqlSelection
+export interface PublicImapSmtpCaldavConnectionParametersGenqlSelection{
+ IMAP?: PublicConnectionParametersOutputGenqlSelection
+ SMTP?: PublicConnectionParametersOutputGenqlSelection
+ CALDAV?: PublicConnectionParametersOutputGenqlSelection
+ __typename?: boolean | number
+ __scalar?: boolean | number
+}
+
+export interface ConnectedAccountPublicDTOGenqlSelection{
+ id?: boolean | number
+ handle?: boolean | number
+ provider?: boolean | number
+ lastCredentialsRefreshedAt?: boolean | number
+ authFailedAt?: boolean | number
+ handleAliases?: boolean | number
+ scopes?: boolean | number
+ lastSignedInAt?: boolean | number
+ userWorkspaceId?: boolean | number
+ connectionProviderId?: boolean | number
+ applicationId?: boolean | number
+ name?: boolean | number
+ visibility?: boolean | number
+ createdAt?: boolean | number
+ updatedAt?: boolean | number
+ connectionParameters?: PublicImapSmtpCaldavConnectionParametersGenqlSelection
+ __typename?: boolean | number
+ __scalar?: boolean | number
+}
+
+export interface ImapSmtpCaldavPublicConnectionParamsGenqlSelection{
+ host?: boolean | number
+ port?: boolean | number
+ username?: boolean | number
+ secure?: boolean | number
+ __typename?: boolean | number
+ __scalar?: boolean | number
+}
+
+export interface ImapSmtpCaldavPublicConnectionParametersGenqlSelection{
+ IMAP?: ImapSmtpCaldavPublicConnectionParamsGenqlSelection
+ SMTP?: ImapSmtpCaldavPublicConnectionParamsGenqlSelection
+ CALDAV?: ImapSmtpCaldavPublicConnectionParamsGenqlSelection
__typename?: boolean | number
__scalar?: boolean | number
}
@@ -5137,7 +5153,7 @@ export interface ConnectedImapSmtpCaldavAccountGenqlSelection{
handle?: boolean | number
provider?: boolean | number
userWorkspaceId?: boolean | number
- connectionParameters?: ImapSmtpCaldavConnectionParametersGenqlSelection
+ connectionParameters?: ImapSmtpCaldavPublicConnectionParametersGenqlSelection
__typename?: boolean | number
__scalar?: boolean | number
}
@@ -5278,65 +5294,6 @@ export interface DuplicatedDashboardGenqlSelection{
__scalar?: boolean | number
}
-export interface ConnectedAccountDTOGenqlSelection{
- id?: boolean | number
- handle?: boolean | number
- provider?: boolean | number
- lastCredentialsRefreshedAt?: boolean | number
- authFailedAt?: boolean | number
- handleAliases?: boolean | number
- scopes?: boolean | number
- connectionParameters?: ImapSmtpCaldavConnectionParametersGenqlSelection
- lastSignedInAt?: boolean | number
- userWorkspaceId?: boolean | number
- connectionProviderId?: boolean | number
- applicationId?: boolean | number
- name?: boolean | number
- visibility?: boolean | number
- createdAt?: boolean | number
- updatedAt?: boolean | number
- __typename?: boolean | number
- __scalar?: boolean | number
-}
-
-export interface PublicConnectionParametersOutputGenqlSelection{
- host?: boolean | number
- port?: boolean | number
- username?: boolean | number
- secure?: boolean | number
- __typename?: boolean | number
- __scalar?: boolean | number
-}
-
-export interface PublicImapSmtpCaldavConnectionParametersGenqlSelection{
- IMAP?: PublicConnectionParametersOutputGenqlSelection
- SMTP?: PublicConnectionParametersOutputGenqlSelection
- CALDAV?: PublicConnectionParametersOutputGenqlSelection
- __typename?: boolean | number
- __scalar?: boolean | number
-}
-
-export interface ConnectedAccountPublicDTOGenqlSelection{
- id?: boolean | number
- handle?: boolean | number
- provider?: boolean | number
- lastCredentialsRefreshedAt?: boolean | number
- authFailedAt?: boolean | number
- handleAliases?: boolean | number
- scopes?: boolean | number
- lastSignedInAt?: boolean | number
- userWorkspaceId?: boolean | number
- connectionProviderId?: boolean | number
- applicationId?: boolean | number
- name?: boolean | number
- visibility?: boolean | number
- createdAt?: boolean | number
- updatedAt?: boolean | number
- connectionParameters?: PublicImapSmtpCaldavConnectionParametersGenqlSelection
- __typename?: boolean | number
- __scalar?: boolean | number
-}
-
export interface SendEmailOutputGenqlSelection{
success?: boolean | number
error?: boolean | number
@@ -5671,9 +5628,9 @@ export interface QueryGenqlSelection{
getViewGroup?: (ViewGroupGenqlSelection & { __args: {id: Scalars['String']} })
myMessageFolders?: (MessageFolderGenqlSelection & { __args?: {messageChannelId?: (Scalars['UUID'] | null)} })
myMessageChannels?: (MessageChannelGenqlSelection & { __args?: {connectedAccountId?: (Scalars['UUID'] | null)} })
- myConnectedAccounts?: ConnectedAccountDTOGenqlSelection
+ myConnectedAccounts?: ConnectedAccountPublicDTOGenqlSelection
connectedAccountById?: (ConnectedAccountPublicDTOGenqlSelection & { __args: {id: Scalars['UUID']} })
- connectedAccounts?: ConnectedAccountDTOGenqlSelection
+ connectedAccounts?: ConnectedAccountPublicDTOGenqlSelection
myCalendarChannels?: (CalendarChannelGenqlSelection & { __args?: {connectedAccountId?: (Scalars['UUID'] | null)} })
webhooks?: WebhookGenqlSelection
webhook?: (WebhookGenqlSelection & { __args: {id: Scalars['UUID']} })
@@ -5866,7 +5823,7 @@ export interface MutationGenqlSelection{
updateMessageChannel?: (MessageChannelGenqlSelection & { __args: {input: UpdateMessageChannelInput} })
createEmailGroupChannel?: (CreateEmailGroupChannelOutputGenqlSelection & { __args: {input: CreateEmailGroupChannelInput} })
deleteEmailGroupChannel?: (MessageChannelGenqlSelection & { __args: {id: Scalars['UUID']} })
- deleteConnectedAccount?: (ConnectedAccountDTOGenqlSelection & { __args: {id: Scalars['UUID']} })
+ deleteConnectedAccount?: (ConnectedAccountPublicDTOGenqlSelection & { __args: {id: Scalars['UUID']} })
updateCalendarChannel?: (CalendarChannelGenqlSelection & { __args: {input: UpdateCalendarChannelInput} })
createWebhook?: (WebhookGenqlSelection & { __args: {input: CreateWebhookInput} })
updateWebhook?: (WebhookGenqlSelection & { __args: {input: UpdateWebhookInput} })
@@ -5935,7 +5892,7 @@ export interface MutationGenqlSelection{
impersonate?: (ImpersonateGenqlSelection & { __args: {userId: Scalars['UUID'], workspaceId: Scalars['UUID']} })
sendEmail?: (SendEmailOutputGenqlSelection & { __args: {input: SendEmailInput} })
startChannelSync?: (ChannelSyncSuccessGenqlSelection & { __args: {connectedAccountId: Scalars['UUID']} })
- saveImapSmtpCaldavAccount?: (ImapSmtpCaldavConnectionSuccessGenqlSelection & { __args: {accountOwnerId: Scalars['UUID'], handle: Scalars['String'], connectionParameters: EmailAccountConnectionParameters, id?: (Scalars['UUID'] | null)} })
+ saveImapSmtpCaldavAccount?: (ImapSmtpCaldavConnectionSuccessGenqlSelection & { __args: {handle: Scalars['String'], connectionParameters: EmailAccountConnectionParameters, id?: (Scalars['UUID'] | null)} })
updateLabPublicFeatureFlag?: (FeatureFlagGenqlSelection & { __args: {input: UpdateLabPublicFeatureFlagInput} })
createPublicDomain?: (PublicDomainGenqlSelection & { __args: {domain: Scalars['String'], applicationId?: (Scalars['String'] | null)} })
updatePublicDomain?: (PublicDomainGenqlSelection & { __args: {domain: Scalars['String'], applicationId?: (Scalars['String'] | null)} })
@@ -6301,9 +6258,9 @@ export interface SendEmailInput {connectedAccountId: Scalars['String'],to: Scala
export interface SendEmailAttachmentInput {id: Scalars['String'],name: Scalars['String']}
-export interface EmailAccountConnectionParameters {IMAP?: (ConnectionParameters | null),SMTP?: (ConnectionParameters | null),CALDAV?: (ConnectionParameters | null)}
+export interface EmailAccountConnectionParameters {IMAP?: (ConnectionParametersInput | null),SMTP?: (ConnectionParametersInput | null),CALDAV?: (ConnectionParametersInput | null)}
-export interface ConnectionParameters {host: Scalars['String'],port: Scalars['Float'],username?: (Scalars['String'] | null),password: Scalars['String'],secure?: (Scalars['Boolean'] | null)}
+export interface ConnectionParametersInput {host: Scalars['String'],port: Scalars['Float'],username?: (Scalars['String'] | null),password?: (Scalars['String'] | null),secure?: (Scalars['Boolean'] | null)}
export interface UpdateLabPublicFeatureFlagInput {publicFeatureFlag: Scalars['String'],value: Scalars['Boolean']}
@@ -7924,18 +7881,42 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
- const ConnectionParametersOutput_possibleTypes: string[] = ['ConnectionParametersOutput']
- export const isConnectionParametersOutput = (obj?: { __typename?: any } | null): obj is ConnectionParametersOutput => {
- if (!obj?.__typename) throw new Error('__typename is missing in "isConnectionParametersOutput"')
- return ConnectionParametersOutput_possibleTypes.includes(obj.__typename)
+ const PublicConnectionParametersOutput_possibleTypes: string[] = ['PublicConnectionParametersOutput']
+ export const isPublicConnectionParametersOutput = (obj?: { __typename?: any } | null): obj is PublicConnectionParametersOutput => {
+ if (!obj?.__typename) throw new Error('__typename is missing in "isPublicConnectionParametersOutput"')
+ return PublicConnectionParametersOutput_possibleTypes.includes(obj.__typename)
}
- const ImapSmtpCaldavConnectionParameters_possibleTypes: string[] = ['ImapSmtpCaldavConnectionParameters']
- export const isImapSmtpCaldavConnectionParameters = (obj?: { __typename?: any } | null): obj is ImapSmtpCaldavConnectionParameters => {
- if (!obj?.__typename) throw new Error('__typename is missing in "isImapSmtpCaldavConnectionParameters"')
- return ImapSmtpCaldavConnectionParameters_possibleTypes.includes(obj.__typename)
+ const PublicImapSmtpCaldavConnectionParameters_possibleTypes: string[] = ['PublicImapSmtpCaldavConnectionParameters']
+ export const isPublicImapSmtpCaldavConnectionParameters = (obj?: { __typename?: any } | null): obj is PublicImapSmtpCaldavConnectionParameters => {
+ if (!obj?.__typename) throw new Error('__typename is missing in "isPublicImapSmtpCaldavConnectionParameters"')
+ return PublicImapSmtpCaldavConnectionParameters_possibleTypes.includes(obj.__typename)
+ }
+
+
+
+ const ConnectedAccountPublicDTO_possibleTypes: string[] = ['ConnectedAccountPublicDTO']
+ export const isConnectedAccountPublicDTO = (obj?: { __typename?: any } | null): obj is ConnectedAccountPublicDTO => {
+ if (!obj?.__typename) throw new Error('__typename is missing in "isConnectedAccountPublicDTO"')
+ return ConnectedAccountPublicDTO_possibleTypes.includes(obj.__typename)
+ }
+
+
+
+ const ImapSmtpCaldavPublicConnectionParams_possibleTypes: string[] = ['ImapSmtpCaldavPublicConnectionParams']
+ export const isImapSmtpCaldavPublicConnectionParams = (obj?: { __typename?: any } | null): obj is ImapSmtpCaldavPublicConnectionParams => {
+ if (!obj?.__typename) throw new Error('__typename is missing in "isImapSmtpCaldavPublicConnectionParams"')
+ return ImapSmtpCaldavPublicConnectionParams_possibleTypes.includes(obj.__typename)
+ }
+
+
+
+ const ImapSmtpCaldavPublicConnectionParameters_possibleTypes: string[] = ['ImapSmtpCaldavPublicConnectionParameters']
+ export const isImapSmtpCaldavPublicConnectionParameters = (obj?: { __typename?: any } | null): obj is ImapSmtpCaldavPublicConnectionParameters => {
+ if (!obj?.__typename) throw new Error('__typename is missing in "isImapSmtpCaldavPublicConnectionParameters"')
+ return ImapSmtpCaldavPublicConnectionParameters_possibleTypes.includes(obj.__typename)
}
@@ -8044,38 +8025,6 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
- const ConnectedAccountDTO_possibleTypes: string[] = ['ConnectedAccountDTO']
- export const isConnectedAccountDTO = (obj?: { __typename?: any } | null): obj is ConnectedAccountDTO => {
- if (!obj?.__typename) throw new Error('__typename is missing in "isConnectedAccountDTO"')
- return ConnectedAccountDTO_possibleTypes.includes(obj.__typename)
- }
-
-
-
- const PublicConnectionParametersOutput_possibleTypes: string[] = ['PublicConnectionParametersOutput']
- export const isPublicConnectionParametersOutput = (obj?: { __typename?: any } | null): obj is PublicConnectionParametersOutput => {
- if (!obj?.__typename) throw new Error('__typename is missing in "isPublicConnectionParametersOutput"')
- return PublicConnectionParametersOutput_possibleTypes.includes(obj.__typename)
- }
-
-
-
- const PublicImapSmtpCaldavConnectionParameters_possibleTypes: string[] = ['PublicImapSmtpCaldavConnectionParameters']
- export const isPublicImapSmtpCaldavConnectionParameters = (obj?: { __typename?: any } | null): obj is PublicImapSmtpCaldavConnectionParameters => {
- if (!obj?.__typename) throw new Error('__typename is missing in "isPublicImapSmtpCaldavConnectionParameters"')
- return PublicImapSmtpCaldavConnectionParameters_possibleTypes.includes(obj.__typename)
- }
-
-
-
- const ConnectedAccountPublicDTO_possibleTypes: string[] = ['ConnectedAccountPublicDTO']
- export const isConnectedAccountPublicDTO = (obj?: { __typename?: any } | null): obj is ConnectedAccountPublicDTO => {
- if (!obj?.__typename) throw new Error('__typename is missing in "isConnectedAccountPublicDTO"')
- return ConnectedAccountPublicDTO_possibleTypes.includes(obj.__typename)
- }
-
-
-
const SendEmailOutput_possibleTypes: string[] = ['SendEmailOutput']
export const isSendEmailOutput = (obj?: { __typename?: any } | null): obj is SendEmailOutput => {
if (!obj?.__typename) throw new Error('__typename is missing in "isSendEmailOutput"')
diff --git a/packages/twenty-client-sdk/src/metadata/generated/types.ts b/packages/twenty-client-sdk/src/metadata/generated/types.ts
index 5cd6d98b4b..4f16132909 100644
--- a/packages/twenty-client-sdk/src/metadata/generated/types.ts
+++ b/packages/twenty-client-sdk/src/metadata/generated/types.ts
@@ -60,26 +60,26 @@ export default {
226,
262,
263,
- 293,
+ 292,
+ 301,
302,
303,
304,
- 305,
+ 306,
307,
308,
309,
310,
311,
312,
- 313,
- 316,
- 318,
- 328,
- 335,
- 342,
- 378,
- 455,
- 467
+ 315,
+ 317,
+ 327,
+ 334,
+ 341,
+ 377,
+ 454,
+ 466
],
"types": {
"BillingProductDTO": {
@@ -4763,7 +4763,7 @@ export default {
1
]
},
- "ConnectionParametersOutput": {
+ "PublicConnectionParametersOutput": {
"host": [
1
],
@@ -4773,7 +4773,88 @@ export default {
"username": [
1
],
- "password": [
+ "secure": [
+ 6
+ ],
+ "__typename": [
+ 1
+ ]
+ },
+ "PublicImapSmtpCaldavConnectionParameters": {
+ "IMAP": [
+ 267
+ ],
+ "SMTP": [
+ 267
+ ],
+ "CALDAV": [
+ 267
+ ],
+ "__typename": [
+ 1
+ ]
+ },
+ "ConnectedAccountPublicDTO": {
+ "id": [
+ 3
+ ],
+ "handle": [
+ 1
+ ],
+ "provider": [
+ 1
+ ],
+ "lastCredentialsRefreshedAt": [
+ 4
+ ],
+ "authFailedAt": [
+ 4
+ ],
+ "handleAliases": [
+ 1
+ ],
+ "scopes": [
+ 1
+ ],
+ "lastSignedInAt": [
+ 4
+ ],
+ "userWorkspaceId": [
+ 3
+ ],
+ "connectionProviderId": [
+ 3
+ ],
+ "applicationId": [
+ 3
+ ],
+ "name": [
+ 1
+ ],
+ "visibility": [
+ 1
+ ],
+ "createdAt": [
+ 4
+ ],
+ "updatedAt": [
+ 4
+ ],
+ "connectionParameters": [
+ 268
+ ],
+ "__typename": [
+ 1
+ ]
+ },
+ "ImapSmtpCaldavPublicConnectionParams": {
+ "host": [
+ 1
+ ],
+ "port": [
+ 11
+ ],
+ "username": [
1
],
"secure": [
@@ -4783,15 +4864,15 @@ export default {
1
]
},
- "ImapSmtpCaldavConnectionParameters": {
+ "ImapSmtpCaldavPublicConnectionParameters": {
"IMAP": [
- 267
+ 270
],
"SMTP": [
- 267
+ 270
],
"CALDAV": [
- 267
+ 270
],
"__typename": [
1
@@ -4811,7 +4892,7 @@ export default {
3
],
"connectionParameters": [
- 268
+ 271
],
"__typename": [
1
@@ -4967,7 +5048,7 @@ export default {
1
],
"series": [
- 274
+ 277
],
"xAxisLabel": [
1
@@ -5016,7 +5097,7 @@ export default {
1
],
"data": [
- 276
+ 279
],
"__typename": [
1
@@ -5024,7 +5105,7 @@ export default {
},
"LineChartData": {
"series": [
- 277
+ 280
],
"xAxisLabel": [
1
@@ -5061,7 +5142,7 @@ export default {
},
"PieChartData": {
"data": [
- 279
+ 282
],
"showLegend": [
6
@@ -5105,143 +5186,6 @@ export default {
1
]
},
- "ConnectedAccountDTO": {
- "id": [
- 3
- ],
- "handle": [
- 1
- ],
- "provider": [
- 1
- ],
- "lastCredentialsRefreshedAt": [
- 4
- ],
- "authFailedAt": [
- 4
- ],
- "handleAliases": [
- 1
- ],
- "scopes": [
- 1
- ],
- "connectionParameters": [
- 268
- ],
- "lastSignedInAt": [
- 4
- ],
- "userWorkspaceId": [
- 3
- ],
- "connectionProviderId": [
- 3
- ],
- "applicationId": [
- 3
- ],
- "name": [
- 1
- ],
- "visibility": [
- 1
- ],
- "createdAt": [
- 4
- ],
- "updatedAt": [
- 4
- ],
- "__typename": [
- 1
- ]
- },
- "PublicConnectionParametersOutput": {
- "host": [
- 1
- ],
- "port": [
- 11
- ],
- "username": [
- 1
- ],
- "secure": [
- 6
- ],
- "__typename": [
- 1
- ]
- },
- "PublicImapSmtpCaldavConnectionParameters": {
- "IMAP": [
- 283
- ],
- "SMTP": [
- 283
- ],
- "CALDAV": [
- 283
- ],
- "__typename": [
- 1
- ]
- },
- "ConnectedAccountPublicDTO": {
- "id": [
- 3
- ],
- "handle": [
- 1
- ],
- "provider": [
- 1
- ],
- "lastCredentialsRefreshedAt": [
- 4
- ],
- "authFailedAt": [
- 4
- ],
- "handleAliases": [
- 1
- ],
- "scopes": [
- 1
- ],
- "lastSignedInAt": [
- 4
- ],
- "userWorkspaceId": [
- 3
- ],
- "connectionProviderId": [
- 3
- ],
- "applicationId": [
- 3
- ],
- "name": [
- 1
- ],
- "visibility": [
- 1
- ],
- "createdAt": [
- 4
- ],
- "updatedAt": [
- 4
- ],
- "connectionParameters": [
- 284
- ],
- "__typename": [
- 1
- ]
- },
"SendEmailOutput": {
"success": [
6
@@ -5292,13 +5236,13 @@ export default {
},
"EventLogQueryResult": {
"records": [
- 287
+ 286
],
"totalCount": [
21
],
"pageInfo": [
- 288
+ 287
],
"__typename": [
1
@@ -5362,7 +5306,7 @@ export default {
1
],
"parts": [
- 272
+ 275
],
"processedAt": [
4
@@ -5376,7 +5320,7 @@ export default {
},
"AgentChatThread": {
"id": [
- 293
+ 292
],
"title": [
1
@@ -5432,7 +5376,7 @@ export default {
},
"AiSystemPromptPreview": {
"sections": [
- 294
+ 293
],
"estimatedTokenCount": [
21
@@ -5508,10 +5452,10 @@ export default {
3
],
"evaluations": [
- 299
+ 298
],
"messages": [
- 291
+ 290
],
"createdAt": [
4
@@ -5528,19 +5472,19 @@ export default {
1
],
"syncStatus": [
- 302
+ 301
],
"syncStage": [
- 303
+ 302
],
"visibility": [
- 304
+ 303
],
"isContactAutoCreationEnabled": [
6
],
"contactAutoCreationPolicy": [
- 305
+ 304
],
"isSyncEnabled": [
6
@@ -5576,22 +5520,22 @@ export default {
3
],
"visibility": [
- 307
+ 306
],
"handle": [
1
],
"type": [
- 308
+ 307
],
"isContactAutoCreationEnabled": [
6
],
"contactAutoCreationPolicy": [
- 309
+ 308
],
"messageFolderImportPolicy": [
- 310
+ 309
],
"excludeNonProfessionalEmails": [
6
@@ -5600,7 +5544,7 @@ export default {
6
],
"pendingGroupEmailsAction": [
- 311
+ 310
],
"isSyncEnabled": [
6
@@ -5609,10 +5553,10 @@ export default {
4
],
"syncStatus": [
- 312
+ 311
],
"syncStage": [
- 313
+ 312
],
"syncStageStartedAt": [
4
@@ -5633,7 +5577,7 @@ export default {
4
],
"connectedAccount": [
- 285
+ 269
],
"__typename": [
1
@@ -5648,7 +5592,7 @@ export default {
"MessageChannelSyncStage": {},
"CreateEmailGroupChannelOutput": {
"messageChannel": [
- 306
+ 305
],
"forwardingAddress": [
1
@@ -5677,7 +5621,7 @@ export default {
1
],
"pendingSyncAction": [
- 316
+ 315
],
"messageChannelId": [
3
@@ -5695,7 +5639,7 @@ export default {
"MessageFolderPendingSyncAction": {},
"CollectionHash": {
"collectionName": [
- 318
+ 317
],
"hash": [
1
@@ -5762,13 +5706,13 @@ export default {
},
"MinimalMetadata": {
"objectMetadataItems": [
- 319
+ 318
],
"views": [
- 320
+ 319
],
"collectionHashes": [
- 317
+ 316
],
"__typename": [
1
@@ -5953,7 +5897,7 @@ export default {
2,
{
"input": [
- 324,
+ 323,
"GetApiKeyInput!"
]
}
@@ -6047,7 +5991,7 @@ export default {
41,
{
"input": [
- 325,
+ 324,
"LogicFunctionIdInput!"
]
}
@@ -6059,7 +6003,7 @@ export default {
15,
{
"input": [
- 325,
+ 324,
"LogicFunctionIdInput!"
]
}
@@ -6068,7 +6012,7 @@ export default {
1,
{
"input": [
- 325,
+ 324,
"LogicFunctionIdInput!"
]
}
@@ -6151,7 +6095,7 @@ export default {
25,
{
"input": [
- 326,
+ 325,
"AgentIdInput!"
]
}
@@ -6160,7 +6104,7 @@ export default {
29
],
"getToolIndex": [
- 271
+ 274
],
"getToolInputSchema": [
15,
@@ -6211,7 +6155,7 @@ export default {
}
],
"myMessageFolders": [
- 315,
+ 314,
{
"messageChannelId": [
3
@@ -6219,7 +6163,7 @@ export default {
}
],
"myMessageChannels": [
- 306,
+ 305,
{
"connectedAccountId": [
3
@@ -6227,10 +6171,10 @@ export default {
}
],
"myConnectedAccounts": [
- 282
+ 269
],
"connectedAccountById": [
- 285,
+ 269,
{
"id": [
3,
@@ -6239,10 +6183,10 @@ export default {
}
],
"connectedAccounts": [
- 282
+ 269
],
"myCalendarChannels": [
- 301,
+ 300,
{
"connectedAccountId": [
3
@@ -6250,10 +6194,10 @@ export default {
}
],
"webhooks": [
- 322
+ 321
],
"webhook": [
- 322,
+ 321,
{
"id": [
3,
@@ -6262,13 +6206,13 @@ export default {
}
],
"minimalMetadata": [
- 321
+ 320
],
"chatThreads": [
- 292
+ 291
],
"chatThread": [
- 292,
+ 291,
{
"id": [
3,
@@ -6277,7 +6221,7 @@ export default {
}
],
"chatMessages": [
- 291,
+ 290,
{
"threadId": [
3,
@@ -6286,7 +6230,7 @@ export default {
}
],
"chatStreamCatchupChunks": [
- 296,
+ 295,
{
"threadId": [
3,
@@ -6295,13 +6239,13 @@ export default {
}
],
"getAiSystemPromptPreview": [
- 295
+ 294
],
"skills": [
- 290
+ 289
],
"skill": [
- 290,
+ 289,
{
"id": [
3,
@@ -6310,7 +6254,7 @@ export default {
}
],
"agentTurns": [
- 300,
+ 299,
{
"agentId": [
3,
@@ -6455,43 +6399,43 @@ export default {
219
],
"eventLogs": [
- 289,
+ 288,
{
"input": [
- 327,
+ 326,
"EventLogQueryInput!"
]
}
],
"pieChartData": [
- 280,
+ 283,
{
"input": [
- 331,
+ 330,
"PieChartDataInput!"
]
}
],
"lineChartData": [
- 278,
+ 281,
{
"input": [
- 332,
+ 331,
"LineChartDataInput!"
]
}
],
"barChartData": [
- 275,
+ 278,
{
"input": [
- 333,
+ 332,
"BarChartDataInput!"
]
}
],
"getConnectedImapSmtpCaldavAccount": [
- 269,
+ 272,
{
"id": [
3,
@@ -6535,7 +6479,7 @@ export default {
253,
{
"input": [
- 334
+ 333
]
}
],
@@ -6571,7 +6515,7 @@ export default {
},
"LogicFunctionIdInput": {
"id": [
- 293
+ 292
],
"__typename": [
1
@@ -6587,10 +6531,10 @@ export default {
},
"EventLogQueryInput": {
"table": [
- 328
+ 327
],
"filters": [
- 329
+ 328
],
"first": [
21
@@ -6611,7 +6555,7 @@ export default {
1
],
"dateRange": [
- 330
+ 329
],
"recordId": [
1
@@ -6678,7 +6622,7 @@ export default {
1
],
"operationTypes": [
- 335
+ 334
],
"__typename": [
1
@@ -6690,7 +6634,7 @@ export default {
6,
{
"input": [
- 337,
+ 336,
"AddQuerySubscriptionInput!"
]
}
@@ -6699,7 +6643,7 @@ export default {
6,
{
"input": [
- 338,
+ 337,
"RemoveQueryFromEventStreamInput!"
]
}
@@ -6708,7 +6652,7 @@ export default {
157,
{
"inputs": [
- 339,
+ 338,
"[CreateNavigationMenuItemInput!]!"
]
}
@@ -6717,7 +6661,7 @@ export default {
157,
{
"input": [
- 339,
+ 338,
"CreateNavigationMenuItemInput!"
]
}
@@ -6726,7 +6670,7 @@ export default {
157,
{
"inputs": [
- 340,
+ 339,
"[UpdateOneNavigationMenuItemInput!]!"
]
}
@@ -6735,7 +6679,7 @@ export default {
157,
{
"input": [
- 340,
+ 339,
"UpdateOneNavigationMenuItemInput!"
]
}
@@ -6762,7 +6706,7 @@ export default {
131,
{
"file": [
- 342,
+ 341,
"Upload!"
]
}
@@ -6783,7 +6727,7 @@ export default {
131,
{
"file": [
- 342,
+ 341,
"Upload!"
]
}
@@ -6792,7 +6736,7 @@ export default {
131,
{
"file": [
- 342,
+ 341,
"Upload!"
]
}
@@ -6801,7 +6745,7 @@ export default {
131,
{
"file": [
- 342,
+ 341,
"Upload!"
]
}
@@ -6810,7 +6754,7 @@ export default {
131,
{
"file": [
- 342,
+ 341,
"Upload!"
]
}
@@ -6819,7 +6763,7 @@ export default {
131,
{
"file": [
- 342,
+ 341,
"Upload!"
],
"fieldMetadataId": [
@@ -6832,7 +6776,7 @@ export default {
131,
{
"file": [
- 342,
+ 341,
"Upload!"
],
"fieldMetadataUniversalIdentifier": [
@@ -6845,7 +6789,7 @@ export default {
61,
{
"input": [
- 343,
+ 342,
"CreateViewFilterGroupInput!"
]
}
@@ -6858,7 +6802,7 @@ export default {
"String!"
],
"input": [
- 344,
+ 343,
"UpdateViewFilterGroupInput!"
]
}
@@ -6885,7 +6829,7 @@ export default {
63,
{
"input": [
- 345,
+ 344,
"CreateViewFilterInput!"
]
}
@@ -6894,7 +6838,7 @@ export default {
63,
{
"input": [
- 346,
+ 345,
"UpdateViewFilterInput!"
]
}
@@ -6903,7 +6847,7 @@ export default {
63,
{
"input": [
- 348,
+ 347,
"DeleteViewFilterInput!"
]
}
@@ -6912,7 +6856,7 @@ export default {
63,
{
"input": [
- 349,
+ 348,
"DestroyViewFilterInput!"
]
}
@@ -6921,7 +6865,7 @@ export default {
69,
{
"input": [
- 350,
+ 349,
"CreateViewInput!"
]
}
@@ -6934,7 +6878,7 @@ export default {
"String!"
],
"input": [
- 351,
+ 350,
"UpdateViewInput!"
]
}
@@ -6961,7 +6905,7 @@ export default {
69,
{
"input": [
- 352,
+ 351,
"UpsertViewWidgetInput!"
]
}
@@ -6970,7 +6914,7 @@ export default {
66,
{
"input": [
- 357,
+ 356,
"CreateViewSortInput!"
]
}
@@ -6979,7 +6923,7 @@ export default {
66,
{
"input": [
- 358,
+ 357,
"UpdateViewSortInput!"
]
}
@@ -6988,7 +6932,7 @@ export default {
6,
{
"input": [
- 360,
+ 359,
"DeleteViewSortInput!"
]
}
@@ -6997,7 +6941,7 @@ export default {
6,
{
"input": [
- 361,
+ 360,
"DestroyViewSortInput!"
]
}
@@ -7006,7 +6950,7 @@ export default {
59,
{
"input": [
- 362,
+ 361,
"UpdateViewFieldInput!"
]
}
@@ -7015,7 +6959,7 @@ export default {
59,
{
"input": [
- 364,
+ 363,
"CreateViewFieldInput!"
]
}
@@ -7024,7 +6968,7 @@ export default {
59,
{
"inputs": [
- 364,
+ 363,
"[CreateViewFieldInput!]!"
]
}
@@ -7033,7 +6977,7 @@ export default {
59,
{
"input": [
- 365,
+ 364,
"DeleteViewFieldInput!"
]
}
@@ -7042,7 +6986,7 @@ export default {
59,
{
"input": [
- 366,
+ 365,
"DestroyViewFieldInput!"
]
}
@@ -7051,7 +6995,7 @@ export default {
68,
{
"input": [
- 367,
+ 366,
"UpdateViewFieldGroupInput!"
]
}
@@ -7060,7 +7004,7 @@ export default {
68,
{
"input": [
- 369,
+ 368,
"CreateViewFieldGroupInput!"
]
}
@@ -7069,7 +7013,7 @@ export default {
68,
{
"inputs": [
- 369,
+ 368,
"[CreateViewFieldGroupInput!]!"
]
}
@@ -7078,7 +7022,7 @@ export default {
68,
{
"input": [
- 370,
+ 369,
"DeleteViewFieldGroupInput!"
]
}
@@ -7087,7 +7031,7 @@ export default {
68,
{
"input": [
- 371,
+ 370,
"DestroyViewFieldGroupInput!"
]
}
@@ -7096,7 +7040,7 @@ export default {
69,
{
"input": [
- 372,
+ 371,
"UpsertFieldsWidgetInput!"
]
}
@@ -7105,7 +7049,7 @@ export default {
2,
{
"input": [
- 375,
+ 374,
"CreateApiKeyInput!"
]
}
@@ -7114,7 +7058,7 @@ export default {
2,
{
"input": [
- 376,
+ 375,
"UpdateApiKeyInput!"
]
}
@@ -7123,7 +7067,7 @@ export default {
2,
{
"input": [
- 377,
+ 376,
"RevokeApiKeyInput!"
]
}
@@ -7165,7 +7109,7 @@ export default {
129,
{
"type": [
- 378,
+ 377,
"AnalyticsType!"
],
"name": [
@@ -7266,7 +7210,7 @@ export default {
130,
{
"input": [
- 379,
+ 378,
"CreateApprovedAccessDomainInput!"
]
}
@@ -7275,7 +7219,7 @@ export default {
6,
{
"input": [
- 380,
+ 379,
"DeleteApprovedAccessDomainInput!"
]
}
@@ -7284,7 +7228,7 @@ export default {
130,
{
"input": [
- 381,
+ 380,
"ValidateApprovedAccessDomainInput!"
]
}
@@ -7293,7 +7237,7 @@ export default {
122,
{
"input": [
- 382,
+ 381,
"CreatePageLayoutTabInput!"
]
}
@@ -7306,7 +7250,7 @@ export default {
"String!"
],
"input": [
- 383,
+ 382,
"UpdatePageLayoutTabInput!"
]
}
@@ -7324,7 +7268,7 @@ export default {
123,
{
"input": [
- 384,
+ 383,
"CreatePageLayoutInput!"
]
}
@@ -7337,7 +7281,7 @@ export default {
"String!"
],
"input": [
- 385,
+ 384,
"UpdatePageLayoutInput!"
]
}
@@ -7359,7 +7303,7 @@ export default {
"String!"
],
"input": [
- 386,
+ 385,
"UpdatePageLayoutWithTabsInput!"
]
}
@@ -7412,7 +7356,7 @@ export default {
84,
{
"input": [
- 390,
+ 389,
"CreatePageLayoutWidgetInput!"
]
}
@@ -7425,7 +7369,7 @@ export default {
"String!"
],
"input": [
- 391,
+ 390,
"UpdatePageLayoutWidgetInput!"
]
}
@@ -7443,7 +7387,7 @@ export default {
41,
{
"input": [
- 325,
+ 324,
"LogicFunctionIdInput!"
]
}
@@ -7452,7 +7396,7 @@ export default {
41,
{
"input": [
- 392,
+ 391,
"CreateLogicFunctionFromSourceInput!"
]
}
@@ -7461,7 +7405,7 @@ export default {
166,
{
"input": [
- 393,
+ 392,
"ExecuteOneLogicFunctionInput!"
]
}
@@ -7470,7 +7414,7 @@ export default {
6,
{
"input": [
- 394,
+ 393,
"UpdateLogicFunctionFromSourceInput!"
]
}
@@ -7479,7 +7423,7 @@ export default {
35,
{
"input": [
- 396,
+ 395,
"CreateCommandMenuItemInput!"
]
}
@@ -7488,7 +7432,7 @@ export default {
35,
{
"input": [
- 397,
+ 396,
"UpdateCommandMenuItemInput!"
]
}
@@ -7506,7 +7450,7 @@ export default {
34,
{
"input": [
- 398,
+ 397,
"CreateFrontComponentInput!"
]
}
@@ -7515,7 +7459,7 @@ export default {
34,
{
"input": [
- 399,
+ 398,
"UpdateFrontComponentInput!"
]
}
@@ -7533,7 +7477,7 @@ export default {
55,
{
"input": [
- 401,
+ 400,
"CreateOneObjectInput!"
]
}
@@ -7542,7 +7486,7 @@ export default {
55,
{
"input": [
- 403,
+ 402,
"DeleteOneObjectInput!"
]
}
@@ -7551,7 +7495,7 @@ export default {
55,
{
"input": [
- 404,
+ 403,
"UpdateOneObjectInput!"
]
}
@@ -7560,7 +7504,7 @@ export default {
25,
{
"input": [
- 406,
+ 405,
"CreateAgentInput!"
]
}
@@ -7569,7 +7513,7 @@ export default {
25,
{
"input": [
- 407,
+ 406,
"UpdateAgentInput!"
]
}
@@ -7578,7 +7522,7 @@ export default {
25,
{
"input": [
- 326,
+ 325,
"AgentIdInput!"
]
}
@@ -7600,7 +7544,7 @@ export default {
29,
{
"createRoleInput": [
- 408,
+ 407,
"CreateRoleInput!"
]
}
@@ -7609,7 +7553,7 @@ export default {
29,
{
"updateRoleInput": [
- 409,
+ 408,
"UpdateRoleInput!"
]
}
@@ -7627,7 +7571,7 @@ export default {
16,
{
"upsertObjectPermissionsInput": [
- 411,
+ 410,
"UpsertObjectPermissionsInput!"
]
}
@@ -7636,7 +7580,7 @@ export default {
27,
{
"upsertPermissionFlagsInput": [
- 413,
+ 412,
"UpsertPermissionFlagsInput!"
]
}
@@ -7645,7 +7589,7 @@ export default {
26,
{
"upsertFieldPermissionsInput": [
- 414,
+ 413,
"UpsertFieldPermissionsInput!"
]
}
@@ -7654,7 +7598,7 @@ export default {
229,
{
"input": [
- 416,
+ 415,
"UpsertRowLevelPermissionPredicatesInput!"
]
}
@@ -7685,7 +7629,7 @@ export default {
43,
{
"input": [
- 419,
+ 418,
"CreateOneFieldMetadataInput!"
]
}
@@ -7694,7 +7638,7 @@ export default {
43,
{
"input": [
- 421,
+ 420,
"UpdateOneFieldMetadataInput!"
]
}
@@ -7703,7 +7647,7 @@ export default {
43,
{
"input": [
- 423,
+ 422,
"DeleteOneFieldInput!"
]
}
@@ -7712,7 +7656,7 @@ export default {
65,
{
"input": [
- 424,
+ 423,
"CreateViewGroupInput!"
]
}
@@ -7721,7 +7665,7 @@ export default {
65,
{
"inputs": [
- 424,
+ 423,
"[CreateViewGroupInput!]!"
]
}
@@ -7730,7 +7674,7 @@ export default {
65,
{
"input": [
- 425,
+ 424,
"UpdateViewGroupInput!"
]
}
@@ -7739,7 +7683,7 @@ export default {
65,
{
"inputs": [
- 425,
+ 424,
"[UpdateViewGroupInput!]!"
]
}
@@ -7748,7 +7692,7 @@ export default {
65,
{
"input": [
- 427,
+ 426,
"DeleteViewGroupInput!"
]
}
@@ -7757,49 +7701,49 @@ export default {
65,
{
"input": [
- 428,
+ 427,
"DestroyViewGroupInput!"
]
}
],
"updateMessageFolder": [
- 315,
+ 314,
{
"input": [
- 429,
+ 428,
"UpdateMessageFolderInput!"
]
}
],
"updateMessageFolders": [
- 315,
+ 314,
{
"input": [
- 431,
+ 430,
"UpdateMessageFoldersInput!"
]
}
],
"updateMessageChannel": [
- 306,
+ 305,
{
"input": [
- 432,
+ 431,
"UpdateMessageChannelInput!"
]
}
],
"createEmailGroupChannel": [
- 314,
+ 313,
{
"input": [
- 434,
+ 433,
"CreateEmailGroupChannelInput!"
]
}
],
"deleteEmailGroupChannel": [
- 306,
+ 305,
{
"id": [
3,
@@ -7808,7 +7752,7 @@ export default {
}
],
"deleteConnectedAccount": [
- 282,
+ 269,
{
"id": [
3,
@@ -7817,34 +7761,34 @@ export default {
}
],
"updateCalendarChannel": [
- 301,
+ 300,
{
"input": [
- 435,
+ 434,
"UpdateCalendarChannelInput!"
]
}
],
"createWebhook": [
- 322,
+ 321,
{
"input": [
- 437,
+ 436,
"CreateWebhookInput!"
]
}
],
"updateWebhook": [
- 322,
+ 321,
{
"input": [
- 438,
+ 437,
"UpdateWebhookInput!"
]
}
],
"deleteWebhook": [
- 322,
+ 321,
{
"id": [
3,
@@ -7853,10 +7797,10 @@ export default {
}
],
"createChatThread": [
- 292
+ 291
],
"sendChatMessage": [
- 297,
+ 296,
{
"threadId": [
3,
@@ -7877,7 +7821,7 @@ export default {
1
],
"fileAttachments": [
- 440,
+ 439,
"[FileAttachmentInput!]"
]
}
@@ -7892,7 +7836,7 @@ export default {
}
],
"renameChatThread": [
- 292,
+ 291,
{
"id": [
3,
@@ -7905,7 +7849,7 @@ export default {
}
],
"archiveChatThread": [
- 292,
+ 291,
{
"id": [
3,
@@ -7914,7 +7858,7 @@ export default {
}
],
"unarchiveChatThread": [
- 292,
+ 291,
{
"id": [
3,
@@ -7941,25 +7885,25 @@ export default {
}
],
"createSkill": [
- 290,
+ 289,
{
"input": [
- 441,
+ 440,
"CreateSkillInput!"
]
}
],
"updateSkill": [
- 290,
+ 289,
{
"input": [
- 442,
+ 441,
"UpdateSkillInput!"
]
}
],
"deleteSkill": [
- 290,
+ 289,
{
"id": [
3,
@@ -7968,7 +7912,7 @@ export default {
}
],
"activateSkill": [
- 290,
+ 289,
{
"id": [
3,
@@ -7977,7 +7921,7 @@ export default {
}
],
"deactivateSkill": [
- 290,
+ 289,
{
"id": [
3,
@@ -7986,7 +7930,7 @@ export default {
}
],
"evaluateAgentTurn": [
- 299,
+ 298,
{
"turnId": [
3,
@@ -7995,7 +7939,7 @@ export default {
}
],
"runEvaluationInput": [
- 300,
+ 299,
{
"agentId": [
3,
@@ -8011,7 +7955,7 @@ export default {
238,
{
"input": [
- 443,
+ 442,
"GetAuthorizationUrlForSSOInput!"
]
}
@@ -8265,7 +8209,7 @@ export default {
196,
{
"input": [
- 444,
+ 443,
"CreateApplicationRegistrationInput!"
]
}
@@ -8274,7 +8218,7 @@ export default {
7,
{
"input": [
- 445,
+ 444,
"UpdateApplicationRegistrationInput!"
]
}
@@ -8301,7 +8245,7 @@ export default {
5,
{
"input": [
- 447,
+ 446,
"CreateApplicationRegistrationVariableInput!"
]
}
@@ -8310,7 +8254,7 @@ export default {
5,
{
"input": [
- 448,
+ 447,
"UpdateApplicationRegistrationVariableInput!"
]
}
@@ -8328,7 +8272,7 @@ export default {
7,
{
"file": [
- 342,
+ 341,
"Upload!"
],
"universalIdentifier": [
@@ -8399,7 +8343,7 @@ export default {
6,
{
"input": [
- 450,
+ 449,
"UpdateWorkspaceMemberSettingsInput!"
]
}
@@ -8433,7 +8377,7 @@ export default {
75,
{
"data": [
- 451,
+ 450,
"ActivateWorkspaceInput!"
]
}
@@ -8442,7 +8386,7 @@ export default {
75,
{
"data": [
- 452,
+ 451,
"UpdateWorkspaceInput!"
]
}
@@ -8469,7 +8413,7 @@ export default {
6,
{
"workspaceMigration": [
- 453,
+ 452,
"WorkspaceMigrationInput!"
]
}
@@ -8487,7 +8431,7 @@ export default {
220,
{
"input": [
- 456,
+ 455,
"SetupOIDCSsoInput!"
]
}
@@ -8496,7 +8440,7 @@ export default {
220,
{
"input": [
- 457,
+ 456,
"SetupSAMLSsoInput!"
]
}
@@ -8505,7 +8449,7 @@ export default {
216,
{
"input": [
- 458,
+ 457,
"DeleteSsoInput!"
]
}
@@ -8514,13 +8458,13 @@ export default {
217,
{
"input": [
- 459,
+ 458,
"EditSsoInput!"
]
}
],
"duplicateDashboard": [
- 281,
+ 284,
{
"id": [
3,
@@ -8542,16 +8486,16 @@ export default {
}
],
"sendEmail": [
- 286,
+ 285,
{
"input": [
- 460,
+ 459,
"SendEmailInput!"
]
}
],
"startChannelSync": [
- 273,
+ 276,
{
"connectedAccountId": [
3,
@@ -8560,18 +8504,14 @@ export default {
}
],
"saveImapSmtpCaldavAccount": [
- 270,
+ 273,
{
- "accountOwnerId": [
- 3,
- "UUID!"
- ],
"handle": [
1,
"String!"
],
"connectionParameters": [
- 462,
+ 461,
"EmailAccountConnectionParameters!"
],
"id": [
@@ -8583,7 +8523,7 @@ export default {
168,
{
"input": [
- 464,
+ 463,
"UpdateLabPublicFeatureFlagInput!"
]
}
@@ -8665,7 +8605,7 @@ export default {
77,
{
"input": [
- 465,
+ 464,
"CreateOneAppTokenInput!"
]
}
@@ -8720,7 +8660,7 @@ export default {
256,
{
"file": [
- 342,
+ 341,
"Upload!"
],
"applicationUniversalIdentifier": [
@@ -8728,7 +8668,7 @@ export default {
"String!"
],
"fileFolder": [
- 467,
+ 466,
"FileFolder!"
],
"filePath": [
@@ -8837,7 +8777,7 @@ export default {
3
],
"update": [
- 341
+ 340
],
"__typename": [
1
@@ -8947,7 +8887,7 @@ export default {
3
],
"update": [
- 347
+ 346
],
"__typename": [
1
@@ -9106,16 +9046,16 @@ export default {
3
],
"viewFields": [
- 353
+ 352
],
"viewFilters": [
- 354
+ 353
],
"viewFilterGroups": [
- 355
+ 354
],
"viewSorts": [
- 356
+ 355
],
"__typename": [
1
@@ -9226,7 +9166,7 @@ export default {
3
],
"update": [
- 359
+ 358
],
"__typename": [
1
@@ -9264,7 +9204,7 @@ export default {
3
],
"update": [
- 363
+ 362
],
"__typename": [
1
@@ -9340,7 +9280,7 @@ export default {
3
],
"update": [
- 368
+ 367
],
"__typename": [
1
@@ -9404,10 +9344,10 @@ export default {
3
],
"groups": [
- 373
+ 372
],
"fields": [
- 374
+ 373
],
"__typename": [
1
@@ -9427,7 +9367,7 @@ export default {
6
],
"fields": [
- 374
+ 373
],
"__typename": [
1
@@ -9596,7 +9536,7 @@ export default {
3
],
"tabs": [
- 387
+ 386
],
"__typename": [
1
@@ -9619,7 +9559,7 @@ export default {
88
],
"widgets": [
- 388
+ 387
],
"__typename": [
1
@@ -9642,7 +9582,7 @@ export default {
3
],
"gridPosition": [
- 389
+ 388
],
"position": [
15
@@ -9691,7 +9631,7 @@ export default {
3
],
"gridPosition": [
- 389
+ 388
],
"position": [
15
@@ -9717,7 +9657,7 @@ export default {
3
],
"gridPosition": [
- 389
+ 388
],
"position": [
15
@@ -9789,7 +9729,7 @@ export default {
3
],
"update": [
- 395
+ 394
],
"__typename": [
1
@@ -9949,7 +9889,7 @@ export default {
3
],
"update": [
- 400
+ 399
],
"__typename": [
1
@@ -9968,7 +9908,7 @@ export default {
},
"CreateOneObjectInput": {
"object": [
- 402
+ 401
],
"__typename": [
1
@@ -10028,7 +9968,7 @@ export default {
},
"UpdateOneObjectInput": {
"update": [
- 405
+ 404
],
"id": [
3
@@ -10200,7 +10140,7 @@ export default {
},
"UpdateRoleInput": {
"update": [
- 410
+ 409
],
"id": [
3
@@ -10255,7 +10195,7 @@ export default {
3
],
"objectPermissions": [
- 412
+ 411
],
"__typename": [
1
@@ -10297,7 +10237,7 @@ export default {
3
],
"fieldPermissions": [
- 415
+ 414
],
"__typename": [
1
@@ -10328,10 +10268,10 @@ export default {
3
],
"predicates": [
- 417
+ 416
],
"predicateGroups": [
- 418
+ 417
],
"__typename": [
1
@@ -10391,7 +10331,7 @@ export default {
},
"CreateOneFieldMetadataInput": {
"field": [
- 420
+ 419
],
"__typename": [
1
@@ -10464,7 +10404,7 @@ export default {
3
],
"update": [
- 422
+ 421
],
"__typename": [
1
@@ -10556,7 +10496,7 @@ export default {
3
],
"update": [
- 426
+ 425
],
"__typename": [
1
@@ -10600,7 +10540,7 @@ export default {
3
],
"update": [
- 430
+ 429
],
"__typename": [
1
@@ -10619,7 +10559,7 @@ export default {
3
],
"update": [
- 430
+ 429
],
"__typename": [
1
@@ -10630,7 +10570,7 @@ export default {
3
],
"update": [
- 433
+ 432
],
"__typename": [
1
@@ -10638,16 +10578,16 @@ export default {
},
"UpdateMessageChannelInputUpdates": {
"visibility": [
- 307
+ 306
],
"isContactAutoCreationEnabled": [
6
],
"contactAutoCreationPolicy": [
- 309
+ 308
],
"messageFolderImportPolicy": [
- 310
+ 309
],
"isSyncEnabled": [
6
@@ -10675,7 +10615,7 @@ export default {
3
],
"update": [
- 436
+ 435
],
"__typename": [
1
@@ -10683,13 +10623,13 @@ export default {
},
"UpdateCalendarChannelInputUpdates": {
"visibility": [
- 304
+ 303
],
"isContactAutoCreationEnabled": [
6
],
"contactAutoCreationPolicy": [
- 305
+ 304
],
"isSyncEnabled": [
6
@@ -10723,7 +10663,7 @@ export default {
3
],
"update": [
- 439
+ 438
],
"__typename": [
1
@@ -10839,7 +10779,7 @@ export default {
1
],
"update": [
- 446
+ 445
],
"__typename": [
1
@@ -10887,7 +10827,7 @@ export default {
1
],
"update": [
- 449
+ 448
],
"__typename": [
1
@@ -11005,7 +10945,7 @@ export default {
},
"WorkspaceMigrationInput": {
"actions": [
- 454
+ 453
],
"__typename": [
1
@@ -11013,10 +10953,10 @@ export default {
},
"WorkspaceMigrationDeleteActionInput": {
"type": [
- 455
+ 454
],
"metadataName": [
- 318
+ 317
],
"universalIdentifier": [
1
@@ -11108,7 +11048,7 @@ export default {
1
],
"files": [
- 461
+ 460
],
"__typename": [
1
@@ -11127,19 +11067,19 @@ export default {
},
"EmailAccountConnectionParameters": {
"IMAP": [
- 463
+ 462
],
"SMTP": [
- 463
+ 462
],
"CALDAV": [
- 463
+ 462
],
"__typename": [
1
]
},
- "ConnectionParameters": {
+ "ConnectionParametersInput": {
"host": [
1
],
@@ -11172,7 +11112,7 @@ export default {
},
"CreateOneAppTokenInput": {
"appToken": [
- 466
+ 465
],
"__typename": [
1
@@ -11201,13 +11141,13 @@ export default {
230,
{
"input": [
- 469,
+ 468,
"LogicFunctionLogsInput!"
]
}
],
"onAgentChatEvent": [
- 298,
+ 297,
{
"threadId": [
3,
diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts
index a659d692d7..219889adf9 100644
--- a/packages/twenty-front/src/generated-metadata/graphql.ts
+++ b/packages/twenty-front/src/generated-metadata/graphql.ts
@@ -916,26 +916,6 @@ export enum CommandMenuItemAvailabilityType {
export type CommandMenuItemPayload = ObjectMetadataCommandMenuItemPayload | PathCommandMenuItemPayload;
-export type ConnectedAccountDto = {
- __typename?: 'ConnectedAccountDTO';
- applicationId?: Maybe;
- authFailedAt?: Maybe;
- connectionParameters?: Maybe;
- connectionProviderId?: Maybe;
- createdAt: Scalars['DateTime'];
- handle: Scalars['String'];
- handleAliases?: Maybe>;
- id: Scalars['UUID'];
- lastCredentialsRefreshedAt?: Maybe;
- lastSignedInAt?: Maybe;
- name?: Maybe;
- provider: Scalars['String'];
- scopes?: Maybe>;
- updatedAt: Scalars['DateTime'];
- userWorkspaceId: Scalars['UUID'];
- visibility: Scalars['String'];
-};
-
export type ConnectedAccountPublicDto = {
__typename?: 'ConnectedAccountPublicDTO';
applicationId?: Maybe;
@@ -958,30 +938,21 @@ export type ConnectedAccountPublicDto = {
export type ConnectedImapSmtpCaldavAccount = {
__typename?: 'ConnectedImapSmtpCaldavAccount';
- connectionParameters?: Maybe;
+ connectionParameters?: Maybe;
handle: Scalars['String'];
id: Scalars['UUID'];
provider: Scalars['String'];
userWorkspaceId: Scalars['UUID'];
};
-export type ConnectionParameters = {
+export type ConnectionParametersInput = {
host: Scalars['String'];
- password: Scalars['String'];
+ password?: InputMaybe;
port: Scalars['Float'];
secure?: InputMaybe;
username?: InputMaybe;
};
-export type ConnectionParametersOutput = {
- __typename?: 'ConnectionParametersOutput';
- host: Scalars['String'];
- password: Scalars['String'];
- port: Scalars['Float'];
- secure?: Maybe;
- username?: Maybe;
-};
-
export type CreateAgentInput = {
description?: InputMaybe;
evaluationInputs?: InputMaybe>;
@@ -1439,9 +1410,9 @@ export type EditSsoInput = {
};
export type EmailAccountConnectionParameters = {
- CALDAV?: InputMaybe;
- IMAP?: InputMaybe;
- SMTP?: InputMaybe;
+ CALDAV?: InputMaybe;
+ IMAP?: InputMaybe;
+ SMTP?: InputMaybe;
};
export type EmailPasswordResetLink = {
@@ -1938,19 +1909,27 @@ export type IframeConfiguration = {
url?: Maybe;
};
-export type ImapSmtpCaldavConnectionParameters = {
- __typename?: 'ImapSmtpCaldavConnectionParameters';
- CALDAV?: Maybe;
- IMAP?: Maybe;
- SMTP?: Maybe;
-};
-
export type ImapSmtpCaldavConnectionSuccess = {
__typename?: 'ImapSmtpCaldavConnectionSuccess';
connectedAccountId: Scalars['String'];
success: Scalars['Boolean'];
};
+export type ImapSmtpCaldavPublicConnectionParameters = {
+ __typename?: 'ImapSmtpCaldavPublicConnectionParameters';
+ CALDAV?: Maybe;
+ IMAP?: Maybe;
+ SMTP?: Maybe;
+};
+
+export type ImapSmtpCaldavPublicConnectionParams = {
+ __typename?: 'ImapSmtpCaldavPublicConnectionParams';
+ host: Scalars['String'];
+ port: Scalars['Float'];
+ secure?: Maybe;
+ username?: Maybe;
+};
+
export type Impersonate = {
__typename?: 'Impersonate';
loginToken: AuthToken;
@@ -2424,7 +2403,7 @@ export type Mutation = {
deleteApprovedAccessDomain: Scalars['Boolean'];
deleteChatThread: Scalars['Boolean'];
deleteCommandMenuItem: CommandMenuItem;
- deleteConnectedAccount: ConnectedAccountDto;
+ deleteConnectedAccount: ConnectedAccountPublicDto;
deleteCurrentWorkspace: Workspace;
deleteEmailGroupChannel: MessageChannel;
deleteEmailingDomain: Scalars['Boolean'];
@@ -3193,7 +3172,6 @@ export type MutationRunWorkspaceMigrationArgs = {
export type MutationSaveImapSmtpCaldavAccountArgs = {
- accountOwnerId: Scalars['UUID'];
connectionParameters: EmailAccountConnectionParameters;
handle: Scalars['String'];
id?: InputMaybe;
@@ -4109,7 +4087,7 @@ export type Query = {
commandMenuItem?: Maybe;
commandMenuItems: Array;
connectedAccountById?: Maybe;
- connectedAccounts: Array;
+ connectedAccounts: Array;
currentUser: User;
currentWorkspace: Workspace;
enterpriseCheckoutSession?: Maybe;
@@ -4179,7 +4157,7 @@ export type Query = {
listPlans: Array;
minimalMetadata: MinimalMetadata;
myCalendarChannels: Array;
- myConnectedAccounts: Array;
+ myConnectedAccounts: Array;
myMessageChannels: Array;
myMessageFolders: Array;
navigationMenuItem?: Maybe;
@@ -6867,7 +6845,7 @@ export type DeleteConnectedAccountMutationVariables = Exact<{
}>;
-export type DeleteConnectedAccountMutation = { __typename?: 'Mutation', deleteConnectedAccount: { __typename?: 'ConnectedAccountDTO', id: string } };
+export type DeleteConnectedAccountMutation = { __typename?: 'Mutation', deleteConnectedAccount: { __typename?: 'ConnectedAccountPublicDTO', id: string } };
export type DeleteEmailGroupChannelMutationVariables = Exact<{
id: Scalars['UUID'];
@@ -6877,7 +6855,6 @@ export type DeleteEmailGroupChannelMutationVariables = Exact<{
export type DeleteEmailGroupChannelMutation = { __typename?: 'Mutation', deleteEmailGroupChannel: { __typename?: 'MessageChannel', id: string } };
export type SaveImapSmtpCaldavAccountMutationVariables = Exact<{
- accountOwnerId: Scalars['UUID'];
handle: Scalars['String'];
connectionParameters: EmailAccountConnectionParameters;
id?: InputMaybe;
@@ -6935,7 +6912,7 @@ export type GetConnectedImapSmtpCaldavAccountQueryVariables = Exact<{
}>;
-export type GetConnectedImapSmtpCaldavAccountQuery = { __typename?: 'Query', getConnectedImapSmtpCaldavAccount: { __typename?: 'ConnectedImapSmtpCaldavAccount', id: string, handle: string, provider: string, userWorkspaceId: string, connectionParameters?: { __typename?: 'ImapSmtpCaldavConnectionParameters', IMAP?: { __typename?: 'ConnectionParametersOutput', host: string, port: number, secure?: boolean | null, username?: string | null, password: string } | null, SMTP?: { __typename?: 'ConnectionParametersOutput', host: string, username?: string | null, port: number, secure?: boolean | null, password: string } | null, CALDAV?: { __typename?: 'ConnectionParametersOutput', host: string, port: number, secure?: boolean | null, username?: string | null, password: string } | null } | null } };
+export type GetConnectedImapSmtpCaldavAccountQuery = { __typename?: 'Query', getConnectedImapSmtpCaldavAccount: { __typename?: 'ConnectedImapSmtpCaldavAccount', id: string, handle: string, provider: string, userWorkspaceId: string, connectionParameters?: { __typename?: 'ImapSmtpCaldavPublicConnectionParameters', IMAP?: { __typename?: 'ImapSmtpCaldavPublicConnectionParams', host: string, port: number, secure?: boolean | null, username?: string | null } | null, SMTP?: { __typename?: 'ImapSmtpCaldavPublicConnectionParams', host: string, username?: string | null, port: number, secure?: boolean | null } | null, CALDAV?: { __typename?: 'ImapSmtpCaldavPublicConnectionParams', host: string, port: number, secure?: boolean | null, username?: string | null } | null } | null } };
export type MyCalendarChannelsQueryVariables = Exact<{
connectedAccountId?: InputMaybe;
@@ -6947,7 +6924,7 @@ export type MyCalendarChannelsQuery = { __typename?: 'Query', myCalendarChannels
export type MyConnectedAccountsQueryVariables = Exact<{ [key: string]: never; }>;
-export type MyConnectedAccountsQuery = { __typename?: 'Query', myConnectedAccounts: Array<{ __typename?: 'ConnectedAccountDTO', id: string, handle: string, provider: string, authFailedAt?: string | null, scopes?: Array | null, handleAliases?: Array | null, lastSignedInAt?: string | null, userWorkspaceId: string, connectionProviderId?: string | null, name?: string | null, visibility: string, lastCredentialsRefreshedAt?: string | null, createdAt: string, updatedAt: string, connectionParameters?: { __typename?: 'ImapSmtpCaldavConnectionParameters', IMAP?: { __typename?: 'ConnectionParametersOutput', host: string, port: number, secure?: boolean | null, username?: string | null, password: string } | null, SMTP?: { __typename?: 'ConnectionParametersOutput', host: string, port: number, secure?: boolean | null, username?: string | null, password: string } | null, CALDAV?: { __typename?: 'ConnectionParametersOutput', host: string, username?: string | null, password: string } | null } | null }> };
+export type MyConnectedAccountsQuery = { __typename?: 'Query', myConnectedAccounts: Array<{ __typename?: 'ConnectedAccountPublicDTO', id: string, handle: string, provider: string, authFailedAt?: string | null, scopes?: Array | null, handleAliases?: Array | null, lastSignedInAt?: string | null, userWorkspaceId: string, connectionProviderId?: string | null, name?: string | null, visibility: string, lastCredentialsRefreshedAt?: string | null, createdAt: string, updatedAt: string, connectionParameters?: { __typename?: 'PublicImapSmtpCaldavConnectionParameters', IMAP?: { __typename?: 'PublicConnectionParametersOutput', host: string, port: number, secure?: boolean | null, username?: string | null } | null, SMTP?: { __typename?: 'PublicConnectionParametersOutput', host: string, port: number, secure?: boolean | null, username?: string | null } | null, CALDAV?: { __typename?: 'PublicConnectionParametersOutput', host: string, username?: string | null } | null } | null }> };
export type MyMessageChannelsQueryVariables = Exact<{
connectedAccountId?: InputMaybe;
@@ -8086,16 +8063,16 @@ export const PieChartDataDocument = {"kind":"Document","definitions":[{"kind":"O
export const CreateEmailGroupChannelDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateEmailGroupChannel"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateEmailGroupChannelInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createEmailGroupChannel"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"messageChannel"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"isSyncEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"excludeGroupEmails"}},{"kind":"Field","name":{"kind":"Name","value":"contactAutoCreationPolicy"}}]}},{"kind":"Field","name":{"kind":"Name","value":"forwardingAddress"}}]}}]}}]} as unknown as DocumentNode;
export const DeleteConnectedAccountDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteConnectedAccount"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteConnectedAccount"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode;
export const DeleteEmailGroupChannelDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteEmailGroupChannel"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteEmailGroupChannel"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode;
-export const SaveImapSmtpCaldavAccountDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SaveImapSmtpCaldavAccount"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"accountOwnerId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"handle"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"connectionParameters"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"EmailAccountConnectionParameters"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"saveImapSmtpCaldavAccount"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"accountOwnerId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"accountOwnerId"}}},{"kind":"Argument","name":{"kind":"Name","value":"handle"},"value":{"kind":"Variable","name":{"kind":"Name","value":"handle"}}},{"kind":"Argument","name":{"kind":"Name","value":"connectionParameters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"connectionParameters"}}},{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"success"}},{"kind":"Field","name":{"kind":"Name","value":"connectedAccountId"}}]}}]}}]} as unknown as DocumentNode;
+export const SaveImapSmtpCaldavAccountDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SaveImapSmtpCaldavAccount"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"handle"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"connectionParameters"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"EmailAccountConnectionParameters"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"saveImapSmtpCaldavAccount"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"handle"},"value":{"kind":"Variable","name":{"kind":"Name","value":"handle"}}},{"kind":"Argument","name":{"kind":"Name","value":"connectionParameters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"connectionParameters"}}},{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"success"}},{"kind":"Field","name":{"kind":"Name","value":"connectedAccountId"}}]}}]}}]} as unknown as DocumentNode;
export const StartChannelSyncDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"StartChannelSync"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"connectedAccountId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"startChannelSync"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"connectedAccountId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"connectedAccountId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"success"}}]}}]}}]} as unknown as DocumentNode;
export const UpdateCalendarChannelDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateCalendarChannel"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateCalendarChannelInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateCalendarChannel"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"isContactAutoCreationEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"contactAutoCreationPolicy"}}]}}]}}]} as unknown as DocumentNode;
export const UpdateMessageChannelDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateMessageChannel"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateMessageChannelInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateMessageChannel"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"contactAutoCreationPolicy"}},{"kind":"Field","name":{"kind":"Name","value":"excludeNonProfessionalEmails"}},{"kind":"Field","name":{"kind":"Name","value":"excludeGroupEmails"}},{"kind":"Field","name":{"kind":"Name","value":"messageFolderImportPolicy"}}]}}]}}]} as unknown as DocumentNode;
export const UpdateMessageFolderDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateMessageFolder"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateMessageFolderInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateMessageFolder"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"isSynced"}}]}}]}}]} as unknown as DocumentNode;
export const UpdateMessageFoldersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateMessageFolders"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateMessageFoldersInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateMessageFolders"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"isSynced"}}]}}]}}]} as unknown as DocumentNode;
export const ConnectedAccountByIdDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ConnectedAccountById"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"connectedAccountById"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}},{"kind":"Field","name":{"kind":"Name","value":"provider"}},{"kind":"Field","name":{"kind":"Name","value":"scopes"}},{"kind":"Field","name":{"kind":"Name","value":"userWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"connectionParameters"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"IMAP"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PublicConnectionParams"}}]}},{"kind":"Field","name":{"kind":"Name","value":"SMTP"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PublicConnectionParams"}}]}},{"kind":"Field","name":{"kind":"Name","value":"CALDAV"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PublicConnectionParams"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PublicConnectionParams"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PublicConnectionParametersOutput"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"host"}},{"kind":"Field","name":{"kind":"Name","value":"port"}},{"kind":"Field","name":{"kind":"Name","value":"secure"}},{"kind":"Field","name":{"kind":"Name","value":"username"}}]}}]} as unknown as DocumentNode;
-export const GetConnectedImapSmtpCaldavAccountDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetConnectedImapSmtpCaldavAccount"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getConnectedImapSmtpCaldavAccount"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}},{"kind":"Field","name":{"kind":"Name","value":"provider"}},{"kind":"Field","name":{"kind":"Name","value":"userWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"connectionParameters"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"IMAP"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"host"}},{"kind":"Field","name":{"kind":"Name","value":"port"}},{"kind":"Field","name":{"kind":"Name","value":"secure"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"password"}}]}},{"kind":"Field","name":{"kind":"Name","value":"SMTP"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"host"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"port"}},{"kind":"Field","name":{"kind":"Name","value":"secure"}},{"kind":"Field","name":{"kind":"Name","value":"password"}}]}},{"kind":"Field","name":{"kind":"Name","value":"CALDAV"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"host"}},{"kind":"Field","name":{"kind":"Name","value":"port"}},{"kind":"Field","name":{"kind":"Name","value":"secure"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"password"}}]}}]}}]}}]}}]} as unknown as DocumentNode;
+export const GetConnectedImapSmtpCaldavAccountDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetConnectedImapSmtpCaldavAccount"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getConnectedImapSmtpCaldavAccount"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}},{"kind":"Field","name":{"kind":"Name","value":"provider"}},{"kind":"Field","name":{"kind":"Name","value":"userWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"connectionParameters"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"IMAP"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"host"}},{"kind":"Field","name":{"kind":"Name","value":"port"}},{"kind":"Field","name":{"kind":"Name","value":"secure"}},{"kind":"Field","name":{"kind":"Name","value":"username"}}]}},{"kind":"Field","name":{"kind":"Name","value":"SMTP"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"host"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"port"}},{"kind":"Field","name":{"kind":"Name","value":"secure"}}]}},{"kind":"Field","name":{"kind":"Name","value":"CALDAV"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"host"}},{"kind":"Field","name":{"kind":"Name","value":"port"}},{"kind":"Field","name":{"kind":"Name","value":"secure"}},{"kind":"Field","name":{"kind":"Name","value":"username"}}]}}]}}]}}]}}]} as unknown as DocumentNode;
export const MyCalendarChannelsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MyCalendarChannels"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"connectedAccountId"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"myCalendarChannels"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"connectedAccountId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"connectedAccountId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"syncStatus"}},{"kind":"Field","name":{"kind":"Name","value":"syncStage"}},{"kind":"Field","name":{"kind":"Name","value":"syncStageStartedAt"}},{"kind":"Field","name":{"kind":"Name","value":"isContactAutoCreationEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"contactAutoCreationPolicy"}},{"kind":"Field","name":{"kind":"Name","value":"isSyncEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"connectedAccountId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode;
-export const MyConnectedAccountsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MyConnectedAccounts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"myConnectedAccounts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}},{"kind":"Field","name":{"kind":"Name","value":"provider"}},{"kind":"Field","name":{"kind":"Name","value":"authFailedAt"}},{"kind":"Field","name":{"kind":"Name","value":"scopes"}},{"kind":"Field","name":{"kind":"Name","value":"handleAliases"}},{"kind":"Field","name":{"kind":"Name","value":"lastSignedInAt"}},{"kind":"Field","name":{"kind":"Name","value":"userWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"connectionProviderId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"lastCredentialsRefreshedAt"}},{"kind":"Field","name":{"kind":"Name","value":"connectionParameters"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"IMAP"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"host"}},{"kind":"Field","name":{"kind":"Name","value":"port"}},{"kind":"Field","name":{"kind":"Name","value":"secure"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"password"}}]}},{"kind":"Field","name":{"kind":"Name","value":"SMTP"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"host"}},{"kind":"Field","name":{"kind":"Name","value":"port"}},{"kind":"Field","name":{"kind":"Name","value":"secure"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"password"}}]}},{"kind":"Field","name":{"kind":"Name","value":"CALDAV"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"host"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"password"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode;
+export const MyConnectedAccountsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MyConnectedAccounts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"myConnectedAccounts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}},{"kind":"Field","name":{"kind":"Name","value":"provider"}},{"kind":"Field","name":{"kind":"Name","value":"authFailedAt"}},{"kind":"Field","name":{"kind":"Name","value":"scopes"}},{"kind":"Field","name":{"kind":"Name","value":"handleAliases"}},{"kind":"Field","name":{"kind":"Name","value":"lastSignedInAt"}},{"kind":"Field","name":{"kind":"Name","value":"userWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"connectionProviderId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"lastCredentialsRefreshedAt"}},{"kind":"Field","name":{"kind":"Name","value":"connectionParameters"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"IMAP"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"host"}},{"kind":"Field","name":{"kind":"Name","value":"port"}},{"kind":"Field","name":{"kind":"Name","value":"secure"}},{"kind":"Field","name":{"kind":"Name","value":"username"}}]}},{"kind":"Field","name":{"kind":"Name","value":"SMTP"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"host"}},{"kind":"Field","name":{"kind":"Name","value":"port"}},{"kind":"Field","name":{"kind":"Name","value":"secure"}},{"kind":"Field","name":{"kind":"Name","value":"username"}}]}},{"kind":"Field","name":{"kind":"Name","value":"CALDAV"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"host"}},{"kind":"Field","name":{"kind":"Name","value":"username"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode;
export const MyMessageChannelsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MyMessageChannels"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"connectedAccountId"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"myMessageChannels"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"connectedAccountId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"connectedAccountId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"isContactAutoCreationEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"contactAutoCreationPolicy"}},{"kind":"Field","name":{"kind":"Name","value":"messageFolderImportPolicy"}},{"kind":"Field","name":{"kind":"Name","value":"excludeNonProfessionalEmails"}},{"kind":"Field","name":{"kind":"Name","value":"excludeGroupEmails"}},{"kind":"Field","name":{"kind":"Name","value":"isSyncEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"syncStatus"}},{"kind":"Field","name":{"kind":"Name","value":"syncStage"}},{"kind":"Field","name":{"kind":"Name","value":"syncStageStartedAt"}},{"kind":"Field","name":{"kind":"Name","value":"connectedAccountId"}},{"kind":"Field","name":{"kind":"Name","value":"connectedAccount"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode;
export const MyMessageFoldersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MyMessageFolders"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"messageChannelId"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"myMessageFolders"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"messageChannelId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"messageChannelId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"isSynced"}},{"kind":"Field","name":{"kind":"Name","value":"isSentFolder"}},{"kind":"Field","name":{"kind":"Name","value":"parentFolderId"}},{"kind":"Field","name":{"kind":"Name","value":"externalId"}},{"kind":"Field","name":{"kind":"Name","value":"messageChannelId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode;
export const DeleteApplicationRegistrationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteApplicationRegistration"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteApplicationRegistration"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}]}}]} as unknown as DocumentNode;
diff --git a/packages/twenty-front/src/modules/accounts/types/ConnectedAccount.ts b/packages/twenty-front/src/modules/accounts/types/ConnectedAccount.ts
index 2b9a4b440c..d6dbac7ac8 100644
--- a/packages/twenty-front/src/modules/accounts/types/ConnectedAccount.ts
+++ b/packages/twenty-front/src/modules/accounts/types/ConnectedAccount.ts
@@ -1,5 +1,5 @@
import { type CalendarChannel } from '@/accounts/types/CalendarChannel';
-import { type ImapSmtpCaldavAccount } from '@/accounts/types/ImapSmtpCaldavAccount';
+import { type ImapSmtpCaldavAccountInput } from '@/accounts/types/ImapSmtpCaldavAccountInput';
import { type ConnectedAccountProvider } from 'twenty-shared/types';
import { type MessageChannel } from './MessageChannel';
@@ -18,7 +18,7 @@ export type ConnectedAccount = {
// (those are upstream-granted OAuth permissions).
visibility: 'user' | 'workspace';
lastCredentialsRefreshedAt: string | null;
- connectionParameters: ImapSmtpCaldavAccount | null;
+ connectionParameters: ImapSmtpCaldavAccountInput | null;
createdAt: string;
updatedAt: string;
messageChannels: MessageChannel[];
diff --git a/packages/twenty-front/src/modules/accounts/types/ImapSmtpCaldavAccount.ts b/packages/twenty-front/src/modules/accounts/types/ImapSmtpCaldavAccount.ts
deleted file mode 100644
index ebb5fe42fe..0000000000
--- a/packages/twenty-front/src/modules/accounts/types/ImapSmtpCaldavAccount.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { type ConnectionParameters } from '~/generated-metadata/graphql';
-
-export type ImapSmtpCaldavAccount = {
- IMAP?: ConnectionParameters;
- SMTP?: ConnectionParameters;
- CALDAV?: ConnectionParameters;
-};
diff --git a/packages/twenty-front/src/modules/accounts/types/ImapSmtpCaldavAccountInput.ts b/packages/twenty-front/src/modules/accounts/types/ImapSmtpCaldavAccountInput.ts
new file mode 100644
index 0000000000..de298463d1
--- /dev/null
+++ b/packages/twenty-front/src/modules/accounts/types/ImapSmtpCaldavAccountInput.ts
@@ -0,0 +1,7 @@
+import { type ConnectionParametersInput } from '~/generated-metadata/graphql';
+
+export type ImapSmtpCaldavAccountInput = {
+ IMAP?: ConnectionParametersInput;
+ SMTP?: ConnectionParametersInput;
+ CALDAV?: ConnectionParametersInput;
+};
diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsConnectionForm.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsConnectionForm.tsx
index d4c60e44c7..af8b22b951 100644
--- a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsConnectionForm.tsx
+++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsConnectionForm.tsx
@@ -1,11 +1,14 @@
import { styled } from '@linaria/react';
import { useLingui } from '@lingui/react/macro';
+import { useState } from 'react';
import { type Control, Controller } from 'react-hook-form';
import { Select } from '@/ui/input/components/Select';
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
+import { SettingsAccountsPasswordController } from '@/settings/accounts/components/SettingsAccountsPasswordController';
import { type ConnectionFormData } from '@/settings/accounts/hooks/useImapSmtpCaldavConnectionForm';
+import { type AccountType } from 'twenty-shared/constants';
import { H2Title } from 'twenty-ui/display';
import { Section } from 'twenty-ui/layout';
import { MOBILE_VIEWPORT, themeCssVariables } from 'twenty-ui/theme-constants';
@@ -60,14 +63,27 @@ const StyledFieldGroup = styled.div`
type SettingsAccountsConnectionFormProps = {
control: Control;
isEditing: boolean;
+ existingProtocols?: AccountType[];
};
export const SettingsAccountsConnectionForm = ({
control,
isEditing,
+ existingProtocols = [],
}: SettingsAccountsConnectionFormProps) => {
const { t } = useLingui();
+ const [isProtocolPasswordBeingEdited, setIsProtocolPasswordBeingEdited] =
+ useState>({
+ IMAP: false,
+ SMTP: false,
+ CALDAV: false,
+ });
+
+ const isPasswordInputDisabled = (protocol: AccountType) =>
+ existingProtocols.includes(protocol) &&
+ !isProtocolPasswordBeingEdited[protocol];
+
const getDescription = () => {
if (isEditing) {
return t`Update your account's configuration. Configure any combination of IMAP, SMTP, and CalDAV as needed.`;
@@ -137,20 +153,17 @@ export const SettingsAccountsConnectionForm = ({
)}
/>
- (
-
- )}
+ disabled={isPasswordInputDisabled('IMAP')}
+ onUnlock={() =>
+ setIsProtocolPasswordBeingEdited((prev) => ({
+ ...prev,
+ IMAP: true,
+ }))
+ }
/>
@@ -235,20 +248,17 @@ export const SettingsAccountsConnectionForm = ({
)}
/>
- (
-
- )}
+ disabled={isPasswordInputDisabled('SMTP')}
+ onUnlock={() =>
+ setIsProtocolPasswordBeingEdited((prev) => ({
+ ...prev,
+ SMTP: true,
+ }))
+ }
/>
@@ -333,20 +343,17 @@ export const SettingsAccountsConnectionForm = ({
)}
/>
- (
-
- )}
+ disabled={isPasswordInputDisabled('CALDAV')}
+ onUnlock={() =>
+ setIsProtocolPasswordBeingEdited((prev) => ({
+ ...prev,
+ CALDAV: true,
+ }))
+ }
/>
diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsEditImapSmtpCaldavConnection.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsEditImapSmtpCaldavConnection.tsx
index 21586ed10f..08072ca8db 100644
--- a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsEditImapSmtpCaldavConnection.tsx
+++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsEditImapSmtpCaldavConnection.tsx
@@ -13,6 +13,7 @@ import { Loader } from 'twenty-ui/feedback';
import { getSettingsPath } from 'twenty-shared/utils';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
+import { ACCOUNT_TYPES } from 'twenty-shared/constants';
import { NotFound } from '~/pages/not-found/NotFound';
import { useImapSmtpCaldavConnectionForm } from '@/settings/accounts/hooks/useImapSmtpCaldavConnectionForm';
import { SettingsAccountsConnectionForm } from './SettingsAccountsConnectionForm';
@@ -56,6 +57,10 @@ export const SettingsAccountsEditImapSmtpCaldavConnection = () => {
return ;
}
+ const existingProtocols = ACCOUNT_TYPES.filter(
+ (protocol) => connectedAccount?.connectionParameters?.[protocol]?.host,
+ );
+
const renderForm = () => (
// oxlint-disable-next-line react/jsx-props-no-spreading
@@ -83,7 +88,11 @@ export const SettingsAccountsEditImapSmtpCaldavConnection = () => {
}
>
-
+
diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsPasswordController.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsPasswordController.tsx
new file mode 100644
index 0000000000..a4c835771c
--- /dev/null
+++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsPasswordController.tsx
@@ -0,0 +1,64 @@
+import { styled } from '@linaria/react';
+import { Trans } from '@lingui/react/macro';
+import { type Control, Controller } from 'react-hook-form';
+
+import { type AccountType } from 'twenty-shared/constants';
+
+import { type ConnectionFormData } from '@/settings/accounts/hooks/useImapSmtpCaldavConnectionForm';
+import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
+import { ClickToActionLink } from 'twenty-ui/navigation';
+import { themeCssVariables } from 'twenty-ui/theme-constants';
+
+const StyledPasswordFieldContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: ${themeCssVariables.spacing[1]};
+`;
+
+const StyledChangePasswordLink = styled(ClickToActionLink)`
+ align-self: flex-end;
+`;
+
+const MASKED_PASSWORD_PLACEHOLDER = '••••••••';
+
+type SettingsAccountsPasswordControllerProps = {
+ protocol: AccountType;
+ label: string;
+ control: Control;
+ disabled: boolean;
+ onUnlock: () => void;
+};
+
+export const SettingsAccountsPasswordController = ({
+ protocol,
+ label,
+ control,
+ disabled,
+ onUnlock,
+}: SettingsAccountsPasswordControllerProps) => {
+ return (
+ (
+
+
+ {disabled && (
+
+ Change password
+
+ )}
+
+ )}
+ />
+ );
+};
diff --git a/packages/twenty-front/src/modules/settings/accounts/constants/AccountProtocols.ts b/packages/twenty-front/src/modules/settings/accounts/constants/AccountProtocols.ts
deleted file mode 100644
index d16da6b231..0000000000
--- a/packages/twenty-front/src/modules/settings/accounts/constants/AccountProtocols.ts
+++ /dev/null
@@ -1 +0,0 @@
-export const ACCOUNT_PROTOCOLS = ['IMAP', 'SMTP', 'CALDAV'] as const;
diff --git a/packages/twenty-front/src/modules/settings/accounts/graphql/mutations/saveImapSmtpCaldavConnection.ts b/packages/twenty-front/src/modules/settings/accounts/graphql/mutations/saveImapSmtpCaldavConnection.ts
index eb0ef11caf..54605b2ef7 100644
--- a/packages/twenty-front/src/modules/settings/accounts/graphql/mutations/saveImapSmtpCaldavConnection.ts
+++ b/packages/twenty-front/src/modules/settings/accounts/graphql/mutations/saveImapSmtpCaldavConnection.ts
@@ -2,13 +2,11 @@ import gql from 'graphql-tag';
export const SAVE_IMAP_SMTP_CALDAV_ACCOUNT = gql`
mutation SaveImapSmtpCaldavAccount(
- $accountOwnerId: UUID!
$handle: String!
$connectionParameters: EmailAccountConnectionParameters!
$id: UUID
) {
saveImapSmtpCaldavAccount(
- accountOwnerId: $accountOwnerId
handle: $handle
connectionParameters: $connectionParameters
id: $id
diff --git a/packages/twenty-front/src/modules/settings/accounts/graphql/queries/getConnectedImapSmtpCaldavAccount.ts b/packages/twenty-front/src/modules/settings/accounts/graphql/queries/getConnectedImapSmtpCaldavAccount.ts
index 1b76eb228e..0042b08b2c 100644
--- a/packages/twenty-front/src/modules/settings/accounts/graphql/queries/getConnectedImapSmtpCaldavAccount.ts
+++ b/packages/twenty-front/src/modules/settings/accounts/graphql/queries/getConnectedImapSmtpCaldavAccount.ts
@@ -13,21 +13,18 @@ export const GET_CONNECTED_IMAP_SMTP_CALDAV_ACCOUNT = gql`
port
secure
username
- password
}
SMTP {
host
username
port
secure
- password
}
CALDAV {
host
port
secure
username
- password
}
}
}
diff --git a/packages/twenty-front/src/modules/settings/accounts/graphql/queries/getMyConnectedAccounts.ts b/packages/twenty-front/src/modules/settings/accounts/graphql/queries/getMyConnectedAccounts.ts
index e1d69c0fb8..6037928092 100644
--- a/packages/twenty-front/src/modules/settings/accounts/graphql/queries/getMyConnectedAccounts.ts
+++ b/packages/twenty-front/src/modules/settings/accounts/graphql/queries/getMyConnectedAccounts.ts
@@ -21,19 +21,16 @@ export const GET_MY_CONNECTED_ACCOUNTS = gql`
port
secure
username
- password
}
SMTP {
host
port
secure
username
- password
}
CALDAV {
host
username
- password
}
}
createdAt
diff --git a/packages/twenty-front/src/modules/settings/accounts/hooks/useImapSmtpCaldavConnectionForm.ts b/packages/twenty-front/src/modules/settings/accounts/hooks/useImapSmtpCaldavConnectionForm.ts
index ae3ee4669c..11c8e0abaf 100644
--- a/packages/twenty-front/src/modules/settings/accounts/hooks/useImapSmtpCaldavConnectionForm.ts
+++ b/packages/twenty-front/src/modules/settings/accounts/hooks/useImapSmtpCaldavConnectionForm.ts
@@ -2,26 +2,29 @@ import { zodResolver } from '@hookform/resolvers/zod';
import { useCallback, useEffect, useMemo } from 'react';
import { useForm } from 'react-hook-form';
-import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
-import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
+import { useMutation } from '@apollo/client/react';
import { t } from '@lingui/core/macro';
import { SettingsPath } from 'twenty-shared/types';
-import { useMutation } from '@apollo/client/react';
import {
- type ConnectionParameters,
+ type ConnectionParametersInput,
SaveImapSmtpCaldavAccountDocument,
} from '~/generated-metadata/graphql';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
-import { type ImapSmtpCaldavAccount } from '@/accounts/types/ImapSmtpCaldavAccount';
-import { ACCOUNT_PROTOCOLS } from '@/settings/accounts/constants/AccountProtocols';
+import { type ImapSmtpCaldavAccountInput } from '@/accounts/types/ImapSmtpCaldavAccountInput';
+import {
+ isProtocolConfigured,
+ isProtocolConfiguredForUpdate,
+} from '@/settings/accounts/utils/isProtocolConfigured';
import {
connectionImapSmtpCalDav,
- isProtocolConfigured,
+ connectionImapSmtpCalDavUpdate,
} from '@/settings/accounts/validation-schemas/connectionImapSmtpCalDav';
import { CombinedGraphQLErrors } from '@apollo/client/errors';
+import { isNonEmptyString } from '@sniptt/guards';
+import { ACCOUNT_TYPES } from 'twenty-shared/constants';
import { isDefined } from 'twenty-shared/utils';
import { useConnectedImapSmtpCaldavAccount } from './useConnectedImapSmtpCaldavAccount';
@@ -32,27 +35,28 @@ type UseConnectionFormProps = {
export type ConnectionFormData = {
handle: string;
-} & ImapSmtpCaldavAccount;
+} & ImapSmtpCaldavAccountInput;
+
+const DEFAULT_PROTOCOL_VALUES: Record = {
+ IMAP: { host: '', port: 993, password: '', secure: true },
+ SMTP: { host: '', username: '', port: 587, password: '', secure: true },
+ CALDAV: { host: '', port: 443, password: '', secure: true },
+};
export const useImapSmtpCaldavConnectionForm = ({
isEditing = false,
connectedAccountId,
}: UseConnectionFormProps = {}) => {
const navigate = useNavigateSettings();
- const currentWorkspaceMember = useAtomStateValue(currentWorkspaceMemberState);
-
- const defaultProtocolValues: Record = {
- IMAP: { host: '', port: 993, password: '', secure: true },
- SMTP: { host: '', username: '', port: 587, password: '', secure: true },
- CALDAV: { host: '', port: 443, password: '', secure: true },
- };
const formMethods = useForm({
mode: 'onSubmit',
- resolver: zodResolver(connectionImapSmtpCalDav),
+ resolver: zodResolver(
+ isEditing ? connectionImapSmtpCalDavUpdate : connectionImapSmtpCalDav,
+ ),
defaultValues: {
handle: '',
- ...defaultProtocolValues,
+ ...DEFAULT_PROTOCOL_VALUES,
},
});
@@ -70,15 +74,15 @@ export const useImapSmtpCaldavConnectionForm = ({
reset({
handle: connectedAccount.handle || '',
IMAP: {
- ...defaultProtocolValues.IMAP,
+ ...DEFAULT_PROTOCOL_VALUES.IMAP,
...connectedAccount.connectionParameters?.IMAP,
},
SMTP: {
- ...defaultProtocolValues.SMTP,
+ ...DEFAULT_PROTOCOL_VALUES.SMTP,
...connectedAccount.connectionParameters?.SMTP,
},
CALDAV: {
- ...defaultProtocolValues.CALDAV,
+ ...DEFAULT_PROTOCOL_VALUES.CALDAV,
...connectedAccount.connectionParameters?.CALDAV,
},
});
@@ -94,16 +98,20 @@ export const useImapSmtpCaldavConnectionForm = ({
const getConfiguredProtocols = useCallback(
(
values: ConnectionFormData = watchedValues,
- ): (keyof ImapSmtpCaldavAccount)[] => {
- return ACCOUNT_PROTOCOLS.filter((protocol) => {
+ ): (keyof ImapSmtpCaldavAccountInput)[] => {
+ const isProtocolConfiguredCheckFunction = isEditing
+ ? isProtocolConfiguredForUpdate
+ : isProtocolConfigured;
+
+ return ACCOUNT_TYPES.filter((protocol) => {
const protocolConfig = values[protocol];
return (
- protocolConfig &&
- isProtocolConfigured(protocolConfig as ConnectionParameters)
+ isDefined(protocolConfig) &&
+ isProtocolConfiguredCheckFunction(protocolConfig)
);
});
},
- [watchedValues],
+ [watchedValues, isEditing],
);
const isValid = useMemo(() => {
@@ -115,10 +123,6 @@ export const useImapSmtpCaldavConnectionForm = ({
const handleSave = useCallback(
async (formValues: ConnectionFormData): Promise => {
- if (!currentWorkspaceMember?.id) {
- throw new Error('Workspace member ID is missing');
- }
-
const configuredProtocols = getConfiguredProtocols(formValues);
if (configuredProtocols.length === 0) {
@@ -126,12 +130,17 @@ export const useImapSmtpCaldavConnectionForm = ({
}
const connectionParameters: Partial<
- Record
+ Record
> = {};
configuredProtocols.forEach((protocol) => {
const protocolConfig = formValues[protocol];
if (isDefined(protocolConfig)) {
- connectionParameters[protocol] = protocolConfig;
+ const { password, ...withoutPassword } = protocolConfig;
+ const hasPassword = isNonEmptyString(password);
+
+ connectionParameters[protocol] = hasPassword
+ ? { ...withoutPassword, password }
+ : withoutPassword;
}
});
@@ -141,7 +150,6 @@ export const useImapSmtpCaldavConnectionForm = ({
...(isEditing && connectedAccountId
? { id: connectedAccountId }
: {}),
- accountOwnerId: currentWorkspaceMember.id,
handle: formValues.handle,
connectionParameters,
},
@@ -167,7 +175,6 @@ export const useImapSmtpCaldavConnectionForm = ({
}
},
[
- currentWorkspaceMember?.id,
getConfiguredProtocols,
saveConnection,
isEditing,
diff --git a/packages/twenty-front/src/modules/settings/accounts/utils/isProtocolConfigured.ts b/packages/twenty-front/src/modules/settings/accounts/utils/isProtocolConfigured.ts
new file mode 100644
index 0000000000..2eeb1c946a
--- /dev/null
+++ b/packages/twenty-front/src/modules/settings/accounts/utils/isProtocolConfigured.ts
@@ -0,0 +1,13 @@
+import { type ConnectionParametersInput } from '~/generated-metadata/graphql';
+
+export const isProtocolConfigured = (
+ config: ConnectionParametersInput,
+): boolean => {
+ return Boolean(config?.host?.trim() && config?.password?.trim());
+};
+
+export const isProtocolConfiguredForUpdate = (
+ config: ConnectionParametersInput,
+): boolean => {
+ return Boolean(config?.host?.trim());
+};
diff --git a/packages/twenty-front/src/modules/settings/accounts/validation-schemas/connectionImapSmtpCalDav.ts b/packages/twenty-front/src/modules/settings/accounts/validation-schemas/connectionImapSmtpCalDav.ts
index 3fb9eee449..d2ba749d87 100644
--- a/packages/twenty-front/src/modules/settings/accounts/validation-schemas/connectionImapSmtpCalDav.ts
+++ b/packages/twenty-front/src/modules/settings/accounts/validation-schemas/connectionImapSmtpCalDav.ts
@@ -1,6 +1,11 @@
-import { ACCOUNT_PROTOCOLS } from '@/settings/accounts/constants/AccountProtocols';
+import { ACCOUNT_TYPES } from 'twenty-shared/constants';
import { z } from 'zod';
-import { type ConnectionParameters } from '~/generated-metadata/graphql';
+import { type ConnectionParametersInput } from '~/generated-metadata/graphql';
+
+import {
+ isProtocolConfigured,
+ isProtocolConfiguredForUpdate,
+} from '@/settings/accounts/utils/isProtocolConfigured';
const connectionParameters = z
.object({
@@ -12,7 +17,7 @@ const connectionParameters = z
})
.refine(
(data) => {
- if (Boolean(data.host?.trim()) && Boolean(data.password?.trim())) {
+ if (Boolean(data.host?.trim())) {
return data.port && data.port > 0;
}
return true;
@@ -32,8 +37,8 @@ export const connectionImapSmtpCalDav = z
})
.refine(
(data) => {
- return ACCOUNT_PROTOCOLS.some((protocol) =>
- isProtocolConfigured(data[protocol] as ConnectionParameters),
+ return ACCOUNT_TYPES.some((protocol) =>
+ isProtocolConfigured(data[protocol] as ConnectionParametersInput),
);
},
{
@@ -43,6 +48,24 @@ export const connectionImapSmtpCalDav = z
},
);
-export const isProtocolConfigured = (config: ConnectionParameters): boolean => {
- return Boolean(config?.host?.trim() && config?.password?.trim());
-};
+export const connectionImapSmtpCalDavUpdate = z
+ .object({
+ handle: z.email('Invalid email address'),
+ IMAP: connectionParameters.optional(),
+ SMTP: connectionParameters.optional(),
+ CALDAV: connectionParameters.optional(),
+ })
+ .refine(
+ (data) => {
+ return ACCOUNT_TYPES.some((protocol) =>
+ isProtocolConfiguredForUpdate(
+ data[protocol] as ConnectionParametersInput,
+ ),
+ );
+ },
+ {
+ path: ['handle'],
+ error:
+ 'At least one account type (IMAP, SMTP, or CalDAV) must be completely configured',
+ },
+ );
diff --git a/packages/twenty-front/src/pages/settings/applications/__tests__/SettingsApplicationConnectionDetail.test.tsx b/packages/twenty-front/src/pages/settings/applications/__tests__/SettingsApplicationConnectionDetail.test.tsx
index e6a07c5a65..28e16a1cd1 100644
--- a/packages/twenty-front/src/pages/settings/applications/__tests__/SettingsApplicationConnectionDetail.test.tsx
+++ b/packages/twenty-front/src/pages/settings/applications/__tests__/SettingsApplicationConnectionDetail.test.tsx
@@ -137,7 +137,7 @@ describe('SettingsApplicationConnectionDetail', () => {
mockedUseMyAppConnectedAccounts.mockReturnValue({
accounts: [
{
- __typename: 'ConnectedAccountDTO',
+ __typename: 'ConnectedAccountPublicDTO',
id: 'account-1',
handle: 'workspace@example.com',
provider: 'app',
diff --git a/packages/twenty-front/src/pages/settings/applications/tabs/__tests__/SettingsApplicationConnectionsSection.test.tsx b/packages/twenty-front/src/pages/settings/applications/tabs/__tests__/SettingsApplicationConnectionsSection.test.tsx
index b62f4ccff9..15ca58abdc 100644
--- a/packages/twenty-front/src/pages/settings/applications/tabs/__tests__/SettingsApplicationConnectionsSection.test.tsx
+++ b/packages/twenty-front/src/pages/settings/applications/tabs/__tests__/SettingsApplicationConnectionsSection.test.tsx
@@ -66,7 +66,7 @@ describe('SettingsApplicationConnectionsSection', () => {
mockedUseMyAppConnectedAccounts.mockReturnValue({
accounts: [
{
- __typename: 'ConnectedAccountDTO',
+ __typename: 'ConnectedAccountPublicDTO',
id: 'account-1',
handle: 'workspace@example.com',
provider: 'app',
diff --git a/packages/twenty-server/.env.test b/packages/twenty-server/.env.test
index 85a50cdd0d..ffa51bdad7 100644
--- a/packages/twenty-server/.env.test
+++ b/packages/twenty-server/.env.test
@@ -17,6 +17,7 @@ ENTERPRISE_VALIDITY_TOKEN=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkZXYtc
AUTH_GOOGLE_ENABLED=false
MESSAGING_PROVIDER_GMAIL_ENABLED=false
IS_IMAP_SMTP_CALDAV_ENABLED=true
+IS_IMAP_SMTP_CALDAV_CONNECTION_TEST_ENABLED=false
CALENDAR_PROVIDER_GOOGLE_ENABLED=false
MESSAGING_PROVIDER_MICROSOFT_ENABLED=false
CALENDAR_PROVIDER_MICROSOFT_ENABLED=false
diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-7/2-7-instance-command-slow-1798000010000-encrypt-connection-parameters.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-7/2-7-instance-command-slow-1798000010000-encrypt-connection-parameters.ts
new file mode 100644
index 0000000000..696ce89d6b
--- /dev/null
+++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-7/2-7-instance-command-slow-1798000010000-encrypt-connection-parameters.ts
@@ -0,0 +1,133 @@
+import { isDefined } from 'twenty-shared/utils';
+import { DataSource, QueryRunner } from 'typeorm';
+
+import { type ImapSmtpCaldavParams } from 'src/engine/core-modules/imap-smtp-caldav-connection/types/imap-smtp-caldav-connection.type';
+import { SECRET_ENCRYPTION_ENVELOPE_V2_PREFIX } from 'src/engine/core-modules/secret-encryption/constants/secret-encryption.constant';
+import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator';
+import { SlowInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/slow-instance-command.interface';
+import { ConnectedAccountTokenEncryptionService } from 'src/engine/metadata-modules/connected-account/services/connected-account-token-encryption.service';
+import { ACCOUNT_TYPES } from 'twenty-shared/constants';
+
+const BACKFILL_BATCH_SIZE = 500;
+
+const CHECK_CONSTRAINT_NAME =
+ 'CHK_connectedAccount_connectionParameters_encrypted';
+
+type ConnectionParametersRow = {
+ id: string;
+ workspaceId: string;
+ connectionParameters: ImapSmtpCaldavParams | null;
+};
+
+const hasPlaintextPassword = (params: ImapSmtpCaldavParams): boolean => {
+ for (const protocol of ACCOUNT_TYPES) {
+ const protocolParams = params[protocol];
+
+ if (
+ isDefined(protocolParams?.password) &&
+ !protocolParams.password.startsWith(SECRET_ENCRYPTION_ENVELOPE_V2_PREFIX)
+ ) {
+ return true;
+ }
+ }
+
+ return false;
+};
+
+@RegisteredInstanceCommand('2.7.0', 1798000010000, { type: 'slow' })
+export class EncryptConnectionParametersSlowInstanceCommand
+ implements SlowInstanceCommand
+{
+ constructor(
+ private readonly connectedAccountTokenEncryptionService: ConnectedAccountTokenEncryptionService,
+ ) {}
+
+ async runDataMigration(dataSource: DataSource): Promise {
+ let cursor = '00000000-0000-0000-0000-000000000000';
+
+ while (true) {
+ const rows: ConnectionParametersRow[] = await dataSource.query(
+ `SELECT id, "workspaceId", "connectionParameters"
+ FROM "core"."connectedAccount"
+ WHERE id > $1
+ AND "connectionParameters" IS NOT NULL
+ ORDER BY id
+ LIMIT $2`,
+ [cursor, BACKFILL_BATCH_SIZE],
+ );
+
+ if (rows.length === 0) {
+ break;
+ }
+
+ for (const row of rows) {
+ if (!isDefined(row.connectionParameters)) {
+ continue;
+ }
+
+ if (!hasPlaintextPassword(row.connectionParameters)) {
+ continue;
+ }
+
+ const plaintextOnly: ImapSmtpCaldavParams = {};
+
+ for (const protocol of ACCOUNT_TYPES) {
+ const protocolParams = row.connectionParameters[protocol];
+
+ if (
+ isDefined(protocolParams?.password) &&
+ !protocolParams.password.startsWith(
+ SECRET_ENCRYPTION_ENVELOPE_V2_PREFIX,
+ )
+ ) {
+ plaintextOnly[protocol] = protocolParams;
+ }
+ }
+
+ const encrypted =
+ this.connectedAccountTokenEncryptionService.encryptConnectionParameters(
+ {
+ connectionParameters: plaintextOnly,
+ workspaceId: row.workspaceId,
+ },
+ );
+
+ const merged: ImapSmtpCaldavParams = {
+ ...row.connectionParameters,
+ ...encrypted,
+ };
+
+ await dataSource.query(
+ `UPDATE "core"."connectedAccount"
+ SET "connectionParameters" = $2
+ WHERE id = $1`,
+ [row.id, JSON.stringify(merged)],
+ );
+ }
+
+ cursor = rows[rows.length - 1].id;
+ }
+ }
+
+ public async up(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(
+ `ALTER TABLE "core"."connectedAccount"
+ ADD CONSTRAINT "${CHECK_CONSTRAINT_NAME}"
+ CHECK (
+ "connectionParameters" IS NULL
+ OR (
+ (("connectionParameters"->'IMAP'->>'password') IS NULL OR ("connectionParameters"->'IMAP'->>'password') LIKE 'enc:v2:%')
+ AND (("connectionParameters"->'SMTP'->>'password') IS NULL OR ("connectionParameters"->'SMTP'->>'password') LIKE 'enc:v2:%')
+ AND (("connectionParameters"->'CALDAV'->>'password') IS NULL OR ("connectionParameters"->'CALDAV'->>'password') LIKE 'enc:v2:%')
+ )
+ )`,
+ );
+ }
+
+ public async down(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(
+ `ALTER TABLE "core"."connectedAccount"
+ DROP CONSTRAINT IF EXISTS "${CHECK_CONSTRAINT_NAME}"`,
+ );
+ }
+}
diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts
index 025b67356c..7e2baf24ea 100644
--- a/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts
+++ b/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts
@@ -47,6 +47,7 @@ import { EncryptApplicationRegistrationVariableSlowInstanceCommand } from 'src/d
import { EncryptSigningKeyPrivateKeysSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-5/2-5-instance-command-slow-1798000007000-encrypt-signing-key-private-keys';
import { EncryptSensitiveConfigStorageSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-5/2-5-instance-command-slow-1798000008000-encrypt-sensitive-config-storage';
import { EncryptTotpSecretsSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-5/2-5-instance-command-slow-1798000009000-encrypt-totp-secrets';
+import { EncryptConnectionParametersSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-7/2-7-instance-command-slow-1798000010000-encrypt-connection-parameters';
import { AddSubFieldNameToViewSortFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-5/2-5-instance-command-fast-1778502963794-add-sub-field-name-to-view-sort';
import { DropPostgresCredentialsTableFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-5/2-5-instance-command-fast-1798500000000-drop-postgres-credentials-table';
import { AddRelationTargetFieldMetadataIdToViewFilterFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-6/2-6-instance-command-fast-1798000005000-add-relation-target-field-metadata-id-to-view-filter';
@@ -100,6 +101,7 @@ export const INSTANCE_COMMANDS = [
EncryptSigningKeyPrivateKeysSlowInstanceCommand,
EncryptSensitiveConfigStorageSlowInstanceCommand,
EncryptTotpSecretsSlowInstanceCommand,
+ EncryptConnectionParametersSlowInstanceCommand,
AddSubFieldNameToViewSortFastInstanceCommand,
DropPostgresCredentialsTableFastInstanceCommand,
AddRelationTargetFieldMetadataIdToViewFilterFastInstanceCommand,
diff --git a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connected-account.dto.ts b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connected-account.dto.ts
index d42f283de2..a29742a578 100644
--- a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connected-account.dto.ts
+++ b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connected-account.dto.ts
@@ -3,7 +3,33 @@ import { Field, ObjectType } from '@nestjs/graphql';
import { ConnectedAccountProvider } from 'twenty-shared/types';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
-import { ImapSmtpCaldavConnectionParametersDTO } from 'src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connection.dto';
+
+@ObjectType('ImapSmtpCaldavPublicConnectionParams')
+class PublicConnectionParamsDTO {
+ @Field(() => String)
+ host: string;
+
+ @Field(() => Number)
+ port: number;
+
+ @Field(() => String, { nullable: true })
+ username?: string;
+
+ @Field(() => Boolean, { nullable: true })
+ secure?: boolean;
+}
+
+@ObjectType('ImapSmtpCaldavPublicConnectionParameters')
+class ImapSmtpCaldavPublicConnectionParametersDTO {
+ @Field(() => PublicConnectionParamsDTO, { nullable: true })
+ IMAP?: PublicConnectionParamsDTO;
+
+ @Field(() => PublicConnectionParamsDTO, { nullable: true })
+ SMTP?: PublicConnectionParamsDTO;
+
+ @Field(() => PublicConnectionParamsDTO, { nullable: true })
+ CALDAV?: PublicConnectionParamsDTO;
+}
@ObjectType('ConnectedImapSmtpCaldavAccount')
export class ConnectedImapSmtpCaldavAccountDTO {
@@ -19,6 +45,6 @@ export class ConnectedImapSmtpCaldavAccountDTO {
@Field(() => UUIDScalarType)
userWorkspaceId: string;
- @Field(() => ImapSmtpCaldavConnectionParametersDTO, { nullable: true })
- connectionParameters: ImapSmtpCaldavConnectionParametersDTO | null;
+ @Field(() => ImapSmtpCaldavPublicConnectionParametersDTO, { nullable: true })
+ connectionParameters: ImapSmtpCaldavPublicConnectionParametersDTO | null;
}
diff --git a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connection.dto.ts b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connection.dto.ts
deleted file mode 100644
index 78c5d4d4fa..0000000000
--- a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connection.dto.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import { Field, InputType, ObjectType } from '@nestjs/graphql';
-
-@InputType()
-export class AccountType {
- @Field(() => String)
- type: 'IMAP' | 'SMTP' | 'CALDAV';
-}
-
-@InputType()
-export class ConnectionParameters {
- @Field(() => String)
- host: string;
-
- @Field(() => Number)
- port: number;
-
- @Field(() => String, { nullable: true })
- username?: string;
-
- /**
- * Note: This field is stored in plain text in the database.
- * While encrypting it could provide an extra layer of defense, we have decided not to,
- * as database access implies a broader compromise. For context, see discussion in PR #12576.
- */
- @Field(() => String)
- password: string;
-
- @Field(() => Boolean, { nullable: true })
- secure?: boolean;
-}
-
-@InputType()
-export class EmailAccountConnectionParameters {
- @Field(() => ConnectionParameters, { nullable: true })
- IMAP?: ConnectionParameters;
-
- @Field(() => ConnectionParameters, { nullable: true })
- SMTP?: ConnectionParameters;
-
- @Field(() => ConnectionParameters, { nullable: true })
- CALDAV?: ConnectionParameters;
-}
-
-@ObjectType('ConnectionParametersOutput')
-export class ConnectionParametersDTO {
- @Field(() => String)
- host: string;
-
- @Field(() => Number)
- port: number;
-
- @Field(() => String, { nullable: true })
- username?: string;
-
- @Field(() => String)
- password: string;
-
- @Field(() => Boolean, { nullable: true })
- secure?: boolean;
-}
-
-@ObjectType('ImapSmtpCaldavConnectionParameters')
-export class ImapSmtpCaldavConnectionParametersDTO {
- @Field(() => ConnectionParametersDTO, { nullable: true })
- IMAP?: ConnectionParametersDTO;
-
- @Field(() => ConnectionParametersDTO, { nullable: true })
- SMTP?: ConnectionParametersDTO;
-
- @Field(() => ConnectionParametersDTO, { nullable: true })
- CALDAV?: ConnectionParametersDTO;
-}
diff --git a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connection.input.ts b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connection.input.ts
new file mode 100644
index 0000000000..2b6d1ec723
--- /dev/null
+++ b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connection.input.ts
@@ -0,0 +1,37 @@
+import { Field, InputType } from '@nestjs/graphql';
+
+@InputType()
+export class AccountType {
+ @Field(() => String)
+ type: 'IMAP' | 'SMTP' | 'CALDAV';
+}
+
+@InputType()
+export class ConnectionParametersInput {
+ @Field(() => String)
+ host: string;
+
+ @Field(() => Number)
+ port: number;
+
+ @Field(() => String, { nullable: true })
+ username?: string;
+
+ @Field(() => String, { nullable: true })
+ password?: string;
+
+ @Field(() => Boolean, { nullable: true })
+ secure?: boolean;
+}
+
+@InputType('EmailAccountConnectionParameters')
+export class EmailAccountConnectionParametersInput {
+ @Field(() => ConnectionParametersInput, { nullable: true })
+ IMAP?: ConnectionParametersInput;
+
+ @Field(() => ConnectionParametersInput, { nullable: true })
+ SMTP?: ConnectionParametersInput;
+
+ @Field(() => ConnectionParametersInput, { nullable: true })
+ CALDAV?: ConnectionParametersInput;
+}
diff --git a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/imap-smtp-caldav-connection.module.ts b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/imap-smtp-caldav-connection.module.ts
index 51159a902b..be4f82dd37 100644
--- a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/imap-smtp-caldav-connection.module.ts
+++ b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/imap-smtp-caldav-connection.module.ts
@@ -1,11 +1,11 @@
import { Module } from '@nestjs/common';
-import { TypeOrmModule } from '@nestjs/typeorm';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { ImapSmtpCaldavValidatorModule } from 'src/engine/core-modules/imap-smtp-caldav-connection/services/imap-smtp-caldav-connection-validator.module';
import { SecureHttpClientModule } from 'src/engine/core-modules/secure-http-client/secure-http-client.module';
import { MessageQueueModule } from 'src/engine/core-modules/message-queue/message-queue.module';
-import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
+import { ConnectedAccountMetadataModule } from 'src/engine/metadata-modules/connected-account/connected-account-metadata.module';
+import { ConnectedAccountTokenEncryptionModule } from 'src/engine/metadata-modules/connected-account/services/connected-account-token-encryption.module';
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
import { CalDavDriverModule } from 'src/modules/calendar/calendar-event-import-manager/drivers/caldav/caldav-driver.module';
@@ -21,11 +21,12 @@ import { ImapSmtpCaldavService } from './services/imap-smtp-caldav-connection.se
@Module({
imports: [
ConnectedAccountModule,
+ ConnectedAccountMetadataModule,
+ ConnectedAccountTokenEncryptionModule,
MessagingIMAPDriverModule,
IMAPAPIsModule,
MessagingImportManagerModule,
MessageQueueModule,
- TypeOrmModule.forFeature([ConnectedAccountEntity]),
TwentyORMModule,
FeatureFlagModule,
ImapSmtpCaldavValidatorModule,
diff --git a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/imap-smtp-caldav-connection.resolver.ts b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/imap-smtp-caldav-connection.resolver.ts
index 4415021dab..090b1ef984 100644
--- a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/imap-smtp-caldav-connection.resolver.ts
+++ b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/imap-smtp-caldav-connection.resolver.ts
@@ -2,23 +2,27 @@ import { UseFilters, UseGuards, UsePipes } from '@nestjs/common';
import { Args, Mutation, Query } from '@nestjs/graphql';
import { PermissionFlagType } from 'twenty-shared/constants';
+import { ConnectedAccountProvider } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
+import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { AuthGraphqlApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-graphql-api-exception.filter';
import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe';
import { UserInputError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { ConnectedImapSmtpCaldavAccountDTO } from 'src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connected-account.dto';
import { ImapSmtpCaldavConnectionSuccessDTO } from 'src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connection-success.dto';
-import { EmailAccountConnectionParameters } from 'src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connection.dto';
-import { ImapSmtpCaldavValidatorService } from 'src/engine/core-modules/imap-smtp-caldav-connection/services/imap-smtp-caldav-connection-validator.service';
+import { EmailAccountConnectionParametersInput } from 'src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connection.input';
import { ImapSmtpCaldavService } from 'src/engine/core-modules/imap-smtp-caldav-connection/services/imap-smtp-caldav-connection.service';
+import { buildPublicConnectionParameters } from 'src/engine/core-modules/imap-smtp-caldav-connection/utils/build-public-connection-parameters.util';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
+import { AuthUserWorkspaceId } from 'src/engine/decorators/auth/auth-user-workspace-id.decorator';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
+import { ConnectedAccountMetadataService } from 'src/engine/metadata-modules/connected-account/connected-account-metadata.service';
+import { ConnectedAccountTokenEncryptionService } from 'src/engine/metadata-modules/connected-account/services/connected-account-token-encryption.service';
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
-import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator';
import { ImapSmtpCalDavAPIService } from 'src/modules/connected-account/services/imap-smtp-caldav-apis.service';
@MetadataResolver()
@@ -26,9 +30,10 @@ import { ImapSmtpCalDavAPIService } from 'src/modules/connected-account/services
@UseFilters(AuthGraphqlApiExceptionFilter, PermissionsGraphqlApiExceptionFilter)
export class ImapSmtpCaldavResolver {
constructor(
- private readonly ImapSmtpCaldavConnectionService: ImapSmtpCaldavService,
+ private readonly imapSmtpCaldavService: ImapSmtpCaldavService,
private readonly imapSmtpCaldavApisService: ImapSmtpCalDavAPIService,
- private readonly mailConnectionValidatorService: ImapSmtpCaldavValidatorService,
+ private readonly connectedAccountMetadataService: ConnectedAccountMetadataService,
+ private readonly connectedAccountTokenEncryptionService: ConnectedAccountTokenEncryptionService,
) {}
@Query(() => ConnectedImapSmtpCaldavAccountDTO)
@@ -39,24 +44,29 @@ export class ImapSmtpCaldavResolver {
async getConnectedImapSmtpCaldavAccount(
@Args('id', { type: () => UUIDScalarType }) id: string,
@AuthWorkspace() workspace: WorkspaceEntity,
+ @AuthUserWorkspaceId() userWorkspaceId: string,
): Promise {
const connectedAccount =
- await this.imapSmtpCaldavApisService.getImapSmtpCaldavConnectedAccount(
- workspace.id,
+ await this.connectedAccountMetadataService.findByIdAndUserWorkspaceId({
id,
- );
+ userWorkspaceId,
+ workspaceId: workspace.id,
+ });
- if (!isDefined(connectedAccount) || !isDefined(connectedAccount?.handle)) {
- throw new UserInputError(
- `Connected mail account with ID ${id} not found`,
- );
+ if (
+ !isDefined(connectedAccount) ||
+ connectedAccount.provider !== ConnectedAccountProvider.IMAP_SMTP_CALDAV
+ ) {
+ throw new UserInputError('Connected account not found');
}
return {
id: connectedAccount.id,
handle: connectedAccount.handle,
provider: connectedAccount.provider,
- connectionParameters: connectedAccount.connectionParameters,
+ connectionParameters: buildPublicConnectionParameters(
+ connectedAccount.connectionParameters,
+ ),
userWorkspaceId: connectedAccount.userWorkspaceId,
};
}
@@ -67,26 +77,52 @@ export class ImapSmtpCaldavResolver {
SettingsPermissionGuard(PermissionFlagType.CONNECTED_ACCOUNTS),
)
async saveImapSmtpCaldavAccount(
- @Args('accountOwnerId', { type: () => UUIDScalarType })
- accountOwnerId: string,
@Args('handle') handle: string,
@Args('connectionParameters')
- connectionParameters: EmailAccountConnectionParameters,
+ connectionParameters: EmailAccountConnectionParametersInput,
@AuthWorkspace() workspace: WorkspaceEntity,
+ @AuthUserWorkspaceId() userWorkspaceId: string,
@Args('id', { type: () => UUIDScalarType, nullable: true }) id?: string,
): Promise {
- const validatedParams = await this.validateAndTestConnectionParameters(
- connectionParameters,
- handle,
- );
+ const existingAccount = isDefined(id)
+ ? await this.connectedAccountMetadataService.findByIdAndUserWorkspaceId({
+ id,
+ userWorkspaceId,
+ workspaceId: workspace.id,
+ })
+ : null;
+
+ if (
+ isDefined(id) &&
+ (!existingAccount ||
+ existingAccount.provider !== ConnectedAccountProvider.IMAP_SMTP_CALDAV)
+ ) {
+ throw new UserInputError('Connected account not found');
+ }
+
+ const decryptedExistingParams = existingAccount?.connectionParameters
+ ? this.connectedAccountTokenEncryptionService.decryptConnectionParameters(
+ {
+ connectionParameters: existingAccount.connectionParameters,
+ workspaceId: workspace.id,
+ },
+ )
+ : null;
+
+ const validatedParams =
+ await this.imapSmtpCaldavService.validateAndTestConnectionParameters({
+ connectionParameters,
+ handle,
+ existingConnectionParameters: decryptedExistingParams,
+ });
const connectedAccountId =
- await this.imapSmtpCaldavApisService.processAccount({
+ await this.imapSmtpCaldavApisService.upsertConnectedAccount({
handle,
- workspaceMemberId: accountOwnerId,
+ userWorkspaceId,
workspaceId: workspace.id,
connectionParameters: validatedParams,
- connectedAccountId: id,
+ existingAccount,
});
return {
@@ -94,34 +130,4 @@ export class ImapSmtpCaldavResolver {
connectedAccountId,
};
}
-
- private async validateAndTestConnectionParameters(
- connectionParameters: EmailAccountConnectionParameters,
- handle: string,
- ): Promise {
- const validatedParams: EmailAccountConnectionParameters = {};
- const protocols = ['IMAP', 'SMTP', 'CALDAV'] as const;
-
- for (const protocol of protocols) {
- const params = connectionParameters[protocol];
-
- if (params) {
- validatedParams[protocol] =
- await this.mailConnectionValidatorService.validateProtocolConnectionParams(
- params,
- );
- const validatedProtocolParams = validatedParams[protocol];
-
- if (validatedProtocolParams) {
- await this.ImapSmtpCaldavConnectionService.testImapSmtpCaldav(
- handle,
- validatedProtocolParams,
- protocol,
- );
- }
- }
- }
-
- return validatedParams;
- }
}
diff --git a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/schemas/connection-parameters-update.schema.ts b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/schemas/connection-parameters-update.schema.ts
new file mode 100644
index 0000000000..9120ec8430
--- /dev/null
+++ b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/schemas/connection-parameters-update.schema.ts
@@ -0,0 +1,8 @@
+import { z } from 'zod';
+
+import { connectionParametersSchema } from 'src/engine/core-modules/imap-smtp-caldav-connection/schemas/connection-parameters.schema';
+
+export const connectionParametersUpdateSchema =
+ connectionParametersSchema.extend({
+ password: z.string().min(1, 'Password is required').optional(),
+ });
diff --git a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/schemas/connection-parameters.schema.ts b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/schemas/connection-parameters.schema.ts
new file mode 100644
index 0000000000..5e589434e0
--- /dev/null
+++ b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/schemas/connection-parameters.schema.ts
@@ -0,0 +1,9 @@
+import { z } from 'zod';
+
+export const connectionParametersSchema = z.object({
+ host: z.string().min(1, 'Host is required'),
+ port: z.int().positive('Port must be a positive number'),
+ username: z.string().optional(),
+ password: z.string().min(1, 'Password is required'),
+ secure: z.boolean().optional(),
+});
diff --git a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/services/__tests__/imap-smtp-caldav-connection.service.spec.ts b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/services/__tests__/imap-smtp-caldav-connection.service.spec.ts
index e23d911d3c..cd3e20d1eb 100644
--- a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/services/__tests__/imap-smtp-caldav-connection.service.spec.ts
+++ b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/services/__tests__/imap-smtp-caldav-connection.service.spec.ts
@@ -1,13 +1,12 @@
import { Test, type TestingModule } from '@nestjs/testing';
-import { getRepositoryToken } from '@nestjs/typeorm';
import { type DAVClient } from 'tsdav';
+import { ImapSmtpCaldavValidatorService } from 'src/engine/core-modules/imap-smtp-caldav-connection/services/imap-smtp-caldav-connection-validator.service';
import { ImapSmtpCaldavService } from 'src/engine/core-modules/imap-smtp-caldav-connection/services/imap-smtp-caldav-connection.service';
import { type ConnectionParameters } from 'src/engine/core-modules/imap-smtp-caldav-connection/types/imap-smtp-caldav-connection.type';
import { SecureHttpClientService } from 'src/engine/core-modules/secure-http-client/secure-http-client.service';
-import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
-import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
+import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { CalDavClientService } from 'src/modules/calendar/calendar-event-import-manager/drivers/caldav/services/caldav-client.service';
import { CalDavFetchEventsService } from 'src/modules/calendar/calendar-event-import-manager/drivers/caldav/services/caldav-fetch-events.service';
@@ -34,9 +33,12 @@ describe('ImapSmtpCaldavService', () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
ImapSmtpCaldavService,
- { provide: GlobalWorkspaceOrmManager, useValue: {} },
- { provide: getRepositoryToken(ConnectedAccountEntity), useValue: {} },
{ provide: SecureHttpClientService, useValue: {} },
+ { provide: ImapSmtpCaldavValidatorService, useValue: {} },
+ {
+ provide: TwentyConfigService,
+ useValue: { get: jest.fn().mockReturnValue(true) },
+ },
{
provide: CalDavClientService,
useValue: mockCalDavClientService,
diff --git a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/services/imap-smtp-caldav-connection-validator.service.ts b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/services/imap-smtp-caldav-connection-validator.service.ts
index 646fd8c7c1..4a74003e89 100644
--- a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/services/imap-smtp-caldav-connection-validator.service.ts
+++ b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/services/imap-smtp-caldav-connection-validator.service.ts
@@ -1,11 +1,14 @@
import { Injectable } from '@nestjs/common';
import { msg } from '@lingui/core/macro';
-import { z } from 'zod';
+import { isNonEmptyString } from '@sniptt/guards';
import { UserInputError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
-import { SecureHttpClientService } from 'src/engine/core-modules/secure-http-client/secure-http-client.service';
+import { ConnectionParametersInput } from 'src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connection.input';
+import { connectionParametersUpdateSchema } from 'src/engine/core-modules/imap-smtp-caldav-connection/schemas/connection-parameters-update.schema';
+import { connectionParametersSchema } from 'src/engine/core-modules/imap-smtp-caldav-connection/schemas/connection-parameters.schema';
import { type ConnectionParameters } from 'src/engine/core-modules/imap-smtp-caldav-connection/types/imap-smtp-caldav-connection.type';
+import { SecureHttpClientService } from 'src/engine/core-modules/secure-http-client/secure-http-client.service';
@Injectable()
export class ImapSmtpCaldavValidatorService {
@@ -13,59 +16,63 @@ export class ImapSmtpCaldavValidatorService {
private readonly secureHttpClientService: SecureHttpClientService,
) {}
- private readonly protocolConnectionSchema = z.object({
- host: z.string().min(1, 'Host is required'),
- port: z.int().positive('Port must be a positive number'),
- username: z.string().optional(),
- password: z.string().min(1, 'Password is required'),
- secure: z.boolean().optional(),
- });
-
- async validateProtocolConnectionParams(
- params: ConnectionParameters,
- ): Promise {
+ async validateProtocolConnectionParams({
+ params,
+ existingProtocolParams,
+ }: {
+ params: ConnectionParametersInput;
+ existingProtocolParams: ConnectionParameters | null;
+ }): Promise {
if (!params) {
throw new UserInputError('Protocol connection parameters are required', {
userFriendlyMessage: msg`Please provide connection details to configure your email account.`,
});
}
- try {
- const validated = this.protocolConnectionSchema.parse(params);
+ const schema = existingProtocolParams
+ ? connectionParametersUpdateSchema
+ : connectionParametersSchema;
- try {
- await this.secureHttpClientService.getValidatedHost(validated.host);
- } catch {
- throw new UserInputError(
- 'Connection to private or internal network addresses is not allowed',
- {
- userFriendlyMessage: msg`The server address you entered is not allowed. Please use a public server address.`,
- },
- );
- }
+ const result = schema.safeParse(params);
- return validated;
- } catch (error) {
- if (error instanceof UserInputError) {
- throw error;
- }
+ if (!result.success) {
+ const errorMessages = result.error.issues
+ .map((issue) => `${issue.path.join('.')}: ${issue.message}`)
+ .join(', ');
- if (error instanceof z.ZodError) {
- const errorMessages = error.issues
- .map((err) => `${err.path.join('.')}: ${err.message}`)
- .join(', ');
-
- throw new UserInputError(
- `Protocol connection validation failed: ${errorMessages}`,
- {
- userFriendlyMessage: msg`Please check your connection settings. Make sure the server host, port, and password are correct.`,
- },
- );
- }
-
- throw new UserInputError('Protocol connection validation failed', {
- userFriendlyMessage: msg`There was an issue with your connection settings. Please try again.`,
- });
+ throw new UserInputError(
+ `Protocol connection validation failed: ${errorMessages}`,
+ {
+ userFriendlyMessage: msg`Please check your connection settings. Make sure the server host, port, and password are correct.`,
+ },
+ );
}
+
+ const validated = result.data;
+
+ try {
+ await this.secureHttpClientService.getValidatedHost(validated.host);
+ } catch {
+ throw new UserInputError(
+ 'Connection to private or internal network addresses is not allowed',
+ {
+ userFriendlyMessage: msg`The server address you entered is not allowed. Please use a public server address.`,
+ },
+ );
+ }
+
+ const password =
+ validated.password ?? existingProtocolParams?.password ?? null;
+
+ if (!isNonEmptyString(password)) {
+ throw new UserInputError(
+ 'Password is required — no existing password found',
+ {
+ userFriendlyMessage: msg`Please provide a password for this connection.`,
+ },
+ );
+ }
+
+ return { ...validated, password };
}
}
diff --git a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/services/imap-smtp-caldav-connection.service.ts b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/services/imap-smtp-caldav-connection.service.ts
index c0ee11bc2b..66ca512a92 100644
--- a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/services/imap-smtp-caldav-connection.service.ts
+++ b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/services/imap-smtp-caldav-connection.service.ts
@@ -1,21 +1,21 @@
import { Injectable, Logger } from '@nestjs/common';
-import { InjectRepository } from '@nestjs/typeorm';
import { msg } from '@lingui/core/macro';
import { ImapFlow } from 'imapflow';
import { createTransport } from 'nodemailer';
-import { ConnectedAccountProvider } from 'twenty-shared/types';
-import { Repository } from 'typeorm';
+import { ACCOUNT_TYPES } from 'twenty-shared/constants';
+import { assertUnreachable } from 'twenty-shared/utils';
import { UserInputError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
-import { SecureHttpClientService } from 'src/engine/core-modules/secure-http-client/secure-http-client.service';
-import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
+import { type EmailAccountConnectionParametersInput } from 'src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connection.input';
+import { ImapSmtpCaldavValidatorService } from 'src/engine/core-modules/imap-smtp-caldav-connection/services/imap-smtp-caldav-connection-validator.service';
import {
type AccountType,
type ConnectionParameters,
+ type ImapSmtpCaldavParams,
} from 'src/engine/core-modules/imap-smtp-caldav-connection/types/imap-smtp-caldav-connection.type';
-import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
-import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
+import { SecureHttpClientService } from 'src/engine/core-modules/secure-http-client/secure-http-client.service';
+import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { CalDavClientService } from 'src/modules/calendar/calendar-event-import-manager/drivers/caldav/services/caldav-client.service';
import { CalDavFetchEventsService } from 'src/modules/calendar/calendar-event-import-manager/drivers/caldav/services/caldav-fetch-events.service';
@@ -24,12 +24,11 @@ export class ImapSmtpCaldavService {
private readonly logger = new Logger(ImapSmtpCaldavService.name);
constructor(
- private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
- @InjectRepository(ConnectedAccountEntity)
- private readonly connectedAccountRepository: Repository,
private readonly secureHttpClientService: SecureHttpClientService,
+ private readonly twentyConfigService: TwentyConfigService,
private readonly caldavClientService: CalDavClientService,
private readonly caldavFetchEventsService: CalDavFetchEventsService,
+ private readonly imapSmtpCaldavValidatorService: ImapSmtpCaldavValidatorService,
) {}
async testImapConnection(
@@ -182,50 +181,71 @@ export class ImapSmtpCaldavService {
return true;
}
- async testImapSmtpCaldav(
- handle: string,
- params: ConnectionParameters,
- accountType: AccountType,
- ): Promise {
- if (accountType === 'IMAP') {
- return this.testImapConnection(handle, params);
+ async testImapSmtpCaldav({
+ handle,
+ params,
+ accountType,
+ }: {
+ handle: string;
+ params: ConnectionParameters;
+ accountType: AccountType;
+ }): Promise {
+ if (
+ !this.twentyConfigService.get(
+ 'IS_IMAP_SMTP_CALDAV_CONNECTION_TEST_ENABLED',
+ )
+ ) {
+ return true;
}
- if (accountType === 'SMTP') {
- return this.testSmtpConnection(handle, params);
+ switch (accountType) {
+ case 'IMAP':
+ return this.testImapConnection(handle, params);
+ case 'SMTP':
+ return this.testSmtpConnection(handle, params);
+ case 'CALDAV':
+ return this.testCaldavConnection(handle, params);
+ default:
+ assertUnreachable(accountType);
}
-
- if (accountType === 'CALDAV') {
- return this.testCaldavConnection(handle, params);
- }
-
- throw new UserInputError(
- 'Invalid account type. Must be one of: IMAP, SMTP, CALDAV',
- {
- userFriendlyMessage: msg`Please select a valid connection type (IMAP, SMTP, or CalDAV) and try again.`,
- },
- );
}
- async getImapSmtpCaldav(
- workspaceId: string,
- connectionId: string,
- ): Promise {
- const authContext = buildSystemAuthContext(workspaceId);
+ async validateAndTestConnectionParameters({
+ connectionParameters,
+ handle,
+ existingConnectionParameters,
+ }: {
+ connectionParameters: EmailAccountConnectionParametersInput;
+ handle: string;
+ existingConnectionParameters: ImapSmtpCaldavParams | null;
+ }): Promise {
+ const validatedParams: ImapSmtpCaldavParams = {};
- return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
- async () => {
- const connectedAccount = await this.connectedAccountRepository.findOne({
- where: {
- id: connectionId,
- provider: ConnectedAccountProvider.IMAP_SMTP_CALDAV,
- workspaceId,
- },
+ for (const protocol of ACCOUNT_TYPES) {
+ const params = connectionParameters[protocol];
+
+ if (params) {
+ const existingProtocolParams =
+ existingConnectionParameters?.[protocol] ?? null;
+
+ const validatedProtocolParams =
+ await this.imapSmtpCaldavValidatorService.validateProtocolConnectionParams(
+ {
+ params,
+ existingProtocolParams,
+ },
+ );
+
+ await this.testImapSmtpCaldav({
+ handle,
+ params: validatedProtocolParams,
+ accountType: protocol,
});
- return connectedAccount;
- },
- authContext,
- );
+ validatedParams[protocol] = validatedProtocolParams;
+ }
+ }
+
+ return validatedParams;
}
}
diff --git a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/types/imap-smtp-caldav-connection.type.ts b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/types/imap-smtp-caldav-connection.type.ts
index 87d1276b39..45f016e1f0 100644
--- a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/types/imap-smtp-caldav-connection.type.ts
+++ b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/types/imap-smtp-caldav-connection.type.ts
@@ -1,12 +1,16 @@
-export type ConnectionParameters = {
- host: string;
- port: number;
- username?: string;
- password: string;
- secure?: boolean;
-};
+import { z } from 'zod';
-export type AccountType = 'IMAP' | 'SMTP' | 'CALDAV';
+import { ACCOUNT_TYPES } from 'twenty-shared/constants';
+import { connectionParametersUpdateSchema } from 'src/engine/core-modules/imap-smtp-caldav-connection/schemas/connection-parameters-update.schema';
+import { connectionParametersSchema } from 'src/engine/core-modules/imap-smtp-caldav-connection/schemas/connection-parameters.schema';
+
+export type ConnectionParameters = z.infer;
+
+export type ConnectionParametersUpdate = z.infer<
+ typeof connectionParametersUpdateSchema
+>;
+
+export type AccountType = (typeof ACCOUNT_TYPES)[number];
export type ImapSmtpCaldavParams = {
IMAP?: ConnectionParameters;
diff --git a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/utils/build-public-connection-parameters.util.ts b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/utils/build-public-connection-parameters.util.ts
new file mode 100644
index 0000000000..7bd8ce9e03
--- /dev/null
+++ b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/utils/build-public-connection-parameters.util.ts
@@ -0,0 +1,42 @@
+import { ACCOUNT_TYPES } from 'twenty-shared/constants';
+import { isDefined } from 'twenty-shared/utils';
+
+import { type ImapSmtpCaldavParams } from 'src/engine/core-modules/imap-smtp-caldav-connection/types/imap-smtp-caldav-connection.type';
+
+type PublicConnectionParams = {
+ host: string;
+ port: number;
+ username?: string;
+ secure?: boolean;
+};
+
+type PublicConnectionParameters = {
+ IMAP?: PublicConnectionParams;
+ SMTP?: PublicConnectionParams;
+ CALDAV?: PublicConnectionParams;
+} | null;
+
+export const buildPublicConnectionParameters = (
+ connectionParameters: ImapSmtpCaldavParams | null | undefined,
+): PublicConnectionParameters => {
+ if (!isDefined(connectionParameters)) {
+ return null;
+ }
+
+ return ACCOUNT_TYPES.reduce>(
+ (result, protocol) => {
+ const params = connectionParameters[protocol];
+
+ if (!isDefined(params)) {
+ return result;
+ }
+
+ const { password: _, ...publicParams } = params;
+
+ result[protocol] = publicParams;
+
+ return result;
+ },
+ {},
+ );
+};
diff --git a/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts b/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts
index bace86bd9e..93d386c804 100644
--- a/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts
+++ b/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts
@@ -175,6 +175,15 @@ export class ConfigVariables {
})
IS_IMAP_SMTP_CALDAV_ENABLED = true;
+ @ConfigVariablesMetadata({
+ group: ConfigVariablesGroup.ADVANCED_SETTINGS,
+ description:
+ 'Enable or disable the connection test when saving IMAP/SMTP/CALDAV accounts',
+ type: ConfigVariableType.BOOLEAN,
+ })
+ @IsOptional()
+ IS_IMAP_SMTP_CALDAV_CONNECTION_TEST_ENABLED = true;
+
@ConfigVariablesMetadata({
group: ConfigVariablesGroup.ADVANCED_SETTINGS,
description:
diff --git a/packages/twenty-server/src/engine/metadata-modules/connected-account/connected-account-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/connected-account/connected-account-metadata.service.ts
index cb938fb647..521b9c61c3 100644
--- a/packages/twenty-server/src/engine/metadata-modules/connected-account/connected-account-metadata.service.ts
+++ b/packages/twenty-server/src/engine/metadata-modules/connected-account/connected-account-metadata.service.ts
@@ -63,6 +63,20 @@ export class ConnectedAccountMetadataService {
return this.repository.findOne({ where: { id, workspaceId } });
}
+ async findByIdAndUserWorkspaceId({
+ id,
+ userWorkspaceId,
+ workspaceId,
+ }: {
+ id: string;
+ userWorkspaceId: string;
+ workspaceId: string;
+ }): Promise {
+ return this.repository.findOne({
+ where: { id, userWorkspaceId, workspaceId },
+ });
+ }
+
async findByIds({
ids,
workspaceId,
diff --git a/packages/twenty-server/src/engine/metadata-modules/connected-account/dtos/connected-account-public.dto.ts b/packages/twenty-server/src/engine/metadata-modules/connected-account/dtos/connected-account-public.dto.ts
index 08d24c4ae5..78a72c3ee4 100644
--- a/packages/twenty-server/src/engine/metadata-modules/connected-account/dtos/connected-account-public.dto.ts
+++ b/packages/twenty-server/src/engine/metadata-modules/connected-account/dtos/connected-account-public.dto.ts
@@ -2,13 +2,22 @@ import { Field, ObjectType, OmitType } from '@nestjs/graphql';
import { IsOptional } from 'class-validator';
-import { ConnectionParametersDTO } from 'src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connection.dto';
import { ConnectedAccountDTO } from 'src/engine/metadata-modules/connected-account/dtos/connected-account.dto';
@ObjectType('PublicConnectionParametersOutput')
-class PublicConnectionParametersDTO extends OmitType(ConnectionParametersDTO, [
- 'password',
-] as const) {}
+class PublicConnectionParametersDTO {
+ @Field(() => String)
+ host: string;
+
+ @Field(() => Number)
+ port: number;
+
+ @Field(() => String, { nullable: true })
+ username?: string;
+
+ @Field(() => Boolean, { nullable: true })
+ secure?: boolean;
+}
@ObjectType('PublicImapSmtpCaldavConnectionParameters')
class PublicImapSmtpCaldavConnectionParametersDTO {
diff --git a/packages/twenty-server/src/engine/metadata-modules/connected-account/dtos/connected-account.dto.ts b/packages/twenty-server/src/engine/metadata-modules/connected-account/dtos/connected-account.dto.ts
index 2218229aef..1a65b3020c 100644
--- a/packages/twenty-server/src/engine/metadata-modules/connected-account/dtos/connected-account.dto.ts
+++ b/packages/twenty-server/src/engine/metadata-modules/connected-account/dtos/connected-account.dto.ts
@@ -10,7 +10,7 @@ import {
} from 'class-validator';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
-import { ImapSmtpCaldavConnectionParametersDTO } from 'src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connection.dto';
+import { type ImapSmtpCaldavParams } from 'src/engine/core-modules/imap-smtp-caldav-connection/types/imap-smtp-caldav-connection.type';
@ObjectType('ConnectedAccountDTO')
export class ConnectedAccountDTO {
@@ -55,9 +55,8 @@ export class ConnectedAccountDTO {
@Field(() => [String], { nullable: true })
scopes: string[] | null;
- @IsOptional()
- @Field(() => ImapSmtpCaldavConnectionParametersDTO, { nullable: true })
- connectionParameters: ImapSmtpCaldavConnectionParametersDTO | null;
+ @HideField()
+ connectionParameters: ImapSmtpCaldavParams | null;
@IsDateString()
@IsOptional()
diff --git a/packages/twenty-server/src/engine/metadata-modules/connected-account/entities/connected-account.entity.ts b/packages/twenty-server/src/engine/metadata-modules/connected-account/entities/connected-account.entity.ts
index e6fd39941e..244da0c97e 100644
--- a/packages/twenty-server/src/engine/metadata-modules/connected-account/entities/connected-account.entity.ts
+++ b/packages/twenty-server/src/engine/metadata-modules/connected-account/entities/connected-account.entity.ts
@@ -34,6 +34,14 @@ export type ConnectedAccountVisibility = 'user' | 'workspace';
'CHK_connectedAccount_refreshToken_encrypted',
`"refreshToken" IS NULL OR "refreshToken" LIKE 'enc:v2:%'`,
)
+@Check(
+ 'CHK_connectedAccount_connectionParameters_encrypted',
+ `"connectionParameters" IS NULL OR (` +
+ `(("connectionParameters"->'IMAP'->>'password') IS NULL OR ("connectionParameters"->'IMAP'->>'password') LIKE 'enc:v2:%') ` +
+ `AND (("connectionParameters"->'SMTP'->>'password') IS NULL OR ("connectionParameters"->'SMTP'->>'password') LIKE 'enc:v2:%') ` +
+ `AND (("connectionParameters"->'CALDAV'->>'password') IS NULL OR ("connectionParameters"->'CALDAV'->>'password') LIKE 'enc:v2:%')` +
+ `)`,
+)
export class ConnectedAccountEntity extends WorkspaceRelatedEntity {
@PrimaryGeneratedColumn('uuid')
id: string;
diff --git a/packages/twenty-server/src/engine/metadata-modules/connected-account/resolvers/connected-account.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/connected-account/resolvers/connected-account.resolver.ts
index ecaabb4eae..d3ba26f55a 100644
--- a/packages/twenty-server/src/engine/metadata-modules/connected-account/resolvers/connected-account.resolver.ts
+++ b/packages/twenty-server/src/engine/metadata-modules/connected-account/resolvers/connected-account.resolver.ts
@@ -24,12 +24,12 @@ export class ConnectedAccountResolver {
private readonly connectedAccountMetadataService: ConnectedAccountMetadataService,
) {}
- @Query(() => [ConnectedAccountDTO])
+ @Query(() => [ConnectedAccountPublicDTO])
@UseGuards(NoPermissionGuard)
async myConnectedAccounts(
@AuthWorkspace() workspace: WorkspaceEntity,
@AuthUserWorkspaceId() userWorkspaceId: string,
- ): Promise {
+ ): Promise {
return this.connectedAccountMetadataService.findByUserWorkspaceId({
userWorkspaceId,
workspaceId: workspace.id,
@@ -48,21 +48,21 @@ export class ConnectedAccountResolver {
});
}
- @Query(() => [ConnectedAccountDTO])
+ @Query(() => [ConnectedAccountPublicDTO])
@UseGuards(SettingsPermissionGuard(PermissionFlagType.CONNECTED_ACCOUNTS))
async connectedAccounts(
@AuthWorkspace() workspace: WorkspaceEntity,
- ): Promise {
+ ): Promise {
return this.connectedAccountMetadataService.findAll(workspace.id);
}
- @Mutation(() => ConnectedAccountDTO)
+ @Mutation(() => ConnectedAccountPublicDTO)
@UseGuards(NoPermissionGuard)
async deleteConnectedAccount(
@Args('id', { type: () => UUIDScalarType }) id: string,
@AuthWorkspace() workspace: WorkspaceEntity,
@AuthUserWorkspaceId() userWorkspaceId: string,
- ): Promise {
+ ): Promise {
await this.connectedAccountMetadataService.verifyOwnership({
id,
userWorkspaceId,
diff --git a/packages/twenty-server/src/engine/metadata-modules/connected-account/services/connected-account-token-encryption.service.ts b/packages/twenty-server/src/engine/metadata-modules/connected-account/services/connected-account-token-encryption.service.ts
index 89bbddaef5..95d412a8fe 100644
--- a/packages/twenty-server/src/engine/metadata-modules/connected-account/services/connected-account-token-encryption.service.ts
+++ b/packages/twenty-server/src/engine/metadata-modules/connected-account/services/connected-account-token-encryption.service.ts
@@ -2,12 +2,17 @@ import { Injectable, Logger } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
+import {
+ type ConnectionParameters,
+ type ImapSmtpCaldavParams,
+} from 'src/engine/core-modules/imap-smtp-caldav-connection/types/imap-smtp-caldav-connection.type';
+import { SECRET_ENCRYPTION_ENVELOPE_PREFIX } from 'src/engine/core-modules/secret-encryption/constants/secret-encryption.constant';
import {
SecretEncryptionException,
SecretEncryptionExceptionCode,
} from 'src/engine/core-modules/secret-encryption/exceptions/secret-encryption.exception';
import { SecretEncryptionService } from 'src/engine/core-modules/secret-encryption/secret-encryption.service';
-import { parseSecretEncryptionEnvelopeOrThrow } from 'src/engine/core-modules/secret-encryption/utils/parse-secret-encryption-envelope-or-throw.util';
+import { ACCOUNT_TYPES } from 'twenty-shared/constants';
@Injectable()
export class ConnectedAccountTokenEncryptionService {
@@ -52,9 +57,6 @@ export class ConnectedAccountTokenEncryptionService {
return this.encrypt({ plaintext, workspaceId });
}
- // v2.4.0 rollout-window tolerance: rows written before the encryption
- // backfill ran may still be plaintext. Returning them as-is lets the slow
- // command finish; once it has run everywhere this branch can throw.
decrypt({
ciphertext,
workspaceId,
@@ -62,14 +64,11 @@ export class ConnectedAccountTokenEncryptionService {
ciphertext: string;
workspaceId: string;
}): string {
- const parsed = parseSecretEncryptionEnvelopeOrThrow({ value: ciphertext });
-
- if (!isDefined(parsed.version)) {
- this.logger.warn(
- 'Decrypted a legacy plaintext token. Expected during the rollout window until the slow instance command finishes backfilling.',
+ if (!ciphertext.startsWith(SECRET_ENCRYPTION_ENVELOPE_PREFIX)) {
+ throw new SecretEncryptionException(
+ 'Received a plaintext value where ciphertext was expected. The encryption backfill migration may not have run.',
+ SecretEncryptionExceptionCode.MALFORMED_ENVELOPE,
);
-
- return ciphertext;
}
return this.secretEncryptionService.decryptVersioned(ciphertext, {
@@ -116,12 +115,85 @@ export class ConnectedAccountTokenEncryptionService {
}
private looksLikeCiphertext(value: string): boolean {
- try {
- const parsed = parseSecretEncryptionEnvelopeOrThrow({ value });
+ return value.startsWith(SECRET_ENCRYPTION_ENVELOPE_PREFIX);
+ }
- return parsed.version === 2;
- } catch {
- return false;
+ encryptConnectionParameters({
+ connectionParameters,
+ workspaceId,
+ }: {
+ connectionParameters: ImapSmtpCaldavParams;
+ workspaceId: string;
+ }): ImapSmtpCaldavParams {
+ const result: ImapSmtpCaldavParams = {};
+
+ for (const protocol of ACCOUNT_TYPES) {
+ const params = connectionParameters[protocol];
+
+ if (!isDefined(params)) {
+ continue;
+ }
+
+ result[protocol] = {
+ ...params,
+ password: this.encrypt({ plaintext: params.password, workspaceId }),
+ };
}
+
+ return result;
+ }
+
+ decryptConnectionParameters({
+ connectionParameters,
+ workspaceId,
+ }: {
+ connectionParameters: ImapSmtpCaldavParams;
+ workspaceId: string;
+ }): ImapSmtpCaldavParams {
+ const result: ImapSmtpCaldavParams = {};
+
+ for (const protocol of ACCOUNT_TYPES) {
+ const params = connectionParameters[protocol];
+
+ if (!isDefined(params)) {
+ continue;
+ }
+
+ result[protocol] = this.decryptProtocolPassword({
+ protocolParams: params,
+ workspaceId,
+ });
+ }
+
+ return result;
+ }
+
+ decryptProtocolPassword({
+ protocolParams,
+ workspaceId,
+ }: {
+ protocolParams: ConnectionParameters;
+ workspaceId: string;
+ }): ConnectionParameters {
+ const isEncrypted = protocolParams.password.startsWith(
+ SECRET_ENCRYPTION_ENVELOPE_PREFIX,
+ );
+
+ // TODO: Remove after 2-5 slow instance command has been run everywhere
+ if (!isEncrypted) {
+ this.logger.warn(
+ 'Protocol password is not encrypted. Expected during the rollout window until the slow instance command finishes backfilling.',
+ );
+
+ return protocolParams;
+ }
+
+ return {
+ ...protocolParams,
+ password: this.decrypt({
+ ciphertext: protocolParams.password,
+ workspaceId,
+ }),
+ };
}
}
diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/caldav/caldav-driver.module.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/caldav/caldav-driver.module.ts
index 1b1cb1f1f7..620aa83005 100644
--- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/caldav/caldav-driver.module.ts
+++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/caldav/caldav-driver.module.ts
@@ -2,12 +2,17 @@ import { Module } from '@nestjs/common';
import { SecureHttpClientModule } from 'src/engine/core-modules/secure-http-client/secure-http-client.module';
import { TwentyConfigModule } from 'src/engine/core-modules/twenty-config/twenty-config.module';
+import { ConnectedAccountTokenEncryptionModule } from 'src/engine/metadata-modules/connected-account/services/connected-account-token-encryption.module';
import { CalDavClientService } from 'src/modules/calendar/calendar-event-import-manager/drivers/caldav/services/caldav-client.service';
import { CalDavFetchEventsService } from 'src/modules/calendar/calendar-event-import-manager/drivers/caldav/services/caldav-fetch-events.service';
import { CalDavGetEventsService } from 'src/modules/calendar/calendar-event-import-manager/drivers/caldav/services/caldav-get-events.service';
@Module({
- imports: [SecureHttpClientModule, TwentyConfigModule],
+ imports: [
+ SecureHttpClientModule,
+ TwentyConfigModule,
+ ConnectedAccountTokenEncryptionModule,
+ ],
providers: [
CalDavClientService,
CalDavFetchEventsService,
diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/caldav/services/caldav-get-events.service.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/caldav/services/caldav-get-events.service.ts
index cb70f369db..6d3071fce2 100644
--- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/caldav/services/caldav-get-events.service.ts
+++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/caldav/services/caldav-get-events.service.ts
@@ -1,9 +1,9 @@
import { Injectable, Logger } from '@nestjs/common';
-import { isNonEmptyString } from '@sniptt/guards';
import { isDefined } from 'twenty-shared/utils';
import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
+import { ConnectedAccountTokenEncryptionService } from 'src/engine/metadata-modules/connected-account/services/connected-account-token-encryption.service';
import { CalDavClientService } from 'src/modules/calendar/calendar-event-import-manager/drivers/caldav/services/caldav-client.service';
import { CalDavFetchEventsService } from 'src/modules/calendar/calendar-event-import-manager/drivers/caldav/services/caldav-fetch-events.service';
import { type CalDavSyncCursor } from 'src/modules/calendar/calendar-event-import-manager/drivers/caldav/types/caldav-sync-cursor';
@@ -20,28 +20,29 @@ export class CalDavGetEventsService {
constructor(
private readonly clientService: CalDavClientService,
private readonly fetchEventsService: CalDavFetchEventsService,
+ private readonly connectedAccountTokenEncryptionService: ConnectedAccountTokenEncryptionService,
) {}
async getCalendarEvents(
connectedAccount: Pick<
ConnectedAccountEntity,
- 'provider' | 'id' | 'connectionParameters' | 'handle'
+ 'provider' | 'id' | 'connectionParameters' | 'handle' | 'workspaceId'
>,
syncCursor?: string,
): Promise {
this.logger.debug(`Getting calendar events for ${connectedAccount.handle}`);
try {
- const params = connectedAccount.connectionParameters?.CALDAV;
-
- if (
- !isNonEmptyString(params?.host) ||
- !isNonEmptyString(params?.password) ||
- !isDefined(connectedAccount.handle)
- ) {
- throw new Error('Missing required CalDAV connection parameters');
+ if (!isDefined(connectedAccount.connectionParameters?.CALDAV)) {
+ throw new Error('CalDAV settings not configured for this account');
}
+ const params =
+ this.connectedAccountTokenEncryptionService.decryptProtocolPassword({
+ protocolParams: connectedAccount.connectionParameters.CALDAV,
+ workspaceId: connectedAccount.workspaceId,
+ });
+
const client = await this.clientService.getClient({
serverUrl: params.host,
username: params.username ?? connectedAccount.handle,
diff --git a/packages/twenty-server/src/modules/connected-account/imap-api/imap-apis.module.ts b/packages/twenty-server/src/modules/connected-account/imap-api/imap-apis.module.ts
index 23686f0e68..cbd7e2a3b1 100644
--- a/packages/twenty-server/src/modules/connected-account/imap-api/imap-apis.module.ts
+++ b/packages/twenty-server/src/modules/connected-account/imap-api/imap-apis.module.ts
@@ -8,6 +8,7 @@ import { TwentyConfigModule } from 'src/engine/core-modules/twenty-config/twenty
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity';
import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
+import { ConnectedAccountTokenEncryptionModule } from 'src/engine/metadata-modules/connected-account/services/connected-account-token-encryption.module';
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
@@ -35,6 +36,7 @@ import { MessagingFolderSyncManagerModule } from 'src/modules/messaging/message-
AuthModule,
CalendarCommonModule,
ConnectedAccountModule,
+ ConnectedAccountTokenEncryptionModule,
MessagingCommonModule,
MessagingFolderSyncManagerModule,
],
diff --git a/packages/twenty-server/src/modules/connected-account/services/imap-smtp-caldav-apis.service.spec.ts b/packages/twenty-server/src/modules/connected-account/services/imap-smtp-caldav-apis.service.spec.ts
index d1de64c9ab..d27e5c60ee 100644
--- a/packages/twenty-server/src/modules/connected-account/services/imap-smtp-caldav-apis.service.spec.ts
+++ b/packages/twenty-server/src/modules/connected-account/services/imap-smtp-caldav-apis.service.spec.ts
@@ -9,7 +9,7 @@ import {
import { CreateCalendarChannelService } from 'src/engine/core-modules/auth/services/create-calendar-channel.service';
import { CreateMessageChannelService } from 'src/engine/core-modules/auth/services/create-message-channel.service';
-import { type EmailAccountConnectionParameters } from 'src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connection.dto';
+import { type ImapSmtpCaldavParams } from 'src/engine/core-modules/imap-smtp-caldav-connection/types/imap-smtp-caldav-connection.type';
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
import { getQueueToken } from 'src/engine/core-modules/message-queue/utils/get-queue-token.util';
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
@@ -19,6 +19,7 @@ import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channe
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import { CalendarChannelSyncStatusService } from 'src/modules/calendar/common/services/calendar-channel-sync-status.service';
import { CalendarEventListFetchJob } from 'src/modules/calendar/calendar-event-import-manager/jobs/calendar-event-list-fetch.job';
+import { ConnectedAccountTokenEncryptionService } from 'src/engine/metadata-modules/connected-account/services/connected-account-token-encryption.service';
import { AccountsToReconnectService } from 'src/modules/connected-account/services/accounts-to-reconnect.service';
import { ImapSmtpCalDavAPIService } from 'src/modules/connected-account/services/imap-smtp-caldav-apis.service';
import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service';
@@ -76,7 +77,9 @@ describe('ImapSmtpCalDavAPIService', () => {
};
const mockUserWorkspaceRepository = {
- findOne: jest.fn().mockResolvedValue({ id: 'user-workspace-id' }),
+ findOne: jest
+ .fn()
+ .mockResolvedValue({ id: 'user-workspace-id', userId: 'user-id' }),
};
const mockWorkspaceMemberRepository = {
@@ -114,6 +117,36 @@ describe('ImapSmtpCalDavAPIService', () => {
resetAndMarkAsCalendarEventListFetchPending: jest.fn(),
};
+ const encryptPassword = (password: string) => `enc:v2:${password}`;
+
+ const withEncryptedPasswords = (
+ params: ImapSmtpCaldavParams,
+ ): ImapSmtpCaldavParams => {
+ const result: ImapSmtpCaldavParams = {};
+
+ for (const protocol of ['IMAP', 'SMTP', 'CALDAV'] as const) {
+ if (params[protocol]) {
+ result[protocol] = {
+ ...params[protocol],
+ password: encryptPassword(params[protocol]!.password),
+ };
+ }
+ }
+
+ return result;
+ };
+
+ const mockConnectedAccountTokenEncryptionService = {
+ encryptConnectionParameters: jest.fn(
+ ({
+ connectionParameters,
+ }: {
+ connectionParameters: ImapSmtpCaldavParams;
+ workspaceId: string;
+ }) => withEncryptedPasswords(connectionParameters),
+ ),
+ };
+
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
@@ -185,6 +218,10 @@ describe('ImapSmtpCalDavAPIService', () => {
provide: CalendarChannelSyncStatusService,
useValue: mockCalendarChannelSyncStatusService,
},
+ {
+ provide: ConnectedAccountTokenEncryptionService,
+ useValue: mockConnectedAccountTokenEncryptionService,
+ },
],
}).compile();
@@ -193,10 +230,10 @@ describe('ImapSmtpCalDavAPIService', () => {
jest.clearAllMocks();
});
- describe('processAccount', () => {
+ describe('upsertConnectedAccount', () => {
const baseInput = {
handle: 'test@example.com',
- workspaceMemberId: 'workspace-member-id',
+ userWorkspaceId: 'user-workspace-id',
workspaceId: 'workspace-id',
connectionParameters: {
IMAP: {
@@ -212,7 +249,7 @@ describe('ImapSmtpCalDavAPIService', () => {
username: 'test@example.com',
password: 'password',
},
- } as EmailAccountConnectionParameters,
+ } as ImapSmtpCaldavParams,
};
it('should create new account with message channel when account does not exist and IMAP is configured', async () => {
@@ -225,15 +262,18 @@ describe('ImapSmtpCalDavAPIService', () => {
});
mockUserWorkspaceRepository.findOne.mockResolvedValue({
id: 'user-workspace-id',
+ userId: 'user-id',
});
- await service.processAccount(baseInput);
+ await service.upsertConnectedAccount(baseInput);
expect(mockTransactionManagerSave).toHaveBeenCalledWith({
id: 'mocked-uuid',
handle: 'test@example.com',
provider: ConnectedAccountProvider.IMAP_SMTP_CALDAV,
- connectionParameters: baseInput.connectionParameters,
+ connectionParameters: withEncryptedPasswords(
+ baseInput.connectionParameters,
+ ),
userWorkspaceId: 'user-workspace-id',
workspaceId: 'workspace-id',
authFailedAt: null,
@@ -286,6 +326,7 @@ describe('ImapSmtpCalDavAPIService', () => {
});
mockUserWorkspaceRepository.findOne.mockResolvedValue({
id: 'user-workspace-id',
+ userId: 'user-id',
});
const inputWithConnectedAccountId = {
@@ -299,17 +340,19 @@ describe('ImapSmtpCalDavAPIService', () => {
username: 'test@example.com',
password: 'password',
},
- } as EmailAccountConnectionParameters,
+ } as ImapSmtpCaldavParams,
connectedAccountId: 'existing-account-id',
};
- await service.processAccount(inputWithConnectedAccountId);
+ await service.upsertConnectedAccount(inputWithConnectedAccountId);
expect(mockTransactionManagerSave).toHaveBeenCalledWith({
id: 'existing-account-id',
handle: 'test@example.com',
provider: ConnectedAccountProvider.IMAP_SMTP_CALDAV,
- connectionParameters: inputWithConnectedAccountId.connectionParameters,
+ connectionParameters: withEncryptedPasswords(
+ inputWithConnectedAccountId.connectionParameters,
+ ),
userWorkspaceId: 'user-workspace-id',
workspaceId: 'workspace-id',
authFailedAt: null,
@@ -374,11 +417,12 @@ describe('ImapSmtpCalDavAPIService', () => {
});
mockUserWorkspaceRepository.findOne.mockResolvedValue({
id: 'user-workspace-id',
+ userId: 'user-id',
});
- await service.processAccount({
+ await service.upsertConnectedAccount({
...baseInput,
- connectedAccountId: 'existing-account-id',
+ existingAccount,
});
expect(
@@ -397,9 +441,10 @@ describe('ImapSmtpCalDavAPIService', () => {
});
mockUserWorkspaceRepository.findOne.mockResolvedValue({
id: 'user-workspace-id',
+ userId: 'user-id',
});
- await service.processAccount(baseInput);
+ await service.upsertConnectedAccount(baseInput);
expect(
mockAccountsToReconnectService.removeAccountToReconnect,
@@ -424,7 +469,7 @@ describe('ImapSmtpCalDavAPIService', () => {
secure: true,
password: 'password',
},
- } as EmailAccountConnectionParameters,
+ } as ImapSmtpCaldavParams,
};
mockConnectedAccountRepository.findOne.mockResolvedValue(null);
@@ -436,9 +481,10 @@ describe('ImapSmtpCalDavAPIService', () => {
});
mockUserWorkspaceRepository.findOne.mockResolvedValue({
id: 'user-workspace-id',
+ userId: 'user-id',
});
- await service.processAccount(imapOnlyInput);
+ await service.upsertConnectedAccount(imapOnlyInput);
expect(
mockCreateMessageChannelService.createMessageChannel,
@@ -459,7 +505,7 @@ describe('ImapSmtpCalDavAPIService', () => {
username: 'test@example.com',
password: 'password',
},
- } as EmailAccountConnectionParameters,
+ } as ImapSmtpCaldavParams,
};
mockConnectedAccountRepository.findOne.mockResolvedValue(null);
@@ -471,9 +517,10 @@ describe('ImapSmtpCalDavAPIService', () => {
});
mockUserWorkspaceRepository.findOne.mockResolvedValue({
id: 'user-workspace-id',
+ userId: 'user-id',
});
- await service.processAccount(caldavOnlyInput);
+ await service.upsertConnectedAccount(caldavOnlyInput);
expect(
mockCreateMessageChannelService.createMessageChannel,
@@ -500,7 +547,7 @@ describe('ImapSmtpCalDavAPIService', () => {
username: 'test@example.com',
password: 'password',
},
- } as EmailAccountConnectionParameters,
+ } as ImapSmtpCaldavParams,
};
mockConnectedAccountRepository.findOne.mockResolvedValue(null);
@@ -512,9 +559,10 @@ describe('ImapSmtpCalDavAPIService', () => {
});
mockUserWorkspaceRepository.findOne.mockResolvedValue({
id: 'user-workspace-id',
+ userId: 'user-id',
});
- await service.processAccount(imapSmtpInput);
+ await service.upsertConnectedAccount(imapSmtpInput);
expect(
mockCreateMessageChannelService.createMessageChannel,
@@ -548,7 +596,7 @@ describe('ImapSmtpCalDavAPIService', () => {
username: 'test@example.com',
password: 'password',
},
- } as EmailAccountConnectionParameters,
+ } as ImapSmtpCaldavParams,
};
mockConnectedAccountRepository.findOne.mockResolvedValue(null);
@@ -560,9 +608,10 @@ describe('ImapSmtpCalDavAPIService', () => {
});
mockUserWorkspaceRepository.findOne.mockResolvedValue({
id: 'user-workspace-id',
+ userId: 'user-id',
});
- await service.processAccount(fullConfigInput);
+ await service.upsertConnectedAccount(fullConfigInput);
expect(
mockCreateMessageChannelService.createMessageChannel,
@@ -592,9 +641,10 @@ describe('ImapSmtpCalDavAPIService', () => {
});
mockUserWorkspaceRepository.findOne.mockResolvedValue({
id: 'user-workspace-id',
+ userId: 'user-id',
});
- await service.processAccount(baseInput);
+ await service.upsertConnectedAccount(baseInput);
expect(mockConnectedAccountRepository.findOne).toHaveBeenCalledWith({
where: {
@@ -608,7 +658,9 @@ describe('ImapSmtpCalDavAPIService', () => {
id: 'existing-account-id',
handle: 'test@example.com',
provider: ConnectedAccountProvider.IMAP_SMTP_CALDAV,
- connectionParameters: baseInput.connectionParameters,
+ connectionParameters: withEncryptedPasswords(
+ baseInput.connectionParameters,
+ ),
userWorkspaceId: 'user-workspace-id',
workspaceId: 'workspace-id',
authFailedAt: null,
@@ -626,7 +678,7 @@ describe('ImapSmtpCalDavAPIService', () => {
username: 'test@example.com',
password: 'password',
},
- } as EmailAccountConnectionParameters,
+ } as ImapSmtpCaldavParams,
};
mockConnectedAccountRepository.findOne.mockResolvedValue(null);
@@ -638,9 +690,10 @@ describe('ImapSmtpCalDavAPIService', () => {
});
mockUserWorkspaceRepository.findOne.mockResolvedValue({
id: 'user-workspace-id',
+ userId: 'user-id',
});
- await service.processAccount(smtpOnlyInput);
+ await service.upsertConnectedAccount(smtpOnlyInput);
expect(
mockCreateMessageChannelService.createMessageChannel,
@@ -660,9 +713,10 @@ describe('ImapSmtpCalDavAPIService', () => {
});
mockUserWorkspaceRepository.findOne.mockResolvedValue({
id: 'user-workspace-id',
+ userId: 'user-id',
});
- await service.processAccount(baseInput);
+ await service.upsertConnectedAccount(baseInput);
expect(
mockConnectedAccountRepository.manager.transaction,
diff --git a/packages/twenty-server/src/modules/connected-account/services/imap-smtp-caldav-apis.service.ts b/packages/twenty-server/src/modules/connected-account/services/imap-smtp-caldav-apis.service.ts
index 2f02c4eb0e..54f4c36ae8 100644
--- a/packages/twenty-server/src/modules/connected-account/services/imap-smtp-caldav-apis.service.ts
+++ b/packages/twenty-server/src/modules/connected-account/services/imap-smtp-caldav-apis.service.ts
@@ -1,4 +1,4 @@
-import { Injectable } from '@nestjs/common';
+import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import {
@@ -7,24 +7,21 @@ import {
MessageChannelSyncStage,
} from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
-import { v4 } from 'uuid';
import { EntityManager, Repository } from 'typeorm';
+import { v4 } from 'uuid';
-import { NotFoundError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { CreateCalendarChannelService } from 'src/engine/core-modules/auth/services/create-calendar-channel.service';
import { CreateMessageChannelService } from 'src/engine/core-modules/auth/services/create-message-channel.service';
-import { type EmailAccountConnectionParameters } from 'src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connection.dto';
+import { NotFoundError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
+import { type ImapSmtpCaldavParams } from 'src/engine/core-modules/imap-smtp-caldav-connection/types/imap-smtp-caldav-connection.type';
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity';
import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
+import { ConnectedAccountTokenEncryptionService } from 'src/engine/metadata-modules/connected-account/services/connected-account-token-encryption.service';
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
-import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
-import { getWorkspaceContext } from 'src/engine/twenty-orm/storage/orm-workspace-context.storage';
-import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
-import { resolveRolePermissionConfig } from 'src/engine/twenty-orm/utils/resolve-role-permission-config.util';
import {
CalendarEventListFetchJob,
type CalendarEventListFetchJobData,
@@ -32,17 +29,17 @@ import {
import { CalendarChannelSyncStatusService } from 'src/modules/calendar/common/services/calendar-channel-sync-status.service';
import { AccountsToReconnectService } from 'src/modules/connected-account/services/accounts-to-reconnect.service';
import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service';
+import { SyncMessageFoldersService } from 'src/modules/messaging/message-folder-manager/services/sync-message-folders.service';
import {
MessagingMessageListFetchJob,
type MessagingMessageListFetchJobData,
} from 'src/modules/messaging/message-import-manager/jobs/messaging-message-list-fetch.job';
-import { SyncMessageFoldersService } from 'src/modules/messaging/message-folder-manager/services/sync-message-folders.service';
-import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
@Injectable()
export class ImapSmtpCalDavAPIService {
+ private readonly logger = new Logger(ImapSmtpCalDavAPIService.name);
+
constructor(
- private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
@InjectRepository(CalendarChannelEntity)
private readonly calendarChannelRepository: Repository,
@InjectRepository(ConnectedAccountEntity)
@@ -61,210 +58,161 @@ export class ImapSmtpCalDavAPIService {
private readonly accountsToReconnectService: AccountsToReconnectService,
private readonly messagingChannelSyncStatusService: MessageChannelSyncStatusService,
private readonly calendarChannelSyncStatusService: CalendarChannelSyncStatusService,
+ private readonly connectedAccountTokenEncryptionService: ConnectedAccountTokenEncryptionService,
) {}
- async getImapSmtpCaldavConnectedAccount(
- workspaceId: string,
- id: string,
- ): Promise {
- const authContext = buildSystemAuthContext(workspaceId);
-
- return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
- async () => {
- const connectedAccount = await this.connectedAccountRepository.findOne({
- where: {
- id,
- provider: ConnectedAccountProvider.IMAP_SMTP_CALDAV,
- workspaceId,
- },
- });
-
- return connectedAccount;
- },
- authContext,
- );
- }
-
- async processAccount(input: {
+ async upsertConnectedAccount(input: {
handle: string;
- workspaceMemberId: string;
+ userWorkspaceId: string;
workspaceId: string;
- connectionParameters: EmailAccountConnectionParameters;
- connectedAccountId?: string;
+ connectionParameters: ImapSmtpCaldavParams;
+ existingAccount?: ConnectedAccountEntity | null;
}): Promise {
- const { handle, workspaceId, workspaceMemberId, connectedAccountId } =
- input;
+ const { handle, workspaceId, userWorkspaceId } = input;
- return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
- async () => {
- const workspaceContext = getWorkspaceContext();
- const rolePermissionConfig = resolveRolePermissionConfig({
- authContext: workspaceContext.authContext,
- userWorkspaceRoleMap: workspaceContext.userWorkspaceRoleMap,
- apiKeyRoleMap: workspaceContext.apiKeyRoleMap,
- });
+ const userWorkspace = await this.userWorkspaceRepository.findOne({
+ where: { id: userWorkspaceId, workspaceId },
+ });
- const workspaceMemberRepo =
- await this.globalWorkspaceOrmManager.getRepository(
- workspaceId,
- 'workspaceMember',
- rolePermissionConfig ?? undefined,
- );
+ if (!isDefined(userWorkspace)) {
+ throw new NotFoundError(
+ `UserWorkspace with id ${userWorkspaceId} not found in workspace ${workspaceId}`,
+ );
+ }
- const member = await workspaceMemberRepo.findOne({
- where: { id: workspaceMemberId },
- });
+ const existingAccount =
+ input.existingAccount ??
+ (await this.connectedAccountRepository.findOne({
+ where: { handle, userWorkspaceId, workspaceId },
+ }));
- if (!member) {
- throw new NotFoundError(
- `Workspace member with id ${workspaceMemberId} not found`,
- );
- }
+ const newOrExistingAccountId = existingAccount?.id ?? v4();
- const userWorkspace = await this.userWorkspaceRepository.findOne({
- where: { userId: member.userId, workspaceId },
- });
+ const existingMessageChannel = existingAccount
+ ? await this.messageChannelRepository.findOne({
+ where: { connectedAccountId: existingAccount.id, workspaceId },
+ })
+ : null;
- if (!userWorkspace) {
- throw new NotFoundError(
- `UserWorkspace not found for userId ${member.userId} in workspace ${workspaceId}`,
- );
- }
+ const existingCalendarChannel = existingAccount
+ ? await this.calendarChannelRepository.findOne({
+ where: { connectedAccountId: existingAccount.id, workspaceId },
+ })
+ : null;
- const userWorkspaceId = userWorkspace.id;
+ const shouldCreateMessageChannel =
+ !isDefined(existingMessageChannel) &&
+ Boolean(input.connectionParameters.IMAP);
- const existingAccount = connectedAccountId
- ? await this.connectedAccountRepository.findOne({
- where: { id: connectedAccountId, workspaceId },
- })
- : await this.connectedAccountRepository.findOne({
- where: {
- handle,
- userWorkspaceId,
- workspaceId,
- },
- });
+ const shouldCreateCalendarChannel =
+ !isDefined(existingCalendarChannel) &&
+ Boolean(input.connectionParameters.CALDAV);
- const newOrExistingAccountId =
- existingAccount?.id ?? connectedAccountId ?? v4();
-
- const existingMessageChannel = existingAccount
- ? await this.messageChannelRepository.findOne({
- where: { connectedAccountId: existingAccount.id, workspaceId },
- })
- : null;
-
- const existingCalendarChannel = existingAccount
- ? await this.calendarChannelRepository.findOne({
- where: { connectedAccountId: existingAccount.id, workspaceId },
- })
- : null;
-
- const shouldCreateMessageChannel =
- !isDefined(existingMessageChannel) &&
- Boolean(input.connectionParameters.IMAP);
-
- const shouldCreateCalendarChannel =
- !isDefined(existingCalendarChannel) &&
- Boolean(input.connectionParameters.CALDAV);
-
- await this.connectedAccountRepository.manager.transaction(
- async (transactionManager: EntityManager) => {
- await transactionManager
- .getRepository(ConnectedAccountEntity)
- .save({
- id: newOrExistingAccountId,
- handle,
- provider: ConnectedAccountProvider.IMAP_SMTP_CALDAV,
- connectionParameters: input.connectionParameters,
- userWorkspaceId,
- workspaceId,
- authFailedAt: null,
- });
-
- if (shouldCreateMessageChannel) {
- await this.createMessageChannelService.createMessageChannel({
- workspaceId,
- connectedAccountId: newOrExistingAccountId,
- handle,
- transactionManager,
- });
- }
-
- if (shouldCreateCalendarChannel) {
- await this.createCalendarChannelService.createCalendarChannel({
- workspaceId,
- connectedAccountId: newOrExistingAccountId,
- handle,
- transactionManager,
- });
- }
- },
- );
-
- if (isDefined(existingAccount)) {
- await this.accountsToReconnectService.removeAccountToReconnect(
- member.userId,
- workspaceId,
- newOrExistingAccountId,
- );
- }
-
- if (shouldCreateMessageChannel) {
- const newMessageChannel = await this.messageChannelRepository.findOne(
+ await this.connectedAccountRepository.manager.transaction(
+ async (transactionManager: EntityManager) => {
+ const encryptedConnectionParameters =
+ this.connectedAccountTokenEncryptionService.encryptConnectionParameters(
{
- where: {
- connectedAccountId: newOrExistingAccountId,
- workspaceId,
- },
- relations: ['connectedAccount', 'messageFolders'],
+ connectionParameters: input.connectionParameters,
+ workspaceId,
},
);
- if (isDefined(newMessageChannel)) {
- await this.syncMessageFoldersService.syncMessageFolders({
- messageChannel: newMessageChannel,
- workspaceId,
- });
- }
- }
+ await transactionManager.getRepository(ConnectedAccountEntity).save({
+ id: newOrExistingAccountId,
+ handle,
+ provider: ConnectedAccountProvider.IMAP_SMTP_CALDAV,
+ connectionParameters: encryptedConnectionParameters,
+ userWorkspaceId,
+ workspaceId,
+ authFailedAt: null,
+ });
- if (
- isDefined(existingMessageChannel) &&
- isDefined(input.connectionParameters.IMAP) &&
- existingMessageChannel.syncStage !==
- MessageChannelSyncStage.PENDING_CONFIGURATION
- ) {
- await this.messagingChannelSyncStatusService.resetAndMarkAsMessagesListFetchPending(
- [existingMessageChannel.id],
+ if (shouldCreateMessageChannel) {
+ await this.createMessageChannelService.createMessageChannel({
workspaceId,
- );
-
- await this.messageQueueService.add(
- MessagingMessageListFetchJob.name,
- { workspaceId, messageChannelId: existingMessageChannel.id },
- );
+ connectedAccountId: newOrExistingAccountId,
+ handle,
+ transactionManager,
+ });
}
- if (
- isDefined(existingCalendarChannel) &&
- isDefined(input.connectionParameters.CALDAV) &&
- existingCalendarChannel.syncStage !==
- CalendarChannelSyncStage.PENDING_CONFIGURATION
- ) {
- await this.calendarChannelSyncStatusService.resetAndMarkAsCalendarEventListFetchPending(
- [existingCalendarChannel.id],
+ if (shouldCreateCalendarChannel) {
+ await this.createCalendarChannelService.createCalendarChannel({
workspaceId,
- );
-
- await this.calendarQueueService.add(
- CalendarEventListFetchJob.name,
- { workspaceId, calendarChannelId: existingCalendarChannel.id },
- );
+ connectedAccountId: newOrExistingAccountId,
+ handle,
+ transactionManager,
+ });
}
-
- return newOrExistingAccountId;
},
);
+
+ if (isDefined(existingAccount)) {
+ await this.accountsToReconnectService.removeAccountToReconnect(
+ userWorkspace.userId,
+ workspaceId,
+ newOrExistingAccountId,
+ );
+ }
+
+ if (shouldCreateMessageChannel) {
+ const newMessageChannel = await this.messageChannelRepository.findOne({
+ where: {
+ connectedAccountId: newOrExistingAccountId,
+ workspaceId,
+ },
+ relations: ['connectedAccount', 'messageFolders'],
+ });
+
+ if (isDefined(newMessageChannel)) {
+ try {
+ await this.syncMessageFoldersService.syncMessageFolders({
+ messageChannel: newMessageChannel,
+ workspaceId,
+ });
+ } catch (error) {
+ this.logger.warn(
+ `Initial folder sync failed for account ${newOrExistingAccountId}, will retry on next scheduled sync: ${error?.message}`,
+ );
+ }
+ }
+ }
+
+ if (
+ isDefined(existingMessageChannel) &&
+ isDefined(input.connectionParameters.IMAP) &&
+ existingMessageChannel.syncStage !==
+ MessageChannelSyncStage.PENDING_CONFIGURATION
+ ) {
+ await this.messagingChannelSyncStatusService.resetAndMarkAsMessagesListFetchPending(
+ [existingMessageChannel.id],
+ workspaceId,
+ );
+
+ await this.messageQueueService.add(
+ MessagingMessageListFetchJob.name,
+ { workspaceId, messageChannelId: existingMessageChannel.id },
+ );
+ }
+
+ if (
+ isDefined(existingCalendarChannel) &&
+ isDefined(input.connectionParameters.CALDAV) &&
+ existingCalendarChannel.syncStage !==
+ CalendarChannelSyncStage.PENDING_CONFIGURATION
+ ) {
+ await this.calendarChannelSyncStatusService.resetAndMarkAsCalendarEventListFetchPending(
+ [existingCalendarChannel.id],
+ workspaceId,
+ );
+
+ await this.calendarQueueService.add(
+ CalendarEventListFetchJob.name,
+ { workspaceId, calendarChannelId: existingCalendarChannel.id },
+ );
+ }
+
+ return newOrExistingAccountId;
}
}
diff --git a/packages/twenty-server/src/modules/messaging/message-folder-manager/drivers/imap/services/__tests__/imap-get-all-folders.service.spec.ts b/packages/twenty-server/src/modules/messaging/message-folder-manager/drivers/imap/services/__tests__/imap-get-all-folders.service.spec.ts
index 285cf6695e..49de268d76 100644
--- a/packages/twenty-server/src/modules/messaging/message-folder-manager/drivers/imap/services/__tests__/imap-get-all-folders.service.spec.ts
+++ b/packages/twenty-server/src/modules/messaging/message-folder-manager/drivers/imap/services/__tests__/imap-get-all-folders.service.spec.ts
@@ -28,12 +28,13 @@ const createMockMailbox = (
const CONNECTED_ACCOUNT: Pick<
ConnectedAccountEntity,
- 'id' | 'provider' | 'connectionParameters' | 'handle'
+ 'id' | 'provider' | 'connectionParameters' | 'handle' | 'workspaceId'
> = {
id: 'account-1',
provider: ConnectedAccountProvider.IMAP_SMTP_CALDAV,
connectionParameters: {},
handle: 'test@example.com',
+ workspaceId: 'workspace-1',
};
const MESSAGE_CHANNEL: Pick =
diff --git a/packages/twenty-server/src/modules/messaging/message-folder-manager/drivers/imap/services/imap-get-all-folders.service.ts b/packages/twenty-server/src/modules/messaging/message-folder-manager/drivers/imap/services/imap-get-all-folders.service.ts
index 658cb6da0c..91eaf4098e 100644
--- a/packages/twenty-server/src/modules/messaging/message-folder-manager/drivers/imap/services/imap-get-all-folders.service.ts
+++ b/packages/twenty-server/src/modules/messaging/message-folder-manager/drivers/imap/services/imap-get-all-folders.service.ts
@@ -29,7 +29,7 @@ export class ImapGetAllFoldersService implements MessageFolderDriver {
public async getAllMessageFolders(
connectedAccount: Pick<
ConnectedAccountEntity,
- 'id' | 'provider' | 'connectionParameters' | 'handle'
+ 'id' | 'provider' | 'connectionParameters' | 'handle' | 'workspaceId'
>,
messageChannel: Pick,
): Promise {
diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/messaging-imap-driver.module.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/messaging-imap-driver.module.ts
index 0d873ea825..c2b6d866e1 100644
--- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/messaging-imap-driver.module.ts
+++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/messaging-imap-driver.module.ts
@@ -5,6 +5,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { SecureHttpClientModule } from 'src/engine/core-modules/secure-http-client/secure-http-client.module';
+import { ConnectedAccountTokenEncryptionModule } from 'src/engine/metadata-modules/connected-account/services/connected-account-token-encryption.module';
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module';
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
import { BlocklistWorkspaceEntity } from 'src/modules/blocklist/standard-objects/blocklist.workspace-entity';
@@ -31,6 +32,7 @@ import { MessageParticipantManagerModule } from 'src/modules/messaging/message-p
EmailAliasManagerModule,
FeatureFlagModule,
SecureHttpClientModule,
+ ConnectedAccountTokenEncryptionModule,
WorkspaceDataSourceModule,
MessageParticipantManagerModule,
],
diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/providers/imap-client.provider.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/providers/imap-client.provider.ts
index e6e02c1eec..6030c0def7 100644
--- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/providers/imap-client.provider.ts
+++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/providers/imap-client.provider.ts
@@ -2,17 +2,16 @@ import { Injectable, Logger } from '@nestjs/common';
import { ImapFlow } from 'imapflow';
import { ConnectedAccountProvider } from 'twenty-shared/types';
-import { CustomError, isDefined } from 'twenty-shared/utils';
+import { isDefined } from 'twenty-shared/utils';
-import { type ImapSmtpCaldavParams } from 'src/engine/core-modules/imap-smtp-caldav-connection/types/imap-smtp-caldav-connection.type';
import { SecureHttpClientService } from 'src/engine/core-modules/secure-http-client/secure-http-client.service';
import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
-import { MessageImportDriverExceptionCode } from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception';
+import { ConnectedAccountTokenEncryptionService } from 'src/engine/metadata-modules/connected-account/services/connected-account-token-encryption.service';
import { parseImapAuthenticationError } from 'src/modules/messaging/message-import-manager/drivers/imap/utils/parse-imap-authentication-error.util';
type ConnectedAccountIdentifier = Pick<
ConnectedAccountEntity,
- 'id' | 'provider' | 'connectionParameters' | 'handle'
+ 'id' | 'provider' | 'connectionParameters' | 'handle' | 'workspaceId'
>;
@Injectable()
@@ -24,6 +23,7 @@ export class ImapClientProvider {
constructor(
private readonly secureHttpClientService: SecureHttpClientService,
+ private readonly connectedAccountTokenEncryptionService: ConnectedAccountTokenEncryptionService,
) {}
async getClient(
@@ -60,31 +60,24 @@ export class ImapClientProvider {
throw new Error('Connected account is not an IMAP provider');
}
- const connectionParameters: ImapSmtpCaldavParams =
- (connectedAccount.connectionParameters as unknown as ImapSmtpCaldavParams) ||
- {};
-
- if (!isDefined(connectedAccount.handle)) {
- throw new CustomError(
- 'Handle is required',
- MessageImportDriverExceptionCode.CHANNEL_MISCONFIGURED,
- );
- }
+ const imapParams =
+ this.connectedAccountTokenEncryptionService.decryptProtocolPassword({
+ protocolParams: connectedAccount.connectionParameters.IMAP,
+ workspaceId: connectedAccount.workspaceId,
+ });
const validatedImapHost =
- await this.secureHttpClientService.getValidatedHost(
- connectionParameters.IMAP?.host || '',
- );
+ await this.secureHttpClientService.getValidatedHost(imapParams.host);
const client = new ImapFlow({
host: validatedImapHost,
- port: connectionParameters.IMAP?.port || 993,
- secure: connectionParameters.IMAP?.secure,
+ port: imapParams.port || 993,
+ secure: imapParams.secure,
auth: {
- user: isDefined(connectionParameters.IMAP?.username)
- ? connectionParameters.IMAP?.username
+ user: isDefined(imapParams.username)
+ ? imapParams.username
: connectedAccount.handle,
- pass: connectionParameters.IMAP?.password || '',
+ pass: imapParams.password,
},
logger: false,
tls: {
diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-messages.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-messages.service.ts
index e6f24008bf..bc901a462f 100644
--- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-messages.service.ts
+++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-messages.service.ts
@@ -18,7 +18,12 @@ import { sanitizeString } from 'src/modules/messaging/message-import-manager/uti
type ConnectedAccount = Pick<
ConnectedAccountEntity,
- 'id' | 'provider' | 'handle' | 'handleAliases' | 'connectionParameters'
+ | 'id'
+ | 'provider'
+ | 'handle'
+ | 'handleAliases'
+ | 'connectionParameters'
+ | 'workspaceId'
>;
@Injectable()
diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/smtp/messaging-smtp-driver.module.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/smtp/messaging-smtp-driver.module.ts
index 765f750958..b310dbcc78 100644
--- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/smtp/messaging-smtp-driver.module.ts
+++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/smtp/messaging-smtp-driver.module.ts
@@ -1,11 +1,12 @@
import { Module } from '@nestjs/common';
import { SecureHttpClientModule } from 'src/engine/core-modules/secure-http-client/secure-http-client.module';
+import { ConnectedAccountTokenEncryptionModule } from 'src/engine/metadata-modules/connected-account/services/connected-account-token-encryption.module';
import { SmtpClientProvider } from './providers/smtp-client.provider';
@Module({
- imports: [SecureHttpClientModule],
+ imports: [SecureHttpClientModule, ConnectedAccountTokenEncryptionModule],
providers: [SmtpClientProvider],
exports: [SmtpClientProvider],
})
diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/smtp/providers/smtp-client.provider.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/smtp/providers/smtp-client.provider.ts
index 1d888443af..669331ff82 100644
--- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/smtp/providers/smtp-client.provider.ts
+++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/smtp/providers/smtp-client.provider.ts
@@ -1,31 +1,42 @@
import { Injectable } from '@nestjs/common';
import { createTransport, type Transporter } from 'nodemailer';
-import { isDefined } from 'twenty-shared/utils';
import type SMTPConnection from 'nodemailer/lib/smtp-connection';
+import { ConnectedAccountProvider } from 'twenty-shared/types';
+import { isDefined } from 'twenty-shared/utils';
+
import { SecureHttpClientService } from 'src/engine/core-modules/secure-http-client/secure-http-client.service';
import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
+import { ConnectedAccountTokenEncryptionService } from 'src/engine/metadata-modules/connected-account/services/connected-account-token-encryption.service';
@Injectable()
export class SmtpClientProvider {
constructor(
private readonly secureHttpClientService: SecureHttpClientService,
+ private readonly connectedAccountTokenEncryptionService: ConnectedAccountTokenEncryptionService,
) {}
public async getSmtpClient(
connectedAccount: Pick<
ConnectedAccountEntity,
- 'connectionParameters' | 'handle'
+ 'provider' | 'connectionParameters' | 'handle' | 'workspaceId'
>,
): Promise {
- const smtpParams = connectedAccount.connectionParameters?.SMTP;
-
- if (!isDefined(smtpParams)) {
- throw new Error('SMTP settings not configured for this account');
+ if (
+ connectedAccount.provider !== ConnectedAccountProvider.IMAP_SMTP_CALDAV ||
+ !isDefined(connectedAccount.connectionParameters?.SMTP)
+ ) {
+ throw new Error('Connected account is not an SMTP provider');
}
+ const smtpParams =
+ this.connectedAccountTokenEncryptionService.decryptProtocolPassword({
+ protocolParams: connectedAccount.connectionParameters.SMTP,
+ workspaceId: connectedAccount.workspaceId,
+ });
+
const validatedSmtpHost =
await this.secureHttpClientService.getValidatedHost(smtpParams.host);
diff --git a/packages/twenty-server/test/integration/metadata/suites/connected-account/__snapshots__/failing-save-imap-smtp-caldav-account.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/connected-account/__snapshots__/failing-save-imap-smtp-caldav-account.integration-spec.ts.snap
new file mode 100644
index 0000000000..9994f74dbb
--- /dev/null
+++ b/packages/twenty-server/test/integration/metadata/suites/connected-account/__snapshots__/failing-save-imap-smtp-caldav-account.integration-spec.ts.snap
@@ -0,0 +1,67 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Connected account creation should fail when CALDAV password is missing on creation 1`] = `
+{
+ "extensions": {
+ "code": "BAD_USER_INPUT",
+ "userFriendlyMessage": "Please check your connection settings. Make sure the server host, port, and password are correct.",
+ },
+ "message": "Protocol connection validation failed: password: Invalid input: expected string, received undefined",
+ "name": "UserInputError",
+}
+`;
+
+exports[`Connected account creation should fail when IMAP host is empty 1`] = `
+{
+ "extensions": {
+ "code": "BAD_USER_INPUT",
+ "userFriendlyMessage": "Please check your connection settings. Make sure the server host, port, and password are correct.",
+ },
+ "message": "Protocol connection validation failed: host: Host is required",
+ "name": "UserInputError",
+}
+`;
+
+exports[`Connected account creation should fail when IMAP password is empty string on creation 1`] = `
+{
+ "extensions": {
+ "code": "BAD_USER_INPUT",
+ "userFriendlyMessage": "Please check your connection settings. Make sure the server host, port, and password are correct.",
+ },
+ "message": "Protocol connection validation failed: password: Password is required",
+ "name": "UserInputError",
+}
+`;
+
+exports[`Connected account creation should fail when IMAP password is missing on creation 1`] = `
+{
+ "extensions": {
+ "code": "BAD_USER_INPUT",
+ "userFriendlyMessage": "Please check your connection settings. Make sure the server host, port, and password are correct.",
+ },
+ "message": "Protocol connection validation failed: password: Invalid input: expected string, received undefined",
+ "name": "UserInputError",
+}
+`;
+
+exports[`Connected account creation should fail when IMAP port is zero 1`] = `
+{
+ "extensions": {
+ "code": "BAD_USER_INPUT",
+ "userFriendlyMessage": "Please check your connection settings. Make sure the server host, port, and password are correct.",
+ },
+ "message": "Protocol connection validation failed: port: Port must be a positive number",
+ "name": "UserInputError",
+}
+`;
+
+exports[`Connected account creation should fail when SMTP password is missing on creation 1`] = `
+{
+ "extensions": {
+ "code": "BAD_USER_INPUT",
+ "userFriendlyMessage": "Please check your connection settings. Make sure the server host, port, and password are correct.",
+ },
+ "message": "Protocol connection validation failed: password: Invalid input: expected string, received undefined",
+ "name": "UserInputError",
+}
+`;
diff --git a/packages/twenty-server/test/integration/metadata/suites/connected-account/failing-save-imap-smtp-caldav-account.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/connected-account/failing-save-imap-smtp-caldav-account.integration-spec.ts
new file mode 100644
index 0000000000..4b8f0e2a36
--- /dev/null
+++ b/packages/twenty-server/test/integration/metadata/suites/connected-account/failing-save-imap-smtp-caldav-account.integration-spec.ts
@@ -0,0 +1,145 @@
+import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util';
+import { saveImapSmtpCaldavAccount } from 'test/integration/metadata/suites/connected-account/utils/save-imap-smtp-caldav-account.util';
+import {
+ eachTestingContextFilter,
+ type EachTestingContext,
+} from 'twenty-shared/testing';
+
+type TestContext = {
+ handle: string;
+ connectionParameters: {
+ IMAP?: {
+ host: string;
+ port: number;
+ username?: string;
+ password?: string;
+ secure?: boolean;
+ };
+ SMTP?: {
+ host: string;
+ port: number;
+ username?: string;
+ password?: string;
+ secure?: boolean;
+ };
+ CALDAV?: {
+ host: string;
+ port: number;
+ username?: string;
+ password?: string;
+ secure?: boolean;
+ };
+ };
+ id?: string;
+};
+
+type FailingTestContext = EachTestingContext[];
+
+const VALID_HANDLE = 'test-failing@example.com';
+
+describe('Connected account creation should fail', () => {
+ const failingCreationTestCases: FailingTestContext = [
+ {
+ title: 'when IMAP password is missing on creation',
+ context: {
+ handle: VALID_HANDLE,
+ connectionParameters: {
+ IMAP: {
+ host: 'imap.example.com',
+ port: 993,
+ username: 'user@example.com',
+ secure: true,
+ },
+ },
+ },
+ },
+ {
+ title: 'when SMTP password is missing on creation',
+ context: {
+ handle: VALID_HANDLE,
+ connectionParameters: {
+ SMTP: {
+ host: 'smtp.example.com',
+ port: 465,
+ username: 'user@example.com',
+ secure: true,
+ },
+ },
+ },
+ },
+ {
+ title: 'when CALDAV password is missing on creation',
+ context: {
+ handle: VALID_HANDLE,
+ connectionParameters: {
+ CALDAV: {
+ host: 'caldav.example.com',
+ port: 443,
+ username: 'user@example.com',
+ secure: true,
+ },
+ },
+ },
+ },
+ {
+ title: 'when IMAP host is empty',
+ context: {
+ handle: VALID_HANDLE,
+ connectionParameters: {
+ IMAP: {
+ host: '',
+ port: 993,
+ password: 'secret',
+ secure: true,
+ },
+ },
+ },
+ },
+ {
+ title: 'when IMAP port is zero',
+ context: {
+ handle: VALID_HANDLE,
+ connectionParameters: {
+ IMAP: {
+ host: 'imap.example.com',
+ port: 0,
+ password: 'secret',
+ secure: true,
+ },
+ },
+ },
+ },
+ {
+ title: 'when IMAP password is empty string on creation',
+ context: {
+ handle: VALID_HANDLE,
+ connectionParameters: {
+ IMAP: {
+ host: 'imap.example.com',
+ port: 993,
+ password: '',
+ secure: true,
+ },
+ },
+ },
+ },
+ ];
+
+ it.each(eachTestingContextFilter(failingCreationTestCases))(
+ '$title',
+ async ({ context }) => {
+ const { errors } = await saveImapSmtpCaldavAccount({
+ expectToFail: true,
+ input: {
+ handle: context.handle,
+ connectionParameters: context.connectionParameters,
+ id: context.id,
+ },
+ });
+
+ expectOneNotInternalServerErrorSnapshot({
+ errors,
+ });
+ },
+ );
+});
diff --git a/packages/twenty-server/test/integration/metadata/suites/connected-account/successful-save-imap-smtp-caldav-account.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/connected-account/successful-save-imap-smtp-caldav-account.integration-spec.ts
new file mode 100644
index 0000000000..0743d678e8
--- /dev/null
+++ b/packages/twenty-server/test/integration/metadata/suites/connected-account/successful-save-imap-smtp-caldav-account.integration-spec.ts
@@ -0,0 +1,237 @@
+import { deleteConnectedAccount } from 'test/integration/metadata/suites/connected-account/utils/delete-connected-account.util';
+import { getConnectedImapSmtpCaldavAccount } from 'test/integration/metadata/suites/connected-account/utils/get-connected-imap-smtp-caldav-account.util';
+import { saveImapSmtpCaldavAccount } from 'test/integration/metadata/suites/connected-account/utils/save-imap-smtp-caldav-account.util';
+
+const createAccount = async (handle: string): Promise => {
+ const { data } = await saveImapSmtpCaldavAccount({
+ expectToFail: false,
+ input: {
+ handle,
+ connectionParameters: {
+ IMAP: {
+ host: 'imap.fastmail.com',
+ port: 993,
+ username: 'user@example.com',
+ password: 'test-password',
+ secure: true,
+ },
+ SMTP: {
+ host: 'smtp.fastmail.com',
+ port: 465,
+ username: 'user@example.com',
+ password: 'test-password',
+ secure: true,
+ },
+ },
+ },
+ });
+
+ return data.connectedAccountId as string;
+};
+
+describe('Successful save IMAP/SMTP/CALDAV account', () => {
+ const accountIdsToCleanup: string[] = [];
+
+ afterAll(async () => {
+ for (const id of accountIdsToCleanup) {
+ await deleteConnectedAccount({ id, expectToFail: false });
+ }
+ });
+
+ it('should create a connected account with IMAP and SMTP', async () => {
+ const { data } = await saveImapSmtpCaldavAccount({
+ expectToFail: false,
+ input: {
+ handle: 'test-create@example.com',
+ connectionParameters: {
+ IMAP: {
+ host: 'imap.fastmail.com',
+ port: 993,
+ username: 'user@example.com',
+ password: 'test-password',
+ secure: true,
+ },
+ SMTP: {
+ host: 'smtp.fastmail.com',
+ port: 465,
+ username: 'user@example.com',
+ password: 'test-password',
+ secure: true,
+ },
+ },
+ },
+ });
+
+ expect(data).toEqual({
+ success: true,
+ connectedAccountId: expect.any(String),
+ });
+
+ accountIdsToCleanup.push(data.connectedAccountId);
+
+ const account = await getConnectedImapSmtpCaldavAccount({
+ id: data.connectedAccountId,
+ });
+
+ expect(account.connectionParameters).toEqual({
+ IMAP: {
+ host: 'imap.fastmail.com',
+ port: 993,
+ username: 'user@example.com',
+ secure: true,
+ },
+ SMTP: {
+ host: 'smtp.fastmail.com',
+ port: 465,
+ username: 'user@example.com',
+ secure: true,
+ },
+ CALDAV: null,
+ });
+ });
+
+ it('should update connection parameters without providing password', async () => {
+ const accountId = await createAccount('test-update-no-pwd@example.com');
+
+ accountIdsToCleanup.push(accountId);
+
+ const { data } = await saveImapSmtpCaldavAccount({
+ expectToFail: false,
+ input: {
+ handle: 'test-update-no-pwd@example.com',
+ connectionParameters: {
+ IMAP: {
+ host: 'imap-updated.fastmail.com',
+ port: 993,
+ username: 'updated-user@example.com',
+ secure: true,
+ },
+ SMTP: {
+ host: 'smtp-updated.fastmail.com',
+ port: 587,
+ username: 'updated-user@example.com',
+ secure: false,
+ },
+ },
+ id: accountId,
+ },
+ });
+
+ expect(data).toEqual({
+ success: true,
+ connectedAccountId: accountId,
+ });
+
+ const account = await getConnectedImapSmtpCaldavAccount({ id: accountId });
+
+ expect(account.connectionParameters).toEqual({
+ IMAP: {
+ host: 'imap-updated.fastmail.com',
+ port: 993,
+ username: 'updated-user@example.com',
+ secure: true,
+ },
+ SMTP: {
+ host: 'smtp-updated.fastmail.com',
+ port: 587,
+ username: 'updated-user@example.com',
+ secure: false,
+ },
+ CALDAV: null,
+ });
+ });
+
+ it('should remove SMTP when updating with only IMAP', async () => {
+ const accountId = await createAccount('test-remove-smtp@example.com');
+
+ accountIdsToCleanup.push(accountId);
+
+ const { data } = await saveImapSmtpCaldavAccount({
+ expectToFail: false,
+ input: {
+ handle: 'test-remove-smtp@example.com',
+ connectionParameters: {
+ IMAP: {
+ host: 'imap.fastmail.com',
+ port: 993,
+ username: 'user@example.com',
+ password: 'test-password',
+ secure: true,
+ },
+ },
+ id: accountId,
+ },
+ });
+
+ expect(data).toEqual({
+ success: true,
+ connectedAccountId: accountId,
+ });
+
+ const account = await getConnectedImapSmtpCaldavAccount({ id: accountId });
+
+ expect(account.connectionParameters).toEqual({
+ IMAP: {
+ host: 'imap.fastmail.com',
+ port: 993,
+ username: 'user@example.com',
+ secure: true,
+ },
+ SMTP: null,
+ CALDAV: null,
+ });
+ });
+
+ it('should update only the password when other fields stay the same', async () => {
+ const accountId = await createAccount('test-update-pwd@example.com');
+
+ accountIdsToCleanup.push(accountId);
+
+ const { data } = await saveImapSmtpCaldavAccount({
+ expectToFail: false,
+ input: {
+ handle: 'test-update-pwd@example.com',
+ connectionParameters: {
+ IMAP: {
+ host: 'imap.fastmail.com',
+ port: 993,
+ username: 'user@example.com',
+ password: 'new-password',
+ secure: true,
+ },
+ SMTP: {
+ host: 'smtp.fastmail.com',
+ port: 465,
+ username: 'user@example.com',
+ password: 'new-password',
+ secure: true,
+ },
+ },
+ id: accountId,
+ },
+ });
+
+ expect(data).toEqual({
+ success: true,
+ connectedAccountId: accountId,
+ });
+
+ const account = await getConnectedImapSmtpCaldavAccount({ id: accountId });
+
+ expect(account.connectionParameters).toEqual({
+ IMAP: {
+ host: 'imap.fastmail.com',
+ port: 993,
+ username: 'user@example.com',
+ secure: true,
+ },
+ SMTP: {
+ host: 'smtp.fastmail.com',
+ port: 465,
+ username: 'user@example.com',
+ secure: true,
+ },
+ CALDAV: null,
+ });
+ });
+});
diff --git a/packages/twenty-server/test/integration/metadata/suites/connected-account/utils/delete-connected-account.util.ts b/packages/twenty-server/test/integration/metadata/suites/connected-account/utils/delete-connected-account.util.ts
new file mode 100644
index 0000000000..885c491689
--- /dev/null
+++ b/packages/twenty-server/test/integration/metadata/suites/connected-account/utils/delete-connected-account.util.ts
@@ -0,0 +1,31 @@
+import { gql } from 'graphql-tag';
+import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
+
+export const deleteConnectedAccount = async ({
+ id,
+ expectToFail,
+}: {
+ id: string;
+ expectToFail: boolean;
+}) => {
+ const response = await makeMetadataAPIRequest({
+ query: gql`
+ mutation DeleteConnectedAccount($id: UUID!) {
+ deleteConnectedAccount(id: $id) {
+ id
+ }
+ }
+ `,
+ variables: { id },
+ });
+
+ if (expectToFail) {
+ expect(response.body.data?.deleteConnectedAccount).toBeNull();
+
+ return { errors: response.body.errors, data: null };
+ }
+
+ expect(response.body.errors).toBeUndefined();
+
+ return { errors: null, data: response.body.data.deleteConnectedAccount };
+};
diff --git a/packages/twenty-server/test/integration/metadata/suites/connected-account/utils/get-connected-imap-smtp-caldav-account.util.ts b/packages/twenty-server/test/integration/metadata/suites/connected-account/utils/get-connected-imap-smtp-caldav-account.util.ts
new file mode 100644
index 0000000000..3304d18206
--- /dev/null
+++ b/packages/twenty-server/test/integration/metadata/suites/connected-account/utils/get-connected-imap-smtp-caldav-account.util.ts
@@ -0,0 +1,46 @@
+import { gql } from 'graphql-tag';
+import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
+
+export const getConnectedImapSmtpCaldavAccount = async ({
+ id,
+}: {
+ id: string;
+}) => {
+ const response = await makeMetadataAPIRequest({
+ query: gql`
+ query GetConnectedImapSmtpCaldavAccount($id: UUID!) {
+ getConnectedImapSmtpCaldavAccount(id: $id) {
+ id
+ handle
+ provider
+ userWorkspaceId
+ connectionParameters {
+ IMAP {
+ host
+ port
+ username
+ secure
+ }
+ SMTP {
+ host
+ port
+ username
+ secure
+ }
+ CALDAV {
+ host
+ port
+ username
+ secure
+ }
+ }
+ }
+ }
+ `,
+ variables: { id },
+ });
+
+ expect(response.body.errors).toBeUndefined();
+
+ return response.body.data.getConnectedImapSmtpCaldavAccount;
+};
diff --git a/packages/twenty-server/test/integration/metadata/suites/connected-account/utils/save-imap-smtp-caldav-account.util.ts b/packages/twenty-server/test/integration/metadata/suites/connected-account/utils/save-imap-smtp-caldav-account.util.ts
new file mode 100644
index 0000000000..d790f2a109
--- /dev/null
+++ b/packages/twenty-server/test/integration/metadata/suites/connected-account/utils/save-imap-smtp-caldav-account.util.ts
@@ -0,0 +1,72 @@
+import { gql } from 'graphql-tag';
+import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
+
+type SaveImapSmtpCaldavAccountInput = {
+ handle: string;
+ connectionParameters: {
+ IMAP?: {
+ host: string;
+ port: number;
+ username?: string;
+ password?: string;
+ secure?: boolean;
+ };
+ SMTP?: {
+ host: string;
+ port: number;
+ username?: string;
+ password?: string;
+ secure?: boolean;
+ };
+ CALDAV?: {
+ host: string;
+ port: number;
+ username?: string;
+ password?: string;
+ secure?: boolean;
+ };
+ };
+ id?: string;
+};
+
+export const saveImapSmtpCaldavAccount = async ({
+ input,
+ expectToFail,
+}: {
+ input: SaveImapSmtpCaldavAccountInput;
+ expectToFail: boolean;
+}) => {
+ const response = await makeMetadataAPIRequest({
+ query: gql`
+ mutation SaveImapSmtpCaldavAccount(
+ $handle: String!
+ $connectionParameters: EmailAccountConnectionParameters!
+ $id: UUID
+ ) {
+ saveImapSmtpCaldavAccount(
+ handle: $handle
+ connectionParameters: $connectionParameters
+ id: $id
+ ) {
+ success
+ connectedAccountId
+ }
+ }
+ `,
+ variables: {
+ handle: input.handle,
+ connectionParameters: input.connectionParameters,
+ ...(input.id && { id: input.id }),
+ },
+ });
+
+ if (expectToFail) {
+ expect(response.body.errors).toBeDefined();
+
+ return { errors: response.body.errors, data: null };
+ }
+
+ expect(response.body.errors).toBeUndefined();
+
+ return { errors: null, data: response.body.data.saveImapSmtpCaldavAccount };
+};
diff --git a/packages/twenty-shared/src/constants/AccountTypes.ts b/packages/twenty-shared/src/constants/AccountTypes.ts
new file mode 100644
index 0000000000..e6131edc25
--- /dev/null
+++ b/packages/twenty-shared/src/constants/AccountTypes.ts
@@ -0,0 +1,3 @@
+export const ACCOUNT_TYPES = ['IMAP', 'SMTP', 'CALDAV'] as const;
+
+export type AccountType = (typeof ACCOUNT_TYPES)[number];
diff --git a/packages/twenty-shared/src/constants/index.ts b/packages/twenty-shared/src/constants/index.ts
index d0920d2d84..236d0f0eae 100644
--- a/packages/twenty-shared/src/constants/index.ts
+++ b/packages/twenty-shared/src/constants/index.ts
@@ -7,6 +7,8 @@
* |___/
*/
+export type { AccountType } from './AccountTypes';
+export { ACCOUNT_TYPES } from './AccountTypes';
export { ALLOWED_FULL_NAME_SORT_SUBFIELDS } from './AllowedFullNameSortSubfields';
export { AUTO_SELECT_FAST_MODEL_ID } from './AutoSelectFastModelId';
export { AUTO_SELECT_SMART_MODEL_ID } from './AutoSelectSmartModelId';