[CONNECTED_ACCOUNT_BREAKING_CHANGE] Encrypt ConnectedAccount connectionParameters (#20673)
# Introduction Prevent any cross user `connectedAccount` `connectionParamaters` leak Also encrypt in db all `connectionParameters` password Never return any password through `DTO` anymore The settings now allow update mutation without providing the password in edition mode Verified all `connectionParameters.password` interaction ## Integration tests - Added more coverage for both failing and successful paths - Introduced a new env var that allow bypass the provider connection test ## Legacy connected Account decryption support Stop allowing non encrypted decryption on `accessToken` and `refreshToken`, only allow legacy decryption on refactored `connectionParameters` ## Upsert ownership Completely got rid of the connected workspace schema context which is legacy Also now a user can only upsert a connected account for him only.. ## New UI <img width="1770" height="1852" alt="image" src="https://github.com/user-attachments/assets/55c1dc89-42ff-4084-95e2-cc5f9e23753b" /> If in edition the password is by default disabled It needs to be selected as being edited to be enabled ## Next - Refactor tool permissions flag not to include connected accounts - Remove the legacy connected standard object - Refactor and improve connected account resolver auth
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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"')
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -916,26 +916,6 @@ export enum CommandMenuItemAvailabilityType {
|
||||
|
||||
export type CommandMenuItemPayload = ObjectMetadataCommandMenuItemPayload | PathCommandMenuItemPayload;
|
||||
|
||||
export type ConnectedAccountDto = {
|
||||
__typename?: 'ConnectedAccountDTO';
|
||||
applicationId?: Maybe<Scalars['UUID']>;
|
||||
authFailedAt?: Maybe<Scalars['DateTime']>;
|
||||
connectionParameters?: Maybe<ImapSmtpCaldavConnectionParameters>;
|
||||
connectionProviderId?: Maybe<Scalars['UUID']>;
|
||||
createdAt: Scalars['DateTime'];
|
||||
handle: Scalars['String'];
|
||||
handleAliases?: Maybe<Array<Scalars['String']>>;
|
||||
id: Scalars['UUID'];
|
||||
lastCredentialsRefreshedAt?: Maybe<Scalars['DateTime']>;
|
||||
lastSignedInAt?: Maybe<Scalars['DateTime']>;
|
||||
name?: Maybe<Scalars['String']>;
|
||||
provider: Scalars['String'];
|
||||
scopes?: Maybe<Array<Scalars['String']>>;
|
||||
updatedAt: Scalars['DateTime'];
|
||||
userWorkspaceId: Scalars['UUID'];
|
||||
visibility: Scalars['String'];
|
||||
};
|
||||
|
||||
export type ConnectedAccountPublicDto = {
|
||||
__typename?: 'ConnectedAccountPublicDTO';
|
||||
applicationId?: Maybe<Scalars['UUID']>;
|
||||
@@ -958,30 +938,21 @@ export type ConnectedAccountPublicDto = {
|
||||
|
||||
export type ConnectedImapSmtpCaldavAccount = {
|
||||
__typename?: 'ConnectedImapSmtpCaldavAccount';
|
||||
connectionParameters?: Maybe<ImapSmtpCaldavConnectionParameters>;
|
||||
connectionParameters?: Maybe<ImapSmtpCaldavPublicConnectionParameters>;
|
||||
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<Scalars['String']>;
|
||||
port: Scalars['Float'];
|
||||
secure?: InputMaybe<Scalars['Boolean']>;
|
||||
username?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type ConnectionParametersOutput = {
|
||||
__typename?: 'ConnectionParametersOutput';
|
||||
host: Scalars['String'];
|
||||
password: Scalars['String'];
|
||||
port: Scalars['Float'];
|
||||
secure?: Maybe<Scalars['Boolean']>;
|
||||
username?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type CreateAgentInput = {
|
||||
description?: InputMaybe<Scalars['String']>;
|
||||
evaluationInputs?: InputMaybe<Array<Scalars['String']>>;
|
||||
@@ -1439,9 +1410,9 @@ export type EditSsoInput = {
|
||||
};
|
||||
|
||||
export type EmailAccountConnectionParameters = {
|
||||
CALDAV?: InputMaybe<ConnectionParameters>;
|
||||
IMAP?: InputMaybe<ConnectionParameters>;
|
||||
SMTP?: InputMaybe<ConnectionParameters>;
|
||||
CALDAV?: InputMaybe<ConnectionParametersInput>;
|
||||
IMAP?: InputMaybe<ConnectionParametersInput>;
|
||||
SMTP?: InputMaybe<ConnectionParametersInput>;
|
||||
};
|
||||
|
||||
export type EmailPasswordResetLink = {
|
||||
@@ -1938,19 +1909,27 @@ export type IframeConfiguration = {
|
||||
url?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type ImapSmtpCaldavConnectionParameters = {
|
||||
__typename?: 'ImapSmtpCaldavConnectionParameters';
|
||||
CALDAV?: Maybe<ConnectionParametersOutput>;
|
||||
IMAP?: Maybe<ConnectionParametersOutput>;
|
||||
SMTP?: Maybe<ConnectionParametersOutput>;
|
||||
};
|
||||
|
||||
export type ImapSmtpCaldavConnectionSuccess = {
|
||||
__typename?: 'ImapSmtpCaldavConnectionSuccess';
|
||||
connectedAccountId: Scalars['String'];
|
||||
success: Scalars['Boolean'];
|
||||
};
|
||||
|
||||
export type ImapSmtpCaldavPublicConnectionParameters = {
|
||||
__typename?: 'ImapSmtpCaldavPublicConnectionParameters';
|
||||
CALDAV?: Maybe<ImapSmtpCaldavPublicConnectionParams>;
|
||||
IMAP?: Maybe<ImapSmtpCaldavPublicConnectionParams>;
|
||||
SMTP?: Maybe<ImapSmtpCaldavPublicConnectionParams>;
|
||||
};
|
||||
|
||||
export type ImapSmtpCaldavPublicConnectionParams = {
|
||||
__typename?: 'ImapSmtpCaldavPublicConnectionParams';
|
||||
host: Scalars['String'];
|
||||
port: Scalars['Float'];
|
||||
secure?: Maybe<Scalars['Boolean']>;
|
||||
username?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
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<Scalars['UUID']>;
|
||||
@@ -4109,7 +4087,7 @@ export type Query = {
|
||||
commandMenuItem?: Maybe<CommandMenuItem>;
|
||||
commandMenuItems: Array<CommandMenuItem>;
|
||||
connectedAccountById?: Maybe<ConnectedAccountPublicDto>;
|
||||
connectedAccounts: Array<ConnectedAccountDto>;
|
||||
connectedAccounts: Array<ConnectedAccountPublicDto>;
|
||||
currentUser: User;
|
||||
currentWorkspace: Workspace;
|
||||
enterpriseCheckoutSession?: Maybe<Scalars['String']>;
|
||||
@@ -4179,7 +4157,7 @@ export type Query = {
|
||||
listPlans: Array<BillingPlan>;
|
||||
minimalMetadata: MinimalMetadata;
|
||||
myCalendarChannels: Array<CalendarChannel>;
|
||||
myConnectedAccounts: Array<ConnectedAccountDto>;
|
||||
myConnectedAccounts: Array<ConnectedAccountPublicDto>;
|
||||
myMessageChannels: Array<MessageChannel>;
|
||||
myMessageFolders: Array<MessageFolder>;
|
||||
navigationMenuItem?: Maybe<NavigationMenuItem>;
|
||||
@@ -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<Scalars['UUID']>;
|
||||
@@ -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<Scalars['UUID']>;
|
||||
@@ -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<string> | null, handleAliases?: Array<string> | 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<string> | null, handleAliases?: Array<string> | 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<Scalars['UUID']>;
|
||||
@@ -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<CreateEmailGroupChannelMutation, CreateEmailGroupChannelMutationVariables>;
|
||||
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<DeleteConnectedAccountMutation, DeleteConnectedAccountMutationVariables>;
|
||||
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<DeleteEmailGroupChannelMutation, DeleteEmailGroupChannelMutationVariables>;
|
||||
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<SaveImapSmtpCaldavAccountMutation, SaveImapSmtpCaldavAccountMutationVariables>;
|
||||
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<SaveImapSmtpCaldavAccountMutation, SaveImapSmtpCaldavAccountMutationVariables>;
|
||||
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<StartChannelSyncMutation, StartChannelSyncMutationVariables>;
|
||||
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<UpdateCalendarChannelMutation, UpdateCalendarChannelMutationVariables>;
|
||||
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<UpdateMessageChannelMutation, UpdateMessageChannelMutationVariables>;
|
||||
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<UpdateMessageFolderMutation, UpdateMessageFolderMutationVariables>;
|
||||
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<UpdateMessageFoldersMutation, UpdateMessageFoldersMutationVariables>;
|
||||
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<ConnectedAccountByIdQuery, ConnectedAccountByIdQueryVariables>;
|
||||
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<GetConnectedImapSmtpCaldavAccountQuery, GetConnectedImapSmtpCaldavAccountQueryVariables>;
|
||||
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<GetConnectedImapSmtpCaldavAccountQuery, GetConnectedImapSmtpCaldavAccountQueryVariables>;
|
||||
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<MyCalendarChannelsQuery, MyCalendarChannelsQueryVariables>;
|
||||
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<MyConnectedAccountsQuery, MyConnectedAccountsQueryVariables>;
|
||||
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<MyConnectedAccountsQuery, MyConnectedAccountsQueryVariables>;
|
||||
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<MyMessageChannelsQuery, MyMessageChannelsQueryVariables>;
|
||||
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<MyMessageFoldersQuery, MyMessageFoldersQueryVariables>;
|
||||
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<DeleteApplicationRegistrationMutation, DeleteApplicationRegistrationMutationVariables>;
|
||||
|
||||
@@ -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[];
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { type ConnectionParameters } from '~/generated-metadata/graphql';
|
||||
|
||||
export type ImapSmtpCaldavAccount = {
|
||||
IMAP?: ConnectionParameters;
|
||||
SMTP?: ConnectionParameters;
|
||||
CALDAV?: ConnectionParameters;
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
import { type ConnectionParametersInput } from '~/generated-metadata/graphql';
|
||||
|
||||
export type ImapSmtpCaldavAccountInput = {
|
||||
IMAP?: ConnectionParametersInput;
|
||||
SMTP?: ConnectionParametersInput;
|
||||
CALDAV?: ConnectionParametersInput;
|
||||
};
|
||||
+46
-39
@@ -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<ConnectionFormData>;
|
||||
isEditing: boolean;
|
||||
existingProtocols?: AccountType[];
|
||||
};
|
||||
|
||||
export const SettingsAccountsConnectionForm = ({
|
||||
control,
|
||||
isEditing,
|
||||
existingProtocols = [],
|
||||
}: SettingsAccountsConnectionFormProps) => {
|
||||
const { t } = useLingui();
|
||||
|
||||
const [isProtocolPasswordBeingEdited, setIsProtocolPasswordBeingEdited] =
|
||||
useState<Record<AccountType, boolean>>({
|
||||
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 = ({
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="IMAP.password"
|
||||
<SettingsAccountsPasswordController
|
||||
protocol="IMAP"
|
||||
label={t`IMAP Password`}
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<SettingsTextInput
|
||||
instanceId="imap-password-connection-form"
|
||||
label={t`IMAP Password`}
|
||||
placeholder={t`••••••••`}
|
||||
type="password"
|
||||
value={field.value || ''}
|
||||
onChange={field.onChange}
|
||||
error={fieldState.error?.message}
|
||||
/>
|
||||
)}
|
||||
disabled={isPasswordInputDisabled('IMAP')}
|
||||
onUnlock={() =>
|
||||
setIsProtocolPasswordBeingEdited((prev) => ({
|
||||
...prev,
|
||||
IMAP: true,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
|
||||
<StyledFieldRow>
|
||||
@@ -235,20 +248,17 @@ export const SettingsAccountsConnectionForm = ({
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="SMTP.password"
|
||||
<SettingsAccountsPasswordController
|
||||
protocol="SMTP"
|
||||
label={t`SMTP Password`}
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<SettingsTextInput
|
||||
instanceId="smtp-password-connection-form"
|
||||
label={t`SMTP Password`}
|
||||
placeholder={t`••••••••`}
|
||||
type="password"
|
||||
value={field.value || ''}
|
||||
onChange={field.onChange}
|
||||
error={fieldState.error?.message}
|
||||
/>
|
||||
)}
|
||||
disabled={isPasswordInputDisabled('SMTP')}
|
||||
onUnlock={() =>
|
||||
setIsProtocolPasswordBeingEdited((prev) => ({
|
||||
...prev,
|
||||
SMTP: true,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
|
||||
<StyledFieldRow>
|
||||
@@ -333,20 +343,17 @@ export const SettingsAccountsConnectionForm = ({
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="CALDAV.password"
|
||||
<SettingsAccountsPasswordController
|
||||
protocol="CALDAV"
|
||||
label={t`CalDAV Password`}
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<SettingsTextInput
|
||||
instanceId="caldav-password-connection-form"
|
||||
label={t`CalDAV Password`}
|
||||
placeholder={t`••••••••`}
|
||||
type="password"
|
||||
value={field.value || ''}
|
||||
onChange={field.onChange}
|
||||
error={fieldState.error?.message}
|
||||
/>
|
||||
)}
|
||||
disabled={isPasswordInputDisabled('CALDAV')}
|
||||
onUnlock={() =>
|
||||
setIsProtocolPasswordBeingEdited((prev) => ({
|
||||
...prev,
|
||||
CALDAV: true,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</StyledConnectionSection>
|
||||
</StyledFormContainer>
|
||||
|
||||
+10
-1
@@ -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 <NotFound />;
|
||||
}
|
||||
|
||||
const existingProtocols = ACCOUNT_TYPES.filter(
|
||||
(protocol) => connectedAccount?.connectionParameters?.[protocol]?.host,
|
||||
);
|
||||
|
||||
const renderForm = () => (
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
<FormProvider {...formMethods}>
|
||||
@@ -83,7 +88,11 @@ export const SettingsAccountsEditImapSmtpCaldavConnection = () => {
|
||||
}
|
||||
>
|
||||
<SettingsPageContainer>
|
||||
<SettingsAccountsConnectionForm control={control} isEditing />
|
||||
<SettingsAccountsConnectionForm
|
||||
control={control}
|
||||
isEditing
|
||||
existingProtocols={existingProtocols}
|
||||
/>
|
||||
</SettingsPageContainer>
|
||||
</SubMenuTopBarContainer>
|
||||
</FormProvider>
|
||||
|
||||
+64
@@ -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<ConnectionFormData>;
|
||||
disabled: boolean;
|
||||
onUnlock: () => void;
|
||||
};
|
||||
|
||||
export const SettingsAccountsPasswordController = ({
|
||||
protocol,
|
||||
label,
|
||||
control,
|
||||
disabled,
|
||||
onUnlock,
|
||||
}: SettingsAccountsPasswordControllerProps) => {
|
||||
return (
|
||||
<Controller
|
||||
name={`${protocol}.password`}
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<StyledPasswordFieldContainer>
|
||||
<SettingsTextInput
|
||||
instanceId={`${protocol.toLowerCase()}-password-connection-form`}
|
||||
label={label}
|
||||
placeholder={disabled ? MASKED_PASSWORD_PLACEHOLDER : ''}
|
||||
type={disabled ? 'text' : 'password'}
|
||||
value={field.value || ''}
|
||||
onChange={field.onChange}
|
||||
error={fieldState.error?.message}
|
||||
disabled={disabled}
|
||||
/>
|
||||
{disabled && (
|
||||
<StyledChangePasswordLink onClick={onUnlock}>
|
||||
<Trans>Change password</Trans>
|
||||
</StyledChangePasswordLink>
|
||||
)}
|
||||
</StyledPasswordFieldContainer>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export const ACCOUNT_PROTOCOLS = ['IMAP', 'SMTP', 'CALDAV'] as const;
|
||||
-2
@@ -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
|
||||
|
||||
-3
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-3
@@ -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
|
||||
|
||||
+40
-33
@@ -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<string, ConnectionParametersInput> = {
|
||||
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<string, ConnectionParameters> = {
|
||||
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<ConnectionFormData>({
|
||||
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<void> => {
|
||||
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<keyof ImapSmtpCaldavAccount, ConnectionParameters>
|
||||
Record<keyof ImapSmtpCaldavAccountInput, ConnectionParametersInput>
|
||||
> = {};
|
||||
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,
|
||||
|
||||
@@ -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());
|
||||
};
|
||||
+31
-8
@@ -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',
|
||||
},
|
||||
);
|
||||
|
||||
+1
-1
@@ -137,7 +137,7 @@ describe('SettingsApplicationConnectionDetail', () => {
|
||||
mockedUseMyAppConnectedAccounts.mockReturnValue({
|
||||
accounts: [
|
||||
{
|
||||
__typename: 'ConnectedAccountDTO',
|
||||
__typename: 'ConnectedAccountPublicDTO',
|
||||
id: 'account-1',
|
||||
handle: 'workspace@example.com',
|
||||
provider: 'app',
|
||||
|
||||
+1
-1
@@ -66,7 +66,7 @@ describe('SettingsApplicationConnectionsSection', () => {
|
||||
mockedUseMyAppConnectedAccounts.mockReturnValue({
|
||||
accounts: [
|
||||
{
|
||||
__typename: 'ConnectedAccountDTO',
|
||||
__typename: 'ConnectedAccountPublicDTO',
|
||||
id: 'account-1',
|
||||
handle: 'workspace@example.com',
|
||||
provider: 'app',
|
||||
|
||||
@@ -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
|
||||
|
||||
+133
@@ -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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."connectedAccount"
|
||||
DROP CONSTRAINT IF EXISTS "${CHECK_CONSTRAINT_NAME}"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
+2
@@ -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,
|
||||
|
||||
+29
-3
@@ -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;
|
||||
}
|
||||
|
||||
-72
@@ -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;
|
||||
}
|
||||
+37
@@ -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;
|
||||
}
|
||||
+4
-3
@@ -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,
|
||||
|
||||
+59
-53
@@ -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<ConnectedImapSmtpCaldavAccountDTO> {
|
||||
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<ImapSmtpCaldavConnectionSuccessDTO> {
|
||||
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<EmailAccountConnectionParameters> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
+8
@@ -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(),
|
||||
});
|
||||
+9
@@ -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(),
|
||||
});
|
||||
+7
-5
@@ -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,
|
||||
|
||||
+53
-46
@@ -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<ConnectionParameters> {
|
||||
async validateProtocolConnectionParams({
|
||||
params,
|
||||
existingProtocolParams,
|
||||
}: {
|
||||
params: ConnectionParametersInput;
|
||||
existingProtocolParams: ConnectionParameters | null;
|
||||
}): Promise<ConnectionParameters> {
|
||||
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 };
|
||||
}
|
||||
}
|
||||
|
||||
+67
-47
@@ -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<ConnectedAccountEntity>,
|
||||
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<boolean> {
|
||||
if (accountType === 'IMAP') {
|
||||
return this.testImapConnection(handle, params);
|
||||
async testImapSmtpCaldav({
|
||||
handle,
|
||||
params,
|
||||
accountType,
|
||||
}: {
|
||||
handle: string;
|
||||
params: ConnectionParameters;
|
||||
accountType: AccountType;
|
||||
}): Promise<boolean> {
|
||||
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<ConnectedAccountEntity | null> {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
async validateAndTestConnectionParameters({
|
||||
connectionParameters,
|
||||
handle,
|
||||
existingConnectionParameters,
|
||||
}: {
|
||||
connectionParameters: EmailAccountConnectionParametersInput;
|
||||
handle: string;
|
||||
existingConnectionParameters: ImapSmtpCaldavParams | null;
|
||||
}): Promise<ImapSmtpCaldavParams> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
+12
-8
@@ -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<typeof connectionParametersSchema>;
|
||||
|
||||
export type ConnectionParametersUpdate = z.infer<
|
||||
typeof connectionParametersUpdateSchema
|
||||
>;
|
||||
|
||||
export type AccountType = (typeof ACCOUNT_TYPES)[number];
|
||||
|
||||
export type ImapSmtpCaldavParams = {
|
||||
IMAP?: ConnectionParameters;
|
||||
|
||||
+42
@@ -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<NonNullable<PublicConnectionParameters>>(
|
||||
(result, protocol) => {
|
||||
const params = connectionParameters[protocol];
|
||||
|
||||
if (!isDefined(params)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const { password: _, ...publicParams } = params;
|
||||
|
||||
result[protocol] = publicParams;
|
||||
|
||||
return result;
|
||||
},
|
||||
{},
|
||||
);
|
||||
};
|
||||
@@ -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:
|
||||
|
||||
+14
@@ -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<ConnectedAccountEntity | null> {
|
||||
return this.repository.findOne({
|
||||
where: { id, userWorkspaceId, workspaceId },
|
||||
});
|
||||
}
|
||||
|
||||
async findByIds({
|
||||
ids,
|
||||
workspaceId,
|
||||
|
||||
+13
-4
@@ -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 {
|
||||
|
||||
+3
-4
@@ -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()
|
||||
|
||||
+8
@@ -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;
|
||||
|
||||
+6
-6
@@ -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<ConnectedAccountDTO[]> {
|
||||
): Promise<ConnectedAccountPublicDTO[]> {
|
||||
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<ConnectedAccountDTO[]> {
|
||||
): Promise<ConnectedAccountPublicDTO[]> {
|
||||
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<ConnectedAccountDTO> {
|
||||
): Promise<ConnectedAccountPublicDTO> {
|
||||
await this.connectedAccountMetadataService.verifyOwnership({
|
||||
id,
|
||||
userWorkspaceId,
|
||||
|
||||
+88
-16
@@ -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,
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+6
-1
@@ -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,
|
||||
|
||||
+11
-10
@@ -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<GetCalendarEventsResponse> {
|
||||
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,
|
||||
|
||||
@@ -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,
|
||||
],
|
||||
|
||||
+80
-26
@@ -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,
|
||||
|
||||
+138
-190
@@ -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<CalendarChannelEntity>,
|
||||
@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<ConnectedAccountEntity | null> {
|
||||
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<string> {
|
||||
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<WorkspaceMemberWorkspaceEntity>(
|
||||
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<MessagingMessageListFetchJobData>(
|
||||
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<CalendarEventListFetchJobData>(
|
||||
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<MessagingMessageListFetchJobData>(
|
||||
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<CalendarEventListFetchJobData>(
|
||||
CalendarEventListFetchJob.name,
|
||||
{ workspaceId, calendarChannelId: existingCalendarChannel.id },
|
||||
);
|
||||
}
|
||||
|
||||
return newOrExistingAccountId;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -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<MessageChannelEntity, 'messageFolderImportPolicy'> =
|
||||
|
||||
+1
-1
@@ -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<MessageChannelEntity, 'messageFolderImportPolicy'>,
|
||||
): Promise<DiscoveredMessageFolder[]> {
|
||||
|
||||
+2
@@ -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,
|
||||
],
|
||||
|
||||
+15
-22
@@ -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: {
|
||||
|
||||
+6
-1
@@ -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()
|
||||
|
||||
+2
-1
@@ -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],
|
||||
})
|
||||
|
||||
+17
-6
@@ -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<Transporter> {
|
||||
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);
|
||||
|
||||
|
||||
+67
@@ -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",
|
||||
}
|
||||
`;
|
||||
+145
@@ -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<TestContext>[];
|
||||
|
||||
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,
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
+237
@@ -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<string> => {
|
||||
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,
|
||||
});
|
||||
});
|
||||
});
|
||||
+31
@@ -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 };
|
||||
};
|
||||
+46
@@ -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;
|
||||
};
|
||||
+72
@@ -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 };
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
export const ACCOUNT_TYPES = ['IMAP', 'SMTP', 'CALDAV'] as const;
|
||||
|
||||
export type AccountType = (typeof ACCOUNT_TYPES)[number];
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user