wildduck/api.js

222 lines
6.1 KiB
JavaScript
Raw Normal View History

2017-03-06 22:13:40 +08:00
'use strict';
2017-07-16 19:37:33 +08:00
const config = require('wild-config');
2017-03-06 22:13:40 +08:00
const restify = require('restify');
const log = require('npmlog');
2017-12-23 00:18:50 +08:00
const logger = require('restify-logger');
const UserHandler = require('./lib/user-handler');
2017-07-21 02:33:41 +08:00
const MailboxHandler = require('./lib/mailbox-handler');
2017-07-21 21:29:57 +08:00
const MessageHandler = require('./lib/message-handler');
const ImapNotifier = require('./lib/imap-notifier');
const db = require('./lib/db');
const certs = require('./lib/certs');
2018-08-28 19:37:06 +08:00
const ObjectID = require('mongodb').ObjectID;
const rootUser = new ObjectID('0'.repeat(24));
2017-07-26 16:52:55 +08:00
const usersRoutes = require('./lib/api/users');
const addressesRoutes = require('./lib/api/addresses');
const mailboxesRoutes = require('./lib/api/mailboxes');
const messagesRoutes = require('./lib/api/messages');
const filtersRoutes = require('./lib/api/filters');
const aspsRoutes = require('./lib/api/asps');
2017-10-10 16:19:10 +08:00
const totpRoutes = require('./lib/api/2fa/totp');
const custom2faRoutes = require('./lib/api/2fa/custom');
2017-10-10 16:19:10 +08:00
const u2fRoutes = require('./lib/api/2fa/u2f');
2017-07-26 16:52:55 +08:00
const updatesRoutes = require('./lib/api/updates');
const authRoutes = require('./lib/api/auth');
2017-07-30 23:07:35 +08:00
const autoreplyRoutes = require('./lib/api/autoreply');
2017-11-29 19:59:58 +08:00
const submitRoutes = require('./lib/api/submit');
2017-12-01 21:04:32 +08:00
const domainaliasRoutes = require('./lib/api/domainaliases');
2017-12-28 19:45:02 +08:00
const dkimRoutes = require('./lib/api/dkim');
2017-03-06 22:13:40 +08:00
2017-07-17 21:32:31 +08:00
const serverOptions = {
2018-01-02 21:04:01 +08:00
name: 'WildDuck API',
strictRouting: true,
formatters: {
'application/json; q=0.4': (req, res, body) => {
2017-07-20 21:10:36 +08:00
let data = body ? JSON.stringify(body, false, 2) + '\n' : 'null';
res.setHeader('Content-Length', Buffer.byteLength(data));
return data;
}
}
2017-07-17 21:32:31 +08:00
};
let certOptions = {};
certs.loadTLSOptions(certOptions, 'api');
if (config.api.secure && certOptions.key) {
serverOptions.key = certOptions.key;
if (certOptions.ca) {
serverOptions.ca = certOptions.ca;
2017-07-17 21:32:31 +08:00
}
serverOptions.certificate = certOptions.cert;
2017-07-17 21:32:31 +08:00
}
const server = restify.createServer(serverOptions);
2017-03-06 22:13:40 +08:00
let userHandler;
2017-07-21 02:33:41 +08:00
let mailboxHandler;
2017-07-21 21:29:57 +08:00
let messageHandler;
let notifier;
2017-03-06 22:13:40 +08:00
// disable compression for EventSource response
// this needs to be called before gzipResponse
server.use((req, res, next) => {
if (req.route.path === '/users/:user/updates') {
req.headers['accept-encoding'] = '';
}
next();
});
2017-12-23 00:18:50 +08:00
server.use(restify.plugins.gzipResponse());
2017-07-11 03:15:16 +08:00
server.use(restify.plugins.queryParser());
2017-06-03 14:51:58 +08:00
server.use(
2017-07-11 03:15:16 +08:00
restify.plugins.bodyParser({
2017-06-03 14:51:58 +08:00
maxBodySize: 0,
mapParams: true,
mapFiles: false,
overrideParams: false
})
);
2017-03-06 22:13:40 +08:00
2017-07-25 20:50:16 +08:00
server.use((req, res, next) => {
2018-08-28 19:37:06 +08:00
let accessToken = req.query.accessToken || req.headers['x-access-token'] || false;
if (req.query.accessToken) {
delete req.query.accessToken;
}
let tokenRequired = false;
let fail = () => {
2017-07-25 20:50:16 +08:00
res.status(403);
res.charSet('utf-8');
return res.json({
2018-08-28 19:37:06 +08:00
error: 'Invalid accessToken value',
code: 'InvalidToken'
2017-07-25 20:50:16 +08:00
});
2018-08-28 19:37:06 +08:00
};
req.validate = permission => {
if (!permission.granted) {
let err = new Error('Not enough privileges');
err.responseCode = 403;
err.code = 'MissingPrivileges';
throw err;
}
};
// hard coded master token
if (config.api.accessToken) {
tokenRequired = true;
if (config.api.accessToken === accessToken) {
req.role = 'root';
req.user = rootUser;
return next();
}
2017-07-25 20:50:16 +08:00
}
2018-08-28 19:37:06 +08:00
// TODO: dynamically allocated tokens
if (tokenRequired) {
// no valid token found
return fail();
2017-07-25 20:50:16 +08:00
}
2018-08-28 19:37:06 +08:00
// allow all
req.role = 'root';
req.user = rootUser;
2017-07-25 20:50:16 +08:00
next();
});
2018-08-28 19:37:06 +08:00
logger.token('user', req => (req.user && req.user.toString()) || '?'.repeat(24));
logger.token('url', req => {
if (/\baccessToken=/.test(req.url)) {
return req.url.replace(/\baccessToken=[^&]+/g, 'accessToken=' + 'x'.repeat(6));
}
return req.url;
});
2017-12-23 00:18:50 +08:00
server.use(
2018-08-28 19:37:06 +08:00
logger(':remote-addr :user :method :url :status :time-spent :append', {
2017-12-23 00:18:50 +08:00
stream: {
write: message => {
message = (message || '').toString();
if (message) {
log.http('API', message.replace('\n', '').trim());
}
}
}
})
);
module.exports = done => {
2018-06-18 21:29:10 +08:00
if (!config.api.enabled) {
2017-04-13 16:35:39 +08:00
return setImmediate(() => done(null, false));
}
let started = false;
notifier = new ImapNotifier({
database: db.database,
redis: db.redis
});
2017-08-07 02:25:10 +08:00
messageHandler = new MessageHandler({
database: db.database,
users: db.users,
redis: db.redis,
gridfs: db.gridfs,
attachments: config.attachments
});
2017-08-07 02:25:10 +08:00
userHandler = new UserHandler({
database: db.database,
users: db.users,
redis: db.redis,
2017-08-08 19:35:18 +08:00
messageHandler,
authlogExpireDays: config.log.authlogExpireDays
});
2017-08-07 02:25:10 +08:00
mailboxHandler = new MailboxHandler({
database: db.database,
users: db.users,
redis: db.redis,
notifier
});
2017-07-26 16:52:55 +08:00
usersRoutes(db, server, userHandler);
addressesRoutes(db, server);
mailboxesRoutes(db, server, mailboxHandler);
messagesRoutes(db, server, messageHandler);
filtersRoutes(db, server);
aspsRoutes(db, server, userHandler);
2017-10-10 16:19:10 +08:00
totpRoutes(db, server, userHandler);
custom2faRoutes(db, server, userHandler);
2017-10-10 16:19:10 +08:00
u2fRoutes(db, server, userHandler);
2017-07-26 16:52:55 +08:00
updatesRoutes(db, server, notifier);
authRoutes(db, server, userHandler);
2017-07-30 23:07:35 +08:00
autoreplyRoutes(db, server);
2017-12-15 20:26:29 +08:00
submitRoutes(db, server, messageHandler, userHandler);
2017-12-01 21:04:32 +08:00
domainaliasRoutes(db, server);
2017-12-28 19:45:02 +08:00
dkimRoutes(db, server);
2017-07-26 16:52:55 +08:00
server.on('error', err => {
if (!started) {
started = true;
return done(err);
}
log.error('API', err);
});
server.listen(config.api.port, config.api.host, () => {
if (started) {
return server.close();
}
started = true;
log.info('API', 'Server listening on %s:%s', config.api.host || '0.0.0.0', config.api.port);
done(null, server);
2017-03-06 22:13:40 +08:00
});
};