Skip to content

Commit

Permalink
fix: touch up admin inquiries and add gaurdrails for inquiries api
Browse files Browse the repository at this point in the history
  • Loading branch information
shaunwarman committed Aug 22, 2024
1 parent 12c253b commit 3b190d0
Show file tree
Hide file tree
Showing 10 changed files with 183 additions and 94 deletions.
38 changes: 38 additions & 0 deletions app/controllers/api/v1/inquiries.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
const Boom = require('@hapi/boom');
const isSANB = require('is-string-and-not-blank');
const { isEmail } = require('validator');
const { simpleParser } = require('mailparser');

const config = require('#config');
const env = require('#config/env');
Expand All @@ -22,6 +23,7 @@ function findHeaderByName(name, headers) {
return null;
}

// eslint-disable-next-line complexity
async function create(ctx) {
const { body } = ctx.request;

Expand All @@ -37,6 +39,14 @@ async function create(ctx) {
Boom.forbidden(ctx.translateError('INVALID_INQUIRY_WEBHOOK_REQUEST'))
);

let parsed;
try {
parsed = await simpleParser(body.raw);
} catch (err) {
ctx.logger.error(err);
return ctx.throw(err);
}

const { headerLines, session, text } = body;
if (!session)
return ctx.throw(
Expand All @@ -48,6 +58,34 @@ async function create(ctx) {
Boom.badRequest(ctx.translateError('INVALID_INQUIRY_WEBHOOK_PAYLOAD'))
);

if (
(parsed.headers.has('Auto-submitted') &&
parsed.headers.get('Auto-submitted') !== 'no') ||
(parsed.headers.has('Auto-Submitted') &&
parsed.headers.get('Auto-Submitted') !== 'no') ||
(parsed.headers.has('X-Auto-Response-Suppress') &&
['dr', 'autoreply', 'auto-reply', 'auto_reply', 'all'].includes(
parsed.headers.get('X-Auto-Response-Suppress').toLowerCase().trim()
)) ||
parsed.headers.has('List-Id') ||
parsed.headers.has('List-id') ||
parsed.headers.has('List-Unsubscribe') ||
parsed.headers.has('List-unsubscribe') ||
parsed.headers.has('Feedback-ID') ||
parsed.headers.has('Feedback-Id') ||
parsed.headers.has('X-Autoreply') ||
parsed.headers.has('X-Auto-Reply') ||
parsed.headers.has('X-AutoReply') ||
parsed.headers.has('X-Autorespond') ||
parsed.headers.has('X-Auto-Respond') ||
parsed.headers.has('X-AutoRespond') ||
(parsed.headers.has('Precedence') &&
['bulk', 'autoreply', 'auto-reply', 'auto_reply', 'list'].includes(
parsed.headers.get('Precedence').toLowerCase().trim()
))
)
return;

const { recipient, sender } = session;

if (!recipient.includes('support@'))
Expand Down
22 changes: 14 additions & 8 deletions app/controllers/web/admin/inquiries.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

const { Buffer } = require('node:buffer');
const path = require('node:path');
const process = require('node:process');
const getStream = require('get-stream');
const _ = require('lodash');
const Boom = require('@hapi/boom');
Expand Down Expand Up @@ -123,7 +122,9 @@ async function list(ctx) {
updated_at: 1,
reference: 1,
email: 1,
plan: 1
plan: 1,
is_resolved: 1,
is_denylist: 1
}
},
{ $match: query },
Expand Down Expand Up @@ -162,17 +163,21 @@ async function list(ctx) {

async function retrieve(ctx) {
const emailTemplatePath = path.join(
process.cwd(),
'app/views/admin/inquiries/custom-email-previews.pug'
config.views.root,
'admin/inquiries/custom-email-previews.pug'
);

ctx.state.result = await Inquiries.findById(ctx.params.id);

ctx.state.messages = await Promise.all(
ctx.state.result.messages.map(async (message) => {
message.html = await previewEmail(message.raw, {
template: emailTemplatePath,
...config.previewEmailOptions
});
if (message.raw) {
message.html = await previewEmail(message.raw, {
template: emailTemplatePath,
...config.previewEmailOptions
});
}

return message;
})
);
Expand Down Expand Up @@ -318,6 +323,7 @@ async function bulkReply(ctx) {

// eslint-disable-next-line no-await-in-loop
const raw = await transporter.sendMail(info?.originalMessage);

inquiry.messages.push({ raw: raw.message });
// eslint-disable-next-line no-await-in-loop
await inquiry.save();
Expand Down
10 changes: 7 additions & 3 deletions app/views/admin/inquiries/_table.pug
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ include ../../_pagination
tr
td.align-middle.text-center
.form-group.form-check.form-check-inline.mb-0
input#is-inquiry-selected.form-check-input(
input.form-check-input(
type="checkbox",
name="is_inquiry_selected",
value=inquiry.id
value=inquiry.id,
data-email=inquiry.email
)
td.align-middle
a(
Expand Down Expand Up @@ -81,9 +82,12 @@ include ../../_pagination
button.close(type="button", data-dismiss="modal", aria-label="Close")
span(aria-hidden="true") ×
.modal-body
.form-group
h5= t("Replying to")
#modal-reply-to-email-list
.text-center
form.form-group
label(for="bulk-reply-message")
label(for="textarea-bulk-reply-message")
h5= t("Message")
= " "
textarea#textarea-bulk-reply-message.form-control(
Expand Down
11 changes: 1 addition & 10 deletions app/views/admin/inquiries/custom-email-previews.pug
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,6 @@ html
min-height: 200px;
display: block;
}
.iframe-container {
width: 100%;
overflow-x: auto;
}
.iframe-content {
width: 1200px;
}

.preview-email-tabs {
display: flex;
flex-wrap: wrap;
Expand Down Expand Up @@ -125,8 +117,7 @@ html
else
code= "Unnamed file"
.preview-email-tabs
.iframe-container
iframe#html.iframe-content(
iframe#html(
sandbox="",
referrerpolicy="no-referrer",
seamless="seamless",
Expand Down
8 changes: 8 additions & 0 deletions app/views/admin/inquiries/index.pug
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
extends ../../layout

block append scripts
script(
defer,
src=manifest("js/inquiries.js"),
integrity=manifest("js/inquiries.js", "integrity"),
crossorigin="anonymous"
)

block body
.container-fluid.py-3
.row.mt-1
Expand Down
25 changes: 12 additions & 13 deletions app/views/admin/inquiries/retrieve.pug
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,17 @@ block body
.row.mt-1
.col
include ../../_breadcrumbs
.threaded-messages
each message in messages
- const isSenderEmail = message?.sender_email?.includes("support@");
iframe.bg-white.border-dark(
sandbox="allow-downloads allow-scripts",
referrerpolicy="no-referrer",
seamless="seamless",
srcdoc=`<base target='_top'>${message.html}`,
height="400px",
width="100%"
)
hr
each message in messages
- const isSenderEmail = message?.sender_email?.includes("support@");
iframe.bg-white.border-dark(
sandbox="allow-downloads allow-scripts",
referrerpolicy="no-referrer",
seamless="seamless",
srcdoc=`<base target='_top'>${message.html}`,
height="400px",
width="100%"
)
hr
form.confirm-prompt(
action=ctx.path,
method="POST",
Expand All @@ -25,7 +24,7 @@ block body
.card.border-themed.card-custom
.card-body
.form-group.floating-label
label.read-only-message.text-muted= result.message
label.read-only-message.text-muted= result.message || result.text
.form-group.floating-label
textarea#input-message.form-control(
rows="8",
Expand Down
14 changes: 14 additions & 0 deletions assets/css/_custom.scss
Original file line number Diff line number Diff line change
Expand Up @@ -359,3 +359,17 @@ a, .markdown-body a {
40% {transform: translateY(-20px);}
60% {transform: translateY(-10px);}
}

#modal-reply-to-email-list {
list-style-type: none;
padding: 0;
}

#modal-reply-to-email-list li {
padding: 8px 12px;
border-bottom: 1px solid #ccc;
}

#modal-reply-to-email-list li:last-child {
border-bottom: none;
}
57 changes: 0 additions & 57 deletions assets/js/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -842,60 +842,3 @@ window.addEventListener(
'resize',
setViewportProperty(document.documentElement)
);

function handleBulkReply() {
const checkboxes = $('#table-inquiries input[type="checkbox"]:checked');
const ids = checkboxes
.map(function () {
return $(this).val();
})
.get();

if (ids.length === 0) {
Swal.fire(window._types.error, 'No inquiries selected.', 'error');
return;
}

if (ids.length === 1) {
const { origin, pathname } = window.location;
const redirectUrl = `${origin}${pathname}/${ids[0]}`;
window.location.href = redirectUrl;
return;
}

$('#bulk-reply-modal').modal('show');
}

async function handleSubmitBulkReply() {
const checkboxes = $('#table-inquiries input[type="checkbox"]:checked');
const ids = checkboxes
.map(function () {
return $(this).val();
})
.get();

const message = $('#textarea-bulk-reply-message').val();

try {
spinner.show();

const url = `${window.location.pathname}/bulk`;
const response = await sendRequest({ ids, message }, url);

if (response.err) {
console.log('error in response', { response });
throw response.err;
}

spinner.hide();

location.reload(true);
} catch (err) {
console.error(err);
spinner.hide();
Swal.fire(window._types.error, err.message, 'error');
}
}

$('#table-inquiries').on('click', '#bulk-reply-button', handleBulkReply);
$('#table-inquiries').on('click', '#submit-bulk-reply', handleSubmitBulkReply);
83 changes: 83 additions & 0 deletions assets/js/inquiries.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* Copyright (c) Forward Email LLC
* SPDX-License-Identifier: BUSL-1.1
*/

const $ = require('jquery');
const Swal = require('sweetalert2/dist/sweetalert2.js');
const { spinner: Spinner } = require('@ladjs/assets');

const sendRequest = require('./send-request');

const spinner = new Spinner($);

function handleBulkReply() {
const checkboxes = $('#table-inquiries input[type="checkbox"]:checked');
const inquiries = checkboxes
.map(function () {
return { id: $(this).val(), email: $(this).data('email') };
})
.get();

const emails = inquiries.map((inquiry) => inquiry.email);
const uniqueEmails = [...new Set(emails)];

if (inquiries.length === 0) {
Swal.fire(window._types.error, 'No inquiries selected.', 'error');
return;
}

if (inquiries.length === 1) {
const { origin, pathname } = window.location;
const redirectUrl = `${origin}${pathname}/${inquiries[0].id}`;
window.location.href = redirectUrl;
return;
}

const $emailList = $('#modal-reply-to-email-list');
$emailList.empty();

for (const email of uniqueEmails) {
const listItem = $('<li class="list-group-item">').text(email);
$emailList.append(listItem);
}

$('#bulk-reply-modal').modal('show');
}

async function handleSubmitBulkReply() {
console.log('testing');
const checkboxes = $('#table-inquiries input[type="checkbox"]:checked');
const inquiries = checkboxes
.map(function () {
return { id: $(this).val(), email: $(this).data('email') };
})
.get();

const ids = inquiries.map((inquiry) => inquiry.id);

const message = $('#textarea-bulk-reply-message').val();

try {
spinner.show();

const url = `${window.location.pathname}/bulk`;
const response = await sendRequest({ ids, message }, url);

if (response.err) {
console.log('error in response', { response });
throw response.err;
}

spinner.hide();

location.reload(true);
} catch (err) {
console.error(err);
spinner.hide();
Swal.fire(window._types.error, err.message, 'error');
}
}

$('#table-inquiries').on('click', '#bulk-reply-button', handleBulkReply);
$('#submit-bulk-reply').on('click', handleSubmitBulkReply);
Loading

0 comments on commit 3b190d0

Please sign in to comment.