made metaData visible for user, added new property internalData

This commit is contained in:
Andris Reinman 2020-09-17 10:32:27 +03:00
parent 2fdf9ec2e4
commit 6fafcaa957
3 changed files with 86 additions and 51 deletions

View file

@ -237,12 +237,12 @@
},
"userlisting": {
"read:own": ["*", "!tags", "!metaData", "!disabledScopes"]
"read:own": ["*", "!tags", "!disabledScopes", "!internalData"]
},
"users": {
"read:own": ["*", "!tags", "!metaData", "!disabledScopes"],
"update:own": ["*", "!tags", "!metaData", "!disabledScopes"]
"read:own": ["*", "!tags", "!disabledScopes", "!internalData"],
"update:own": ["*", "!tags", "!disabledScopes", "!internalData"]
},
"asps": {

View file

@ -31,6 +31,7 @@ module.exports = (db, server, userHandler) => {
* @apiParam {String} [tags] Comma separated list of tags. The User must have at least one to be set
* @apiParam {String} [requiredTags] Comma separated list of tags. The User must have all listed tags to be set
* @apiParam {Boolean} [metaData] If true, then includes <code>metaData</code> in the response
* @apiParam {Boolean} [internalData] If true, then includes <code>internalData</code> in the response. Not shown for user-role tokens.
* @apiParam {Number} [limit=20] How many records to return
* @apiParam {Number} [page=1] Current page number. Informational only, page numbers start from 1
* @apiParam {Number} [next] Cursor value for next page, retrieved from <code>nextCursor</code> response value
@ -54,6 +55,7 @@ module.exports = (db, server, userHandler) => {
* @apiSuccess {Boolean} results.encryptForwarded If <code>true</code> then forwarded messages are encrypted
* @apiSuccess {Object} results.quota Quota usage limits
* @apiSuccess {Object} [results.metaData] Custom metadata value. Included if <code>metaData</code> query argument was true
* @apiSuccess {Object} [results.internalData] Custom metadata value for internal use. Included if <code>internalData</code> query argument was true and request was not made using user-role token
* @apiSuccess {Number} results.quota.allowed Allowed quota of the user in bytes
* @apiSuccess {Number} results.quota.used Space used in bytes
* @apiSuccess {Boolean} results.hasPasswordSet If <code>true</code> then the User has a password set and can authenticate
@ -114,6 +116,7 @@ module.exports = (db, server, userHandler) => {
tags: Joi.string().trim().empty('').max(1024),
requiredTags: Joi.string().trim().empty('').max(1024),
metaData: booleanSchema,
internalData: booleanSchema,
limit: Joi.number().default(20).min(1).max(250),
next: nextPageCursorSchema,
previous: previousPageCursorSchema,
@ -253,6 +256,10 @@ module.exports = (db, server, userHandler) => {
opts.fields.projection.metaData = true;
}
if (result.value.internalData) {
opts.fields.projection.internalData = true;
}
if (pageNext) {
opts.next = pageNext;
} else if ((!page || page > 1) && pagePrevious) {
@ -307,6 +314,10 @@ module.exports = (db, server, userHandler) => {
values.metaData = formatMetaData(userData.metaData);
}
if (userData.internalData) {
values.internalData = formatMetaData(userData.internalData);
}
return permission.filter(values);
})
};
@ -342,6 +353,7 @@ module.exports = (db, server, userHandler) => {
* @apiParam {Boolean} [encryptForwarded] If <code>true</code> then forwarded messages are encrypted
* @apiParam {String} [pubKey] Public PGP key for the User that is used for encryption. Use empty string to remove the key
* @apiParam {Object|String} [metaData] Optional metadata, must be an object or JSON formatted string of an object
* @apiParam {Object|String} [internalData] Optional metadata for internal use, must be an object or JSON formatted string of an object. Not available for user-role tokens
* @apiParam {String} [language] Language code for the User
* @apiParam {String[]} [targets] 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
* @apiParam {Number} [spamLevel=50] Relative scale for detecting spam. 0 means that everything is spam, 100 means that nothing is spam
@ -493,6 +505,14 @@ module.exports = (db, server, userHandler) => {
Joi.object()
),
internalData: Joi.alternatives().try(
Joi.string()
.empty('')
.trim()
.max(1024 * 1024),
Joi.object()
),
pubKey: Joi.string()
.empty('')
.trim()
@ -659,29 +679,31 @@ module.exports = (db, server, userHandler) => {
return next();
}
if (result.value.metaData) {
if (typeof result.value.metaData === 'object') {
try {
result.value.metaData = JSON.stringify(result.value.metaData);
} catch (err) {
res.json({
error: 'metaData value must be serializable to JSON',
code: 'InputValidationError'
});
return next();
}
} else {
try {
let value = JSON.parse(result.value.metaData);
if (!value || typeof value !== 'object') {
throw new Error('Not an object');
for (let key of ['metaData', 'internalData']) {
if (result.value[key]) {
if (typeof result.value[key] === 'object') {
try {
result.value[key] = JSON.stringify(result.value[key]);
} catch (err) {
res.json({
error: `${key} value must be serializable to JSON`,
code: 'InputValidationError'
});
return next();
}
} else {
try {
let value = JSON.parse(result.value[key]);
if (!value || typeof value !== 'object') {
throw new Error('Not an object');
}
} catch (err) {
res.json({
error: `${key} value must be valid JSON object string`,
code: 'InputValidationError'
});
return next();
}
} catch (err) {
res.json({
error: 'metaData value must be valid JSON object string',
code: 'InputValidationError'
});
return next();
}
}
}
@ -842,6 +864,7 @@ module.exports = (db, server, userHandler) => {
* @apiSuccess {String} keyInfo.address E-mail address listed in public key
* @apiSuccess {String} keyInfo.fingerprint Fingerprint of the public key
* @apiSuccess {Object} metaData Custom metadata object set for this user
* @apiSuccess {Object} internalData Custom interna metadata object set for this user. Not available for user-role tokens
* @apiSuccess {String[]} targets List of forwarding targets
* @apiSuccess {Number} spamLevel Relative scale for detecting spam. 0 means that everything is spam, 100 means that nothing is spam
* @apiSuccess {Object} limits Account limits and usage
@ -1088,6 +1111,7 @@ module.exports = (db, server, userHandler) => {
keyInfo,
metaData: formatMetaData(userData.metaData),
internalData: formatMetaData(userData.internalData),
targets: [].concat(userData.targets || []).map(targetData => targetData.value),
@ -1183,6 +1207,7 @@ module.exports = (db, server, userHandler) => {
* @apiParam {Boolean} [encryptForwarded] If <code>true</code> then forwarded messages are encrypted
* @apiParam {String} [pubKey] Public PGP key for the User that is used for encryption. Use empty string to remove the key
* @apiParam {Object|String} [metaData] Optional metadata, must be an object or JSON formatted string of an object
* @apiParam {Object|String} [internalData] Optional internal metadata, must be an object or JSON formatted string of an object. Not available for user-role tokens
* @apiParam {String} [language] Language code for the User
* @apiParam {String[]} [targets] 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
* @apiParam {Number} [spamLevel] Relative scale for detecting spam. 0 means that everything is spam, 100 means that nothing is spam
@ -1266,6 +1291,14 @@ module.exports = (db, server, userHandler) => {
Joi.object()
),
internalData: Joi.alternatives().try(
Joi.string()
.empty('')
.trim()
.max(1024 * 1024),
Joi.object()
),
pubKey: Joi.string()
.empty('')
.trim()
@ -1417,29 +1450,31 @@ module.exports = (db, server, userHandler) => {
return next();
}
if (result.value.metaData) {
if (typeof result.value.metaData === 'object') {
try {
result.value.metaData = JSON.stringify(result.value.metaData);
} catch (err) {
res.json({
error: 'metaData value must be serializable to JSON',
code: 'InputValidationError'
});
return next();
}
} else {
try {
let value = JSON.parse(result.value.metaData);
if (!value || typeof value !== 'object') {
throw new Error('Not an object');
for (let key of ['metaData', 'internalData']) {
if (result.value[key]) {
if (typeof result.value[key] === 'object') {
try {
result.value[key] = JSON.stringify(result.value[key]);
} catch (err) {
res.json({
error: `${key} value must be serializable to JSON`,
code: 'InputValidationError'
});
return next();
}
} else {
try {
let value = JSON.parse(result.value[key]);
if (!value || typeof value !== 'object') {
throw new Error('Not an object');
}
} catch (err) {
res.json({
error: `${key} value must be valid JSON object string`,
code: 'InputValidationError'
});
return next();
}
} catch (err) {
res.json({
error: 'metaData value must be valid JSON object string',
code: 'InputValidationError'
});
return next();
}
}
}

View file

@ -15,11 +15,11 @@
"author": "Andris Reinman",
"license": "EUPL-1.1+",
"devDependencies": {
"ajv": "6.12.4",
"ajv": "6.12.5",
"apidoc": "0.25.0",
"chai": "4.2.0",
"docsify-cli": "4.4.1",
"eslint": "7.8.1",
"eslint": "7.9.0",
"eslint-config-nodemailer": "1.2.0",
"eslint-config-prettier": "6.11.0",
"grunt": "1.3.0",
@ -57,7 +57,7 @@
"mailsplit": "5.0.0",
"mobileconfig": "2.3.1",
"mongo-cursor-pagination": "7.3.1",
"mongodb": "3.6.1",
"mongodb": "3.6.2",
"mongodb-extended-json": "1.11.0",
"node-forge": "0.10.0",
"nodemailer": "6.4.11",
@ -76,7 +76,7 @@
"unixcrypt": "1.0.11",
"uuid": "8.3.0",
"wild-config": "1.5.1",
"yargs": "16.0.0"
"yargs": "16.0.3"
},
"repository": {
"type": "git",