Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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,45 @@
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 ISendMailArgs {
integrationId: string;
conversationId?: string;
subject: string;
body: string;
from: string;
customerId?: string;
to: string[];
attachments?: IIMapAttachmentParams[];
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