Skip to content

Commit

Permalink
feat(custom-account-headers): Allos setting account specific custom w…
Browse files Browse the repository at this point in the history
…ebhook headers
  • Loading branch information
andris9 committed Apr 10, 2024
1 parent 3faa977 commit f4c4c8b
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 16 deletions.
5 changes: 5 additions & 0 deletions lib/account.js
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,8 @@ class Account {
}
break;

// generic JSON values
case 'webhooksCustomHeaders':
case 'subconnections':
try {
let value = JSON.parse(accountData[key]);
Expand Down Expand Up @@ -351,7 +353,9 @@ class Account {
}
break;

// generic JSON values
case 'subconnections':
case 'webhooksCustomHeaders':
try {
result[key] = JSON.stringify(accountData[key]);
} catch (err) {
Expand Down Expand Up @@ -412,6 +416,7 @@ class Account {
}
break;

// boolean values
case 'copy':
case 'logs':
if (typeof accountData[key] === 'boolean') {
Expand Down
39 changes: 32 additions & 7 deletions lib/routes-ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -7217,19 +7217,20 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
throw error;
}

let values = Object.assign({}, flattenObjectKeys(accountData), {
const values = Object.assign({}, flattenObjectKeys(accountData), {
imap: true,
imap_disabled: (!accountData.imap && !accountData.oauth2) || (accountData.imap && accountData.imap.disabled),
smtp: !!accountData.smtp,
oauth2: !!accountData.oauth2,

imap_auth_pass: '',
smtp_auth_pass: ''
});
smtp_auth_pass: '',

if (values.path === '*') {
values.path = '';
}
customHeaders: []
.concat(accountData.webhooksCustomHeaders || [])
.map(entry => `${entry.key}: ${entry.value}`.trim())
.join('\n')
});

let mailboxes = await getMailboxListing(accountObject);

Expand Down Expand Up @@ -7289,6 +7290,24 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
webhooks: request.payload.webhooks
};

updates.webhooksCustomHeaders = request.payload.customHeaders
.split(/[\r\n]+/)
.map(header => header.trim())
.filter(header => header)
.map(line => {
let sep = line.indexOf(':');
if (sep >= 0) {
return {
key: line.substring(0, sep).trim(),
value: line.substring(sep + 1).trim()
};
}
return {
key: line,
value: ''
};
});

if (request.payload.imap) {
let imapAuth = Object.assign((oldData.imap && oldData.imap.auth) || {}, { user: request.payload.imap_auth_user });
let imapTls = (oldData.imap && oldData.imap.tls) || {};
Expand Down Expand Up @@ -7475,7 +7494,13 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
.falsy('N', 'false', 0, '')
.default(false)
.example(true)
.description('Should connection use TLS. Usually true for port 465')
.description('Should connection use TLS. Usually true for port 465'),

customHeaders: Joi.string()
.allow('')
.trim()
.max(10 * 1024)
.description('Custom request headers')
})
}
}
Expand Down
4 changes: 3 additions & 1 deletion lib/schemas.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,16 @@ const settingsSchema = {
webhooksCustomHeaders: Joi.array()
.items(
Joi.object({
key: Joi.string().trim().empty('').max(1024).required(),
key: Joi.string().trim().empty('').max(1024).required().example('Authorization'),
value: Joi.string()
.trim()
.empty('')
.max(10 * 1024)
.default('')
.example('Bearer <secret-token>')
}).label('WebhooksCustomHeader')
)
.description('Custom HTTP headers added to webhook requests')
.label('WebhooksCustomHeaders'),

notifyHeaders: Joi.array().items(Joi.string().max(256).example('List-ID')),
Expand Down
18 changes: 10 additions & 8 deletions views/accounts/edit.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -283,16 +283,18 @@
</div>

<div class="form-group">
<label for="path">Path to sync</label>
<input type="text" class="form-control basicAutoComplete {{#if errors.path}}is-invalid{{/if}}" id="path"
value="{{values.path}}" placeholder="Folder path like &quot;INBOX&quot;" name="path"
data-lpignore="true" autocomplete="off">
{{#if errors.path}}
<span class="invalid-feedback">{{errors.path}}</span>
<label for="customHeaders">List of custom webhook request headers</label>

<textarea class="form-control text-monospace {{#if errors.customHeaders}}is-invalid{{/if}}"
id="customHeaders" name="customHeaders" rows="5" spellcheck="false" data-enable-grammarly="false"
placeholder="Custom headers like &quot;Authorization: Bearer secret_token&quot;&mldr;">{{values.customHeaders}}</textarea>
{{#if errors.customHeaders}}
<span class="invalid-feedback">{{errors.customHeaders}}</span>
{{/if}}
<small id="pathHelpBlock" class="form-text text-muted">Set a specific folder path to sync. By
default all folders are synced. Leave it empty to keep the default.</small>
<small class="form-text text-muted">Keep one header per line. These headers will be added to the HTTP
request when posting webhooks for this email account.</small>
</div>

</div>
</div>

Expand Down
7 changes: 7 additions & 0 deletions workers/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -2151,6 +2151,8 @@ When making API calls remember that requests against the same account are queued

oauth2: Joi.object(oauth2Schema).allow(false).description('OAuth2 configuration').label('OAuth2'),

webhooksCustomHeaders: settingsSchema.webhooksCustomHeaders.label('AccountWebhooksCustomHeaders'),

locale: Joi.string().empty('').max(100).example('fr').description('Optional locale'),
tz: Joi.string().empty('').max(100).example('Europe/Tallinn').description('Optional timezone')
}).label('CreateAccount')
Expand Down Expand Up @@ -2402,6 +2404,8 @@ When making API calls remember that requests against the same account are queued
smtp: Joi.object(smtpUpdateSchema).allow(false).description('SMTP configuration').label('SMTPUpdate'),
oauth2: Joi.object(oauth2UpdateSchema).allow(false).description('OAuth2 configuration').label('OAuth2Update'),

webhooksCustomHeaders: settingsSchema.webhooksCustomHeaders.label('AccountWebhooksCustomHeaders'),

locale: Joi.string().empty('').max(100).example('fr').description('Optional locale'),
tz: Joi.string().empty('').max(100).example('Europe/Tallinn').description('Optional timezone')
}).label('UpdateAccount')
Expand Down Expand Up @@ -2851,6 +2855,7 @@ When making API calls remember that requests against the same account are queued
'smtpStatus',
'syncError',
'connections',
'webhooksCustomHeaders',
'locale',
'tz'
]) {
Expand Down Expand Up @@ -3003,6 +3008,8 @@ When making API calls remember that requests against the same account are queued
.description('Information about the last SMTP connection attempt')
.label('SMTPInfoStatus'),

webhooksCustomHeaders: settingsSchema.webhooksCustomHeaders.label('AccountWebhooksCustomHeaders'),

locale: Joi.string().empty('').max(100).example('fr').description('Optional locale'),
tz: Joi.string().empty('').max(100).example('Europe/Tallinn').description('Optional timezone'),

Expand Down
23 changes: 23 additions & 0 deletions workers/webhooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,23 @@ const notifyWorker = new Worker(
return;
}

let accountWebhooksCustomHeaders;
let accountWebhooksCustomHeadersJson = await redis.hget(accountKey, 'webhooksCustomHeaders');
if (accountWebhooksCustomHeadersJson) {
try {
accountWebhooksCustomHeaders = JSON.parse(accountWebhooksCustomHeadersJson);
} catch (err) {
logger.debug({
msg: 'Failed to parse custom webhook headers',
action: 'webhook',
event: job.name,
account: job.data.account,
json: accountWebhooksCustomHeadersJson,
err
});
}
}

if (!customRoute) {
// custom routes have their own mappings
let webhookEvents = (await settings.get('webhookEvents')) || [];
Expand Down Expand Up @@ -270,6 +287,12 @@ const notifyWorker = new Worker(
}
}

if (accountWebhooksCustomHeaders) {
for (let header of accountWebhooksCustomHeaders || []) {
headers[header.key] = header.value;
}
}

let start = Date.now();
let duration;

Expand Down

0 comments on commit f4c4c8b

Please sign in to comment.