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';