From 5ce9989313ae84284b0350c63813c3a448afb9ef Mon Sep 17 00:00:00 2001 From: NickOvt Date: Tue, 8 Apr 2025 14:45:39 +0300 Subject: [PATCH] fix: ZMS-210 API and docs fallback values updates (#802) * in APIs remove fallbacks to false and fallback to undefined. Update documentation * update docs * Updae autoreply test. Don't account for undefined fields (not present in response) --- docs/api/openapidocs.json | 72 ++++++++--------------- lib/api/addresses.js | 16 ++--- lib/api/asps.js | 16 ++--- lib/api/autoreply.js | 15 ++--- lib/api/storage.js | 16 ++--- lib/api/users.js | 8 +-- lib/schemas/response/addresses-schemas.js | 12 ++-- test/api-test.js | 11 +--- 8 files changed, 63 insertions(+), 103 deletions(-) diff --git a/docs/api/openapidocs.json b/docs/api/openapidocs.json index a4355c0f..7567d781 100644 --- a/docs/api/openapidocs.json +++ b/docs/api/openapidocs.json @@ -6,7 +6,7 @@ "contact": { "url": "https://github.com/zone-eu/wildduck" }, - "version": "1.45.4" + "version": "1.45.5" }, "servers": [ { @@ -278,6 +278,10 @@ }, "description": "An array of forwarding targets. The value could either be an email address or a relay url to next MX server (\"smtp://mx2.zone.eu:25\") or an URL where mail contents are POSTed to" }, + "mtaRelay": { + "type": "string", + "description": "An address of an SMTP MTA relay. The value should be a relay url. If specified uses the this relay as the outbound MTA." + }, "spamLevel": { "type": "number", "description": "Relative scale for detecting spam. 0 means that everything is spam, 100 means that nothing is spam", @@ -592,6 +596,10 @@ }, "description": "An array of forwarding targets. The value could either be an email address or a relay url to next MX server (\"smtp://mx2.zone.eu:25\") or an URL where mail contents are POSTed to" }, + "mtaRelay": { + "type": "string", + "description": "An address of an SMTP MTA relay. The value should be a relay url. If specified uses the this relay as the outbound MTA." + }, "spamLevel": { "type": "number", "description": "Relative scale for detecting spam. 0 means that everything is spam, 100 means that nothing is spam" @@ -9339,7 +9347,7 @@ }, "retention": { "type": "number", - "description": "Default retention time (in ms). false if not enabled" + "description": "Default retention time (in ms). Not present if not enabled" }, "enabled2fa": { "type": "array", @@ -9384,6 +9392,10 @@ }, "description": "List of forwarding targets" }, + "mtaRelay": { + "type": "string", + "description": "MTA Relay url" + }, "spamLevel": { "type": "number", "description": "Relative scale for detecting spam. 0 means that everything is spam, 100 means that nothing is spam" @@ -9440,7 +9452,6 @@ "username", "name", "address", - "retention", "enabled2fa", "autoreply", "encryptMessages", @@ -9723,7 +9734,6 @@ }, "required": [ "id", - "name", "address", "user", "forwarded", @@ -9853,7 +9863,6 @@ }, "required": [ "id", - "name", "address", "main", "created", @@ -9931,7 +9940,6 @@ "required": [ "success", "id", - "name", "address", "main", "created", @@ -10079,11 +10087,7 @@ } }, "required": [ - "status", - "name", - "subject", - "text", - "html" + "status" ] }, "GetForwardedAddressResponse": { @@ -10150,7 +10154,6 @@ "success", "id", "address", - "name", "limits", "autoreply", "created", @@ -11542,26 +11545,12 @@ "description": "File ID" }, "filename": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "boolean" - } - ], - "description": "Filename. False if none" + "type": "string", + "description": "Filename" }, "contentType": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "boolean" - } - ], - "description": "Content-Type of the file. False if none" + "type": "string", + "description": "Content-Type of the file" }, "cid": { "type": "string", @@ -11583,8 +11572,6 @@ }, "required": [ "id", - "filename", - "contentType", "size", "created", "md5" @@ -12136,18 +12123,14 @@ "properties": { "time": { "type": "string", - "description": "Datestring of last use or false if password has not been used", + "description": "Datestring of last use or not present if password has not been used", "format": "date-time" }, "event": { "type": "string", "description": "Event ID of the security log for the last authentication" } - }, - "required": [ - "time", - "event" - ] + } }, "GetASPsResult": { "type": "object", @@ -12978,18 +12961,12 @@ "start": { "type": "string", "description": "Datestring of the start of the autoreply or boolean false to disable start checks", - "format": "date-time", - "enum": [ - false - ] + "format": "date-time" }, "end": { "type": "string", "description": "Datestring of the end of the autoreply or boolean false to disable end checks", - "format": "date-time", - "enum": [ - false - ] + "format": "date-time" }, "created": { "type": "string", @@ -12998,8 +12975,7 @@ } }, "required": [ - "success", - "created" + "success" ] }, "Reference": { diff --git a/lib/api/addresses.js b/lib/api/addresses.js index 73091f85..932588dd 100644 --- a/lib/api/addresses.js +++ b/lib/api/addresses.js @@ -245,7 +245,7 @@ module.exports = (db, server, userHandler, settingsHandler) => { results: (listing.results || []).map(addressData => { let values = { id: addressData._id.toString(), - name: addressData.name || false, + name: addressData.name || undefined, address: addressData.address, user: addressData.user && addressData.user.toString(), forwarded: !!addressData.targets, @@ -704,7 +704,7 @@ module.exports = (db, server, userHandler, settingsHandler) => { results: addresses.map(addressData => { let values = { id: addressData._id.toString(), - name: addressData.name || false, + name: addressData.name || undefined, address: addressData.address, main: addressData.address === userData.address, tags: addressData.tags || [], @@ -747,7 +747,7 @@ module.exports = (db, server, userHandler, settingsHandler) => { model: Joi.object({ success: successRes, id: addressId, - name: Joi.string().required().description('Identity name'), + name: Joi.string().description('Identity name'), address: addressEmail, main: booleanSchema.required().description('Indicates if this is the default address for the User'), created: Joi.date().required().description('Datestring of the time the address was created'), @@ -850,7 +850,7 @@ module.exports = (db, server, userHandler, settingsHandler) => { let value = { success: true, id: addressData._id.toString(), - name: addressData.name || false, + name: addressData.name || undefined, address: addressData.address, main: addressData.address === userData.address, tags: addressData.tags || [], @@ -1428,7 +1428,7 @@ module.exports = (db, server, userHandler, settingsHandler) => { success: true, results: addresses.map(addressData => { - let name = addressData.name || false; + let name = addressData.name || undefined; try { // try to decode if (name) { @@ -1439,7 +1439,7 @@ module.exports = (db, server, userHandler, settingsHandler) => { } return { id: addressData._id.toString(), - name: addressData.name || false, + name: addressData.name || undefined, address: addressData.address }; }) @@ -2167,7 +2167,7 @@ module.exports = (db, server, userHandler, settingsHandler) => { success: successRes, id: addressId, address: addressEmail, - name: Joi.string().required().description('Identity name'), + name: Joi.string().description('Identity name'), targets: Joi.array().items(Joi.string()).description('List of forwarding targets'), limits: AddressLimits, autoreply: AutoreplyInfo, @@ -2254,7 +2254,7 @@ module.exports = (db, server, userHandler, settingsHandler) => { const values = { success: true, id: addressData._id.toString(), - name: addressData.name || false, + name: addressData.name || undefined, address: addressData.address, targets: addressData.targets && addressData.targets.map(t => t.value), limits: { diff --git a/lib/api/asps.js b/lib/api/asps.js index 2a417906..582adc34 100644 --- a/lib/api/asps.js +++ b/lib/api/asps.js @@ -50,8 +50,8 @@ module.exports = (db, server, userHandler) => { .required() .description('Allowed scopes for the Application Password'), lastUse: Joi.object({ - time: Joi.date().required().description('Datestring of last use or false if password has not been used'), - event: Joi.string().required().description('Event ID of the security log for the last authentication') + time: Joi.date().description('Datestring of last use or not present if password has not been used'), + event: Joi.string().description('Event ID of the security log for the last authentication') }) .required() .$_setFlag('objectName', 'LastUse') @@ -170,8 +170,8 @@ module.exports = (db, server, userHandler) => { description: asp.description, scopes: asp.scopes.includes('*') ? [...consts.SCOPES] : asp.scopes, lastUse: { - time: asp.used || false, - event: asp.authEvent || false + time: asp.used || undefined, + event: asp.authEvent || undefined }, expires: asp.expires, created: asp.created @@ -209,8 +209,8 @@ module.exports = (db, server, userHandler) => { .required() .description('Allowed scopes for the Application Password'), lastUse: Joi.object({ - time: Joi.date().required().description('Datestring of last use or false if password has not been used'), - event: Joi.string().required().description('Event ID of the security log for the last authentication') + time: Joi.date().description('Datestring of last use or not present if password has not been used'), + event: Joi.string().description('Event ID of the security log for the last authentication') }) .required() .$_setFlag('objectName', 'LastUse') @@ -286,8 +286,8 @@ module.exports = (db, server, userHandler) => { description: aspData.description, scopes: aspData.scopes.includes('*') ? [...consts.SCOPES] : aspData.scopes, lastUse: { - time: aspData.used || false, - event: aspData.authEvent || false + time: aspData.used || undefined, + event: aspData.authEvent || undefined }, expires: asp.expires, created: aspData.created diff --git a/lib/api/autoreply.js b/lib/api/autoreply.js index 14bafdeb..13470fbd 100644 --- a/lib/api/autoreply.js +++ b/lib/api/autoreply.js @@ -181,12 +181,9 @@ module.exports = (db, server) => { .trim() .max(128 * 1024) .description('HTML formatted content of the autoreply message'), - start: Joi.date() - .empty('') - .allow(false) - .description('Datestring of the start of the autoreply or boolean false to disable start checks'), - end: Joi.date().empty('').allow(false).description('Datestring of the end of the autoreply or boolean false to disable end checks'), - created: Joi.date().required().description('Datestring of when the Autoreply was created') + start: Joi.date().empty('').description('Datestring of the start of the autoreply or boolean false to disable start checks'), + end: Joi.date().empty('').description('Datestring of the end of the autoreply or boolean false to disable end checks'), + created: Joi.date().description('Datestring of when the Autoreply was created') }).$_setFlag('objectName', 'GetAutoreplyResponse') } } @@ -236,9 +233,9 @@ module.exports = (db, server) => { subject: entry.subject || '', text: entry.text || '', html: entry.html || '', - start: entry.start || false, - end: entry.end || false, - created: entry.created || entry._id?.getTimestamp() || false + start: entry.start || undefined, + end: entry.end || undefined, + created: entry.created || entry._id?.getTimestamp() || undefined }); }) ); diff --git a/lib/api/storage.js b/lib/api/storage.js index de457e06..250247da 100644 --- a/lib/api/storage.js +++ b/lib/api/storage.js @@ -6,7 +6,7 @@ const ObjectId = require('mongodb').ObjectId; const tools = require('../tools'); const roles = require('../roles'); const consts = require('../consts'); -const { nextPageCursorSchema, previousPageCursorSchema, pageNrSchema, sessSchema, sessIPSchema, booleanSchema } = require('../schemas'); +const { nextPageCursorSchema, previousPageCursorSchema, pageNrSchema, sessSchema, sessIPSchema } = require('../schemas'); const { userId } = require('../schemas/request/general-schemas'); const { successRes, totalRes, pageRes, previousCursorRes, nextCursorRes } = require('../schemas/response/general-schemas'); @@ -157,14 +157,8 @@ module.exports = (db, server, storageHandler) => { .items( Joi.object({ id: Joi.string().required().description('File ID'), - filename: Joi.alternatives() - .try(Joi.string().required(), booleanSchema.required()) - .required() - .description('Filename. False if none'), - contentType: Joi.alternatives() - .try(Joi.string().required(), booleanSchema.required()) - .required() - .description('Content-Type of the file. False if none'), + filename: Joi.string().description('Filename'), + contentType: Joi.string().description('Content-Type of the file'), cid: Joi.string().description('Content ID'), size: Joi.number().required().description('File size'), created: Joi.date().required().description('Created datestring'), @@ -297,8 +291,8 @@ module.exports = (db, server, storageHandler) => { nextCursor: listing.hasNext ? listing.next : false, results: (listing.results || []).map(fileData => ({ id: fileData._id.toString(), - filename: fileData.filename || false, - contentType: fileData.contentType || false, + filename: fileData.filename || undefined, + contentType: fileData.contentType || undefined, cid: fileData.metadata?.cid, size: fileData.length, created: fileData.uploadDate.toISOString(), diff --git a/lib/api/users.js b/lib/api/users.js index ea05c960..706ea464 100644 --- a/lib/api/users.js +++ b/lib/api/users.js @@ -793,7 +793,7 @@ module.exports = (db, server, userHandler, settingsHandler) => { username: Joi.string().required().description('Username of the User'), name: Joi.string().required().description('Name of the User'), address: Joi.string().required().description('Main email address of the User'), - retention: Joi.number().required().description('Default retention time (in ms). false if not enabled'), + retention: Joi.number().description('Default retention time (in ms). Not present if not enabled'), enabled2fa: Joi.array().items(Joi.string()).required().description('List of enabled 2FA methods'), autoreply: booleanSchema .required() @@ -814,7 +814,7 @@ module.exports = (db, server, userHandler, settingsHandler) => { .required() .description('Custom internal metadata object set for this user. Not available for user-role tokens'), targets: Joi.array().items(Joi.string()).required().description('List of forwarding targets'), - mtaRelay: Joi.string().required().description('MTA Relay url'), + mtaRelay: Joi.string().description('MTA Relay url'), spamLevel: Joi.number() .required() .description('Relative scale for detecting spam. 0 means that everything is spam, 100 means that nothing is spam'), @@ -1073,7 +1073,7 @@ module.exports = (db, server, userHandler, settingsHandler) => { address: userData.address, language: userData.language, - retention: userData.retention || false, + retention: userData.retention || undefined, enabled2fa: tools.getEnabled2fa(userData.enabled2fa), autoreply: !!userData.autoreply, @@ -1092,7 +1092,7 @@ module.exports = (db, server, userHandler, settingsHandler) => { .map(target => target.value) .filter(target => target), - mtaRelay: userData.mtaRelay?.value || false, + mtaRelay: userData.mtaRelay?.value || undefined, limits: { quota: { diff --git a/lib/schemas/response/addresses-schemas.js b/lib/schemas/response/addresses-schemas.js index 22dbf993..f29e8577 100644 --- a/lib/schemas/response/addresses-schemas.js +++ b/lib/schemas/response/addresses-schemas.js @@ -6,7 +6,7 @@ const { addressId, addressEmail } = require('../request/general-schemas'); const GetAddressesResult = Joi.object({ id: Joi.string().required().description('ID of the Address'), - name: Joi.string().required().description('Identity name'), + name: Joi.string().description('Identity name'), address: Joi.string().required().description('E-mail address string'), user: Joi.string().required().description('User ID this address belongs to if this is a User address'), forwarded: booleanSchema.required().description('If true then it is a forwarded address'), @@ -19,7 +19,7 @@ const GetAddressesResult = Joi.object({ const GetUserAddressesResult = Joi.object({ id: addressId, - name: Joi.string().required().description('Identity name'), + name: Joi.string().description('Identity name'), address: addressEmail, main: booleanSchema.required().description('Indicates if this is the default address for the User'), created: Joi.date().required().description('Datestring of the time the address was created'), @@ -50,10 +50,10 @@ const AddressLimits = Joi.object({ const AutoreplyInfo = Joi.object({ status: booleanSchema.required().description('If true, then autoreply is enabled for this address'), - name: Joi.string().required().description('Name that is used for the From: header in autoreply message'), - subject: Joi.string().required().description('Autoreply subject line'), - text: Joi.string().required().description('Autoreply plaintext content'), - html: Joi.string().required().description('Autoreply HTML content') + name: Joi.string().description('Name that is used for the From: header in autoreply message'), + subject: Joi.string().description('Autoreply subject line'), + text: Joi.string().description('Autoreply plaintext content'), + html: Joi.string().description('Autoreply HTML content') }) .required() .description('Autoreply information') diff --git a/test/api-test.js b/test/api-test.js index eefa681f..ca5b1560 100644 --- a/test/api-test.js +++ b/test/api-test.js @@ -415,10 +415,7 @@ describe('API tests', function () { name: '', subject: '', text: '', - html: '', - start: false, - end: false, - created: false + html: '' }); r = await server @@ -467,7 +464,6 @@ describe('API tests', function () { subject: '', text: 'Away from office until Dec.19', html: '', - start: false, end: '2017-12-19T00:00:00.000Z', created: r.body.created // created might have been changed to new date }); @@ -480,10 +476,7 @@ describe('API tests', function () { name: '', subject: '', text: '', - html: '', - start: false, - end: false, - created: false + html: '' }); }); });