Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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 { IIMapIntegrationDocument } from '@/integrations/imap/@types/integrations';
import { IIMapCustomerDocument } from '@/integrations/imap/@types/customers';
import { IIMapMessageDocument } 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 {
IIMapCustomerModel,
loadImapCustomerClass,
} from '@/integrations/imap/db/models/Customers';
import {
IIMapIntegrationModel,
loadImapIntegrationClass,
} from '@/integrations/imap/db/models/Integrations';
import {
IIMapMessageModel,
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: IIMapCustomerModel;
ImapIntegrations: IIMapIntegrationModel;
ImapMessages: IIMapMessageModel;
}

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<IIMapCustomerDocument, IIMapCustomerModel>(
'imap_customers',
loadImapCustomerClass(models),
);
models.ImapIntegrations = db.model<IIMapIntegrationDocument, IIMapIntegrationModel>(
'imap_integrations',
loadImapIntegrationClass(models),
);

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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Document } from 'mongoose';
export interface IIMapCustomer {
inboxIntegrationId: string;
contactsId: string;
email: string;
firstName?: string;
lastName?: string;
integrationId?: string;
}

export interface IIMapCustomerDocument extends IIMapCustomer, Document {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Document } from 'mongoose';
export interface IIMapIntegration {
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 IIMapIntegrationDocument extends IIMapIntegration, Document {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Document } from 'mongoose';

interface IIMapMail {
name: string;
address: string;
}
export interface IIMapAttachmentParams {
filename: string;
size: number;
mimeType: string;
data?: string;
attachmentId?: string;
}
export interface IIMapMessage {
inboxIntegrationId: string;
inboxConversationId: string;
messageId: string;
subject: string;
body: string;
to: IIMapMail[];
cc: IIMapMail[];
bcc: IIMapMail[];
from: IIMapMail[];
attachments?: IIMapAttachmentParams[];
createdAt: Date;
}

export interface ImapSendMailInput {
to: string | string[];
subject: string;
body:string;
text: string;
html?: string;
from?: string;
conversationId?: string;
integrationId?: string;
customerId?: string;
attachments?: Array<{name: string; url: string; type?: string; size?: number}>;
replyToMessageId?: string;
shouldOpen?: boolean;
shouldResolve?: boolean;
cc?: string[];
bcc?: string[];
replyTo?: string;
}

export interface IIMapMessageDocument extends IIMapMessage, 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: { type: String, required: true },
contactsId:{ type: String},
email: { type: String, unique: true },
firstName: { type: String },
lastName: { type: String },
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Schema } from 'mongoose';
export const integrationSchema = new Schema({
inboxId: { type: String, required: true },
host: { type: String, required: true },
smtpHost: { type: String, required: true },
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: { type: String, required: true },
user: { type: String, required: true },
password: { type: String, required: true },
healthStatus: String,
error: String,
lastFetchDate: Date,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Schema } from 'mongoose';
export const attachmentSchema = new Schema({
filename: { type: String, required: true },
mimeType: { type: String, required: true },
type: { type: String, required: true },
size: {
type: Number,
required: true,
min: 1,
validate: {
validator: Number.isFinite,
message: 'Size must be a valid number'
}
},
attachmentId: { type: String, required: true }
}, { _id: false });

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

export const messageSchema = new Schema({
inboxIntegrationId: { type: String, required: true },
inboxConversationId: { type: String, required: true },
subject: { type: String, required: true },
messageId: { type: String, required: true, unique: true },
inReplyTo: { type: String },
references: [String],
body: { type: String, required: true },
to: { type: [emailSchema], required: true },
cc: [emailSchema],
bcc: [emailSchema],
from: { type: [emailSchema], required: true },
attachments: [attachmentSchema],
createdAt: { type: Date, index: true, default: Date.now },
type: {
type: String,
enum: ['SENT', 'INBOX'],
required: true
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Model } from 'mongoose';
import {IIMapCustomerDocument} from '@/integrations/imap/@types/customers';
import { customerSchema } from '@/integrations/imap/db/definitions/customers'
export type IIMapCustomerModel = Model<IIMapCustomerDocument>;


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 { IIMapIntegrationDocument} from '@/integrations/imap/@types/integrations';
import { integrationSchema } from '@/integrations/imap/db/definitions/integrations'
export type IIMapIntegrationModel = Model<IIMapIntegrationDocument>;

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

integrationSchema.loadClass(Integration);

return integrationSchema;
};
Loading