wildduck/lib/api/webhooks.js
2022-11-24 12:26:10 +02:00

316 lines
10 KiB
JavaScript

'use strict';
const Joi = require('joi');
const MongoPaging = require('mongo-cursor-pagination');
const ObjectId = require('mongodb').ObjectId;
const tools = require('../tools');
const roles = require('../roles');
const { nextPageCursorSchema, previousPageCursorSchema, pageNrSchema, sessSchema, sessIPSchema } = require('../schemas');
module.exports = (db, server) => {
server.get(
{ name: 'webhooks', path: '/webhooks' },
tools.responseWrapper(async (req, res) => {
res.charSet('utf-8');
const schema = Joi.object().keys({
type: Joi.string().empty('').lowercase().max(128),
user: Joi.string().hex().lowercase().length(24),
limit: Joi.number().default(20).min(1).max(250),
next: nextPageCursorSchema,
previous: previousPageCursorSchema,
page: pageNrSchema,
sess: sessSchema,
ip: sessIPSchema
});
const result = schema.validate(req.params, {
abortEarly: false,
convert: true,
allowUnknown: true
});
if (result.error) {
res.status(400);
return res.json({
error: result.error.message,
code: 'InputValidationError',
details: tools.validationErrors(result)
});
}
let permission;
let ownOnly = false;
permission = roles.can(req.role).readAny('webhooks');
if (!permission.granted && req.user && ObjectId.isValid(req.user)) {
permission = roles.can(req.role).readOwn('webhooks');
if (permission.granted) {
ownOnly = true;
}
}
// permissions check
req.validate(permission);
let query = {};
if (result.value.type) {
query.type = result.value.type;
}
let user = result.value.user ? new ObjectId(result.value.user) : null;
if (ownOnly) {
user = new ObjectId(req.user);
}
if (user) {
query.user = user;
}
let limit = result.value.limit;
let page = result.value.page;
let pageNext = result.value.next;
let pagePrevious = result.value.previous;
let total = await db.users.collection('webhooks').countDocuments(query);
let opts = {
limit,
query,
fields: {
// FIXME: hack to keep _id in response
_id: true,
// FIXME: MongoPaging inserts fields value as second argument to col.find()
projection: {
_id: true,
type: true,
user: true,
url: true
}
},
// _id gets removed in response if not explicitly set in paginatedField
paginatedField: '_id',
sortAscending: true
};
if (pageNext) {
opts.next = pageNext;
} else if ((!page || page > 1) && pagePrevious) {
opts.previous = pagePrevious;
}
let listing;
try {
listing = await MongoPaging.find(db.users.collection('webhooks'), opts);
} catch (err) {
res.status(500);
return res.json({
error: 'MongoDB Error: ' + err.message,
code: 'InternalDatabaseError'
});
}
if (!listing.hasPrevious) {
page = 1;
}
let response = {
success: true,
type: result.value.type,
user,
total,
page,
previousCursor: listing.hasPrevious ? listing.previous : false,
nextCursor: listing.hasNext ? listing.next : false,
results: (listing.results || []).map(webhookData => {
let values = {
id: webhookData._id.toString(),
type: webhookData.type,
user: webhookData.user ? webhookData.user.toString() : null,
url: webhookData.url
};
return permission.filter(values);
})
};
return res.json(response);
})
);
server.post(
'/webhooks',
tools.responseWrapper(async (req, res) => {
res.charSet('utf-8');
const schema = Joi.object().keys({
type: Joi.array().items(Joi.string().trim().max(128).lowercase()).required(),
user: Joi.string().hex().lowercase().length(24),
url: Joi.string()
.uri({ scheme: [/smtps?/, /https?/], allowRelative: false, relativeOnly: false })
.required(),
sess: sessSchema,
ip: sessIPSchema
});
const result = schema.validate(req.params, {
abortEarly: false,
convert: true
});
if (result.error) {
res.status(400);
return res.json({
error: result.error.message,
code: 'InputValidationError',
details: tools.validationErrors(result)
});
}
// permissions check
let permission;
if (req.user && req.user === result.value.user) {
permission = roles.can(req.role).createOwn('webhooks');
} else {
permission = roles.can(req.role).createAny('webhooks');
}
req.validate(permission);
result.value = permission.filter(result.value);
let type = result.value.type;
let user = result.value.user ? new ObjectId(result.value.user) : null;
let url = result.value.url;
let userData;
if (user) {
try {
userData = await db.users.collection('users').findOne(
{
_id: user
},
{
projection: {
address: true
}
}
);
} catch (err) {
res.status(500);
return res.json({
error: 'MongoDB Error: ' + err.message,
code: 'InternalDatabaseError'
});
}
if (!userData) {
res.status(404);
return res.json({
error: 'This user does not exist',
code: 'UserNotFound'
});
}
}
let webhookData = {
type,
user,
url,
created: new Date()
};
let r;
// insert alias address to email address registry
try {
r = await db.users.collection('webhooks').insertOne(webhookData);
} catch (err) {
res.status(500);
return res.json({
error: 'MongoDB Error: ' + err.message,
code: 'InternalDatabaseError'
});
}
let insertId = r.insertedId;
return res.json({
success: !!insertId,
id: insertId
});
})
);
server.del(
'/webhooks/:webhook',
tools.responseWrapper(async (req, res) => {
res.charSet('utf-8');
const schema = Joi.object().keys({
webhook: Joi.string().hex().lowercase().length(24).required(),
sess: sessSchema,
ip: sessIPSchema
});
const result = schema.validate(req.params, {
abortEarly: false,
convert: true
});
if (result.error) {
res.status(400);
return res.json({
error: result.error.message,
code: 'InputValidationError',
details: tools.validationErrors(result)
});
}
let webhook = new ObjectId(result.value.webhook);
let webhookData;
try {
webhookData = await db.users.collection('webhooks').findOne({
_id: webhook
});
} catch (err) {
res.status(500);
return res.json({
error: 'MongoDB Error: ' + err.message,
code: 'InternalDatabaseError'
});
}
// permissions check
if (req.user && webhookData && webhookData.user && req.user === webhookData.user.toString()) {
req.validate(roles.can(req.role).deleteOwn('webhooks'));
} else {
req.validate(roles.can(req.role).deleteAny('webhooks'));
}
if (!webhookData) {
res.status(404);
return res.json({
error: 'Invalid or unknown webhook identifier',
code: 'WebhookNotFound'
});
}
// delete address from email address registry
let r;
try {
r = await db.users.collection('webhooks').deleteOne({
_id: webhook
});
} catch (err) {
res.status(500);
return res.json({
error: 'MongoDB Error: ' + err.message,
code: 'InternalDatabaseError'
});
}
return res.json({
success: !!r.deletedCount
});
})
);
};