NotificationLatch/src/routes/api.js

176 lines
4.3 KiB
JavaScript

class ApiRoutes
{
static create = container => new this(container.Logger, container.NotificationFacade, container.Config);
constructor(logger, notificationFacade, config)
{
this.logger = logger;
this.notificationFacade = notificationFacade;
this.authToken = config.authToken;
this.allowedIps = config.allowedIps;
}
createRouter(express)
{
const router = express.Router();
router.post('/notification', this._wrapAsyncHandler(this._handlePostNotification));
router.get('/notification/latched', this._wrapAsyncHandler(this._handleLatchedNotifications));
router.post('/notification/:token/reset', this._wrapAsyncHandler(this._handleResetNotification));
router.post('/notification/:token/reminders/enable', this._wrapAsyncHandler(this._handleEnableReminders));
router.post('/notification/:token/reminders/disable', this._wrapAsyncHandler(this._handleDisableReminders));
router.get('/ip', this._handleIp.bind(this));
return router;
}
async _handlePostNotification(req, res)
{
if (!this._isIpAllowed(req, this.allowedIps.post))
{
this._logRequestWarning(req, 'IP address not in allowedIps.post');
res.sendStatus(401);
return;
}
if (req.headers.authorization !== 'Bearer ' + this.authToken)
{
this._logRequestWarning(req, 'Missing or invalid authorization header');
res.sendStatus(401);
return;
}
if (!req.body || !req.body.subject || !req.body.message)
{
this._logRequestWarning(req, 'Missing body, subject and/or message parameters');
res.sendStatus(400);
return;
}
await this.notificationFacade.postNotification(req.body.subject, req.body.message, req.body.priority || 0);
res.sendStatus(200);
}
async _handleResetNotification(req, res)
{
if (!this._isIpAllowed(req, this.allowedIps.manage))
{
this._logRequestWarning(req, 'IP address not in allowedIps.manage');
res.sendStatus(401);
return;
}
await this.notificationFacade.resetNotification(req.params.token);
res.sendStatus(200);
}
async _handleEnableReminders(req, res)
{
if (!this._isIpAllowed(req, this.allowedIps.manage))
{
this._logRequestWarning(req, 'IP address not in allowedIps.manage');
res.sendStatus(401);
return;
}
await this.notificationFacade.setReminders(req.params.token, true);
res.sendStatus(200);
}
async _handleDisableReminders(req, res)
{
if (!this._isIpAllowed(req, this.allowedIps.manage))
{
this._logRequestWarning(req, 'IP address not in allowedIps.manage');
res.sendStatus(401);
return;
}
await this.notificationFacade.setReminders(req.params.token, false);
res.sendStatus(200);
}
async _handleLatchedNotifications(req, res)
{
if (!this._isIpAllowed(req, this.allowedIps.manage))
{
this._logRequestWarning(req, 'IP address not in allowedIps.manage');
res.sendStatus(401);
return;
}
if (!req.headers.authorization || !req.headers.authorization.startsWith('Bearer '))
{
this._logRequestWarning(req, 'Missing or invalid authorization header');
res.sendStatus(401);
return;
}
const token = req.headers.authorization.substr(7);
const notifications = this.notificationFacade.getLatchedNotifications(token);
if (notifications == null)
{
this._logRequestWarning(req, `Invalid token: ${token}`);
res.sendStatus(401);
return;
}
res.send(JSON.stringify(notifications));
}
_handleIp(req, res)
{
res.send(this._getIp(req));
}
_wrapAsyncHandler(handler)
{
const boundHandler = handler.bind(this);
return async (req, res) =>
{
try
{
await boundHandler(req, res);
}
catch (err)
{
this.logger.error(`Unhandled exception in request handler: ${err}`);
this.logger.verbose(err.stack);
res.sendStatus(500);
}
}
}
_logRequestWarning(req, message)
{
const ip = this._getIp(req);
this.logger.warn(`[${ip}] ${message}`);
}
_isIpAllowed(req, whitelist)
{
return whitelist === null || whitelist.includes(this._getIp(req));
}
_getIp(req)
{
return req.headers['x-forwarded-for'] || req.ip;
}
}
module.exports = ApiRoutes;