Backported API tests from the Hapi branch

This commit is contained in:
Andris Reinman 2022-05-16 00:18:24 +03:00
parent 7932b3ee79
commit 6045004dc5
No known key found for this signature in database
GPG key ID: DC6C83F4D584D364
11 changed files with 890 additions and 30 deletions

View file

@ -148,6 +148,7 @@ module.exports = (db, server, userHandler, settingsHandler) => {
name: true,
user: true,
tags: true,
tagsview: true,
targets: true,
forwardedDisabled: true
}
@ -437,7 +438,7 @@ module.exports = (db, server, userHandler, settingsHandler) => {
if (result.value.tags) {
addressData.tags = result.value.tags;
addressData.tagsview = result.value.tags;
addressData.tagsview = result.value.tags.map(tag => tag.toLowerCase());
}
if (result.value.metaData) {
@ -1073,8 +1074,10 @@ module.exports = (db, server, userHandler, settingsHandler) => {
}
if (addressData.address === userData.address) {
res.status(400);
res.json({
error: 'Trying to delete main address. Set a new main address first'
error: 'Can not delete main address',
code: 'NotPermitted'
});
return next();
}
@ -1509,7 +1512,7 @@ module.exports = (db, server, userHandler, settingsHandler) => {
if (result.value.tags) {
addressData.tags = result.value.tags;
addressData.tagsview = result.value.tags;
addressData.tagsview = result.value.tags.map(tag => tag.toLowerCase());
}
if (result.value.metaData) {

View file

@ -357,7 +357,7 @@ module.exports = (db, server) => {
// permissions check
req.validate(roles.can(req.role).readAny('certs'));
let cert = new ObjectId(result.value.certs);
let cert = new ObjectId(result.value.cert);
let response;
try {

View file

@ -1629,7 +1629,8 @@ async function getKeyInfo(pubKeyArmored) {
let ciphertext = await openpgp.encrypt({
message: await openpgp.createMessage({ text: 'Hello, World!' }),
encryptionKeys: pubKey, // for encryption
format: 'armored'
format: 'armored',
config: { minRSABits: 1024 }
});
if (/^-----BEGIN PGP MESSAGE/.test(ciphertext)) {

View file

@ -1767,7 +1767,8 @@ class MessageHandler {
ciphertext = await openpgp.encrypt({
message: await openpgp.createMessage({ binary: Buffer.concat([Buffer.from(bodyHeaders + '\r\n\r\n'), body]) }),
encryptionKeys: pubKey,
format: 'armored'
format: 'armored',
config: { minRSABits: 1024 }
});
} catch (err) {
return false;

View file

@ -726,7 +726,7 @@ class UserHandler {
throw err;
}
if (success) {
if (userData.validAfter > now) {
if (userData.tempPassword.validAfter && userData.tempPassword.validAfter > now) {
let err = new Error('Temporary password is not yet activated');
err.responseCode = 403;
err.code = 'TempPasswordNotYetValid';

View file

@ -19,23 +19,23 @@
},
"license": "EUPL-1.2",
"devDependencies": {
"ajv": "8.10.0",
"ajv": "8.11.0",
"chai": "4.3.6",
"docsify-cli": "4.4.3",
"eslint": "8.10.0",
"docsify-cli": "4.4.4",
"eslint": "8.15.0",
"eslint-config-nodemailer": "1.2.0",
"eslint-config-prettier": "8.5.0",
"grunt": "1.4.1",
"grunt": "1.5.3",
"grunt-cli": "1.4.3",
"grunt-eslint": "24.0.0",
"grunt-mocha-test": "0.13.3",
"grunt-shell-spawn": "0.4.0",
"grunt-wait": "0.3.0",
"imapflow": "1.0.85",
"mailparser": "3.4.0",
"mocha": "9.2.1",
"imapflow": "1.0.95",
"mailparser": "3.5.0",
"mocha": "10.0.0",
"request": "2.88.2",
"supertest": "6.2.2"
"supertest": "6.2.3"
},
"dependencies": {
"@fidm/x509": "1.2.1",
@ -44,36 +44,36 @@
"@root/csr": "0.8.1",
"accesscontrol": "2.2.1",
"argon2-browser": "1.18.0",
"axios": "0.26.0",
"axios": "0.27.2",
"base32.js": "0.1.0",
"bcryptjs": "2.4.3",
"bull": "3.29.3",
"fido2-lib": "^2.8.1",
"fido2-lib": "3.1.4",
"gelf": "2.0.1",
"generate-password": "1.7.0",
"he": "1.2.0",
"html-to-text": "8.1.0",
"html-to-text": "8.2.0",
"humanname": "0.2.2",
"iconv-lite": "0.6.3",
"ioredfour": "1.2.0-ioredis-06",
"ioredis": "4.28.5",
"ioredis": "5.0.4",
"ipaddr.js": "2.0.1",
"isemail": "3.2.0",
"joi": "17.6.0",
"js-yaml": "4.1.0",
"key-fingerprint": "1.1.0",
"libbase64": "1.2.1",
"libmime": "5.0.0",
"libmime": "5.1.0",
"libqp": "1.1.0",
"mailsplit": "5.3.1",
"mailsplit": "5.3.2",
"mobileconfig": "2.4.0",
"mongo-cursor-pagination": "7.6.1",
"mongodb": "4.4.1",
"mongodb": "4.6.0",
"mongodb-extended-json": "1.11.1",
"node-forge": "1.2.1",
"nodemailer": "6.7.2",
"npmlog": "6.0.1",
"openpgp": "5.2.0",
"node-forge": "1.3.1",
"nodemailer": "6.7.5",
"npmlog": "6.0.2",
"openpgp": "5.2.1",
"pem-jwk": "2.0.0",
"punycode": "2.1.1",
"pwnedpasswords": "1.0.6",
@ -83,18 +83,19 @@
"restify-logger": "2.0.1",
"saslprep": "1.0.3",
"seq-index": "1.1.0",
"smtp-server": "3.10.0",
"smtp-server": "3.11.0",
"speakeasy": "2.0.0",
"unixcrypt": "1.0.13",
"unix-crypt-td-js": "1.1.4",
"unixcrypt": "1.1.0",
"uuid": "8.3.2",
"wild-config": "1.6.0",
"yargs": "17.3.1"
"yargs": "17.5.0"
},
"repository": {
"type": "git",
"url": "git://github.com/wildduck-email/wildduck.git"
},
"engines": {
"node": ">=12.0.0"
"node": ">=12.0.0 <17"
}
}

214
test/api/addresses-test.js Normal file
View file

@ -0,0 +1,214 @@
/*eslint no-unused-expressions: 0, prefer-arrow-callback: 0, no-console:0 */
/* globals before: false, after: false */
'use strict';
const supertest = require('supertest');
const chai = require('chai');
const expect = chai.expect;
chai.config.includeStack = true;
const server = supertest.agent('http://localhost:8080');
describe('API Users', function () {
this.timeout(10000); // eslint-disable-line no-invalid-this
let user, user2, forwarded;
before(async () => {
// ensure that we have an existing user account
const response = await server
.post('/users')
.send({
username: 'addressuser',
password: 'secretvalue',
address: 'addressuser.addrtest@example.com',
name: 'address user'
})
.expect(200);
expect(response.body.success).to.be.true;
expect(response.body.id).to.exist;
user = response.body.id;
const response2 = await server
.post('/users')
.send({
username: 'addressuser2',
password: 'secretvalue',
address: 'addressuser2.addrtest@example.com',
name: 'address user 2'
})
.expect(200);
expect(response2.body.success).to.be.true;
expect(response2.body.id).to.exist;
user2 = response2.body.id;
});
after(async () => {
if (!user) {
return;
}
const response = await server.delete(`/users/${user}`).expect(200);
expect(response.body.success).to.be.true;
user = false;
});
it('should POST /users/{user}/addresses', async () => {
const response = await server
.post(`/users/${user}/addresses`)
.send({
address: `user1.1.addrtest@example.com`,
tags: ['TAG1', 'tag2']
})
.expect(200);
expect(response.body.success).to.be.true;
const response2 = await server
.post(`/users/${user2}/addresses`)
.send({
address: `user2.1.addrtest@example.com`
})
.expect(200);
expect(response2.body.success).to.be.true;
const response3 = await server
.post(`/users/${user}/addresses`)
.send({
address: `user1.2.addrtest@example.com`,
tags: ['TAG2', 'tag3']
})
.expect(200);
expect(response3.body.success).to.be.true;
});
it('should GET /addresses', async () => {
const addressListResponse = await server.get(`/addresses`).expect(200);
expect(addressListResponse.body.success).to.be.true;
expect(addressListResponse.body.total).to.gt(3);
});
it('should GET /addresses with tags', async () => {
const addressListResponse = await server.get(`/addresses?tags=tag2,tag3`).expect(200);
expect(addressListResponse.body.success).to.be.true;
expect(addressListResponse.body.total).to.equal(2);
});
it('should GET /addresses with required tags', async () => {
const addressListResponse = await server.get(`/addresses?requiredTags=tag2,tag3`).expect(200);
expect(addressListResponse.body.success).to.be.true;
expect(addressListResponse.body.total).to.equal(1);
});
it('should GET /addresses with a user token', async () => {
const authResponse = await server
.post('/authenticate')
.send({
username: 'addressuser',
password: 'secretvalue',
token: true
})
.expect(200);
expect(authResponse.body.success).to.be.true;
expect(authResponse.body.token).to.exist;
let token = authResponse.body.token;
const userListResponse = await server.get(`/addresses?accessToken=${token}`).expect(200);
expect(userListResponse.body.success).to.be.true;
expect(userListResponse.body.total).to.equal(3);
});
it('should GET /users/{user}/addresses', async () => {
const addressListResponse = await server.get(`/users/${user}/addresses`).expect(200);
expect(addressListResponse.body.success).to.be.true;
expect(addressListResponse.body.results.length).to.equal(3);
expect(addressListResponse.body.results.filter(addr => addr.main).length).to.equal(1);
expect(addressListResponse.body.results.find(addr => addr.main).address).to.equal('addressuser.addrtest@example.com');
});
it('should PUT /users/{user}/addresses/{address}', async () => {
let addressListResponse = await server.get(`/users/${user}/addresses`).expect(200);
expect(addressListResponse.body.success).to.be.true;
let addresses = addressListResponse.body.results;
let address = addresses.find(addr => addr.address === 'user1.1.addrtest@example.com').id;
const response = await server
.put(`/users/${user}/addresses/${address}`)
.send({
main: true
})
.expect(200);
expect(response.body.success).to.be.true;
addressListResponse = await server.get(`/users/${user}/addresses`).expect(200);
expect(addressListResponse.body.success).to.be.true;
expect(addressListResponse.body.results.length).to.equal(3);
expect(addressListResponse.body.results.filter(addr => addr.main).length).to.equal(1);
expect(addressListResponse.body.results.find(addr => addr.main).address).to.equal('user1.1.addrtest@example.com');
});
it('should DELETE /users/{user}/addresses/{address} and fail', async () => {
let addressListResponse = await server.get(`/users/${user}/addresses`).expect(200);
expect(addressListResponse.body.success).to.be.true;
let addresses = addressListResponse.body.results;
let address = addresses.find(addr => addr.main).id;
// trying to delete a main address should fail
const response = await server.delete(`/users/${user}/addresses/${address}`).expect(400);
expect(response.body.code).to.equal('NotPermitted');
});
it('should DELETE /users/{user}/addresses/{address}', async () => {
let addressListResponse = await server.get(`/users/${user}/addresses`).expect(200);
expect(addressListResponse.body.success).to.be.true;
let addresses = addressListResponse.body.results;
let address = addresses.find(addr => addr.address === 'user1.2.addrtest@example.com').id;
const response = await server.delete(`/users/${user}/addresses/${address}`).expect(200);
expect(response.body.success).to.be.true;
});
it('should POST /addresses/forwarded', async () => {
const response = await server
.post(`/addresses/forwarded`)
.send({
address: `forwarded.1.addrtest@example.com`,
targets: ['andris@ethereal.email'],
tags: ['TAG1', 'tag2']
})
.expect(200);
expect(response.body.success).to.be.true;
forwarded = response.body.id;
});
it('should GET /addresses with query', async () => {
const addressListResponse = await server.get(`/addresses?query=forwarded.1.addrtest`).expect(200);
expect(addressListResponse.body.success).to.be.true;
expect(addressListResponse.body.total).to.equal(1);
expect(forwarded).to.exist;
});
it('should PUT /addresses/forwarded/{address}', async () => {
const response = await server
.put(`/addresses/forwarded/${forwarded}`)
.send({
tags: ['tAG2', 'tAg3']
})
.expect(200);
expect(response.body.success).to.be.true;
const addressListResponse = await server.get(`/addresses?query=forwarded.1.addrtest`).expect(200);
expect(addressListResponse.body.total).to.equal(1);
});
});

111
test/api/certs-test.js Normal file
View file

@ -0,0 +1,111 @@
/*eslint no-unused-expressions: 0, prefer-arrow-callback: 0, no-console:0 */
'use strict';
const supertest = require('supertest');
const chai = require('chai');
const expect = chai.expect;
chai.config.includeStack = true;
const server = supertest.agent('http://localhost:8080');
describe('API Certs', function () {
let cert;
this.timeout(10000); // eslint-disable-line no-invalid-this
it('should POST /certs', async () => {
const response = await server
.post('/certs')
.send({
servername: 'example.com',
privateKey: `-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDUwsIjU4ItLme5
8bhvsdU3ifpYxCA0sz1GaDnUIeH62JW1XLR5dM9yh1vNCZNeMnZ2YASA7dh1+SNT
J8O46OWaIaDMt3PqBeYs8seuJDiHc1kZtsXWaoKQpeA0LkQsDMTpHm/m2j4lpMeI
+c0GjEcYYYupnbJPxfMW9Xzxmer7p1SduKl5g9x0Izicv6ZvuBnQSpaYQUruqIXU
CdK7RAVUinNU2Bjik/bg773CXvCgEf4QpJQdOMaGB45MWs61x3yv+qoIE7oSE36/
RsstJC9NbnavFhheitvc8rPJchq7LWMdADD/C/OdYXM3j9AyVdAbCMYpgcOLNdmG
+mGZUKexAgMBAAECggEBAMsH/9NOQY90FS/wZ5zPCzUwymIi5sjjsrmZhHXWz5td
S6ACk4bD3aLhYM1NMgBWD43vGt0eG86YrQkRjUjLly96n8Q73LWaY4jJNZwMnJVF
keVj8W8nvOjkIgwpioyussnzbb3SzjOGB5PDLc/t1XqCu5BlGF/f+pYSNeUoiIET
8xCMLQ7yWyTA7b8mL3Lx+ZJsW4nbugK7FwOnRktK+RQlAPiJsLCzFL+9AZPRrMom
Tq0z5F7iYGet5vdc/3IiEDG0sH7H51Gtjbc0sLT901Faw+fw2Ca9C6tuR30SFuNY
8SPt4ETViVdueSjuAzCnDAqjeHm7H9lWb8GGjXBfjHECgYEA92lxgXb6xwNSQ9Rx
1bCjlpoLNPvbxUYiKBeCNGUor5i1aLWd8hbF6nMCx6/2AzOFCzK1WxKEFfreY3bG
IyiJxxFYCWNoS2+dCM0IbbC4oY1VQzbagv3V8gGCh8to47dUDBV8nNE8Iqi03Hpk
WDVqk3jnzUQ77IYTcPjHCm+uw2sCgYEA3CVlxKiyRV6f/TboSfio8+jCm2Z/eYP1
UoaWBOwdpFzsOMn74MXtfwQhDm6tf0vjnEDFWWrS0d8lxGV4rSCMJT0o+sgZrs/2
D+MSZxLyqq+NewsqaEU6Hl13/Ic09xIP2Gz8Fk36ddl6f/MO9j/pAdhxF21jNSIW
/dlxnPvU5FMCgYEAt5TpIUyctlEzmJspwIsqR5SUHkOIBnCM5bzT43bwYqNocILa
6QiW4OloNa3OWP/Ah9eflC1AD2Mv4xP935az7R9keMrnV5pBJoek6meICG/rxU0N
hMc/Giyeo45+jQG6fqDu7xmeioUudq7miEFSjIzZS4mHAXFXOauPXaITRnMCgYA5
JSwJpJDCGRIGtN4PdZDF38HEfRLSBEMGLRF8LZ50L/rRsvzDGB3SPswl5uz6gkSP
JvETiPs4p2gyVvTAXBaFBB9DGfYwvqLs9NCuGOkNDYz4R6m2b2Hqx/CBiMdi6zlZ
wNCfKZa+SLnXxMw5d9WQORMCNc7u1+6H7o3jZiuZKQKBgF92xcje7ROjMas6bLru
XzoNjcESSn09LuY0Jmm6eq927QPWvr7HGpvHZJCtsoPhSAqoVVL2f4SlDfxko+NG
5RD9W3AE6jSBumZSpGD+3Pm1p/3fRAbrfOcKJai9O9/K3ZQi3aSQgRQgAhAUZ1C4
gWkJtB9ZKR6nboyDYCFNjfYw
-----END PRIVATE KEY-----
`,
cert: `-----BEGIN CERTIFICATE-----
MIIDADCCAegCCQCPXSqvTzty/zANBgkqhkiG9w0BAQsFADBCMRQwEgYDVQQDDAtl
eGFtcGxlLmNvbTEdMBsGA1UECgwUTXkgQ29tcGFueSBOYW1lIExURC4xCzAJBgNV
BAYTAlVTMB4XDTIxMTEzMDEzMjUyOFoXDTIyMTEzMDEzMjUyOFowQjEUMBIGA1UE
AwwLZXhhbXBsZS5jb20xHTAbBgNVBAoMFE15IENvbXBhbnkgTmFtZSBMVEQuMQsw
CQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANTCwiNT
gi0uZ7nxuG+x1TeJ+ljEIDSzPUZoOdQh4frYlbVctHl0z3KHW80Jk14ydnZgBIDt
2HX5I1Mnw7jo5ZohoMy3c+oF5izyx64kOIdzWRm2xdZqgpCl4DQuRCwMxOkeb+ba
PiWkx4j5zQaMRxhhi6mdsk/F8xb1fPGZ6vunVJ24qXmD3HQjOJy/pm+4GdBKlphB
Su6ohdQJ0rtEBVSKc1TYGOKT9uDvvcJe8KAR/hCklB04xoYHjkxazrXHfK/6qggT
uhITfr9Gyy0kL01udq8WGF6K29zys8lyGrstYx0AMP8L851hczeP0DJV0BsIximB
w4s12Yb6YZlQp7ECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAxpun+z6fLOW8xlWx
ej7XUmaI5emFC6wFSaGh3022ASvqS8TOR9qnY9yN+a1notLyqIiKUvoY4uvjPpk8
OAcMa6e7NRjsBQ/Zry3dxC88CCs4oR0SHeKy/4d3VmqUax5Ufn+X1+in+Sb4FDBD
rDnBTi9TJnAo8JMQ7FwkBFnMsieelX9IXLSsFE0yhz0U97r9B0JFcUEP0OsY9Tz0
NbFXanIpFENKxoXRzAvq0XlE3p446wIiUlIle/PXQpOx8s5Ae0eEmX0/2DY+1MZs
nBhCzyAvD7Z2TQjrszlbekiIeqTgN/D+r7WWJ3Urpf2NdfLOGNWTDe9cVgZlR85n
rp+tEw==
-----END CERTIFICATE-----
`,
description: 'Some text about this certificate',
sess: '12345',
ip: '127.0.0.1'
})
.expect(200);
expect(response.body.success).to.be.true;
expect(/^[0-9a-f]{24}$/.test(response.body.id)).to.be.true;
expect(response.body.servername).to.equal('example.com');
expect(response.body.altNames).to.deep.equal(['example.com']);
cert = response.body.id;
});
it('should GET /certs/:cert', async () => {
const response = await server.get(`/certs/${cert}`).expect(200);
expect(response.body.success).to.be.true;
expect(response.body.id).to.equal(cert);
});
it('should GET /certs/resolve/:servername', async () => {
const response = await server.get(`/certs/resolve/example.com`).expect(200);
expect(response.body.success).to.be.true;
expect(response.body.id).to.equal(cert);
});
it('should GET /certs', async () => {
const response = await server.get(`/certs`).expect(200);
expect(response.body.success).to.be.true;
expect(response.body.results.length).to.gte(1);
expect(response.body.results.find(entry => entry.id === cert)).to.exist;
});
it('should DELETE /certs/:cert', async () => {
const response = await server.delete(`/certs/${cert}`).expect(200);
expect(response.body.success).to.be.true;
});
});

62
test/api/dkim-test.js Normal file
View file

@ -0,0 +1,62 @@
/*eslint no-unused-expressions: 0, prefer-arrow-callback: 0, no-console:0 */
'use strict';
const supertest = require('supertest');
const chai = require('chai');
const expect = chai.expect;
chai.config.includeStack = true;
const server = supertest.agent('http://localhost:8080');
describe('API DKIM', function () {
let dkim;
this.timeout(10000); // eslint-disable-line no-invalid-this
it('should POST /dkim', async () => {
const response = await server
.post('/dkim')
.send({
domain: 'example.com',
selector: 'wildduck',
privateKey:
'-----BEGIN RSA PRIVATE KEY-----\nMIICXQIBAAKBgQDFCPszabID2MLAzzfja3/TboKp4dHUGSkl6hNSly7IRdAhfh6J\nh6vNa+2Y7pyNagX00ukycZ/03O/93X3UxjzX/NpLESo3GwSjp39R4AgdW91nKt7X\nzGoz4ZQELAao+AH1QhJ8vumXFLFc6sS9l7Eu3+cZcAdWij2TCPKrB56tMQIDAQAB\nAoGAAQNfz07e1Hg74CPwpKG74Yly8I6xtoZ+mKxQdx9B5VO+kz2DyK9C6eaBLUUk\n1vFRoIWpH1JIQUkVjtehuwNd8rgPacPZRjSJrGuvwtP/bjzA8m/z/lI0+rfQW7L7\nRfPoi2fl6MJ3KkjNypmVPPNvtJA42aPUDW6SFcXFvSv43gECQQD12RFLlZ5H3W6z\n2ncJXiZha508LoyABkYeb+veCFwicoNEreQrToDgC3GuBRkODsUgRZaVu2sa4tlv\nzO0rwkXRAkEAzSvmAxTvkSf/gMy5mO+sZKeUEtMHibF4LKxw7Men2oADgVTnS38r\nf8uYJteLt3lkfHfV5ezEOERvQutKnMfpYQJBAL7apceUvkyyBWfQWIrIMWl9vpHi\n3SXiOPsWDfjPap8/YNKnYDOSfQ/xMm5S/NFh+/yCqVVSKuKzavOVFiXbapECQQDC\nhWdK7rN/xRNaUz93/2xL9hHOkyNnacoNWOSrqVO8NnicSxoLmyNrw2SbFusRZdde\npuM2XfdffYqbQKd545OhAkBiCm/hUl5+hCJI6xl4wh3aR4h8j/TA6/u4ohPjqYco\nLUPpKBaWeKdwQRbkkpMsVz6lFtpyZlV6V8joGEd8OLMO\n-----END RSA PRIVATE KEY-----',
description: 'Some text about this DKIM certificate',
sess: '12345',
ip: '127.0.0.1'
})
.expect(200);
expect(response.body.success).to.be.true;
expect(/^[0-9a-f]{24}$/.test(response.body.id)).to.be.true;
dkim = response.body.id;
});
it('should GET /dkim/:dkim', async () => {
const response = await server.get(`/dkim/${dkim}`).expect(200);
expect(response.body.success).to.be.true;
expect(response.body.id).to.equal(dkim);
});
it('should GET /dkim/resolve/:domain', async () => {
const response = await server.get(`/dkim/resolve/example.com`).expect(200);
expect(response.body.success).to.be.true;
expect(response.body.id).to.equal(dkim);
});
it('should GET /dkim', async () => {
const response = await server.get(`/dkim`).expect(200);
expect(response.body.success).to.be.true;
expect(response.body.results.length).to.equal(1);
expect(response.body.results.find(entry => entry.id === dkim)).to.exist;
});
it('should DELETE /dkim/:dkim', async () => {
const response = await server.delete(`/dkim/${dkim}`).expect(200);
expect(response.body.success).to.be.true;
});
});

View file

@ -0,0 +1,59 @@
/*eslint no-unused-expressions: 0, prefer-arrow-callback: 0, no-console:0 */
'use strict';
const supertest = require('supertest');
const chai = require('chai');
const expect = chai.expect;
chai.config.includeStack = true;
const server = supertest.agent('http://localhost:8080');
describe('API DomainAliases', function () {
let domainalias;
this.timeout(10000); // eslint-disable-line no-invalid-this
it('should POST /domainaliases', async () => {
const response = await server
.post('/domainaliases')
.send({
domain: 'example.com',
alias: 'alias.example.com',
sess: '12345',
ip: '127.0.0.1'
})
.expect(200);
expect(response.body.success).to.be.true;
expect(/^[0-9a-f]{24}$/.test(response.body.id)).to.be.true;
domainalias = response.body.id;
});
it('should GET /domainaliases/:domainalias', async () => {
const response = await server.get(`/domainaliases/${domainalias}`).expect(200);
expect(response.body.success).to.be.true;
expect(response.body.id).to.equal(domainalias);
});
it('should GET /domainaliases/resolve/:domain', async () => {
const response = await server.get(`/domainaliases/resolve/alias.example.com`).expect(200);
expect(response.body.success).to.be.true;
expect(response.body.id).to.equal(domainalias);
});
it('should GET /domainaliases', async () => {
const response = await server.get(`/domainaliases?query=alias.example.com`).expect(200);
expect(response.body.success).to.be.true;
expect(response.body.results.length).to.gte(1);
expect(response.body.results.find(entry => entry.id === domainalias)).to.exist;
});
it('should DELETE /domainaliases/:domainalias', async () => {
const response = await server.delete(`/domainaliases/${domainalias}`).expect(200);
expect(response.body.success).to.be.true;
});
});

408
test/api/users-test.js Normal file
View file

@ -0,0 +1,408 @@
/*eslint no-unused-expressions: 0, prefer-arrow-callback: 0, no-console:0 */
'use strict';
const supertest = require('supertest');
const chai = require('chai');
const expect = chai.expect;
chai.config.includeStack = true;
const server = supertest.agent('http://localhost:8080');
describe('API Users', function () {
this.timeout(10000); // eslint-disable-line no-invalid-this
let user, user2, token;
it('should POST /users', async () => {
const response = await server
.post('/users')
.send({
username: 'myuser2',
name: 'John Smith',
address: 'john@example.com',
password: 'secretvalue',
hashedPassword: false,
emptyAddress: false,
language: 'et',
retention: 0,
targets: ['user@example.com', 'https://example.com/upload/email'],
spamLevel: 50,
quota: 1073741824,
recipients: 2000,
forwards: 2000,
requirePasswordChange: false,
imapMaxUpload: 5368709120,
imapMaxDownload: 21474836480,
pop3MaxDownload: 21474836480,
pop3MaxMessages: 300,
imapMaxConnections: 15,
receivedMax: 60,
fromWhitelist: ['user@alternative.domain', '*@example.com'],
tags: ['status:user', 'account:example.com'],
addTagsToAddress: false,
uploadSentMessages: false,
mailboxes: {
sent: 'Saadetud kirjad',
trash: 'Prügikast',
junk: 'Praht',
drafts: 'Mustandid'
},
disabledScopes: ['imap', 'pop3', 'smtp'],
metaData: {
accountIcon: 'avatar.png'
},
internalData: {
inTrial: true
},
pubKey: '-----BEGIN PGP PUBLIC KEY BLOCK-----\nVersion: Keybase OpenPGP v1.0.0\nComment: https://keybase.io/crypto\n\nxo0EYb0PqAEEANJtI/ivwudfCMmxm+a77Fll5YwSzaaI2nqhcp6pMRJ4l0aafsX3\nBcXUQpsyyELelt2xFtwTNygR4RFWVTn4OoXmO5zFtWCSegAwSyUNK7R/GXi2GTKk\nkYtxUwGcNKBkfY7yAn5KsaeuZL1feDXUGt0YHUmBds5i+6ylI+i4tNbRABEBAAHN\nH1dpbGQgRHVjayA8dGVzdEB3aWxkZHVjay5lbWFpbD7CrQQTAQoAFwUCYb0PqAIb\nLwMLCQcDFQoIAh4BAheAAAoJEJVLs8wf5gSCzBoD/3gz32OfJM1D4IrmKVwyLKxC\n1P81kL7E6ICWD2A0JF9EkojsMHl+/zagwoJejBQhmzTNkFmui5zwmdLGforKl303\ntB0l9vCTb5+eDDHOTUatJrvlw76Fz2ZjIhQTqD4xEM7MWx4xwTGY8bC5roIpdZJD\n9+vr81MXxiq9LZJDBXIyzo0EYb0PqAEEAL/uCTOrAncTRC/3cOQz+kLIzF4A9OTe\n6yxdNWWmx+uo9yJxnBv59Xz9qt8OT8Ih7SD/A4kFCuQqlyd0OFVhyd3KTAQ3CEml\nYOgL5jOE11YrEQjr36xPqO646JZuZIorKDf9PoIyipAMG89BlAoAjSXB1oeQADYn\n5fFLFVm1S7pLABEBAAHCwIMEGAEKAA8FAmG9D6gFCQ8JnAACGy4AqAkQlUuzzB/m\nBIKdIAQZAQoABgUCYb0PqAAKCRBhR/oKY9pg/YqnA/0Szmy4q4TnTBby+j57oXtn\nX/7H/xiaqlCd6bA3lbj3cPK4ybn/gnI4ECsfZfmSFG3T5C9EcZU0e9ByzimH6sxi\nOwPgKFWeJzpl5o8toR7m4wQVhv2NZRUukHe+2JH7nITS0gKeIBHMq2TbufcH6do1\n8s2G7XyLSd5Kkljxx7YmNiKoA/9CQ4l2WkARAFByyEJT9BEE4NBO0m0bI8sg0HRK\nGuP3FKcUu0Pz9R8AExEecofh8s4kaxofa2sbrTcK+L0p0hdR/39JWNuTJbxwEU3C\nA0mZKthjzL7seiRTG7Eny5gGenejRp2x0ziyMEaTgkvf44LPi06XiuE6FGnhElOc\nC7JoIc6NBGG9D6gBBADzW30GOysnqYkexL+bY9o+ai1mL+X58GPLilXJ5WXgEEdf\n8Pg/9jlEOzOnWTTgJAQDGHtwm0duKmK7EJGozLEY94QGOzRjAir6tMF2OYDQIDgj\nAoXavPAc5chFABEVUS12hUPPLoW6YgvaIb3AAZbIM8603BLXTaLGbtZ0z7eYxwAR\nAQABwsCDBBgBCgAPBQJhvQ+oBQkPCZwAAhsuAKgJEJVLs8wf5gSCnSAEGQEKAAYF\nAmG9D6gACgkQ58zrS0TNGbAiVAP/UIxYiSdoHDnBW5qB7onEiUVL5ZFk1Xk+NB0z\n7jOm1oAV0RH8I5NRQBtZ+75xar0vPTX122IdkgpaiNT0wy5Kd/2vz4LKVK9apyJI\neaZ+D7dt5Ipu1p0lWtglqL0xtjOSWuwHFwHuiRYg6eyhGN1RylFpuiKi5KykhrBS\nuBL/BHrk6AP/boRA+KIlb6s19KHNt54Kl8n8G4ZApCwZbUc2jzvbP5DZL5rcjlHd\ns4i4XE+uIJxsiX3iJZtVXzhTKuQlaoEljlhPs/TZYUmxeJ3TdV4o7emWiZ4gE8EQ\nhfxV37ew/GoYm6yME3tAZLIXbv2+bj6HZ4eE8bAMmPvpcQ+UwNJXvnk=\n=dR+x\n-----END PGP PUBLIC KEY BLOCK-----',
encryptMessages: false,
encryptForwarded: false
})
.expect(200);
expect(response.body.success).to.be.true;
expect(/^[0-9a-f]{24}$/.test(response.body.id)).to.be.true;
user = response.body.id;
});
it('should POST /authenticate', async () => {
const authResponse = await server
.post('/authenticate')
.send({
username: 'myuser2',
password: 'secretvalue'
})
.expect(200);
expect(authResponse.body.success).to.be.true;
expect(authResponse.body).to.deep.equal({
success: true,
id: user,
username: 'myuser2',
scope: 'master',
require2fa: false,
requirePasswordChange: false
});
});
it('should POST /authenticate and fail', async () => {
const authResponse = await server
.post('/authenticate')
.send({
username: 'myuser2',
password: 'invalidpass'
})
.expect(403);
expect(authResponse.body.code).to.equal('AuthFailed');
});
it('should POST /users and fail - invalid username', async () => {
const response = await server
.post('/users')
.send({
username: 'ömyuser2',
name: 'John Smith',
password: 'secretvalue'
})
.expect(400);
expect(response.body.details.username).to.exist;
});
it('should POST /authenticate and request a token', async () => {
const authResponse = await server
.post('/authenticate')
.send({
username: 'myuser2',
password: 'secretvalue',
token: true
})
.expect(200);
expect(authResponse.body.success).to.be.true;
expect(authResponse.body.token).to.exist;
token = authResponse.body.token;
});
it('should POST /users with hashed password', async () => {
const response = await server
.post('/users')
.send({
username: 'myuser2hash',
name: 'John Smith',
// password: 'test',
password: '$argon2i$v=19$m=16,t=2,p=1$SFpGczI1bWV1RVRpYjNYaw$EBE/WnOGeWint3eQ+SQ7Sg',
hashedPassword: true
})
.expect(200);
expect(response.body.success).to.be.true;
user2 = response.body.id;
const authResponse = await server
.post('/authenticate')
.send({
username: 'myuser2hash',
password: 'test'
})
.expect(200);
expect(authResponse.body.success).to.be.true;
expect(authResponse.body).to.deep.equal({
success: true,
id: user2,
username: 'myuser2hash',
scope: 'master',
require2fa: false,
requirePasswordChange: false
});
});
it('should GET /users/resolve/{username}', async () => {
const response = await server.get('/users/resolve/myuser2').expect(200);
expect(response.body).to.deep.equal({
success: true,
id: user
});
});
it('should GET /users/resolve/{username} and fail', async () => {
const response = await server.get('/users/resolve/myuser2invalid').expect(404);
expect(response.body.code).to.equal('UserNotFound');
});
it('should GET /users', async () => {
const response = await server.get('/users?query=myuser2').expect(200);
expect(response.body.success).to.be.true;
expect(response.body.results.find(entry => entry.id === user)).to.exist;
});
it('should GET /users/{user}', async () => {
let response = await server.get(`/users/${user}`).expect(200);
expect(response.body.success).to.be.true;
expect(response.body.id).to.equal(user);
});
it('should GET /users/{user} using a token', async () => {
let response = await server.get(`/users/${user}?accessToken=${token}`).expect(200);
expect(response.body.success).to.be.true;
expect(response.body.id).to.equal(user);
});
it('should GET /users/me using a token', async () => {
let response = await server.get(`/users/me?accessToken=${token}`).expect(200);
expect(response.body.success).to.be.true;
expect(response.body.id).to.equal(user);
});
it('should GET /users/{user} using a token and fail against other user', async () => {
let response = await server.get(`/users/${user2}?accessToken=${token}`);
expect(response.body.code).to.equal('MissingPrivileges');
});
it('should DELETE /authenticate', async () => {
let response = await server.delete(`/authenticate?accessToken=${token}`).expect(200);
expect(response.body.success).to.be.true;
});
it('should DELETE /authenticate with false', async () => {
// token is not valid anymore
await server.delete(`/authenticate?accessToken=${token}`).expect(403);
});
it('should PUT /users/{user}', async () => {
const name = 'John Smith 2';
// update user data
const response = await server
.put(`/users/${user}`)
.send({
name
})
.expect(200);
expect(response.body.success).to.be.true;
// request and verify
let getResponse = await server.get(`/users/${user}`);
expect(getResponse.body.success).to.be.true;
expect(getResponse.body.id).to.equal(user);
expect(getResponse.body.name).to.equal(name);
});
it('should PUT /users/{user} and renew a token', async () => {
const authResponse1 = await server
.post('/authenticate')
.send({
username: 'myuser2',
password: 'secretvalue',
token: true
})
.expect(200);
expect(authResponse1.body.success).to.be.true;
expect(authResponse1.body.token).to.exist;
let token1 = authResponse1.body.token;
const authResponse2 = await server
.post('/authenticate')
.send({
username: 'myuser2',
password: 'secretvalue',
token: true
})
.expect(200);
expect(authResponse2.body.success).to.be.true;
expect(authResponse2.body.token).to.exist;
let token2 = authResponse2.body.token;
// try out token 1
let getResponse1 = await server.get(`/users/me?accessToken=${token1}`).expect(200);
expect(getResponse1.body.success).to.be.true;
expect(getResponse1.body.id).to.equal(user);
// try out token 2
let getResponse2 = await server.get(`/users/me?accessToken=${token2}`).expect(200);
expect(getResponse2.body.success).to.be.true;
expect(getResponse2.body.id).to.equal(user);
// update password using a token
const response = await server
.put(`/users/me?accessToken=${token1}`)
.send({
password: 'secretvalue'
})
.expect(200);
expect(response.body.success).to.be.true;
// try out token 1, should have been renewed
let getResponse3 = await server.get(`/users/me?accessToken=${token1}`).expect(200);
expect(getResponse3.body.success).to.be.true;
expect(getResponse3.body.id).to.equal(user);
// try out token 2, should fail as it was not renewed
await server.get(`/users/me?accessToken=${token2}`).expect(403);
});
it('should PUT /users/{user}/logout', async () => {
// request logout
const response = await server.put(`/users/${user}/logout`).send({ reason: 'Just because' }).expect(200);
expect(response.body.success).to.be.true;
});
it('should POST /users/{user}/quota/reset', async () => {
const response = await server.post(`/users/${user}/quota/reset`).send({}).expect(200);
expect(response.body.success).to.be.true;
expect(response.body.storageUsed).to.exist;
expect(response.body.previousStorageUsed).to.exist;
});
it('should POST /quota/reset', async () => {
const response = await server.post(`/quota/reset`).send({}).expect(200);
expect(response.body.success).to.be.true;
expect(response.body.task).to.exist;
});
it('should POST /users/{user}/password/reset', async () => {
const response = await server.post(`/users/${user}/password/reset`).send({}).expect(200);
expect(response.body.success).to.be.true;
expect(response.body.password).to.exist;
const authResponse = await server
.post('/authenticate')
.send({
username: 'myuser2',
password: response.body.password
})
.expect(200);
expect(authResponse.body.success).to.be.true;
expect(authResponse.body).to.deep.equal({
success: true,
id: user,
username: 'myuser2',
scope: 'master',
require2fa: false,
// using a temporary password requires a password change
requirePasswordChange: true
});
});
it('should POST /users/{user}/password/reset using a future date', async () => {
const response = await server
.post(`/users/${user}/password/reset`)
.send({
validAfter: new Date(Date.now() + 1 * 3600 * 1000).toISOString()
})
.expect(200);
expect(response.body.success).to.be.true;
expect(response.body.password).to.exist;
// password not yet valid
await server
.post('/authenticate')
.send({
username: 'myuser2',
password: response.body.password
})
.expect(403);
});
it('should DELETE /users/{user}', async () => {
// first set the user password
const passwordUpdateResponse = await server
.put(`/users/${user}`)
.send({
password: 'secretvalue',
ip: '1.2.3.5'
})
.expect(200);
expect(passwordUpdateResponse.body.success).to.be.true;
// Delete user
const response = await server.delete(`/users/${user}?deleteAfter=${encodeURIComponent(new Date(Date.now() + 3600 * 1000).toISOString())}`).expect(200);
expect(response.body.success).to.be.true;
expect(response.body.addresses.deleted).to.gte(1);
expect(response.body.task).to.exist;
// Try to authenticate, should fail
await server
.post('/authenticate')
.send({
username: 'myuser2',
password: 'secretvalue'
})
.expect(403);
});
it('should GET /users/{user}/restore', async () => {
const response = await server.get(`/users/${user}/restore`).expect(200);
expect(response.body.success).to.be.true;
expect(response.body.username).to.equal('myuser2');
expect(response.body.recoverableAddresses).to.deep.equal(['john@example.com']);
});
it('should POST /users/{user}/restore', async () => {
const response = await server.post(`/users/${user}/restore`).send({}).expect(200);
expect(response.body.success).to.be.true;
expect(response.body.addresses.recovered).to.gte(1);
expect(response.body.addresses.main).to.equal('john@example.com');
});
});