2016-12-07 02:42:32 +08:00
|
|
|
const _ = require('underscore')
|
2016-12-06 09:59:47 +08:00
|
|
|
const Joi = require('joi');
|
2016-12-07 02:42:32 +08:00
|
|
|
const IMAPErrors = require('./imap-errors')
|
|
|
|
const IMAPConnection = require('./imap-connection')
|
2016-12-06 09:59:47 +08:00
|
|
|
|
|
|
|
const imapSmtpSettings = Joi.object().keys({
|
|
|
|
imap_host: [Joi.string().ip().required(), Joi.string().hostname().required()],
|
|
|
|
imap_port: Joi.number().integer().required(),
|
|
|
|
imap_username: Joi.string().required(),
|
|
|
|
imap_password: Joi.string().required(),
|
|
|
|
smtp_host: [Joi.string().ip().required(), Joi.string().hostname().required()],
|
|
|
|
smtp_port: Joi.number().integer().required(),
|
|
|
|
smtp_username: Joi.string().required(),
|
|
|
|
smtp_password: Joi.string().required(),
|
2017-01-15 06:46:54 +08:00
|
|
|
smtp_custom_config: Joi.object(),
|
2016-12-06 09:59:47 +08:00
|
|
|
ssl_required: Joi.boolean().required(),
|
|
|
|
}).required();
|
|
|
|
|
|
|
|
const resolvedGmailSettings = Joi.object().keys({
|
|
|
|
xoauth2: Joi.string().required(),
|
2016-12-17 03:38:45 +08:00
|
|
|
expiry_date: Joi.number().integer().required(),
|
2016-12-06 09:59:47 +08:00
|
|
|
}).required();
|
|
|
|
|
2016-12-17 05:53:05 +08:00
|
|
|
const office365Settings = Joi.object().keys({
|
|
|
|
name: Joi.string().required(),
|
|
|
|
type: Joi.string().valid('office365').required(),
|
|
|
|
email: Joi.string().required(),
|
2016-12-06 09:59:47 +08:00
|
|
|
password: Joi.string().required(),
|
2016-12-17 05:53:05 +08:00
|
|
|
username: Joi.string().required(),
|
2016-12-06 09:59:47 +08:00
|
|
|
}).required();
|
|
|
|
|
2016-12-08 02:10:34 +08:00
|
|
|
const USER_ERRORS = {
|
2016-12-17 03:38:45 +08:00
|
|
|
AUTH_500: "Please contact support@nylas.com. An unforeseen error has occurred.",
|
2016-12-08 02:10:34 +08:00
|
|
|
IMAP_AUTH: "Incorrect username or password",
|
|
|
|
IMAP_RETRY: "We were unable to reach your mail provider. Please try again.",
|
2017-01-25 10:48:24 +08:00
|
|
|
IMAP_CERT: "We couldn't make a secure connection to your mail provider. Please contact support@nylas.com.",
|
2016-12-08 02:10:34 +08:00
|
|
|
}
|
2016-12-07 06:53:24 +08:00
|
|
|
|
2017-01-15 09:24:13 +08:00
|
|
|
const SUPPORTED_PROVIDERS = new Set(
|
|
|
|
['gmail', 'office365', 'imap', 'icloud', 'yahoo', 'fastmail']
|
|
|
|
);
|
|
|
|
|
2016-12-17 05:53:05 +08:00
|
|
|
function credentialsForProvider({provider, settings, email}) {
|
|
|
|
if (provider === "gmail") {
|
|
|
|
const connectionSettings = {
|
|
|
|
imap_username: email,
|
|
|
|
imap_host: 'imap.gmail.com',
|
|
|
|
imap_port: 993,
|
|
|
|
smtp_username: email,
|
|
|
|
smtp_host: 'smtp.gmail.com',
|
|
|
|
smtp_port: 465,
|
|
|
|
ssl_required: true,
|
|
|
|
}
|
|
|
|
const connectionCredentials = {
|
|
|
|
xoauth2: settings.xoauth2,
|
|
|
|
expiry_date: settings.expiry_date,
|
|
|
|
}
|
|
|
|
return {connectionSettings, connectionCredentials}
|
|
|
|
} else if (provider === "office365") {
|
|
|
|
const connectionSettings = {
|
|
|
|
imap_host: 'outlook.office365.com',
|
|
|
|
imap_port: 993,
|
|
|
|
ssl_required: true,
|
2016-12-21 05:08:30 +08:00
|
|
|
smtp_custom_config: {
|
|
|
|
host: 'smtp.office365.com',
|
|
|
|
port: 587,
|
|
|
|
secure: false,
|
|
|
|
tls: {ciphers: 'SSLv3'},
|
|
|
|
},
|
2016-12-17 05:53:05 +08:00
|
|
|
}
|
2017-01-15 09:24:13 +08:00
|
|
|
|
2016-12-17 05:53:05 +08:00
|
|
|
const connectionCredentials = {
|
|
|
|
imap_username: email,
|
|
|
|
imap_password: settings.password,
|
|
|
|
smtp_username: email,
|
2016-12-21 05:08:30 +08:00
|
|
|
smtp_password: settings.password,
|
2016-12-17 05:53:05 +08:00
|
|
|
}
|
|
|
|
return {connectionSettings, connectionCredentials}
|
2017-01-15 09:24:13 +08:00
|
|
|
} else if (SUPPORTED_PROVIDERS.has(provider)) {
|
|
|
|
const connectionSettings = _.pick(settings, [
|
|
|
|
'imap_host', 'imap_port',
|
|
|
|
'smtp_host', 'smtp_port',
|
|
|
|
'ssl_required', 'smtp_custom_config',
|
|
|
|
]);
|
|
|
|
const connectionCredentials = _.pick(settings, [
|
|
|
|
'imap_username', 'imap_password',
|
|
|
|
'smtp_username', 'smtp_password',
|
|
|
|
]);
|
|
|
|
return {connectionSettings, connectionCredentials}
|
2016-12-17 05:53:05 +08:00
|
|
|
}
|
|
|
|
throw new Error(`Invalid provider: ${provider}`)
|
|
|
|
}
|
|
|
|
|
2016-12-06 09:59:47 +08:00
|
|
|
module.exports = {
|
2017-01-16 06:30:48 +08:00
|
|
|
SUPPORTED_PROVIDERS,
|
2017-02-18 10:38:50 +08:00
|
|
|
credentialsForProvider,
|
2016-12-07 02:42:32 +08:00
|
|
|
imapAuthRouteConfig() {
|
2016-12-06 09:59:47 +08:00
|
|
|
return {
|
|
|
|
description: 'Authenticates a new account.',
|
|
|
|
tags: ['accounts'],
|
|
|
|
auth: false,
|
|
|
|
validate: {
|
|
|
|
payload: {
|
|
|
|
email: Joi.string().email().required(),
|
|
|
|
name: Joi.string().required(),
|
2017-01-15 09:24:13 +08:00
|
|
|
provider: Joi.string().valid(...SUPPORTED_PROVIDERS).required(),
|
2016-12-17 05:53:05 +08:00
|
|
|
settings: Joi.alternatives().try(imapSmtpSettings, office365Settings, resolvedGmailSettings),
|
2016-12-06 09:59:47 +08:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
},
|
2016-12-07 02:42:32 +08:00
|
|
|
|
2016-12-20 01:25:07 +08:00
|
|
|
imapAuthHandler(upsertAccount) {
|
2017-01-19 09:29:58 +08:00
|
|
|
const MAX_RETRIES = 2
|
|
|
|
const authHandler = (request, reply, retryNum = 0) => {
|
2016-12-07 02:42:32 +08:00
|
|
|
const dbStub = {};
|
|
|
|
const connectionChecks = [];
|
2016-12-17 05:53:05 +08:00
|
|
|
const {email, provider, name} = request.payload;
|
2016-12-07 02:42:32 +08:00
|
|
|
|
2016-12-17 05:53:05 +08:00
|
|
|
const {connectionSettings, connectionCredentials} = credentialsForProvider(request.payload)
|
2016-12-07 02:42:32 +08:00
|
|
|
|
|
|
|
connectionChecks.push(IMAPConnection.connect({
|
|
|
|
settings: Object.assign({}, connectionSettings, connectionCredentials),
|
|
|
|
logger: request.logger,
|
|
|
|
db: dbStub,
|
|
|
|
}));
|
|
|
|
|
|
|
|
Promise.all(connectionChecks).then((conns) => {
|
|
|
|
for (const conn of conns) {
|
|
|
|
if (conn) { conn.end(); }
|
|
|
|
}
|
|
|
|
const accountParams = {
|
|
|
|
name: name,
|
|
|
|
provider: provider,
|
|
|
|
emailAddress: email,
|
|
|
|
connectionSettings: connectionSettings,
|
|
|
|
}
|
2016-12-20 01:25:07 +08:00
|
|
|
return upsertAccount(accountParams, connectionCredentials)
|
2016-12-07 02:42:32 +08:00
|
|
|
})
|
|
|
|
.then(({account, token}) => {
|
|
|
|
const response = account.toJSON();
|
|
|
|
response.account_token = token.value;
|
2017-01-19 09:29:58 +08:00
|
|
|
reply(JSON.stringify(response));
|
|
|
|
return
|
2016-12-07 02:42:32 +08:00
|
|
|
})
|
|
|
|
.catch((err) => {
|
2017-01-28 03:47:52 +08:00
|
|
|
const logger = request.logger.child({
|
|
|
|
account_name: name,
|
|
|
|
account_provider: provider,
|
|
|
|
account_email: email,
|
|
|
|
connection_settings: connectionSettings,
|
|
|
|
error: err,
|
|
|
|
error_message: err.message,
|
|
|
|
})
|
2016-12-07 06:53:24 +08:00
|
|
|
if (err instanceof IMAPErrors.IMAPAuthenticationError) {
|
2017-01-28 03:47:52 +08:00
|
|
|
logger.error({err}, 'Encountered authentication error while attempting to authenticate')
|
2017-01-19 09:29:58 +08:00
|
|
|
reply({message: USER_ERRORS.IMAP_AUTH, type: "api_error"}).code(401);
|
|
|
|
return
|
2016-12-07 06:53:24 +08:00
|
|
|
}
|
2017-01-25 10:48:24 +08:00
|
|
|
if (err instanceof IMAPErrors.IMAPCertificateError) {
|
2017-01-28 03:47:52 +08:00
|
|
|
logger.error({err}, 'Encountered certificate error while attempting to authenticate')
|
2017-01-25 10:48:24 +08:00
|
|
|
reply({message: USER_ERRORS.IMAP_CERT, type: "api_error"}).code(401);
|
|
|
|
return
|
|
|
|
}
|
2016-12-08 02:10:34 +08:00
|
|
|
if (err instanceof IMAPErrors.RetryableError) {
|
2017-01-19 09:29:58 +08:00
|
|
|
if (retryNum < MAX_RETRIES) {
|
|
|
|
setTimeout(() => {
|
|
|
|
request.logger.info(`IMAP Timeout. Retry #${retryNum + 1}`)
|
|
|
|
authHandler(request, reply, retryNum + 1)
|
|
|
|
}, 100)
|
|
|
|
return
|
|
|
|
}
|
2017-01-28 03:47:52 +08:00
|
|
|
logger.error({err}, 'Encountered retryable error while attempting to authenticate')
|
2017-01-19 09:29:58 +08:00
|
|
|
reply({message: USER_ERRORS.IMAP_RETRY, type: "api_error"}).code(408);
|
|
|
|
return
|
2016-12-08 02:10:34 +08:00
|
|
|
}
|
2017-01-28 03:47:52 +08:00
|
|
|
logger.error({err}, 'Encountered unknown error while attempting to authenticate')
|
2017-01-19 09:29:58 +08:00
|
|
|
reply({message: USER_ERRORS.AUTH_500, type: "api_error"}).code(500);
|
|
|
|
return
|
2016-12-07 02:42:32 +08:00
|
|
|
})
|
|
|
|
}
|
2017-01-19 09:29:58 +08:00
|
|
|
return authHandler
|
2016-12-07 02:42:32 +08:00
|
|
|
},
|
2016-12-06 09:59:47 +08:00
|
|
|
}
|