diff --git a/lib/api/domainaccess.js b/lib/api/domainaccess.js index e8625ba0..a2de7130 100644 --- a/lib/api/domainaccess.js +++ b/lib/api/domainaccess.js @@ -5,22 +5,49 @@ const ObjectId = require('mongodb').ObjectId; const tools = require('../tools'); const roles = require('../roles'); const { sessSchema, sessIPSchema } = require('../schemas'); +const { successRes } = require('../schemas/response/general-schemas'); module.exports = (db, server) => { server.post( - '/domainaccess/:tag/:action', + { + path: '/domainaccess/:tag/allow', + tags: ['DomainAccess'], + summary: 'Add domain to allowlist', + description: 'If an email is sent from a domain that is listed in the allowlist then it is never marked as spam. Lists apply for tagged users.', + validationObjs: { + requestBody: { + domain: Joi.string() + .max(255) + //.hostname() + .required() + .description('Domain name to allowlist for users/addresses that include this tag'), + sess: sessSchema, + ip: sessIPSchema + }, + queryParams: {}, + pathParams: { + tag: Joi.string().trim().max(128).required().description('Tag to look for') + }, + response: { + 200: { + description: 'Success', + model: Joi.object({ + success: successRes, + id: Joi.string().required().description('ID for the created record') + }) + } + } + } + }, tools.responseWrapper(async (req, res) => { res.charSet('utf-8'); - const schema = Joi.object().keys({ - tag: Joi.string().trim().max(128).required(), - domain: Joi.string() - .max(255) - //.hostname() - .required(), - action: Joi.string().valid('allow', 'block').required(), - sess: sessSchema, - ip: sessIPSchema + const { pathParams, requestBody, queryParams } = req.route.spec.validationObjs; + + const schema = Joi.object({ + ...pathParams, + ...requestBody, + ...queryParams }); const result = schema.validate(req.params, { @@ -43,7 +70,110 @@ module.exports = (db, server) => { let domain = tools.normalizeDomain(result.value.domain); let tag = result.value.tag; let tagview = tag.toLowerCase(); - let action = result.value.action; + let action = 'allow'; + + let r; + try { + r = await db.database.collection('domainaccess').findOneAndUpdate( + { + tagview, + domain + }, + { + $setOnInsert: { + tag, + tagview, + domain + }, + + $set: { + action + } + }, + { + upsert: true, + projection: { _id: true }, + returnDocument: 'after' + } + ); + } catch (err) { + res.status(500); + return res.json({ + error: 'MongoDB Error: ' + err.message, + code: 'InternalDatabaseError' + }); + } + + return res.json({ + success: !!(r && r.value), + id: ((r && r.value && r.value._id) || '').toString() + }); + }) + ); + + server.post( + { + path: '/domainaccess/:tag/block', + tags: ['DomainAccess'], + summary: 'Add domain to blocklist', + description: 'If an email is sent from a domain that is listed in the blocklist then it is always marked as spam. Lists apply for tagged users.', + validationObjs: { + requestBody: { + domain: Joi.string() + .max(255) + //.hostname() + .required() + .description('Domain name to blocklist for users/addresses that include this tag'), + sess: sessSchema, + ip: sessIPSchema + }, + queryParams: {}, + pathParams: { + tag: Joi.string().trim().max(128).required().description('Tag to look for') + }, + response: { + 200: { + description: 'Success', + model: Joi.object({ + success: successRes, + id: Joi.string().required().description('ID for the created record') + }) + } + } + } + }, + tools.responseWrapper(async (req, res) => { + res.charSet('utf-8'); + + const { pathParams, requestBody, queryParams } = req.route.spec.validationObjs; + + const schema = Joi.object({ + ...pathParams, + ...requestBody, + ...queryParams + }); + + 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 + req.validate(roles.can(req.role).createAny('domainaccess')); + + let domain = tools.normalizeDomain(result.value.domain); + let tag = result.value.tag; + let tagview = tag.toLowerCase(); + let action = 'block'; let r; try { @@ -85,16 +215,50 @@ module.exports = (db, server) => { ); server.get( - '/domainaccess/:tag/:action', + { + path: '/domainaccess/:tag/allow', + tags: ['DomainAccess'], + summary: 'List allowlisted domains', + validationObjs: { + requestBody: {}, + queryParams: { + ess: sessSchema, + ip: sessIPSchema + }, + pathParams: { + tag: Joi.string().trim().max(128).required().description('Tag to look for') + }, + response: { + 200: { + description: 'Success', + model: Joi.object({ + success: successRes, + results: Joi.array() + .items( + Joi.object({ + id: Joi.string().required().description('Entry ID'), + domain: Joi.string().required().description('Allowlisted domain name'), + action: Joi.string().required().description('Action: `allow`').example('allow') + }) + .required() + .$_setFlag('objectName', 'GetAllowedDomainResult') + ) + .description('Domain list') + .required() + }) + } + } + } + }, tools.responseWrapper(async (req, res) => { res.charSet('utf-8'); - const schema = Joi.object().keys({ - tag: Joi.string().trim().max(128).required(), - action: Joi.string().valid('allow', 'block').required(), + const { pathParams, requestBody, queryParams } = req.route.spec.validationObjs; - sess: sessSchema, - ip: sessIPSchema + const schema = Joi.object({ + ...pathParams, + ...requestBody, + ...queryParams }); const result = schema.validate(req.params, { @@ -116,7 +280,110 @@ module.exports = (db, server) => { let tag = result.value.tag; let tagview = tag.toLowerCase(); - let action = result.value.action; + let action = 'action'; + + let domains; + try { + domains = await db.database + .collection('domainaccess') + .find({ + tagview, + action + }) + .sort({ + domain: 1 + }) + .toArray(); + } catch (err) { + res.status(500); + return res.json({ + error: 'MongoDB Error: ' + err.message, + code: 'InternalDatabaseError' + }); + } + + if (!domains) { + domains = []; + } + + return res.json({ + success: true, + results: domains.map(domainData => ({ + id: domainData._id.toString(), + domain: domainData.domain, + action + })) + }); + }) + ); + + server.get( + { + path: '/domainaccess/:tag/block', + tags: ['DomainAccess'], + summary: 'List blocklisted domains', + validationObjs: { + requestBody: {}, + queryParams: { + ess: sessSchema, + ip: sessIPSchema + }, + pathParams: { + tag: Joi.string().trim().max(128).required().description('Tag to look for') + }, + response: { + 200: { + description: 'Success', + model: Joi.object({ + success: successRes, + results: Joi.array() + .items( + Joi.object({ + id: Joi.string().required().description('Entry ID'), + domain: Joi.string().required().description('Blocklisted domain name'), + action: Joi.string().required().description('Action: `block`').example('block') + }) + .required() + .$_setFlag('objectName', 'GetBlockedDomainResult') + ) + .description('Domain list') + .required() + }) + } + } + } + }, + tools.responseWrapper(async (req, res) => { + res.charSet('utf-8'); + + const { pathParams, requestBody, queryParams } = req.route.spec.validationObjs; + + const schema = Joi.object({ + ...pathParams, + ...requestBody, + ...queryParams + }); + + 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 + req.validate(roles.can(req.role).readAny('domainaccess')); + + let tag = result.value.tag; + let tagview = tag.toLowerCase(); + let action = 'block'; let domains; try { @@ -154,14 +421,39 @@ module.exports = (db, server) => { ); server.del( - '/domainaccess/:domain', + { + path: '/domainaccess/:domain', + tags: ['DomainAccess'], + summary: 'Delete a Domain from listing', + validationObjs: { + requestBody: {}, + queryParams: { + sess: sessSchema, + ip: sessIPSchema + }, + pathParams: { + domain: Joi.string().hex().lowercase().length(24).required().description("Listed domain's unique ID") + }, + response: { + 200: { + description: 'Success', + model: Joi.object({ + success: successRes, + deleted: Joi.string().required().description("Deleted domain's unique ID") + }) + } + } + } + }, tools.responseWrapper(async (req, res) => { res.charSet('utf-8'); - const schema = Joi.object().keys({ - domain: Joi.string().hex().lowercase().length(24).required(), - sess: sessSchema, - ip: sessIPSchema + const { pathParams, requestBody, queryParams } = req.route.spec.validationObjs; + + const schema = Joi.object({ + ...pathParams, + ...requestBody, + ...queryParams }); const result = schema.validate(req.params, {