Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions backend/plugins/frontline_api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"erxes-api-shared": "workspace:^",
"fbgraph": "^1.4.4",
"moment-timezone": "^0.5.48",
"nodemailer": "^7.0.3",
"strip": "^3.0.0"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import { channelMutations } from '@/inbox/graphql/resolvers/mutations/channels';
import { conversationMutations } from '@/inbox/graphql/resolvers/mutations/conversations';
import { integrationMutations } from '@/inbox/graphql/resolvers/mutations/integrations';
import { facebookMutations } from '@/integrations/facebook/graphql/resolvers/mutations';

import { imapMutations } from '@/integrations/imap/graphql/resolvers/mutations';
export const mutations = {
...channelMutations,
...conversationMutations,
...integrationMutations,
...facebookMutations
...facebookMutations,
...imapMutations
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import { channelQueries } from '@/inbox/graphql/resolvers/queries/channels';
import { conversationQueries } from '@/inbox/graphql/resolvers/queries/conversations';
import { integrationQueries } from '@/inbox/graphql/resolvers/queries/integrations';
import { facebookQueries } from '@/integrations/facebook/graphql/resolvers/queries';
import { imapQueries } from '@/integrations/imap/graphql/resolvers/queries';

export const queries = {
...channelQueries,
...conversationQueries,
...integrationQueries,
...facebookQueries
...facebookQueries,
...imapQueries
};
10 changes: 10 additions & 0 deletions backend/plugins/frontline_api/src/apollo/schema/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,34 @@ import {
types as FacebookTypes,
} from '@/integrations/facebook/graphql/schema/facebook';


import {
mutations as IMapMutations,
queries as IMapQueries,
types as IMapTypes,
} from '@/integrations/imap/graphql/schemas';

export const types = `
${TypeExtensions}
${ChannelsTypes}
${ConversationsTypes}
${IntegrationsTypes}
${FacebookTypes}
${IMapTypes}
`;
export const queries = `
${ChannelsQueries}
${ConversationsQueries}
${IntegrationsQueries}
${FacebookQueries}
${IMapQueries}
`;

export const mutations = `
${ChannelsMutations}
${ConversationsMutations}
${IntegrationsMutations}
${FacebookMutations}
${IMapMutations}
`;
export default { types, queries, mutations };
33 changes: 32 additions & 1 deletion backend/plugins/frontline_api/src/connectionResolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import { IIntegrationDocument } from '@/inbox/@types/integrations';
import { IConversationDocument } from '@/inbox/@types/conversations';
import { IMessageDocument } from '@/inbox/@types/conversationMessages';
import { IFacebookIntegrationDocument } from '@/integrations/facebook/@types/integrations';
import { IMapIntegrationDocument } from '@/integrations/imap/@types/integrations';
import { IMapCustomerDocument } from '@/integrations/imap/@types/customers';
import { IMapMessageDocument } from '@/integrations/imap/@types/messages';
import { IFacebookLogDocument } from '@/integrations/facebook/@types/logs';
import { IFacebookAccountDocument } from '@/integrations/facebook/@types/accounts';
import { IFacebookCustomerDocument } from '@/integrations/facebook/@types/customers';
Expand Down Expand Up @@ -68,7 +71,18 @@ import {
IFacebookConfigModel,
loadFacebookConfigClass,
} from '@/integrations/facebook/db/models/Config';

import {
IMapCustomerModel,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: The interface names for the imap models (e.g. IMapCustomerModel) use Map instead of Imap. For consistency with the filename paths and loader functions (loadImapCustomerClass), consider renaming these to IImapCustomerModel, IImapIntegrationModel, and IImapMessageModel.

loadImapCustomerClass,
} from '@/integrations/imap/db/models/Customers';
import {
IMapIntegrationModel,
loadImapIntegrationClass,
} from '@/integrations/imap/db/models/Integrations';
import {
IMapMessageModel,
loadImapMessageClass,
} from '@/integrations/imap/db/models/Messages';
export interface IModels {
Channels: IChannelModel;
Integrations: IIntegrationModel;
Expand All @@ -84,6 +98,9 @@ export interface IModels {
FacebookLogs: IFacebookLogModel;
FacebookPostConversations: IFacebookPostConversationModel;
FacebookConfigs: IFacebookConfigModel;
ImapCustomers: IMapCustomerModel;
ImapIntegrations: IMapIntegrationModel;
ImapMessages: IMapMessageModel;
}

export interface IContext extends IMainContext {
Expand Down Expand Up @@ -163,6 +180,20 @@ export const loadClasses = (
IFacebookConfigDocument,
IFacebookConfigModel
>('facebook_configs', loadFacebookConfigClass(models));

models.ImapCustomers = db.model<IMapCustomerDocument, IMapCustomerModel>(
'imap_customers',
loadImapCustomerClass(models),
);
models.ImapIntegrations = db.model<IMapIntegrationDocument, IMapIntegrationModel>(
'imap_integrations',
loadImapIntegrationClass(models),
);

models.ImapMessages = db.model<IMapMessageDocument, IMapMessageModel>(
'imap_messages',
loadImapMessageClass(models),
);
return models;
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import Document from mongoose to type IMapCustomerDocument.

export interface IMapCustomer {
inboxIntegrationId: string;
contactsId: string;
email: string;
firstName?: string;
lastName?: string;
integrationId?: string;
}

export interface IMapCustomerDocument extends IMapCustomer, Document {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export interface IMapIntegration {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import Document from mongoose to properly type IMapIntegrationDocument.

inboxId: string;
host: string;
smtpHost: string;
smtpPort: string;
mainUser: string;
user: string;
password: string;
Comment on lines +6 to +9
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Store credentials securely

The interface stores sensitive data like password as plain text. This could pose a security risk if database records are exposed.

Consider implementing credential encryption at rest. One approach is to use environment-specific encryption keys to encrypt sensitive fields before storage and decrypt when needed.


🏁 Script executed:

#!/bin/bash
# Check if other sensitive fields are stored securely in the codebase

echo "Checking for credential storage patterns in the codebase..."
rg -i "password.*string|secret.*string|token.*string" --type ts

Length of output: 16805


🏁 Script executed:

#!/bin/bash
set -e

echo "Inspecting IMAP integration DB definitions around 'password'..."
rg -A 5 -n "password" backend/plugins/frontline_api/src/modules/integrations/imap/db/definitions/integrations.ts

echo
echo "Searching for any encryption-related code or libraries in the repo..."
rg -i --context 3 "encrypt" .

Length of output: 2149


Encrypt IMAP integration credentials at rest

The IMAP integration’s password (and similar sensitive fields) are currently defined and stored as plain strings with no encryption layer applied:

• backend/plugins/frontline_api/src/modules/integrations/imap/@types/integrations.ts

  password: string;

• backend/plugins/frontline_api/src/modules/integrations/imap/db/definitions/integrations.ts

  password: String,

Our searches (rg -i encrypt) show no encryption implementation in the repo beyond lockfile entries and a frontend ENCRYPTION_KEY constant. To protect these credentials:

  • Adopt field-level encryption, for example:
    – Mongoose plugins (e.g., mongoose-field-encryption)
    – MongoDB Client-Side Field Level Encryption (mongodb-client-encryption)
  • Store and rotate encryption keys via secured environment variables (e.g., ENCRYPTION_KEY).
  • Encrypt IMAP credentials before persisting and decrypt on read in the integration service.
  • Cover key-rotation, error handling, and decryption-failure scenarios in tests and docs.
🤖 Prompt for AI Agents
In
backend/plugins/frontline_api/src/modules/integrations/imap/@types/integrations.ts
around lines 5 to 8, the password and other sensitive fields are currently typed
as plain strings, which means they are stored without encryption. To fix this,
implement field-level encryption for these sensitive fields by integrating an
encryption mechanism such as a Mongoose encryption plugin or MongoDB client-side
field-level encryption. Modify the data handling logic to encrypt these fields
before saving to the database and decrypt them when reading, using
environment-secured encryption keys. Also, ensure to handle key rotation, error
cases, and decryption failures appropriately in your service and tests.

healthStatus?: string;
error?: string;
lastFetchDate?: Date;
}

export interface IMapIntegrationDocument extends IMapIntegration, Document {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Document } from 'mongoose';

interface IMapMail {
name: string;
address: string;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add export to IMapMail interface

The IMapMail interface is used by exported interfaces but isn't exported itself, potentially causing issues if it's needed in other files.

-interface IMapMail {
+export interface IMapMail {
  name: string;
  address: string;
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
interface IMapMail {
name: string;
address: string;
}
export interface IMapMail {
name: string;
address: string;
}
🤖 Prompt for AI Agents
In
backend/plugins/frontline_api/src/modules/integrations/imap/@types/messages.ts
around lines 3 to 6, the IMapMail interface is declared but not exported. To fix
this, add the export keyword before the interface declaration so it can be used
in other files that import it.

export interface IMapAttachmentParams {
filename: string;
size: number;
mimeType: string;
data?: string;
attachmentId?: string;
}
export interface IMapMessage {
inboxIntegrationId: string;
inboxConversationId: string;
messageId: string;
subject: string;
body: string;
to: IMapMail[];
cc: IMapMail[];
bcc: IMapMail[];
from: IMapMail[];
attachments?: IMapAttachmentParams[];
createdAt: Date;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add inReplyTo and references fields to match schema

The IMapMessage interface is missing the inReplyTo and references fields that exist in the Mongoose schema. This could cause inconsistency between the TypeScript interface and the database schema.

 export interface IMapMessage {
   inboxIntegrationId: string;
   inboxConversationId: string;
   messageId: string;
+  inReplyTo?: string;
+  references?: string[];
   subject: string;
   body: string;
   to: IMapMail[];
   cc: IMapMail[];
   bcc: IMapMail[];
   from: IMapMail[];
   attachments?: IMapAttachmentParams[];
   createdAt: Date;
+  type: 'SENT' | 'INBOX';
 }

The type field is also missing, which is defined in the schema with an enum restriction to 'SENT' or 'INBOX'.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export interface IMapMessage {
inboxIntegrationId: string;
inboxConversationId: string;
messageId: string;
subject: string;
body: string;
to: IMapMail[];
cc: IMapMail[];
bcc: IMapMail[];
from: IMapMail[];
attachments?: IMapAttachmentParams[];
createdAt: Date;
}
export interface IMapMessage {
inboxIntegrationId: string;
inboxConversationId: string;
messageId: string;
inReplyTo?: string;
references?: string[];
subject: string;
body: string;
to: IMapMail[];
cc: IMapMail[];
bcc: IMapMail[];
from: IMapMail[];
attachments?: IMapAttachmentParams[];
createdAt: Date;
type: 'SENT' | 'INBOX';
}
🤖 Prompt for AI Agents
In
backend/plugins/frontline_api/src/modules/integrations/imap/@types/messages.ts
between lines 14 and 26, the IMapMessage interface lacks the inReplyTo and
references fields present in the Mongoose schema, causing inconsistency.
Additionally, the type field with enum values 'SENT' or 'INBOX' is missing. To
fix this, add the inReplyTo and references fields with appropriate types (likely
string or string array) and include the type field as a string enum restricted
to 'SENT' or 'INBOX' to align the interface with the schema.


export interface IMapMessageDocument extends IMapMessage, Document {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

import { Schema } from 'mongoose';
export const customerSchema = new Schema({
inboxIntegrationId: String,
contactsId: String,
email: { type: String, unique: true },
firstName: String,
lastName: String
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Schema } from 'mongoose';
export const integrationSchema = new Schema({
inboxId: String,
host: String,
smtpHost: String,
smtpPort: String,
mainUser: String,
user: String,
password: String,
healthStatus: String,
error: String,
lastFetchDate: Date
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Security concern: password stored as plain text

The integration schema stores the password as plain text, which is a security risk. Consider:

  1. Encrypting the password field
  2. Making critical fields required
  3. Adding validation for fields like smtpPort
  4. Including timestamps for tracking
export const integrationSchema = new Schema({
-  inboxId: String,
+  inboxId: { type: String, required: true },
-  host: String,
+  host: { type: String, required: true },
-  smtpHost: String,
+  smtpHost: { type: String, required: true },
-  smtpPort: String,
+  smtpPort: { 
+    type: String, 
+    required: true,
+    validate: {
+      validator: (v: string) => /^\d+$/.test(v) && parseInt(v) > 0 && parseInt(v) <= 65535,
+      message: 'smtpPort must be a valid port number'
+    }
+  },
  mainUser: String,
-  user: String,
+  user: { type: String, required: true },
-  password: String,
+  password: { type: String, required: true },
  healthStatus: String,
  error: String,
  lastFetchDate: Date
+}, {
+  timestamps: true
});

Additionally, consider implementing password encryption either through:

  1. A pre-save hook that encrypts the password
  2. A separate service for securely managing credentials
🤖 Prompt for AI Agents
In
backend/plugins/frontline_api/src/modules/integrations/imap/db/definitions/integrations.ts
lines 2 to 13, the password field is stored as plain text, posing a security
risk. To fix this, implement encryption for the password field using a pre-save
hook in the schema that encrypts the password before saving. Also, update the
schema to make critical fields like password and smtpPort required, add
validation to smtpPort to ensure it is a valid number, and enable timestamps in
the schema options to track creation and update times.

Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Schema } from 'mongoose';
export const attachmentSchema = new Schema(
{
filename: String,
mimeType: String,
type: String,
size: Number,
attachmentId: String
},
{ _id: false }
);

const emailSchema = new Schema(
{
name: String,
address: String
},
{ _id: false }
);

export const messageSchema = new Schema({
inboxIntegrationId: String,
inboxConversationId: String,
subject: String,
messageId: { type: String, unique: true },
inReplyTo: String,
references: [String],
body: String,
to: [emailSchema],
cc: [emailSchema],
bcc: [emailSchema],
from: [emailSchema],
attachments: [attachmentSchema],
createdAt: { type: Date, index: true, default: new Date() },
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use Date.now (or a function) for the default value of createdAt instead of new Date(), to ensure the timestamp is set at document creation.

Suggested change
createdAt: { type: Date, index: true, default: new Date() },
createdAt: { type: Date, index: true, default: Date.now },

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick (bug_risk): Use Date.now instead of new Date() for default

default: new Date() sets the same timestamp for all documents. Use default: Date.now to assign the creation time when each document is created.

type: { type: String, enum: ['SENT', 'INBOX'] }
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Model } from 'mongoose';
import {IMapCustomerDocument} from '@/integrations/imap/@types/customers';
import { customerSchema } from '@/integrations/imap/db/definitions/customers'
export type IMapCustomerModel = Model<IMapCustomerDocument>;


export const loadImapCustomerClass = (models) => {
class Customer {}

customerSchema.loadClass(Customer);

return customerSchema;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Model } from 'mongoose';
import { IMapIntegrationDocument} from '@/integrations/imap/@types/integrations';
import { integrationSchema } from '@/integrations/imap/db/definitions/integrations'
export type IMapIntegrationModel = Model<IMapIntegrationDocument>;

export const loadImapIntegrationClass = (models) => {
class Integration {}

integrationSchema.loadClass(Integration);

return integrationSchema;
};
Loading