Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bb10d6ed71 | |||
| 304d9dafb8 | |||
| 9771de5d97 | |||
| b317ac8258 | |||
| 7f8060dd14 | |||
| 7d48887428 | |||
| 6f6afa1b6a | |||
| 3634e47794 | |||
| 5be39bc271 | |||
| 6b55d7585c | |||
| 8e4b3f9a4a | |||
| 15cf0c3c19 |
@@ -2,7 +2,6 @@
|
||||
"node": true,
|
||||
"browser": true,
|
||||
"unused": true,
|
||||
"multistr": true,
|
||||
"globalstrict": true,
|
||||
"predef": [ "angular", "$" ],
|
||||
"esnext": true
|
||||
|
||||
@@ -1210,16 +1210,3 @@
|
||||
* Report dependency error for clone if backup or domain was not found
|
||||
* Enable auto-updates for major versions
|
||||
|
||||
[2.0.0]
|
||||
* Multi-domain support
|
||||
* Update Haraka to 2.8.18
|
||||
* Split box and app autoupdate pattern settings
|
||||
* Stop and disable any pre-installed postfix server
|
||||
* Migrate altDomain as a manual DNS provider
|
||||
* Use node's native dns resolve instead of dig
|
||||
* DNS records can now be a A record or a CNAME record
|
||||
* Fix generation of fallback certificates to include naked domain
|
||||
* Merge multi-string DKIM records
|
||||
* scheduler: do not start cron jobs all at once
|
||||
* scheduler: give cron jobs a grace period of 30 minutes to complete
|
||||
|
||||
|
||||
@@ -59,6 +59,6 @@ the containers in the Cloudron.
|
||||
|
||||
## Community
|
||||
|
||||
* [Forum](https://forum.cloudron.io/)
|
||||
* [Chat](https://chat.cloudron.io/)
|
||||
* [Support](mailto:support@cloudron.io)
|
||||
|
||||
|
||||
@@ -105,7 +105,3 @@ systemctl disable bind9 || true
|
||||
systemctl stop dnsmasq || true
|
||||
systemctl disable dnsmasq || true
|
||||
|
||||
# on ssdnodes postfix seems to run by default
|
||||
systemctl stop postfix || true
|
||||
systemctl disable postfix || true
|
||||
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.runSql('SELECT * FROM settings WHERE name=?', ['autoupdate_pattern'], function (error, results) {
|
||||
if (error || results.length === 0) return callback(error); // will use defaults from box code
|
||||
|
||||
// migrate the 'daily' update pattern
|
||||
var appUpdatePattern = results[0].value;
|
||||
if (appUpdatePattern === '00 00 1,3,5,23 * * *') appUpdatePattern = '00 30 1,3,5,23 * * *';
|
||||
|
||||
async.series([
|
||||
db.runSql.bind(db, 'START TRANSACTION;'),
|
||||
db.runSql.bind(db, 'DELETE FROM settings WHERE name=?', ['autoupdate_pattern']),
|
||||
db.runSql.bind(db, 'INSERT settings (name, value) VALUES(?, ?)', ['app_autoupdate_pattern', appUpdatePattern]),
|
||||
db.runSql.bind(db, 'COMMIT')
|
||||
], callback);
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function(db, callback) {
|
||||
callback();
|
||||
};
|
||||
@@ -1,121 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async'),
|
||||
crypto = require('crypto'),
|
||||
fs = require('fs'),
|
||||
os = require('os'),
|
||||
path = require('path'),
|
||||
safe = require('safetydance'),
|
||||
tldjs = require('tldjs');
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.all('SELECT * FROM apps', function (error, apps) {
|
||||
if (error) return callback(error);
|
||||
|
||||
async.eachSeries(apps, function (app, callback) {
|
||||
if (!app.altDomain) {
|
||||
console.log('App %s does not use altDomain, skip', app.id);
|
||||
return callback();
|
||||
}
|
||||
|
||||
const domain = tldjs.getDomain(app.altDomain);
|
||||
const subdomain = tldjs.getSubdomain(app.altDomain);
|
||||
const mailboxName = (subdomain ? subdomain : JSON.parse(app.manifestJson).title.toLowerCase().replace(/[^a-zA-Z0-9]/g, '')) + '.app';
|
||||
|
||||
console.log('App %s is on domain %s and subdomain %s with mailbox', app.id, domain, subdomain, mailboxName);
|
||||
|
||||
async.series([
|
||||
// Add domain if not exists
|
||||
function (callback) {
|
||||
const query = 'INSERT INTO domains (domain, zoneName, provider, configJson, tlsConfigJson) VALUES (?, ?, ?, ?, ?)';
|
||||
const args = [ domain, domain, 'manual', JSON.stringify({}), JSON.stringify({ provider: 'letsencrypt-prod' }) ];
|
||||
|
||||
db.runSql(query, args, function (error) {
|
||||
if (error && error.code !== 'ER_DUP_ENTRY') return callback(error);
|
||||
|
||||
console.log('Added domain %s', domain);
|
||||
|
||||
// ensure we have a fallback cert for the newly added domain. This is the same as in reverseproxy.js
|
||||
// WARNING this will only work on the cloudron itself not during local testing!
|
||||
const certFilePath = `/home/yellowtent/boxdata/certs/${domain}.host.cert`;
|
||||
const keyFilePath = `/home/yellowtent/boxdata/certs/${domain}.host.key`;
|
||||
|
||||
if (!fs.existsSync(certFilePath) || !fs.existsSync(keyFilePath)) { // generate it
|
||||
let opensslConf = safe.fs.readFileSync('/etc/ssl/openssl.cnf', 'utf8');
|
||||
let opensslConfWithSan = `${opensslConf}\n[SAN]\nsubjectAltName=DNS:${domain}\n`;
|
||||
let configFile = path.join(os.tmpdir(), 'openssl-' + crypto.randomBytes(4).readUInt32LE(0) + '.conf');
|
||||
let certCommand = `openssl req -x509 -newkey rsa:2048 -keyout ${keyFilePath} -out ${certFilePath} -days 3650 -subj /CN=*.${domain} -extensions SAN -config ${configFile} -nodes`;
|
||||
|
||||
safe.fs.writeFileSync(configFile, opensslConfWithSan, 'utf8');
|
||||
if (!safe.child_process.execSync(certCommand)) return callback(safe.error.message);
|
||||
safe.fs.unlinkSync(configFile);
|
||||
}
|
||||
|
||||
callback();
|
||||
});
|
||||
},
|
||||
// Add domain to mail table if not exists
|
||||
function (callback) {
|
||||
const query = 'INSERT INTO mail (domain, enabled, mailFromValidation, catchAllJson, relayJson) VALUES (?, ?, ?, ?, ?)';
|
||||
const args = [ domain, 0, 1, '[]', JSON.stringify({ provider: 'cloudron-smtp' }) ];
|
||||
|
||||
db.runSql(query, args, function (error) {
|
||||
if (error && error.code !== 'ER_DUP_ENTRY') return callback(error);
|
||||
|
||||
console.log('Added domain %s to mail table', domain);
|
||||
|
||||
callback();
|
||||
});
|
||||
},
|
||||
// Remove old mailbox record if any
|
||||
function (callback) {
|
||||
const query = 'DELETE FROM mailboxes WHERE ownerId=?';
|
||||
const args = [ app.id ];
|
||||
|
||||
db.runSql(query, args, function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
console.log('Cleaned up mailbox record for app %s', app.id);
|
||||
|
||||
callback();
|
||||
});
|
||||
},
|
||||
// Add new mailbox record
|
||||
function (callback) {
|
||||
const query = 'INSERT INTO mailboxes (name, domain, ownerId, ownerType) VALUES (?, ?, ?, ?)';
|
||||
const args = [ mailboxName, domain, app.id, 'app' /* mailboxdb.TYPE_APP */ ];
|
||||
|
||||
db.runSql(query, args, function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
console.log('Added mailbox record for app %s', app.id);
|
||||
|
||||
callback();
|
||||
});
|
||||
},
|
||||
// Update app record
|
||||
function (callback) {
|
||||
const query = 'UPDATE apps SET location=?, domain=?, altDomain=? WHERE id=?';
|
||||
const args = [ subdomain, domain, '', app.id ];
|
||||
|
||||
db.runSql(query, args, function (error) {
|
||||
if (error) return error;
|
||||
|
||||
console.log('Updated app %s with new domain', app.id);
|
||||
|
||||
callback();
|
||||
});
|
||||
}
|
||||
], callback);
|
||||
}, function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
// finally drop the altDomain db field
|
||||
db.runSql('ALTER TABLE apps DROP COLUMN altDomain', [], callback);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function(db, callback) {
|
||||
db.runSql('ALTER TABLE apps ADD COLUMN altDomain VARCHAR(256)', [], callback);
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
async.series([
|
||||
db.runSql.bind(db, 'START TRANSACTION;'),
|
||||
db.runSql.bind(db, 'ALTER TABLE mailboxes DROP FOREIGN KEY mailboxes_domain_constraint'),
|
||||
db.runSql.bind(db, 'ALTER TABLE mailboxes ADD CONSTRAINT mailboxes_domain_constraint FOREIGN KEY(domain) REFERENCES mail(domain)'),
|
||||
db.runSql.bind(db, 'COMMIT')
|
||||
], callback);
|
||||
};
|
||||
|
||||
exports.down = function(db, callback) {
|
||||
db.runSql('ALTER TABLE mailboxes DROP FOREIGN KEY mailboxes_domain_constraint', function (error) {
|
||||
if (error) console.error(error);
|
||||
callback(error);
|
||||
});
|
||||
};
|
||||
+18
-16
@@ -72,6 +72,7 @@ CREATE TABLE IF NOT EXISTS apps(
|
||||
createdAt TIMESTAMP(2) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updatedAt TIMESTAMP(2) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
memoryLimit BIGINT DEFAULT 0,
|
||||
altDomain VARCHAR(256),
|
||||
xFrameOptions VARCHAR(512),
|
||||
sso BOOLEAN DEFAULT 1, // whether user chose to enable SSO
|
||||
debugModeJson TEXT, // options for development mode
|
||||
@@ -80,8 +81,8 @@ CREATE TABLE IF NOT EXISTS apps(
|
||||
|
||||
// the following fields do not belong here, they can be removed when we use a queue for apptask
|
||||
restoreConfigJson VARCHAR(256), // used to pass backupId to restore from to apptask
|
||||
oldConfigJson TEXT, // used to pass old config to apptask (configure, restore)
|
||||
updateConfigJson TEXT, // used to pass new config to apptask (update)
|
||||
oldConfigJson TEXT, // used to pass old config for apptask (configure, restore)
|
||||
updateConfigJson TEXT, // used to pass new config for apptask (update)
|
||||
|
||||
FOREIGN KEY(domain) REFERENCES domains(domain),
|
||||
PRIMARY KEY(id));
|
||||
@@ -133,6 +134,21 @@ CREATE TABLE IF NOT EXISTS eventlog(
|
||||
|
||||
PRIMARY KEY (id));
|
||||
|
||||
/* Future fields:
|
||||
* accessRestriction - to determine who can access it. So this has foreign keys
|
||||
* quota - per mailbox quota
|
||||
*/
|
||||
CREATE TABLE IF NOT EXISTS mailboxes(
|
||||
name VARCHAR(128) NOT NULL,
|
||||
ownerId VARCHAR(128) NOT NULL, /* app id or user id or group id */
|
||||
ownerType VARCHAR(16) NOT NULL, /* 'app' or 'user' or 'group' */
|
||||
aliasTarget VARCHAR(128), /* the target name type is an alias */
|
||||
creationTime TIMESTAMP,
|
||||
domain VARCHAR(128),
|
||||
|
||||
FOREIGN KEY(domain) REFERENCES domains(domain),
|
||||
UNIQUE (name, domain));
|
||||
|
||||
CREATE TABLE IF NOT EXISTS domains(
|
||||
domain VARCHAR(128) NOT NULL UNIQUE, /* if this needs to be larger, InnoDB has a limit of 767 bytes for PRIMARY KEY values! */
|
||||
zoneName VARCHAR(128) NOT NULL, /* this mostly contains the domain itself again */
|
||||
@@ -158,18 +174,4 @@ CREATE TABLE IF NOT EXISTS mail(
|
||||
|
||||
CHARACTER SET utf8 COLLATE utf8_bin;
|
||||
|
||||
/* Future fields:
|
||||
* accessRestriction - to determine who can access it. So this has foreign keys
|
||||
* quota - per mailbox quota
|
||||
*/
|
||||
CREATE TABLE IF NOT EXISTS mailboxes(
|
||||
name VARCHAR(128) NOT NULL,
|
||||
ownerId VARCHAR(128) NOT NULL, /* app id or user id or group id */
|
||||
ownerType VARCHAR(16) NOT NULL, /* 'app' or 'user' or 'group' */
|
||||
aliasTarget VARCHAR(128), /* the target name type is an alias */
|
||||
creationTime TIMESTAMP,
|
||||
domain VARCHAR(128),
|
||||
|
||||
FOREIGN KEY(domain) REFERENCES mail(domain),
|
||||
UNIQUE (name, domain));
|
||||
|
||||
|
||||
Generated
+225
-252
@@ -5,76 +5,117 @@
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@google-cloud/common": {
|
||||
"version": "0.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-0.16.1.tgz",
|
||||
"integrity": "sha512-1sufDsSfgJ7fuBLq+ux8t3TlydMlyWl9kPZx2WdLINkGtf5RjvXX6EWYZiCMKe8flJ3oC0l95j5atN2uX5n3rg==",
|
||||
"version": "0.13.6",
|
||||
"resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-0.13.6.tgz",
|
||||
"integrity": "sha1-qdjhN7xCmkSrqWif5qDkMxeE+FM=",
|
||||
"requires": {
|
||||
"array-uniq": "1.0.3",
|
||||
"arrify": "1.0.1",
|
||||
"concat-stream": "1.6.0",
|
||||
"create-error-class": "3.0.2",
|
||||
"duplexify": "3.5.3",
|
||||
"duplexify": "3.5.1",
|
||||
"ent": "2.2.0",
|
||||
"extend": "3.0.1",
|
||||
"google-auto-auth": "0.9.4",
|
||||
"google-auto-auth": "0.7.2",
|
||||
"is": "3.2.1",
|
||||
"log-driver": "1.2.5",
|
||||
"methmeth": "1.1.0",
|
||||
"modelo": "4.2.3",
|
||||
"modelo": "4.2.0",
|
||||
"request": "2.83.0",
|
||||
"retry-request": "3.3.1",
|
||||
"split-array-stream": "1.0.3",
|
||||
"stream-events": "1.0.2",
|
||||
"string-format-obj": "1.1.1",
|
||||
"string-format-obj": "1.1.0",
|
||||
"through2": "2.0.3"
|
||||
}
|
||||
},
|
||||
"@google-cloud/dns": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@google-cloud/dns/-/dns-0.7.1.tgz",
|
||||
"integrity": "sha512-rOq14I6vUc/Fd+8/cY4wCd6af8/+eN2wTIUSAQQM3zccn3HxgmG9mW54Wuq2iI/LBUYP5Z/+H4MNj3ZrkRIxIA==",
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@google-cloud/dns/-/dns-0.7.0.tgz",
|
||||
"integrity": "sha512-WL9PXmWiXOAKWq06nLzrMvJdumqFGOsB9q1f/DeCK7a7b/iihkyuQGNsSvpy0pf2FPYmDc8qEFtWjRw9xIApuw==",
|
||||
"requires": {
|
||||
"@google-cloud/common": "0.16.1",
|
||||
"@google-cloud/common": "0.13.6",
|
||||
"arrify": "1.0.1",
|
||||
"dns-zonefile": "0.2.2",
|
||||
"extend": "3.0.1",
|
||||
"is": "3.2.1",
|
||||
"methmeth": "1.1.0",
|
||||
"string-format-obj": "1.1.1"
|
||||
"string-format-obj": "1.1.0"
|
||||
}
|
||||
},
|
||||
"@google-cloud/storage": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-1.6.0.tgz",
|
||||
"integrity": "sha512-yQ63bJYoiwY220gn/KdTLPoHppAPwFHfG7VFLPwJ+1R5U1eqUN5XV2a7uPj1szGF8/gxlKm2UbE8DgoJJ76DFw==",
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-1.5.1.tgz",
|
||||
"integrity": "sha512-7yaYzFWQYgi+n1dw/i5ysgzHD3ACJndxR8CbFnunQNT3NduolYSTr5WbYwWdhN5/7QiKx9g+obgP4tVz904bXw==",
|
||||
"requires": {
|
||||
"@google-cloud/common": "0.16.1",
|
||||
"@google-cloud/common": "0.15.1",
|
||||
"arrify": "1.0.1",
|
||||
"async": "2.6.0",
|
||||
"compressible": "2.0.13",
|
||||
"concat-stream": "1.6.0",
|
||||
"create-error-class": "3.0.2",
|
||||
"duplexify": "3.5.3",
|
||||
"duplexify": "3.5.1",
|
||||
"extend": "3.0.1",
|
||||
"gcs-resumable-upload": "0.9.0",
|
||||
"gcs-resumable-upload": "0.8.2",
|
||||
"hash-stream-validation": "0.2.1",
|
||||
"is": "3.2.1",
|
||||
"mime": "2.2.0",
|
||||
"mime-types": "2.1.17",
|
||||
"once": "1.4.0",
|
||||
"pumpify": "1.4.0",
|
||||
"pumpify": "1.3.5",
|
||||
"request": "2.83.0",
|
||||
"safe-buffer": "5.1.1",
|
||||
"snakeize": "0.1.0",
|
||||
"stream-events": "1.0.2",
|
||||
"string-format-obj": "1.1.1",
|
||||
"string-format-obj": "1.1.0",
|
||||
"through2": "2.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"mime": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.2.0.tgz",
|
||||
"integrity": "sha512-0Qz9uF1ATtl8RKJG4VRfOymh7PyEor6NbrI/61lRfuRe4vx9SNATrvAeTj2EWVRKjEQGskrzWkJBBY5NbaVHIA=="
|
||||
"@google-cloud/common": {
|
||||
"version": "0.15.1",
|
||||
"resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-0.15.1.tgz",
|
||||
"integrity": "sha512-cnVtHLvyiSQvb1RzXWDp7PA1sA8Jmc47+wp/xwHwdGOlQZfKog5iluZ0C/LB8iklFXpcTwlNMorqLuZ/qH0DDA==",
|
||||
"requires": {
|
||||
"array-uniq": "1.0.3",
|
||||
"arrify": "1.0.1",
|
||||
"concat-stream": "1.6.0",
|
||||
"create-error-class": "3.0.2",
|
||||
"duplexify": "3.5.1",
|
||||
"ent": "2.2.0",
|
||||
"extend": "3.0.1",
|
||||
"google-auto-auth": "0.8.1",
|
||||
"is": "3.2.1",
|
||||
"log-driver": "1.2.5",
|
||||
"methmeth": "1.1.0",
|
||||
"modelo": "4.2.0",
|
||||
"request": "2.83.0",
|
||||
"retry-request": "3.3.1",
|
||||
"split-array-stream": "1.0.3",
|
||||
"stream-events": "1.0.2",
|
||||
"string-format-obj": "1.1.0",
|
||||
"through2": "2.0.3"
|
||||
}
|
||||
},
|
||||
"google-auth-library": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-0.12.0.tgz",
|
||||
"integrity": "sha512-79qCXtJ1VweBmmLr4yLq9S4clZB2p5Y+iACvuKk9gu4JitEnPc+bQFmYvtCYehVR44MQzD1J8DVmYW2w677IEw==",
|
||||
"requires": {
|
||||
"gtoken": "1.2.3",
|
||||
"jws": "3.1.4",
|
||||
"lodash.isstring": "4.0.1",
|
||||
"lodash.merge": "4.6.0",
|
||||
"request": "2.83.0"
|
||||
}
|
||||
},
|
||||
"google-auto-auth": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/google-auto-auth/-/google-auto-auth-0.8.1.tgz",
|
||||
"integrity": "sha512-v5a4mHIkhvbtKNILxnOYgOw+cin/jfLR0pEj1ids2jn9p0OyxYUXjSJbCEciuAorQao9Y55w0zJIc8yW1rIPaA==",
|
||||
"requires": {
|
||||
"async": "2.6.0",
|
||||
"gcp-metadata": "0.3.1",
|
||||
"google-auth-library": "0.12.0",
|
||||
"request": "2.83.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -118,11 +159,11 @@
|
||||
}
|
||||
},
|
||||
"JSONStream": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.2.tgz",
|
||||
"integrity": "sha1-wQI3G27Dp887hHygDCC7D85Mbeo=",
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-0.10.0.tgz",
|
||||
"integrity": "sha1-dDSdDYlSK3HzDwoD/5vSDKbxKsA=",
|
||||
"requires": {
|
||||
"jsonparse": "1.3.1",
|
||||
"jsonparse": "0.0.5",
|
||||
"through": "2.3.8"
|
||||
}
|
||||
},
|
||||
@@ -373,11 +414,12 @@
|
||||
}
|
||||
},
|
||||
"aws-sdk": {
|
||||
"version": "2.201.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.201.0.tgz",
|
||||
"integrity": "sha1-ekUoJi2Ql6vjfr9Oa/nRhR20cAM=",
|
||||
"version": "2.170.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.170.0.tgz",
|
||||
"integrity": "sha1-T5z9JqJo72opadNHXtgTS7OJlSU=",
|
||||
"requires": {
|
||||
"buffer": "4.9.1",
|
||||
"crypto-browserify": "1.0.9",
|
||||
"events": "1.1.1",
|
||||
"jmespath": "0.15.0",
|
||||
"querystring": "0.2.0",
|
||||
@@ -386,13 +428,6 @@
|
||||
"uuid": "3.1.0",
|
||||
"xml2js": "0.4.17",
|
||||
"xmlbuilder": "4.2.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"uuid": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",
|
||||
"integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"aws-sign2": {
|
||||
@@ -405,15 +440,6 @@
|
||||
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz",
|
||||
"integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4="
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.17.1",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.17.1.tgz",
|
||||
"integrity": "sha1-LY4+XQvb1zJ/kbyBT1xXZg+Bgk0=",
|
||||
"requires": {
|
||||
"follow-redirects": "1.4.1",
|
||||
"is-buffer": "1.1.6"
|
||||
}
|
||||
},
|
||||
"backoff": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/backoff/-/backoff-2.5.0.tgz",
|
||||
@@ -443,9 +469,9 @@
|
||||
}
|
||||
},
|
||||
"base64-js": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.3.tgz",
|
||||
"integrity": "sha512-MsAhsUW1GxCdgYSO6tAfZrNapmUKk7mWx/k5mFY/A1gBtkaCaNapTg+FExCw1r9yeaZhqx/xPg43xgTFH6KL5w=="
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz",
|
||||
"integrity": "sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw=="
|
||||
},
|
||||
"base64url": {
|
||||
"version": "2.0.0",
|
||||
@@ -599,7 +625,7 @@
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
|
||||
"integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
|
||||
"requires": {
|
||||
"base64-js": "1.2.3",
|
||||
"base64-js": "1.2.1",
|
||||
"ieee754": "1.1.8",
|
||||
"isarray": "1.0.0"
|
||||
}
|
||||
@@ -625,10 +651,21 @@
|
||||
"resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.12.tgz",
|
||||
"integrity": "sha1-8VDw9nSKvdcq6uhPBEA74u8RN5c=",
|
||||
"requires": {
|
||||
"dtrace-provider": "0.8.6",
|
||||
"dtrace-provider": "0.8.5",
|
||||
"moment": "2.19.4",
|
||||
"mv": "2.1.1",
|
||||
"safe-json-stringify": "1.1.0"
|
||||
"safe-json-stringify": "1.0.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"dtrace-provider": {
|
||||
"version": "0.8.5",
|
||||
"resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.5.tgz",
|
||||
"integrity": "sha1-mOu6Ihr6xG4cOf02hY2Pk2dSS5I=",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"nan": "2.8.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"bytes": {
|
||||
@@ -825,7 +862,6 @@
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
|
||||
"integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"string-width": "1.0.2",
|
||||
"strip-ansi": "3.0.1",
|
||||
@@ -836,7 +872,6 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
|
||||
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"code-point-at": "1.1.0",
|
||||
"is-fullwidth-code-point": "1.0.0",
|
||||
@@ -1009,21 +1044,6 @@
|
||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
|
||||
"integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY="
|
||||
},
|
||||
"compressible": {
|
||||
"version": "2.0.13",
|
||||
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.13.tgz",
|
||||
"integrity": "sha1-DRAgq5JLL9tNYnmHXH1tq6a6p6k=",
|
||||
"requires": {
|
||||
"mime-db": "1.33.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"mime-db": {
|
||||
"version": "1.33.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz",
|
||||
"integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
@@ -1055,7 +1075,7 @@
|
||||
"requires": {
|
||||
"dot-prop": "4.2.0",
|
||||
"graceful-fs": "4.1.11",
|
||||
"make-dir": "1.2.0",
|
||||
"make-dir": "1.1.0",
|
||||
"unique-string": "1.0.0",
|
||||
"write-file-atomic": "2.3.0",
|
||||
"xdg-basedir": "3.0.0"
|
||||
@@ -1288,6 +1308,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"crypto-browserify": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-1.0.9.tgz",
|
||||
"integrity": "sha1-zFRJaF37hesRyYKKzHy4erW7/MA="
|
||||
},
|
||||
"crypto-random-string": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz",
|
||||
@@ -1525,9 +1550,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"db-migrate": {
|
||||
"version": "0.10.5",
|
||||
"resolved": "https://registry.npmjs.org/db-migrate/-/db-migrate-0.10.5.tgz",
|
||||
"integrity": "sha512-jddRpFIxe+24U9frQBz51wtjFyVu2LnJ4QPcNRiiR4Hvuu8BKwdN2Ez42M6hLGbl7v1rXL2u+bUcNTvPyL/OwQ==",
|
||||
"version": "0.10.2",
|
||||
"resolved": "https://registry.npmjs.org/db-migrate/-/db-migrate-0.10.2.tgz",
|
||||
"integrity": "sha512-uU3AXQ89DPKAePCHHoZSJRnLam6/zgOBhgSXVXB9Z3JeHms5OMTQuCdj1leUwYvI6ygHdQ3Z9CS6S9yOuELdcw==",
|
||||
"requires": {
|
||||
"balanced-match": "0.4.2",
|
||||
"bluebird": "3.5.1",
|
||||
@@ -1537,13 +1562,14 @@
|
||||
"final-fs": "1.6.1",
|
||||
"inflection": "1.12.0",
|
||||
"mkdirp": "0.5.1",
|
||||
"moment": "2.19.4",
|
||||
"optimist": "0.6.1",
|
||||
"parse-database-url": "0.3.0",
|
||||
"pkginfo": "0.4.1",
|
||||
"prompt": "1.0.0",
|
||||
"rc": "1.2.5",
|
||||
"rc": "1.2.2",
|
||||
"resolve": "1.5.0",
|
||||
"semver": "5.5.0",
|
||||
"semver": "5.4.1",
|
||||
"tunnel-ssh": "4.1.3"
|
||||
}
|
||||
},
|
||||
@@ -1709,16 +1735,24 @@
|
||||
"integrity": "sha1-PqIQoYmgD3Zn93Cnl0KNjmYnJn8="
|
||||
},
|
||||
"docker-modem": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-1.0.5.tgz",
|
||||
"integrity": "sha512-i3J4TYW9iNp+nWzkgGvj9UwSmT6ZUFg2OsjRlUraHCaDCv8z6f0fN3q4ur0Qq27/1GPYXSjShGaE7fDznIJKUg==",
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-1.0.4.tgz",
|
||||
"integrity": "sha512-pkXB9p7KWagegOXm2NsbVDBluQQLCBJzX9uYJzVbL6CHwe4d2sSbcACJ4K8ISX1l1JUUmFSiwNkBKc1uTiU4MA==",
|
||||
"requires": {
|
||||
"JSONStream": "1.3.2",
|
||||
"debug": "3.1.0",
|
||||
"JSONStream": "0.10.0",
|
||||
"debug": "2.6.9",
|
||||
"readable-stream": "1.0.34",
|
||||
"split-ca": "1.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"isarray": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
||||
@@ -1743,12 +1777,12 @@
|
||||
}
|
||||
},
|
||||
"dockerode": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/dockerode/-/dockerode-2.5.4.tgz",
|
||||
"integrity": "sha512-esqrDATdckYhkOFn4BSOrqnkj3jgBkHT07uEqTRwK6na4/Rg60vjXWRopv2BbRpvFruMmKvOSNVY4MbmVBUnWw==",
|
||||
"version": "2.5.3",
|
||||
"resolved": "https://registry.npmjs.org/dockerode/-/dockerode-2.5.3.tgz",
|
||||
"integrity": "sha512-LQKXR5jyI+G/+5OhZCi40m0ArY4j46g7Tl71Vtn10Ekt5TiyDzZAoqXOCS6edQpEuGbdFgSDJxleFqLxACpKJg==",
|
||||
"requires": {
|
||||
"concat-stream": "1.5.2",
|
||||
"docker-modem": "1.0.5",
|
||||
"docker-modem": "1.0.4",
|
||||
"tar-fs": "1.12.0"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -1806,9 +1840,9 @@
|
||||
"integrity": "sha1-vXWcNXqqcDZeAclrewvsCKbg2Uk="
|
||||
},
|
||||
"dtrace-provider": {
|
||||
"version": "0.8.6",
|
||||
"resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.6.tgz",
|
||||
"integrity": "sha1-QooiOv4DQl0s1tY0f99AxmkDVj0=",
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.7.1.tgz",
|
||||
"integrity": "sha1-wGswjy8Q1dWDiuycVx5dWI3HHQQ=",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"nan": "2.8.0"
|
||||
@@ -1850,9 +1884,9 @@
|
||||
}
|
||||
},
|
||||
"duplexify": {
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.5.3.tgz",
|
||||
"integrity": "sha512-g8ID9OroF9hKt2POf8YLayy+9594PzmM3scI00/uBXocX3TWNgoB67hjzkFe9ITAbQOne/lLdBxHXvYUM4ZgGA==",
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.5.1.tgz",
|
||||
"integrity": "sha512-j5goxHTwVED1Fpe5hh3q9R93Kip0Bg2KVAt4f8CEYM3UEwYcPSvWbXaUQOzdX/HtiNomipv+gU7ASQPDbV7pGQ==",
|
||||
"requires": {
|
||||
"end-of-stream": "1.4.0",
|
||||
"inherits": "2.0.3",
|
||||
@@ -2450,14 +2484,6 @@
|
||||
"integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=",
|
||||
"dev": true
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.4.1.tgz",
|
||||
"integrity": "sha512-uxYePVPogtya1ktGnAAXOacnbIuRMB4dkvqeNz2qTtTQsuzSfbDolV+wMMKxAmCx0bLgAKLbBOkjItMbbkR1vg==",
|
||||
"requires": {
|
||||
"debug": "3.1.0"
|
||||
}
|
||||
},
|
||||
"for-in": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
|
||||
@@ -2606,24 +2632,23 @@
|
||||
}
|
||||
},
|
||||
"gcp-metadata": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-0.6.1.tgz",
|
||||
"integrity": "sha512-Ju3brkV7kYOBP5s3Z6HS2xd7gyH9MDfuKeB+y51SsI8GPrD37NOB5Re9fWXQQVAkd74zzVOScnNic1lcRsWD9w==",
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-0.3.1.tgz",
|
||||
"integrity": "sha512-5kJPX/RXuqoLmHiOOgkSDk/LI0QaXpEvZ3pvQP4ifjGGDKZKVSOjL/GcDjXA5kLxppFCOjmmsu0Uoop9d1upaQ==",
|
||||
"requires": {
|
||||
"axios": "0.17.1",
|
||||
"extend": "3.0.1",
|
||||
"retry-axios": "0.3.0"
|
||||
"retry-request": "3.3.1"
|
||||
}
|
||||
},
|
||||
"gcs-resumable-upload": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/gcs-resumable-upload/-/gcs-resumable-upload-0.9.0.tgz",
|
||||
"integrity": "sha512-+Zrmr0JKO2y/2mg953TW6JLu+NAMHqQsKzqCm7CIT24gMQakolPJCMzDleVpVjXAqB7ZCD276tcUq2ebOfqTug==",
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/gcs-resumable-upload/-/gcs-resumable-upload-0.8.2.tgz",
|
||||
"integrity": "sha512-PBl1OFABYxubxfYPh000I0+JLbQzBRtNqxzgxYboIQk2tdw7BvjJ2dVukk3YH4QM6GiUwqItyNqWBuxjLH8GhA==",
|
||||
"requires": {
|
||||
"buffer-equal": "1.0.0",
|
||||
"configstore": "3.1.1",
|
||||
"google-auto-auth": "0.9.4",
|
||||
"pumpify": "1.4.0",
|
||||
"google-auto-auth": "0.7.2",
|
||||
"pumpify": "1.3.5",
|
||||
"request": "2.83.0",
|
||||
"stream-events": "1.0.2",
|
||||
"through2": "2.0.3"
|
||||
@@ -2868,25 +2893,24 @@
|
||||
}
|
||||
},
|
||||
"google-auth-library": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-0.12.0.tgz",
|
||||
"integrity": "sha512-79qCXtJ1VweBmmLr4yLq9S4clZB2p5Y+iACvuKk9gu4JitEnPc+bQFmYvtCYehVR44MQzD1J8DVmYW2w677IEw==",
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-0.10.0.tgz",
|
||||
"integrity": "sha1-bhW6vuhf0d0U2NEoopW2g41SE24=",
|
||||
"requires": {
|
||||
"gtoken": "1.2.3",
|
||||
"jws": "3.1.4",
|
||||
"lodash.isstring": "4.0.1",
|
||||
"lodash.merge": "4.6.1",
|
||||
"lodash.noop": "3.0.1",
|
||||
"request": "2.83.0"
|
||||
}
|
||||
},
|
||||
"google-auto-auth": {
|
||||
"version": "0.9.4",
|
||||
"resolved": "https://registry.npmjs.org/google-auto-auth/-/google-auto-auth-0.9.4.tgz",
|
||||
"integrity": "sha512-a/gSNZ2RCaJxriBO/A010IHmdiQeoZS0EE83G7R/yV/OGXM9zd3otRqlcfRUomBLXf9XgsJ0h6bCp7bo+qaPvw==",
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/google-auto-auth/-/google-auto-auth-0.7.2.tgz",
|
||||
"integrity": "sha512-ux2n2AE2g3+vcLXwL4dP/M12SFMRX5dzCzBfhAEkTeAB7dpyGdOIEj7nmUx0BHKaCcUQrRWg9kT63X/Mmtk1+A==",
|
||||
"requires": {
|
||||
"async": "2.6.0",
|
||||
"gcp-metadata": "0.6.1",
|
||||
"google-auth-library": "0.12.0",
|
||||
"gcp-metadata": "0.3.1",
|
||||
"google-auth-library": "0.10.0",
|
||||
"request": "2.83.0"
|
||||
}
|
||||
},
|
||||
@@ -3594,7 +3618,8 @@
|
||||
"is-buffer": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
|
||||
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
|
||||
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
|
||||
"dev": true
|
||||
},
|
||||
"is-builtin-module": {
|
||||
"version": "1.0.0",
|
||||
@@ -3974,9 +3999,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"jsonparse": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
|
||||
"integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA="
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-0.0.5.tgz",
|
||||
"integrity": "sha1-MwVCrT8KZUZlt3jz6y2an6UHrGQ="
|
||||
},
|
||||
"jsonpointer": {
|
||||
"version": "4.0.1",
|
||||
@@ -4060,16 +4085,16 @@
|
||||
}
|
||||
},
|
||||
"ldapjs": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/ldapjs/-/ldapjs-1.0.2.tgz",
|
||||
"integrity": "sha1-VE/3Ayt7g8aPBwEyjZKXqmlDQPk=",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ldapjs/-/ldapjs-1.0.1.tgz",
|
||||
"integrity": "sha1-NSuBKudLCo6WVJpLiWBg7uG5pUY=",
|
||||
"requires": {
|
||||
"asn1": "0.2.3",
|
||||
"assert-plus": "1.0.0",
|
||||
"backoff": "2.5.0",
|
||||
"bunyan": "1.8.12",
|
||||
"dashdash": "1.14.1",
|
||||
"dtrace-provider": "0.8.6",
|
||||
"dtrace-provider": "0.7.1",
|
||||
"ldap-filter": "0.2.2",
|
||||
"once": "1.4.0",
|
||||
"vasync": "1.6.4",
|
||||
@@ -4266,9 +4291,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"lodash.merge": {
|
||||
"version": "4.6.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.1.tgz",
|
||||
"integrity": "sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ=="
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.0.tgz",
|
||||
"integrity": "sha1-aYhLoUSsM/5plzemCG3v+t0PicU="
|
||||
},
|
||||
"lodash.mergewith": {
|
||||
"version": "4.6.0",
|
||||
@@ -4276,6 +4301,11 @@
|
||||
"integrity": "sha1-FQzwoWeR9ZA7iJHqsVRgknS96lU=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.noop": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.noop/-/lodash.noop-3.0.1.tgz",
|
||||
"integrity": "sha1-OBiPTWUKOkdCWEObluxFsyYXEzw="
|
||||
},
|
||||
"lodash.restparam": {
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz",
|
||||
@@ -4361,9 +4391,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"make-dir": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.2.0.tgz",
|
||||
"integrity": "sha512-aNUAa4UMg/UougV25bbrU4ZaaKNjJ/3/xnvg/twpmKROPdKZPZ9wGgI0opdZzO8q/zUFawoUuixuOv33eZ61Iw==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.1.0.tgz",
|
||||
"integrity": "sha512-0Pkui4wLJ7rxvmfUvs87skoEaxmu0hCUApF8nonzpl7q//FWp9zu8W61Scz4sd/kUiqDxvUhtoam2efDyiBzcA==",
|
||||
"requires": {
|
||||
"pify": "3.0.0"
|
||||
}
|
||||
@@ -4440,7 +4470,7 @@
|
||||
"resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz",
|
||||
"integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=",
|
||||
"requires": {
|
||||
"mimic-fn": "1.2.0"
|
||||
"mimic-fn": "1.1.0"
|
||||
}
|
||||
},
|
||||
"memoizee": {
|
||||
@@ -4522,9 +4552,9 @@
|
||||
}
|
||||
},
|
||||
"mime": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.2.0.tgz",
|
||||
"integrity": "sha512-0Qz9uF1ATtl8RKJG4VRfOymh7PyEor6NbrI/61lRfuRe4vx9SNATrvAeTj2EWVRKjEQGskrzWkJBBY5NbaVHIA=="
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.0.3.tgz",
|
||||
"integrity": "sha512-TrpAd/vX3xaLPDgVRm6JkZwLR0KHfukMdU2wTEbqMDdCnY6Yo3mE+mjs9YE6oMNw2QRfXVeBEYpmpO94BIqiug=="
|
||||
},
|
||||
"mime-db": {
|
||||
"version": "1.30.0",
|
||||
@@ -4540,9 +4570,9 @@
|
||||
}
|
||||
},
|
||||
"mimic-fn": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
|
||||
"integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ=="
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.1.0.tgz",
|
||||
"integrity": "sha1-5md4PZLonb00KBi1IwudYqZyrRg="
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
@@ -4587,9 +4617,9 @@
|
||||
}
|
||||
},
|
||||
"mocha": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mocha/-/mocha-5.0.1.tgz",
|
||||
"integrity": "sha512-SpwyojlnE/WRBNGtvJSNfllfm5PqEDFxcWluSIgLeSBJtXG4DmoX2NNAeEA7rP5kK+79VgtVq8nG6HskaL1ykg==",
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mocha/-/mocha-4.0.1.tgz",
|
||||
"integrity": "sha512-evDmhkoA+cBNiQQQdSKZa2b9+W2mpLoj50367lhy+Klnx9OV8XlCIhigUnn1gaTFLQCa0kdNhEGDr0hCXOQFDw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"browser-stdout": "1.3.0",
|
||||
@@ -4630,9 +4660,9 @@
|
||||
}
|
||||
},
|
||||
"modelo": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/modelo/-/modelo-4.2.3.tgz",
|
||||
"integrity": "sha512-9DITV2YEMcw7XojdfvGl3gDD8J9QjZTJ7ZOUuSAkP+F3T6rDbzMJuPktxptsdHYEvZcmXrCD3LMOhdSAEq6zKA=="
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/modelo/-/modelo-4.2.0.tgz",
|
||||
"integrity": "sha1-O0tCACOmbKfjK9uhbnEJN+FNGws="
|
||||
},
|
||||
"moment": {
|
||||
"version": "2.19.4",
|
||||
@@ -4821,7 +4851,7 @@
|
||||
"mkdirp": "0.5.1",
|
||||
"propagate": "0.4.0",
|
||||
"qs": "6.5.1",
|
||||
"semver": "5.5.0"
|
||||
"semver": "5.4.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
@@ -5056,7 +5086,7 @@
|
||||
"stringstream": "0.0.5",
|
||||
"tough-cookie": "2.3.3",
|
||||
"tunnel-agent": "0.4.3",
|
||||
"uuid": "3.2.1"
|
||||
"uuid": "3.1.0"
|
||||
}
|
||||
},
|
||||
"sntp": {
|
||||
@@ -5077,9 +5107,9 @@
|
||||
}
|
||||
},
|
||||
"nodemailer": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-4.6.0.tgz",
|
||||
"integrity": "sha512-yYTtNAmukmu0qI/zAYGggOvOdMgwacp4jmvCyXrGao/84Elz+2lDGOhVg6bkvOhkRK/hnun3XoJ+AV7w8x4wbQ=="
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-4.4.1.tgz",
|
||||
"integrity": "sha512-1bnszJJXatcHJhLpxQ1XMkLDjCjPKvGKMtRQ73FOsoNln3UQjddEQmz6fAwM3aj0GtQ3dQX9qtMHPelz63GU7A=="
|
||||
},
|
||||
"nodemailer-fetch": {
|
||||
"version": "1.6.0",
|
||||
@@ -5126,7 +5156,7 @@
|
||||
"requires": {
|
||||
"hosted-git-info": "2.5.0",
|
||||
"is-builtin-module": "1.0.0",
|
||||
"semver": "5.5.0",
|
||||
"semver": "5.4.1",
|
||||
"validate-npm-package-license": "3.0.1"
|
||||
}
|
||||
},
|
||||
@@ -5475,26 +5505,18 @@
|
||||
"integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4="
|
||||
},
|
||||
"p-limit": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.2.0.tgz",
|
||||
"integrity": "sha512-Y/OtIaXtUPr4/YpMv1pCL5L5ed0rumAaAeBSj12F+bSlMdys7i8oQF/GUJmfpTS/QoaRrS/k6pma29haJpsMng==",
|
||||
"requires": {
|
||||
"p-try": "1.0.0"
|
||||
}
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.1.0.tgz",
|
||||
"integrity": "sha1-sH/y2aXYi+yAYDWJWiurZqJ5iLw="
|
||||
},
|
||||
"p-locate": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
|
||||
"integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
|
||||
"requires": {
|
||||
"p-limit": "1.2.0"
|
||||
"p-limit": "1.1.0"
|
||||
}
|
||||
},
|
||||
"p-try": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
|
||||
"integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M="
|
||||
},
|
||||
"parse-database-url": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/parse-database-url/-/parse-database-url-0.3.0.tgz",
|
||||
@@ -6856,24 +6878,13 @@
|
||||
}
|
||||
},
|
||||
"pumpify": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.4.0.tgz",
|
||||
"integrity": "sha512-2kmNR9ry+Pf45opRVirpNuIFotsxUGLaYqxIwuR77AYrYRMuFCz9eryHBS52L360O+NcR383CL4QYlMKPq4zYA==",
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.3.5.tgz",
|
||||
"integrity": "sha1-G2ccYZlAq8rqwK0OOjwWS+dgmTs=",
|
||||
"requires": {
|
||||
"duplexify": "3.5.3",
|
||||
"duplexify": "3.5.1",
|
||||
"inherits": "2.0.3",
|
||||
"pump": "2.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"pump": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
|
||||
"integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
|
||||
"requires": {
|
||||
"end-of-stream": "1.4.0",
|
||||
"once": "1.4.0"
|
||||
}
|
||||
}
|
||||
"pump": "1.0.3"
|
||||
}
|
||||
},
|
||||
"punycode": {
|
||||
@@ -6929,9 +6940,9 @@
|
||||
}
|
||||
},
|
||||
"rc": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.5.tgz",
|
||||
"integrity": "sha1-J1zWh/bjs2zHVrqibf7oCnkDAf0=",
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.2.tgz",
|
||||
"integrity": "sha1-2M6ctX6NZNnHut2YdsfDTL48cHc=",
|
||||
"requires": {
|
||||
"deep-extend": "0.4.2",
|
||||
"ini": "1.3.5",
|
||||
@@ -7146,7 +7157,7 @@
|
||||
"stringstream": "0.0.5",
|
||||
"tough-cookie": "2.3.3",
|
||||
"tunnel-agent": "0.6.0",
|
||||
"uuid": "3.2.1"
|
||||
"uuid": "3.1.0"
|
||||
}
|
||||
},
|
||||
"require-directory": {
|
||||
@@ -7183,11 +7194,6 @@
|
||||
"integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
|
||||
"dev": true
|
||||
},
|
||||
"retry-axios": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/retry-axios/-/retry-axios-0.3.0.tgz",
|
||||
"integrity": "sha512-6vOCghodB5p5N/ZOqug7A3WsT42TULZ7NErUi4lP3KtwtXgz4hE/43LWHsFuHuBfXRmOm/tjXBWAjnObrcy+yg=="
|
||||
},
|
||||
"retry-request": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/retry-request/-/retry-request-3.3.1.tgz",
|
||||
@@ -7236,9 +7242,9 @@
|
||||
"integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
|
||||
},
|
||||
"safe-json-stringify": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.1.0.tgz",
|
||||
"integrity": "sha512-EzBtUaFH9bHYPc69wqjp0efJI/DPNHdFbGE3uIMn4sVbO0zx8vZ8cG4WKxQfOpUOKsQyGBiT2mTqnCw+6nLswA==",
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.0.4.tgz",
|
||||
"integrity": "sha1-gaCY9Efku8P/MxKiQ1IbwGDvWRE=",
|
||||
"optional": true
|
||||
},
|
||||
"safetydance": {
|
||||
@@ -7349,9 +7355,9 @@
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
|
||||
"integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA=="
|
||||
"version": "5.4.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
|
||||
"integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg=="
|
||||
},
|
||||
"send": {
|
||||
"version": "0.16.1",
|
||||
@@ -7465,7 +7471,7 @@
|
||||
"resolved": "https://registry.npmjs.org/showdown/-/showdown-1.8.5.tgz",
|
||||
"integrity": "sha1-QYOQzFvS0Y3ql5uaGyKhVqep7Nc=",
|
||||
"requires": {
|
||||
"yargs": "10.1.2"
|
||||
"yargs": "10.0.3"
|
||||
}
|
||||
},
|
||||
"sigmund": {
|
||||
@@ -7726,7 +7732,7 @@
|
||||
"integrity": "sha1-URGNFUVV31Rp7h9n4M8efoosDjo=",
|
||||
"requires": {
|
||||
"asn1": "0.2.3",
|
||||
"semver": "5.5.0",
|
||||
"semver": "5.4.1",
|
||||
"streamsearch": "0.1.2"
|
||||
}
|
||||
},
|
||||
@@ -7833,9 +7839,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"string-format-obj": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string-format-obj/-/string-format-obj-1.1.1.tgz",
|
||||
"integrity": "sha512-Mm+sROy+pHJmx0P/0Bs1uxIX6UhGJGj6xDGQZ5zh9v/SZRmLGevp+p0VJxV7lirrkAmQ2mvva/gHKpnF/pTb+Q=="
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/string-format-obj/-/string-format-obj-1.1.0.tgz",
|
||||
"integrity": "sha1-djVhCx7zlwE+hHi+mKFw4EmD0Gg="
|
||||
},
|
||||
"string-width": {
|
||||
"version": "2.1.1",
|
||||
@@ -8075,9 +8081,9 @@
|
||||
}
|
||||
},
|
||||
"tldjs": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tldjs/-/tldjs-2.3.1.tgz",
|
||||
"integrity": "sha512-W/YVH/QczLUxVjnQhFC61Iq232NWu3TqDdO0S/MtXVz4xybejBov4ud+CIwN9aYqjOecEqIy0PscGkwpG9ZyTw==",
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tldjs/-/tldjs-2.2.0.tgz",
|
||||
"integrity": "sha512-5b5t+HKprfccAFRAsH/fzDR4O+UgO6vStvbaJo10jvMcUavlwxR3Jrn2WmXfjG3k22T7b4pqqfput38nr1RpJQ==",
|
||||
"requires": {
|
||||
"punycode": "1.4.1"
|
||||
}
|
||||
@@ -8525,9 +8531,9 @@
|
||||
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
|
||||
},
|
||||
"uuid": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz",
|
||||
"integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA=="
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",
|
||||
"integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g=="
|
||||
},
|
||||
"v8flags": {
|
||||
"version": "2.1.1",
|
||||
@@ -8554,9 +8560,9 @@
|
||||
}
|
||||
},
|
||||
"validator": {
|
||||
"version": "9.4.1",
|
||||
"resolved": "https://registry.npmjs.org/validator/-/validator-9.4.1.tgz",
|
||||
"integrity": "sha512-YV5KjzvRmSyJ1ee/Dm5UED0G+1L4GZnLN3w6/T+zZm8scVua4sOhYKWTUrKa0H/tMiJyO9QLHMPN+9mB/aMunA=="
|
||||
"version": "9.2.0",
|
||||
"resolved": "https://registry.npmjs.org/validator/-/validator-9.2.0.tgz",
|
||||
"integrity": "sha512-6Ij4Eo0KM4LkR0d0IegOwluG5453uqT5QyF5SV5Ezvm8/zmkKI/L4eoraafZGlZPC9guLkwKzgypcw8VGWWnGA=="
|
||||
},
|
||||
"vary": {
|
||||
"version": "1.1.2",
|
||||
@@ -8825,9 +8831,9 @@
|
||||
}
|
||||
},
|
||||
"ws": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz",
|
||||
"integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==",
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-3.3.2.tgz",
|
||||
"integrity": "sha512-t+WGpsNxhMR4v6EClXS8r8km5ZljKJzyGhJf7goJz9k5Ye3+b5Bvno5rjqPuIBn5mnn5GBb7o8IrIWHxX1qOLQ==",
|
||||
"requires": {
|
||||
"async-limiter": "1.0.0",
|
||||
"safe-buffer": "5.1.1",
|
||||
@@ -8878,11 +8884,11 @@
|
||||
"integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI="
|
||||
},
|
||||
"yargs": {
|
||||
"version": "10.1.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-10.1.2.tgz",
|
||||
"integrity": "sha512-ivSoxqBGYOqQVruxD35+EyCFDYNEFL/Uo6FcOnz+9xZdZzK0Zzw4r4KhbrME1Oo2gOggwJod2MnsdamSG7H9ig==",
|
||||
"version": "10.0.3",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-10.0.3.tgz",
|
||||
"integrity": "sha512-DqBpQ8NAUX4GyPP/ijDGHsJya4tYqLQrjPr95HNsr1YwL3+daCfvBwg7+gIC6IdJhR2kATh3hb61vjzMWEtjdw==",
|
||||
"requires": {
|
||||
"cliui": "4.0.0",
|
||||
"cliui": "3.2.0",
|
||||
"decamelize": "1.2.0",
|
||||
"find-up": "2.1.0",
|
||||
"get-caller-file": "1.0.2",
|
||||
@@ -8893,40 +8899,7 @@
|
||||
"string-width": "2.1.1",
|
||||
"which-module": "2.0.0",
|
||||
"y18n": "3.2.1",
|
||||
"yargs-parser": "8.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
|
||||
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
|
||||
},
|
||||
"cliui": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-4.0.0.tgz",
|
||||
"integrity": "sha512-nY3W5Gu2racvdDk//ELReY+dHjb9PlIcVDFXP72nVIhq2Gy3LuVXYwJoPVudwQnv1shtohpgkdCKT2YaKY0CKw==",
|
||||
"requires": {
|
||||
"string-width": "2.1.1",
|
||||
"strip-ansi": "4.0.0",
|
||||
"wrap-ansi": "2.1.0"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
|
||||
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
|
||||
"requires": {
|
||||
"ansi-regex": "3.0.0"
|
||||
}
|
||||
},
|
||||
"yargs-parser": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-8.1.0.tgz",
|
||||
"integrity": "sha512-yP+6QqN8BmrgW2ggLtTbdrOyBNSI7zBa4IykmiV5R1wl1JWNxQvWhMfMdmzIYtKU7oP3OOInY/tl2ov3BDjnJQ==",
|
||||
"requires": {
|
||||
"camelcase": "4.1.0"
|
||||
}
|
||||
}
|
||||
"yargs-parser": "8.0.0"
|
||||
}
|
||||
},
|
||||
"yargs-parser": {
|
||||
|
||||
+15
-15
@@ -14,11 +14,11 @@
|
||||
"node": ">=4.0.0 <=4.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@google-cloud/dns": "^0.7.1",
|
||||
"@google-cloud/storage": "^1.6.0",
|
||||
"@google-cloud/dns": "^0.7.0",
|
||||
"@google-cloud/storage": "^1.2.1",
|
||||
"@sindresorhus/df": "^2.1.0",
|
||||
"async": "^2.6.0",
|
||||
"aws-sdk": "^2.201.0",
|
||||
"aws-sdk": "^2.151.0",
|
||||
"body-parser": "^1.18.2",
|
||||
"cloudron-manifestformat": "^2.11.0",
|
||||
"connect-ensure-login": "^0.1.1",
|
||||
@@ -28,24 +28,24 @@
|
||||
"cookie-session": "^1.3.2",
|
||||
"cron": "^1.3.0",
|
||||
"csurf": "^1.6.6",
|
||||
"db-migrate": "^0.10.5",
|
||||
"db-migrate": "^0.10.0-beta.24",
|
||||
"db-migrate-mysql": "^1.1.10",
|
||||
"debug": "^3.1.0",
|
||||
"dockerode": "^2.5.4",
|
||||
"dockerode": "^2.5.3",
|
||||
"ejs": "^2.5.7",
|
||||
"ejs-cli": "^2.0.0",
|
||||
"express": "^4.16.2",
|
||||
"express-session": "^1.15.6",
|
||||
"hat": "0.0.3",
|
||||
"json": "^9.0.3",
|
||||
"ldapjs": "^1.0.2",
|
||||
"ldapjs": "^1.0.0",
|
||||
"lodash.chunk": "^4.2.0",
|
||||
"mime": "^2.2.0",
|
||||
"mime": "^2.0.3",
|
||||
"moment-timezone": "^0.5.14",
|
||||
"morgan": "^1.9.0",
|
||||
"multiparty": "^4.1.2",
|
||||
"mysql": "^2.15.0",
|
||||
"nodemailer": "^4.6.0",
|
||||
"nodemailer": "^4.4.0",
|
||||
"nodemailer-smtp-transport": "^2.7.4",
|
||||
"oauth2orize": "^1.11.0",
|
||||
"once": "^1.3.2",
|
||||
@@ -62,19 +62,19 @@
|
||||
"request": "^2.83.0",
|
||||
"s3-block-read-stream": "^0.2.0",
|
||||
"safetydance": "^0.7.1",
|
||||
"semver": "^5.5.0",
|
||||
"semver": "^5.4.1",
|
||||
"showdown": "^1.8.2",
|
||||
"split": "^1.0.0",
|
||||
"superagent": "^3.8.1",
|
||||
"supererror": "^0.7.1",
|
||||
"tar-fs": "^1.16.0",
|
||||
"tar-stream": "^1.5.5",
|
||||
"tldjs": "^2.3.1",
|
||||
"tldjs": "^2.2.0",
|
||||
"underscore": "^1.7.0",
|
||||
"uuid": "^3.2.1",
|
||||
"uuid": "^3.1.0",
|
||||
"valid-url": "^1.0.9",
|
||||
"validator": "^9.4.1",
|
||||
"ws": "^3.3.3"
|
||||
"validator": "^9.1.1",
|
||||
"ws": "^3.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"bootstrap-sass": "^3.3.3",
|
||||
@@ -91,12 +91,12 @@
|
||||
"hock": "^1.3.2",
|
||||
"istanbul": "*",
|
||||
"js2xmlparser": "^3.0.0",
|
||||
"mocha": "^5.0.1",
|
||||
"mocha": "*",
|
||||
"mock-aws-s3": "git+https://github.com/cloudron-io/mock-aws-s3.git",
|
||||
"nock": "^9.0.14",
|
||||
"node-sass": "^4.6.1",
|
||||
"readdirp": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz",
|
||||
"yargs": "^10.1.2"
|
||||
"yargs": "^10.0.3"
|
||||
},
|
||||
"scripts": {
|
||||
"migrate_local": "DATABASE_URL=mysql://root:@localhost/box node_modules/.bin/db-migrate up",
|
||||
|
||||
@@ -26,10 +26,6 @@ readonly physical_memory=$(LC_ALL=C free -m | awk '/Mem:/ { print $2 }')
|
||||
readonly disk_size_bytes=$(LC_ALL=C df --output=size / | tail -n1)
|
||||
readonly disk_size_gb=$((${disk_size_bytes}/1024/1024))
|
||||
|
||||
readonly RED='\033[31m'
|
||||
readonly GREEN='\033[32m'
|
||||
readonly DONE='\033[m'
|
||||
|
||||
# verify the system has minimum requirements met
|
||||
if [[ "${rootfs_type}" != "ext4" ]]; then
|
||||
echo "Error: Cloudron requires '/' to be ext4" # see #364
|
||||
@@ -124,7 +120,7 @@ echo ""
|
||||
echo " Follow setup logs in a second terminal with:"
|
||||
echo " $ tail -f ${LOG_FILE}"
|
||||
echo ""
|
||||
echo " Join us at https://forum.cloudron.io for any questions."
|
||||
echo " Join us at https://chat.cloudron.io for any questions."
|
||||
echo ""
|
||||
|
||||
if [[ "${initBaseImage}" == "true" ]]; then
|
||||
@@ -203,10 +199,10 @@ while true; do
|
||||
sleep 10
|
||||
done
|
||||
|
||||
echo -e "\n\n${GREEN}Visit https://<IP> to finish setup once the server has rebooted.${DONE}"
|
||||
echo -e "\n\nVisit https://<IP> to finish setup once the server has rebooted.\n"
|
||||
|
||||
if [[ "${rebootServer}" == "true" ]]; then
|
||||
echo -e "\nRebooting this server now to let changes take effect.\n"
|
||||
echo -e "\n\nRebooting this server now to let bootloader changes take effect.\n"
|
||||
systemctl stop mysql # sometimes mysql ends up having corrupt privilege tables
|
||||
systemctl reboot
|
||||
fi
|
||||
|
||||
@@ -41,11 +41,7 @@ fi
|
||||
|
||||
box_version=$(cd "${SOURCE_DIR}" && git rev-parse "HEAD")
|
||||
branch=$(git rev-parse --abbrev-ref HEAD)
|
||||
if [[ "${branch}" == "master" ]]; then
|
||||
webadmin_version=$(cd "${SOURCE_DIR}/../webadmin" && git rev-parse "${branch}")
|
||||
else
|
||||
webadmin_version=$(cd "${SOURCE_DIR}/../webadmin" && git fetch && git rev-parse "origin/${branch}")
|
||||
fi
|
||||
webadmin_version=$(cd "${SOURCE_DIR}/../webadmin" && git fetch && git rev-parse "origin/${branch}")
|
||||
bundle_dir=$(mktemp -d -t box 2>/dev/null || mktemp -d box-XXXXXXXXXX --tmpdir=$TMPDIR)
|
||||
[[ -z "$bundle_file" ]] && bundle_file="${TMPDIR}/box-${box_version:0:10}-${webadmin_version:0:10}.tar.gz"
|
||||
|
||||
|
||||
+18
-53
@@ -28,7 +28,6 @@ var appdb = require('./appdb.js'),
|
||||
generatePassword = require('password-generator'),
|
||||
hat = require('hat'),
|
||||
infra = require('./infra_version.js'),
|
||||
mail = require('./mail.js'),
|
||||
mailboxdb = require('./mailboxdb.js'),
|
||||
once = require('once'),
|
||||
path = require('path'),
|
||||
@@ -113,9 +112,10 @@ var KNOWN_ADDONS = {
|
||||
var RMAPPDIR_CMD = path.join(__dirname, 'scripts/rmappdir.sh');
|
||||
|
||||
function debugApp(app, args) {
|
||||
assert(typeof app === 'object');
|
||||
assert(!app || typeof app === 'object');
|
||||
|
||||
debug(app.fqdn + ' ' + util.format.apply(util, Array.prototype.slice.call(arguments, 1)));
|
||||
var prefix = app ? app.intrinsicFqdn : '(no app)';
|
||||
debug(prefix + ' ' + util.format.apply(util, Array.prototype.slice.call(arguments, 1)));
|
||||
}
|
||||
|
||||
function setupAddons(app, addons, callback) {
|
||||
@@ -250,7 +250,7 @@ function setupOauth(app, options, callback) {
|
||||
if (!app.sso) return callback(null);
|
||||
|
||||
var appId = app.id;
|
||||
var redirectURI = 'https://' + app.fqdn;
|
||||
var redirectURI = 'https://' + (app.altDomain || app.intrinsicFqdn);
|
||||
var scope = 'profile';
|
||||
|
||||
clients.delByAppIdAndType(appId, clients.TYPE_OAUTH, function (error) { // remove existing creds
|
||||
@@ -291,27 +291,20 @@ function setupEmail(app, options, callback) {
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
mail.getAll(function (error, mailDomains) {
|
||||
if (error) return callback(error);
|
||||
// note that "external" access info can be derived from MAIL_DOMAIN (since it's part of user documentation)
|
||||
var env = [
|
||||
{ name: 'MAIL_SMTP_SERVER', value: 'mail' },
|
||||
{ name: 'MAIL_SMTP_PORT', value: '2525' },
|
||||
{ name: 'MAIL_IMAP_SERVER', value: 'mail' },
|
||||
{ name: 'MAIL_IMAP_PORT', value: '9993' },
|
||||
{ name: 'MAIL_SIEVE_SERVER', value: 'mail' },
|
||||
{ name: 'MAIL_SIEVE_PORT', value: '4190' },
|
||||
{ name: 'MAIL_DOMAIN', value: app.domain }
|
||||
];
|
||||
|
||||
const mailInDomains = mailDomains.filter(function (d) { return d.enabled; }).map(function (d) { return d.domain; }).join(',');
|
||||
debugApp(app, 'Setting up Email');
|
||||
|
||||
// note that "external" access info can be derived from MAIL_DOMAIN (since it's part of user documentation)
|
||||
var env = [
|
||||
{ name: 'MAIL_SMTP_SERVER', value: 'mail' },
|
||||
{ name: 'MAIL_SMTP_PORT', value: '2525' },
|
||||
{ name: 'MAIL_IMAP_SERVER', value: 'mail' },
|
||||
{ name: 'MAIL_IMAP_PORT', value: '9993' },
|
||||
{ name: 'MAIL_SIEVE_SERVER', value: 'mail' },
|
||||
{ name: 'MAIL_SIEVE_PORT', value: '4190' },
|
||||
{ name: 'MAIL_DOMAIN', value: app.domain },
|
||||
{ name: 'MAIL_DOMAINS', value: mailInDomains }
|
||||
];
|
||||
|
||||
debugApp(app, 'Setting up Email');
|
||||
|
||||
appdb.setAddonConfig(app.id, 'email', env, callback);
|
||||
});
|
||||
appdb.setAddonConfig(app.id, 'email', env, callback);
|
||||
}
|
||||
|
||||
function teardownEmail(app, options, callback) {
|
||||
@@ -467,10 +460,6 @@ function teardownMySql(app, options, callback) {
|
||||
}
|
||||
|
||||
function backupMySql(app, options, callback) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
debugApp(app, 'Backing up mysql');
|
||||
|
||||
callback = once(callback); // ChildProcess exit may or may not be called after error
|
||||
@@ -484,10 +473,6 @@ function backupMySql(app, options, callback) {
|
||||
}
|
||||
|
||||
function restoreMySql(app, options, callback) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
callback = once(callback); // ChildProcess exit may or may not be called after error
|
||||
|
||||
setupMySql(app, options, function (error) {
|
||||
@@ -540,10 +525,6 @@ function teardownPostgreSql(app, options, callback) {
|
||||
}
|
||||
|
||||
function backupPostgreSql(app, options, callback) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
debugApp(app, 'Backing up postgresql');
|
||||
|
||||
callback = once(callback); // ChildProcess exit may or may not be called after error
|
||||
@@ -557,10 +538,6 @@ function backupPostgreSql(app, options, callback) {
|
||||
}
|
||||
|
||||
function restorePostgreSql(app, options, callback) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
callback = once(callback);
|
||||
|
||||
setupPostgreSql(app, options, function (error) {
|
||||
@@ -614,10 +591,6 @@ function teardownMongoDb(app, options, callback) {
|
||||
}
|
||||
|
||||
function backupMongoDb(app, options, callback) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
debugApp(app, 'Backing up mongodb');
|
||||
|
||||
callback = once(callback); // ChildProcess exit may or may not be called after error
|
||||
@@ -631,10 +604,6 @@ function backupMongoDb(app, options, callback) {
|
||||
}
|
||||
|
||||
function restoreMongoDb(app, options, callback) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
callback = once(callback); // ChildProcess exit may or may not be called after error
|
||||
|
||||
setupMongoDb(app, options, function (error) {
|
||||
@@ -678,7 +647,7 @@ function setupRedis(app, options, callback) {
|
||||
}
|
||||
|
||||
const tag = infra.images.redis.tag, redisName = 'redis-' + app.id;
|
||||
const label = app.fqdn;
|
||||
const label = app.intrinsicFqdn;
|
||||
// note that we do not add appId label because this interferes with the stop/start app logic
|
||||
const cmd = `docker run --restart=always -d --name=${redisName} \
|
||||
--label=location=${label} \
|
||||
@@ -728,7 +697,7 @@ function teardownRedis(app, options, callback) {
|
||||
|
||||
safe.fs.unlinkSync(paths.ADDON_CONFIG_DIR, 'redis-' + app.id + '_vars.sh');
|
||||
|
||||
shell.sudo('teardownRedis', [ RMAPPDIR_CMD, app.id + '/redis', true /* delete directory */ ], function (error /* ,stdout , stderr*/) {
|
||||
shell.sudo('teardownRedis', [ RMAPPDIR_CMD, app.id + '/redis', true /* delete directory */ ], function (error, stdout, stderr) {
|
||||
if (error) return callback(new Error('Error removing redis data:' + error));
|
||||
|
||||
appdb.unsetAddonConfig(app.id, 'redis', callback);
|
||||
@@ -737,10 +706,6 @@ function teardownRedis(app, options, callback) {
|
||||
}
|
||||
|
||||
function backupRedis(app, options, callback) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
debugApp(app, 'Backing up redis');
|
||||
|
||||
var cmd = [ '/addons/redis/service.sh', 'backup' ]; // the redis dir is volume mounted
|
||||
|
||||
+4
-5
@@ -61,7 +61,7 @@ var assert = require('assert'),
|
||||
var APPS_FIELDS_PREFIXED = [ 'apps.id', 'apps.appStoreId', 'apps.installationState', 'apps.installationProgress', 'apps.runState',
|
||||
'apps.health', 'apps.containerId', 'apps.manifestJson', 'apps.httpPort', 'apps.location', 'apps.domain', 'apps.dnsRecordId',
|
||||
'apps.accessRestrictionJson', 'apps.restoreConfigJson', 'apps.oldConfigJson', 'apps.updateConfigJson', 'apps.memoryLimit',
|
||||
'apps.xFrameOptions', 'apps.sso', 'apps.debugModeJson', 'apps.robotsTxt', 'apps.enableBackup',
|
||||
'apps.altDomain', 'apps.xFrameOptions', 'apps.sso', 'apps.debugModeJson', 'apps.robotsTxt', 'apps.enableBackup',
|
||||
'apps.creationTime', 'apps.updateTime' ].join(',');
|
||||
|
||||
var PORT_BINDINGS_FIELDS = [ 'hostPort', 'environmentVariable', 'appId' ].join(',');
|
||||
@@ -196,18 +196,17 @@ function add(id, appStoreId, manifest, location, domain, portBindings, data, cal
|
||||
var accessRestriction = data.accessRestriction || null;
|
||||
var accessRestrictionJson = JSON.stringify(accessRestriction);
|
||||
var memoryLimit = data.memoryLimit || 0;
|
||||
var altDomain = data.altDomain || null;
|
||||
var xFrameOptions = data.xFrameOptions || '';
|
||||
var installationState = data.installationState || exports.ISTATE_PENDING_INSTALL;
|
||||
var restoreConfigJson = data.restoreConfig ? JSON.stringify(data.restoreConfig) : null; // used when cloning
|
||||
var sso = 'sso' in data ? data.sso : null;
|
||||
var robotsTxt = 'robotsTxt' in data ? data.robotsTxt : null;
|
||||
var debugModeJson = data.debugMode ? JSON.stringify(data.debugMode) : null;
|
||||
|
||||
var queries = [];
|
||||
queries.push({
|
||||
query: 'INSERT INTO apps (id, appStoreId, manifestJson, installationState, location, domain, accessRestrictionJson, memoryLimit, xFrameOptions, restoreConfigJson, sso, debugModeJson, robotsTxt) ' +
|
||||
' VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
||||
args: [ id, appStoreId, manifestJson, installationState, location, domain, accessRestrictionJson, memoryLimit, xFrameOptions, restoreConfigJson, sso, debugModeJson, robotsTxt ]
|
||||
query: 'INSERT INTO apps (id, appStoreId, manifestJson, installationState, location, domain, accessRestrictionJson, memoryLimit, altDomain, xFrameOptions, restoreConfigJson, sso, debugModeJson) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
||||
args: [ id, appStoreId, manifestJson, installationState, location, domain, accessRestrictionJson, memoryLimit, altDomain, xFrameOptions, restoreConfigJson, sso, debugModeJson ]
|
||||
});
|
||||
|
||||
Object.keys(portBindings).forEach(function (env) {
|
||||
|
||||
@@ -5,6 +5,7 @@ var appdb = require('./appdb.js'),
|
||||
assert = require('assert'),
|
||||
async = require('async'),
|
||||
DatabaseError = require('./databaseerror.js'),
|
||||
config = require('./config.js'),
|
||||
debug = require('debug')('box:apphealthmonitor'),
|
||||
docker = require('./docker.js').connection,
|
||||
mailer = require('./mailer.js'),
|
||||
@@ -23,9 +24,13 @@ var gRunTimeout = null;
|
||||
var gDockerEventStream = null;
|
||||
|
||||
function debugApp(app) {
|
||||
assert(typeof app === 'object');
|
||||
assert(!app || typeof app === 'object');
|
||||
|
||||
debug(app.fqdn + ' ' + app.manifest.id + ' ' + util.format.apply(util, Array.prototype.slice.call(arguments, 1)) + ' - ' + app.id);
|
||||
var prefix = app ? app.intrinsicFqdn : '(no app)';
|
||||
var manifestAppId = app ? app.manifest.id : '';
|
||||
var id = app ? app.id : '';
|
||||
|
||||
debug(prefix + ' ' + manifestAppId + ' ' + util.format.apply(util, Array.prototype.slice.call(arguments, 1)) + ' - ' + id);
|
||||
}
|
||||
|
||||
function setHealth(app, health, callback) {
|
||||
@@ -66,9 +71,6 @@ function setHealth(app, health, callback) {
|
||||
|
||||
// callback is called with error for fatal errors and not if health check failed
|
||||
function checkAppHealth(app, callback) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
if (app.installationState !== appdb.ISTATE_INSTALLED || app.runState !== appdb.RSTATE_RUNNING) {
|
||||
debugApp(app, 'skipped. istate:%s rstate:%s', app.installationState, app.runState);
|
||||
return callback(null);
|
||||
|
||||
+78
-71
@@ -306,19 +306,17 @@ function getDuplicateErrorDetails(location, portBindings, error) {
|
||||
return new AppsError(AppsError.ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
// app configs that is useful for 'archival' into the app backup config.json
|
||||
function getAppConfig(app) {
|
||||
return {
|
||||
manifest: app.manifest,
|
||||
location: app.location,
|
||||
domain: app.domain,
|
||||
fqdn: app.fqdn,
|
||||
intrinsicFqdn: app.intrinsicFqdn,
|
||||
accessRestriction: app.accessRestriction,
|
||||
portBindings: app.portBindings,
|
||||
memoryLimit: app.memoryLimit,
|
||||
xFrameOptions: app.xFrameOptions || 'SAMEORIGIN',
|
||||
robotsTxt: app.robotsTxt,
|
||||
sso: app.sso
|
||||
altDomain: app.altDomain
|
||||
};
|
||||
}
|
||||
|
||||
@@ -364,8 +362,10 @@ function get(appId, callback) {
|
||||
domaindb.get(app.domain, function (error, result) {
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
|
||||
|
||||
app.intrinsicFqdn = domains.fqdn(app.location, app.domain, result.provider);
|
||||
app.iconUrl = getIconUrlSync(app);
|
||||
app.fqdn = domains.fqdn(app.location, app.domain, result.provider);
|
||||
app.fqdn = app.altDomain || app.intrinsicFqdn;
|
||||
app.cnameTarget = app.altDomain ? app.intrinsicFqdn : null;
|
||||
|
||||
callback(null, app);
|
||||
});
|
||||
@@ -386,8 +386,10 @@ function getByIpAddress(ip, callback) {
|
||||
domaindb.get(app.domain, function (error, result) {
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
|
||||
|
||||
app.intrinsicFqdn = domains.fqdn(app.location, app.domain, result.provider);
|
||||
app.iconUrl = getIconUrlSync(app);
|
||||
app.fqdn = domains.fqdn(app.location, app.domain, result.provider);
|
||||
app.fqdn = app.altDomain || app.intrinsicFqdn;
|
||||
app.cnameTarget = app.altDomain ? app.intrinsicFqdn : null;
|
||||
|
||||
callback(null, app);
|
||||
});
|
||||
@@ -405,8 +407,10 @@ function getAll(callback) {
|
||||
domaindb.get(app.domain, function (error, result) {
|
||||
if (error) return iteratorDone(new AppsError(AppsError.INTERNAL_ERROR, error));
|
||||
|
||||
app.intrinsicFqdn = domains.fqdn(app.location, app.domain, result.provider);
|
||||
app.iconUrl = getIconUrlSync(app);
|
||||
app.fqdn = domains.fqdn(app.location, app.domain, result.provider);
|
||||
app.fqdn = app.altDomain || app.intrinsicFqdn;
|
||||
app.cnameTarget = app.altDomain ? app.intrinsicFqdn : null;
|
||||
|
||||
iteratorDone();
|
||||
});
|
||||
@@ -464,6 +468,7 @@ function install(data, auditSource, callback) {
|
||||
cert = data.cert || null,
|
||||
key = data.key || null,
|
||||
memoryLimit = data.memoryLimit || 0,
|
||||
altDomain = data.altDomain || null,
|
||||
xFrameOptions = data.xFrameOptions || 'SAMEORIGIN',
|
||||
sso = 'sso' in data ? data.sso : null,
|
||||
debugMode = data.debugMode || null,
|
||||
@@ -508,6 +513,8 @@ function install(data, auditSource, callback) {
|
||||
// if sso was unspecified, enable it by default if possible
|
||||
if (sso === null) sso = !!manifest.addons['ldap'] || !!manifest.addons['oauth'];
|
||||
|
||||
if (altDomain !== null && !validator.isFQDN(altDomain)) return callback(new AppsError(AppsError.BAD_FIELD, 'Invalid external domain'));
|
||||
|
||||
var appId = uuid.v4();
|
||||
|
||||
if (icon) {
|
||||
@@ -522,13 +529,13 @@ function install(data, auditSource, callback) {
|
||||
if (error && error.reason === DomainError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND, 'No such domain'));
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Could not get domain info:' + error.message));
|
||||
|
||||
var fqdn = domains.fqdn(location, domain, domainObject.provider);
|
||||
var intrinsicFqdn = domains.fqdn(location, domain, domainObject.provider);
|
||||
|
||||
error = validateHostname(location, domain, fqdn);
|
||||
error = validateHostname(location, domain, intrinsicFqdn);
|
||||
if (error) return callback(error);
|
||||
|
||||
if (cert && key) {
|
||||
error = reverseProxy.validateCertificate(fqdn, cert, key);
|
||||
error = reverseProxy.validateCertificate(intrinsicFqdn, cert, key);
|
||||
if (error) return callback(new AppsError(AppsError.BAD_CERTIFICATE, error.message));
|
||||
}
|
||||
|
||||
@@ -543,36 +550,32 @@ function install(data, auditSource, callback) {
|
||||
var data = {
|
||||
accessRestriction: accessRestriction,
|
||||
memoryLimit: memoryLimit,
|
||||
altDomain: altDomain,
|
||||
xFrameOptions: xFrameOptions,
|
||||
sso: sso,
|
||||
debugMode: debugMode,
|
||||
mailboxName: (location ? location : manifest.title.toLowerCase().replace(/[^a-zA-Z0-9]/g, '')) + '.app',
|
||||
restoreConfig: backupId ? { backupId: backupId, backupFormat: backupFormat } : null,
|
||||
enableBackup: enableBackup,
|
||||
robotsTxt: robotsTxt
|
||||
robotsTxt: robotsTxt,
|
||||
intrinsicFqdn: intrinsicFqdn
|
||||
};
|
||||
|
||||
appdb.add(appId, appStoreId, manifest, location, domain, portBindings, data, function (error) {
|
||||
if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(getDuplicateErrorDetails(location, portBindings, error));
|
||||
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND, error.message));
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
|
||||
|
||||
// save cert to boxdata/certs
|
||||
if (cert && key) {
|
||||
if (!safe.fs.writeFileSync(path.join(paths.APP_CERTS_DIR, fqdn + '.user.cert'), cert)) return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Error saving cert: ' + safe.error.message));
|
||||
if (!safe.fs.writeFileSync(path.join(paths.APP_CERTS_DIR, fqdn + '.user.key'), key)) return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Error saving key: ' + safe.error.message));
|
||||
if (!safe.fs.writeFileSync(path.join(paths.APP_CERTS_DIR, intrinsicFqdn + '.user.cert'), cert)) return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Error saving cert: ' + safe.error.message));
|
||||
if (!safe.fs.writeFileSync(path.join(paths.APP_CERTS_DIR, intrinsicFqdn + '.user.key'), key)) return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Error saving key: ' + safe.error.message));
|
||||
}
|
||||
|
||||
taskmanager.restartAppTask(appId);
|
||||
|
||||
// fetch fresh app object for eventlog
|
||||
get(appId, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
eventlog.add(eventlog.ACTION_APP_INSTALL, auditSource, { appId: appId, location: location, domain: domain, manifest: manifest, backupId: backupId });
|
||||
|
||||
eventlog.add(eventlog.ACTION_APP_INSTALL, auditSource, { appId: appId, app: result });
|
||||
|
||||
callback(null, { id : appId });
|
||||
});
|
||||
callback(null, { id : appId });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -585,8 +588,9 @@ function configure(appId, data, auditSource, callback) {
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
get(appId, function (error, app) {
|
||||
if (error) return callback(error);
|
||||
appdb.get(appId, function (error, app) {
|
||||
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND, 'No such app'));
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
|
||||
|
||||
var domain, location, portBindings, values = { };
|
||||
if ('location' in data) location = values.location = data.location.toLowerCase();
|
||||
@@ -601,6 +605,11 @@ function configure(appId, data, auditSource, callback) {
|
||||
if (error) return callback(error);
|
||||
}
|
||||
|
||||
if ('altDomain' in data) {
|
||||
values.altDomain = data.altDomain;
|
||||
if (values.altDomain !== null && !validator.isFQDN(values.altDomain)) return callback(new AppsError(AppsError.BAD_FIELD, 'Invalid external domain'));
|
||||
}
|
||||
|
||||
if ('portBindings' in data) {
|
||||
portBindings = values.portBindings = data.portBindings;
|
||||
error = validatePortBindings(values.portBindings, app.manifest.tcpPorts);
|
||||
@@ -637,22 +646,24 @@ function configure(appId, data, auditSource, callback) {
|
||||
if (error && error.reason === DomainError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND, 'No such domain'));
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Could not get domain info:' + error.message));
|
||||
|
||||
var fqdn = domains.fqdn(location, domain, domainObject.provider);
|
||||
var intrinsicFqdn = domains.fqdn(location, domain, domainObject.provider);
|
||||
|
||||
error = validateHostname(location, domain, fqdn);
|
||||
error = validateHostname(location, domain, intrinsicFqdn);
|
||||
if (error) return callback(error);
|
||||
|
||||
// save cert to boxdata/certs. TODO: move this to apptask when we have a real task queue
|
||||
if ('cert' in data && 'key' in data) {
|
||||
if (data.cert && data.key) {
|
||||
error = reverseProxy.validateCertificate(fqdn, data.cert, data.key);
|
||||
var vhost = values.altDomain || intrinsicFqdn;
|
||||
|
||||
error = reverseProxy.validateCertificate(vhost, data.cert, data.key);
|
||||
if (error) return callback(new AppsError(AppsError.BAD_CERTIFICATE, error.message));
|
||||
|
||||
if (!safe.fs.writeFileSync(path.join(paths.APP_CERTS_DIR, `${fqdn}.user.cert`), data.cert)) return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Error saving cert: ' + safe.error.message));
|
||||
if (!safe.fs.writeFileSync(path.join(paths.APP_CERTS_DIR, `${fqdn}.user.key`), data.key)) return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Error saving key: ' + safe.error.message));
|
||||
if (!safe.fs.writeFileSync(path.join(paths.APP_CERTS_DIR, `${vhost}.user.cert`), data.cert)) return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Error saving cert: ' + safe.error.message));
|
||||
if (!safe.fs.writeFileSync(path.join(paths.APP_CERTS_DIR, `${vhost}.user.key`), data.key)) return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Error saving key: ' + safe.error.message));
|
||||
} else { // remove existing cert/key
|
||||
if (!safe.fs.unlinkSync(path.join(paths.APP_CERTS_DIR, `${fqdn}.user.cert`))) debug('Error removing cert: ' + safe.error.message);
|
||||
if (!safe.fs.unlinkSync(path.join(paths.APP_CERTS_DIR, `${fqdn}..user.key`))) debug('Error removing key: ' + safe.error.message);
|
||||
if (!safe.fs.unlinkSync(path.join(paths.APP_CERTS_DIR, `${vhost}.user.cert`))) debug('Error removing cert: ' + safe.error.message);
|
||||
if (!safe.fs.unlinkSync(path.join(paths.APP_CERTS_DIR, `${vhost}..user.key`))) debug('Error removing key: ' + safe.error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -676,14 +687,9 @@ function configure(appId, data, auditSource, callback) {
|
||||
|
||||
taskmanager.restartAppTask(appId);
|
||||
|
||||
// fetch fresh app object for eventlog
|
||||
get(appId, function (error, result) {
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
|
||||
eventlog.add(eventlog.ACTION_APP_CONFIGURE, auditSource, { appId: appId });
|
||||
|
||||
eventlog.add(eventlog.ACTION_APP_CONFIGURE, auditSource, { appId: appId, app: result });
|
||||
|
||||
callback(null);
|
||||
});
|
||||
callback(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -723,8 +729,9 @@ function update(appId, data, auditSource, callback) {
|
||||
}
|
||||
}
|
||||
|
||||
get(appId, function (error, app) {
|
||||
if (error) return callback(error);
|
||||
appdb.get(appId, function (error, app) {
|
||||
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND, 'No such app'));
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
|
||||
|
||||
// prevent user from installing a app with different manifest id over an existing app
|
||||
// this allows cloudron install -f --app <appid> for an app installed from the appStore
|
||||
@@ -749,7 +756,7 @@ function update(appId, data, auditSource, callback) {
|
||||
|
||||
taskmanager.restartAppTask(appId);
|
||||
|
||||
eventlog.add(eventlog.ACTION_APP_UPDATE, auditSource, { appId: appId, toManifest: manifest, fromManifest: app.manifest, force: data.force, app: app });
|
||||
eventlog.add(eventlog.ACTION_APP_UPDATE, auditSource, { appId: appId, toManifest: manifest, fromManifest: app.manifest, force: data.force });
|
||||
|
||||
// clear update indicator, if update fails, it will come back through the update checker
|
||||
updateChecker.resetAppUpdateInfo(appId);
|
||||
@@ -773,8 +780,10 @@ function getLogs(appId, options, callback) {
|
||||
|
||||
debug('Getting logs for %s', appId);
|
||||
|
||||
get(appId, function (error, app) {
|
||||
if (error) return callback(error);
|
||||
appdb.get(appId, function (error, app) {
|
||||
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND));
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
|
||||
|
||||
|
||||
var lines = options.lines || 100,
|
||||
follow = !!options.follow,
|
||||
@@ -818,8 +827,9 @@ function restore(appId, data, auditSource, callback) {
|
||||
|
||||
debug('Will restore app with id:%s', appId);
|
||||
|
||||
get(appId, function (error, app) {
|
||||
if (error) return callback(error);
|
||||
appdb.get(appId, function (error, app) {
|
||||
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND));
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
|
||||
|
||||
// for empty or null backupId, use existing manifest to mimic a reinstall
|
||||
var func = data.backupId ? backups.get.bind(null, data.backupId) : function (next) { return next(null, { manifest: app.manifest }); };
|
||||
@@ -848,7 +858,7 @@ function restore(appId, data, auditSource, callback) {
|
||||
|
||||
taskmanager.restartAppTask(appId);
|
||||
|
||||
eventlog.add(eventlog.ACTION_APP_RESTORE, auditSource, { appId: appId, app: app });
|
||||
eventlog.add(eventlog.ACTION_APP_RESTORE, auditSource, { appId: appId });
|
||||
|
||||
callback(null);
|
||||
});
|
||||
@@ -874,8 +884,9 @@ function clone(appId, data, auditSource, callback) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof portBindings, 'object');
|
||||
|
||||
get(appId, function (error, app) {
|
||||
if (error) return callback(error);
|
||||
appdb.get(appId, function (error, app) {
|
||||
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND));
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
|
||||
|
||||
backups.get(backupId, function (error, backupInfo) {
|
||||
if (error && error.reason === BackupsError.EXTERNAL_ERROR) return callback(new AppsError(AppsError.EXTERNAL_ERROR, error.message));
|
||||
@@ -895,7 +906,9 @@ function clone(appId, data, auditSource, callback) {
|
||||
if (error && error.reason === DomainError.NOT_FOUND) return callback(new AppsError(AppsError.EXTERNAL_ERROR, 'No such domain'));
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Could not get domain info:' + error.message));
|
||||
|
||||
error = validateHostname(location, domain, domains.fqdn(location, domain, domainObject.provider));
|
||||
var intrinsicFqdn = domains.fqdn(location, domain, domainObject.provider);
|
||||
|
||||
error = validateHostname(location, domain, intrinsicFqdn);
|
||||
if (error) return callback(error);
|
||||
|
||||
var newAppId = uuid.v4(), manifest = backupInfo.manifest;
|
||||
@@ -913,9 +926,7 @@ function clone(appId, data, auditSource, callback) {
|
||||
xFrameOptions: app.xFrameOptions,
|
||||
restoreConfig: { backupId: backupId, backupFormat: backupInfo.format },
|
||||
sso: !!app.sso,
|
||||
mailboxName: (location ? location : manifest.title.toLowerCase().replace(/[^a-zA-Z0-9]/g, '')) + '.app',
|
||||
enableBackup: app.enableBackup,
|
||||
robotsTxt: app.robotsTxt
|
||||
mailboxName: (location ? location : manifest.title.toLowerCase().replace(/[^a-zA-Z0-9]/g, '')) + '.app'
|
||||
};
|
||||
|
||||
appdb.add(newAppId, app.appStoreId, manifest, location, domain, portBindings, data, function (error) {
|
||||
@@ -924,14 +935,9 @@ function clone(appId, data, auditSource, callback) {
|
||||
|
||||
taskmanager.restartAppTask(newAppId);
|
||||
|
||||
// fetch fresh app object for eventlog
|
||||
get(appId, function (error, result) {
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
|
||||
eventlog.add(eventlog.ACTION_APP_CLONE, auditSource, { appId: newAppId, oldAppId: appId, backupId: backupId, location: location, manifest: manifest });
|
||||
|
||||
eventlog.add(eventlog.ACTION_APP_CLONE, auditSource, { appId: newAppId, oldAppId: appId, backupId: backupId, oldApp: app, newApp: result });
|
||||
|
||||
callback(null, { id : newAppId });
|
||||
});
|
||||
callback(null, { id : newAppId });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -946,10 +952,10 @@ function uninstall(appId, auditSource, callback) {
|
||||
|
||||
debug('Will uninstall app with id:%s', appId);
|
||||
|
||||
get(appId, function (error, app) {
|
||||
get(appId, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
appstore.unpurchase(appId, app.appStoreId, function (error) {
|
||||
appstore.unpurchase(appId, result.appStoreId, function (error) {
|
||||
if (error && error.reason === AppstoreError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND));
|
||||
if (error && error.reason === AppstoreError.BILLING_REQUIRED) return callback(new AppsError(AppsError.BILLING_REQUIRED, error.message));
|
||||
if (error && error.reason === AppstoreError.EXTERNAL_ERROR) return callback(new AppsError(AppsError.EXTERNAL_ERROR, error.message));
|
||||
@@ -960,7 +966,7 @@ function uninstall(appId, auditSource, callback) {
|
||||
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND, 'No such app'));
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
|
||||
|
||||
eventlog.add(eventlog.ACTION_APP_UNINSTALL, auditSource, { appId: appId, app: app });
|
||||
eventlog.add(eventlog.ACTION_APP_UNINSTALL, auditSource, { appId: appId });
|
||||
|
||||
taskmanager.startAppTask(appId, callback);
|
||||
});
|
||||
@@ -1011,7 +1017,7 @@ function checkManifestConstraints(manifest) {
|
||||
}
|
||||
|
||||
if (semver.valid(manifest.minBoxVersion) && semver.gt(manifest.minBoxVersion, config.version())) {
|
||||
return new AppsError(AppsError.BAD_FIELD, 'App version requires a new platform version');
|
||||
return new AppsError(AppsError.BAD_FIELD, 'minBoxVersion exceeds Box version');
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -1025,8 +1031,9 @@ function exec(appId, options, callback) {
|
||||
var cmd = options.cmd || [ '/bin/bash' ];
|
||||
assert(util.isArray(cmd) && cmd.length > 0);
|
||||
|
||||
get(appId, function (error, app) {
|
||||
if (error) return callback(error);
|
||||
appdb.get(appId, function (error, app) {
|
||||
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND, 'No such app'));
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
|
||||
|
||||
if (app.installationState !== appdb.ISTATE_INSTALLED || app.runState !== appdb.RSTATE_RUNNING) {
|
||||
return callback(new AppsError(AppsError.BAD_STATE, 'App not installed or running'));
|
||||
@@ -1165,17 +1172,17 @@ function listBackups(page, perPage, appId, callback) {
|
||||
function restoreInstalledApps(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
getAll(function (error, apps) {
|
||||
appdb.getAll(function (error, apps) {
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
|
||||
|
||||
async.map(apps, function (app, iteratorDone) {
|
||||
debug('marking %s for restore', app.intrinsicFqdn);
|
||||
|
||||
backups.getByAppIdPaged(1, 1, app.id, function (error, results) {
|
||||
var restoreConfig = !error && results.length ? { backupId: results[0].id, backupFormat: results[0].format } : null;
|
||||
|
||||
debug(`marking ${app.fqdn} for restore using restore config ${JSON.stringify(restoreConfig)}`);
|
||||
|
||||
appdb.setInstallationCommand(app.id, appdb.ISTATE_PENDING_RESTORE, { restoreConfig: restoreConfig, oldConfig: null }, function (error) {
|
||||
if (error) debug(`Error marking ${app.fqdn} for restore: ${JSON.stringify(error)}`);
|
||||
if (error) debug('did not mark %s for restore', app.intrinsicFqdn, error);
|
||||
|
||||
iteratorDone(); // always succeed
|
||||
});
|
||||
@@ -1187,14 +1194,14 @@ function restoreInstalledApps(callback) {
|
||||
function configureInstalledApps(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
getAll(function (error, apps) {
|
||||
appdb.getAll(function (error, apps) {
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
|
||||
|
||||
async.map(apps, function (app, iteratorDone) {
|
||||
debug(`marking ${app.fqdn} for reconfigure`);
|
||||
debug('marking %s for reconfigure', app.intrinsicFqdn);
|
||||
|
||||
appdb.setInstallationCommand(app.id, appdb.ISTATE_PENDING_CONFIGURE, { oldConfig: null }, function (error) {
|
||||
if (error) debug(`Error marking ${app.fqdn} for reconfigure: ${JSON.stringify(error)}`);
|
||||
if (error) debug('did not mark %s for reconfigure', app.intrinsicFqdn, error);
|
||||
|
||||
iteratorDone(); // always succeed
|
||||
});
|
||||
|
||||
+56
-97
@@ -18,12 +18,9 @@ exports = module.exports = {
|
||||
AppstoreError: AppstoreError
|
||||
};
|
||||
|
||||
var apps = require('./apps.js'),
|
||||
assert = require('assert'),
|
||||
async = require('async'),
|
||||
var assert = require('assert'),
|
||||
config = require('./config.js'),
|
||||
debug = require('debug')('box:appstore'),
|
||||
domains = require('./domains.js'),
|
||||
eventlog = require('./eventlog.js'),
|
||||
mail = require('./mail.js'),
|
||||
os = require('os'),
|
||||
@@ -130,7 +127,7 @@ function unpurchase(appId, appstoreId, callback) {
|
||||
if (error && !error.response) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, error));
|
||||
if (result.statusCode === 403 || result.statusCode === 401) return callback(new AppstoreError(AppstoreError.BILLING_REQUIRED));
|
||||
if (result.statusCode === 404) return callback(null); // was never purchased
|
||||
if (result.statusCode !== 201 && result.statusCode !== 200) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, util.format('App unpurchase failed. %s %j', result.status, result.body)));
|
||||
if (result.statusCode !== 201 && result.statusCode !== 200) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, util.format('App purchase failed. %s %j', result.status, result.body)));
|
||||
|
||||
superagent.del(url).query({ accessToken: appstoreConfig.token }).timeout(30 * 1000).end(function (error, result) {
|
||||
if (error && !error.response) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, error));
|
||||
@@ -143,87 +140,62 @@ function unpurchase(appId, appstoreId, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function sendAliveStatus(callback) {
|
||||
function sendAliveStatus(data, callback) {
|
||||
callback = callback || NOOP_CALLBACK;
|
||||
|
||||
var allSettings, allDomains, mailDomains, loginEvents;
|
||||
settings.getAll(function (error, result) {
|
||||
if (error) return callback(new AppstoreError(AppstoreError.INTERNAL_ERROR, error));
|
||||
|
||||
async.series([
|
||||
function (callback) {
|
||||
settings.getAll(function (error, result) {
|
||||
mail.getAll(function (error, mailDomains) {
|
||||
if (error) return callback(new AppstoreError(AppstoreError.INTERNAL_ERROR, error));
|
||||
|
||||
eventlog.getAllPaged(eventlog.ACTION_USER_LOGIN, null, 1, 1, function (error, loginEvents) {
|
||||
if (error) return callback(new AppstoreError(AppstoreError.INTERNAL_ERROR, error));
|
||||
allSettings = result;
|
||||
callback();
|
||||
});
|
||||
},
|
||||
function (callback) {
|
||||
domains.getAll(function (error, result) {
|
||||
if (error) return callback(new AppstoreError(AppstoreError.INTERNAL_ERROR, error));
|
||||
allDomains = result;
|
||||
callback();
|
||||
});
|
||||
},
|
||||
function (callback) {
|
||||
mail.getAll(function (error, result) {
|
||||
if (error) return callback(new AppstoreError(AppstoreError.INTERNAL_ERROR, error));
|
||||
mailDomains = result;
|
||||
callback();
|
||||
});
|
||||
},
|
||||
function (callback) {
|
||||
eventlog.getAllPaged([ eventlog.ACTION_USER_LOGIN ], null, 1, 1, function (error, result) {
|
||||
if (error) return callback(new AppstoreError(AppstoreError.INTERNAL_ERROR, error));
|
||||
loginEvents = result;
|
||||
callback();
|
||||
});
|
||||
}
|
||||
], function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
var backendSettings = {
|
||||
backupConfig: {
|
||||
provider: allSettings[settings.BACKUP_CONFIG_KEY].provider,
|
||||
hardlinks: !allSettings[settings.BACKUP_CONFIG_KEY].noHardlinks
|
||||
},
|
||||
domainConfig: {
|
||||
count: allDomains.length,
|
||||
domains: Array.from(new Set(allDomains.map(function (d) { return { domain: d.domain, provider: d.provider }; })))
|
||||
},
|
||||
mailConfig: {
|
||||
outboundCount: mailDomains.length,
|
||||
inboundCount: mailDomains.filter(function (d) { return d.enabled; }).length,
|
||||
catchAllCount: mailDomains.filter(function (d) { return d.catchAll.length !== 0; }).length,
|
||||
relayProviders: Array.from(new Set(mailDomains.map(function (d) { return d.relay.provider; })))
|
||||
},
|
||||
appAutoupdatePattern: allSettings[settings.APP_AUTOUPDATE_PATTERN_KEY],
|
||||
boxAutoupdatePattern: allSettings[settings.BOX_AUTOUPDATE_PATTERN_KEY],
|
||||
timeZone: allSettings[settings.TIME_ZONE_KEY],
|
||||
};
|
||||
var backendSettings = {
|
||||
backupConfig: {
|
||||
provider: result[settings.BACKUP_CONFIG_KEY].provider,
|
||||
hardlinks: !result[settings.BACKUP_CONFIG_KEY].noHardlinks
|
||||
},
|
||||
domainConfig: {
|
||||
count: mailDomains.length
|
||||
},
|
||||
mailConfig: {
|
||||
outboundCount: mailDomains.length,
|
||||
inboundCount: mailDomains.filter(function (d) { return d.enabled; }).length,
|
||||
catchAllCount: mailDomains.filter(function (d) { return d.catchAll.length !== 0; }).length,
|
||||
relayProviders: Array.from(new Set(mailDomains.map(function (d) { return d.relay.provider; })))
|
||||
},
|
||||
autoupdatePattern: result[settings.AUTOUPDATE_PATTERN_KEY],
|
||||
timeZone: result[settings.TIME_ZONE_KEY],
|
||||
};
|
||||
|
||||
var data = {
|
||||
version: config.version(),
|
||||
adminFqdn: config.adminFqdn(),
|
||||
provider: config.provider(),
|
||||
backendSettings: backendSettings,
|
||||
machine: {
|
||||
cpus: os.cpus(),
|
||||
totalmem: os.totalmem()
|
||||
},
|
||||
events: {
|
||||
lastLogin: loginEvents[0] ? (new Date(loginEvents[0].creationTime).getTime()) : 0
|
||||
}
|
||||
};
|
||||
var data = {
|
||||
version: config.version(),
|
||||
adminFqdn: config.adminFqdn(),
|
||||
provider: config.provider(),
|
||||
backendSettings: backendSettings,
|
||||
machine: {
|
||||
cpus: os.cpus(),
|
||||
totalmem: os.totalmem()
|
||||
},
|
||||
events: {
|
||||
lastLogin: loginEvents[0] ? (new Date(loginEvents[0].creationTime).getTime()) : 0
|
||||
}
|
||||
};
|
||||
|
||||
getAppstoreConfig(function (error, appstoreConfig) {
|
||||
if (error) return callback(error);
|
||||
getAppstoreConfig(function (error, appstoreConfig) {
|
||||
if (error) return callback(error);
|
||||
|
||||
var url = config.apiServerOrigin() + '/api/v1/users/' + appstoreConfig.userId + '/cloudrons/' + appstoreConfig.cloudronId + '/alive';
|
||||
superagent.post(url).send(data).query({ accessToken: appstoreConfig.token }).timeout(30 * 1000).end(function (error, result) {
|
||||
if (error && !error.response) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, error));
|
||||
if (result.statusCode === 404) return callback(new AppstoreError(AppstoreError.NOT_FOUND));
|
||||
if (result.statusCode !== 201) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, util.format('Sending alive status failed. %s %j', result.status, result.body)));
|
||||
var url = config.apiServerOrigin() + '/api/v1/users/' + appstoreConfig.userId + '/cloudrons/' + appstoreConfig.cloudronId + '/alive';
|
||||
superagent.post(url).send(data).query({ accessToken: appstoreConfig.token }).timeout(30 * 1000).end(function (error, result) {
|
||||
if (error && !error.response) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, error));
|
||||
if (result.statusCode === 404) return callback(new AppstoreError(AppstoreError.NOT_FOUND));
|
||||
if (result.statusCode !== 201) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, util.format('Sending alive status failed. %s %j', result.status, result.body)));
|
||||
|
||||
callback(null);
|
||||
callback(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -270,12 +242,9 @@ function getAppUpdate(app, callback) {
|
||||
|
||||
const updateInfo = result.body;
|
||||
|
||||
// for the appstore, x.y.z is the same as x.y.z-0 but in semver, x.y.z > x.y.z-0
|
||||
const curAppVersion = semver.prerelease(app.manifest.version) ? app.manifest.version : `${app.manifest.version}-0`;
|
||||
|
||||
// do some sanity checks
|
||||
if (!safe.query(updateInfo, 'manifest.version') || semver.gt(curAppVersion, safe.query(updateInfo, 'manifest.version'))) {
|
||||
debug('Skipping malformed update of app %s version: %s. got %j', app.id, curAppVersion, updateInfo);
|
||||
if (!safe.query(updateInfo, 'manifest.version') || semver.gt(app.manifest.version, safe.query(updateInfo, 'manifest.version'))) {
|
||||
debug('Skipping malformed update of app %s version: %s. got %j', app.id, app.manifest.version, updateInfo);
|
||||
return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, util.format('Malformed update: %s %s', result.statusCode, result.text)));
|
||||
}
|
||||
|
||||
@@ -312,26 +281,16 @@ function sendFeedback(info, callback) {
|
||||
assert.strictEqual(typeof info.description, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
function collectAppInfoIfNeeded(callback) {
|
||||
if (!info.appId) return callback();
|
||||
apps.get(info.appId, callback);
|
||||
}
|
||||
|
||||
getAppstoreConfig(function (error, appstoreConfig) {
|
||||
if (error) return callback(error);
|
||||
|
||||
collectAppInfoIfNeeded(function (error, result) {
|
||||
if (error) console.error('Unable to get app info', error);
|
||||
if (result) info.app = result;
|
||||
var url = config.apiServerOrigin() + '/api/v1/users/' + appstoreConfig.userId + '/cloudrons/' + appstoreConfig.cloudronId + '/feedback';
|
||||
|
||||
var url = config.apiServerOrigin() + '/api/v1/users/' + appstoreConfig.userId + '/cloudrons/' + appstoreConfig.cloudronId + '/feedback';
|
||||
superagent.post(url).query({ accessToken: appstoreConfig.token }).send(info).timeout(10 * 1000).end(function (error, result) {
|
||||
if (error && !error.response) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, error));
|
||||
if (result.statusCode !== 201) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, util.format('Bad response: %s %s', result.statusCode, result.text)));
|
||||
|
||||
superagent.post(url).query({ accessToken: appstoreConfig.token }).send(info).timeout(10 * 1000).end(function (error, result) {
|
||||
if (error && !error.response) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, error));
|
||||
if (result.statusCode !== 201) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, util.format('Bad response: %s %s', result.statusCode, result.text)));
|
||||
|
||||
callback(null);
|
||||
});
|
||||
callback(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
+34
-9
@@ -15,7 +15,8 @@ exports = module.exports = {
|
||||
_verifyManifest: verifyManifest,
|
||||
_registerSubdomain: registerSubdomain,
|
||||
_unregisterSubdomain: unregisterSubdomain,
|
||||
_waitForDnsPropagation: waitForDnsPropagation
|
||||
_waitForDnsPropagation: waitForDnsPropagation,
|
||||
_waitForAltDomainDnsPropagation: waitForAltDomainDnsPropagation
|
||||
};
|
||||
|
||||
require('supererror')({ splatchError: true });
|
||||
@@ -70,7 +71,8 @@ function initialize(callback) {
|
||||
function debugApp(app) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
|
||||
debug(app.fqdn + ' ' + util.format.apply(util, Array.prototype.slice.call(arguments, 1)));
|
||||
var prefix = app ? (app.intrinsicFqdn || '(bare)') : '(no app)';
|
||||
debug(prefix + ' ' + util.format.apply(util, Array.prototype.slice.call(arguments, 1)));
|
||||
}
|
||||
|
||||
// updates the app object and the database
|
||||
@@ -264,16 +266,16 @@ function registerSubdomain(app, overwrite, callback) {
|
||||
if (error) return callback(error);
|
||||
|
||||
async.retry({ times: 200, interval: 5000 }, function (retryCallback) {
|
||||
debugApp(app, 'Registering subdomain location [%s] overwrite: %s', app.fqdn, overwrite);
|
||||
debugApp(app, 'Registering subdomain location [%s] overwrite: %s', app.intrinsicFqdn, overwrite);
|
||||
|
||||
// get the current record before updating it
|
||||
domains.getDnsRecords(app.location, app.domain, 'A', function (error, values) {
|
||||
domains.getDNSRecords(app.location, app.domain, 'A', function (error, values) {
|
||||
if (error) return retryCallback(error);
|
||||
|
||||
// refuse to update any existing DNS record for custom domains that we did not create
|
||||
if (values.length !== 0 && !overwrite) return retryCallback(null, new Error('DNS Record already exists'));
|
||||
|
||||
domains.upsertDnsRecords(app.location, app.domain, 'A', [ ip ], function (error, changeId) {
|
||||
domains.upsertDNSRecords(app.location, app.domain, 'A', [ ip ], function (error, changeId) {
|
||||
if (error && (error.reason === DomainError.STILL_BUSY || error.reason === DomainError.EXTERNAL_ERROR)) return retryCallback(error); // try again
|
||||
|
||||
retryCallback(null, error || changeId);
|
||||
@@ -303,9 +305,9 @@ function unregisterSubdomain(app, location, domain, callback) {
|
||||
if (error) return callback(error);
|
||||
|
||||
async.retry({ times: 30, interval: 5000 }, function (retryCallback) {
|
||||
debugApp(app, 'Unregistering subdomain: %s', app.fqdn);
|
||||
debugApp(app, 'Unregistering subdomain: %s', app.intrinsicFqdn);
|
||||
|
||||
domains.removeDnsRecords(location, domain, 'A', [ ip ], function (error) {
|
||||
domains.removeDNSRecords(location, domain, 'A', [ ip ], function (error) {
|
||||
if (error && error.reason === DomainError.NOT_FOUND) return retryCallback(null, null); // domain can be not found if oldConfig.domain or restoreConfig.domain was removed
|
||||
if (error && (error.reason === DomainError.STILL_BUSY || error.reason === DomainError.EXTERNAL_ERROR)) return retryCallback(error); // try again
|
||||
|
||||
@@ -341,10 +343,27 @@ function waitForDnsPropagation(app, callback) {
|
||||
sysinfo.getPublicIp(function (error, ip) {
|
||||
if (error) return callback(error);
|
||||
|
||||
domains.waitForDnsRecord(app.fqdn, app.domain, ip, { interval: 5000, times: 120 }, callback);
|
||||
domains.waitForDNSRecord(app.intrinsicFqdn, app.domain, ip, 'A', { interval: 5000, times: 120 }, callback);
|
||||
});
|
||||
}
|
||||
|
||||
function waitForAltDomainDnsPropagation(app, callback) {
|
||||
if (!app.altDomain) return callback(null);
|
||||
|
||||
// try for 10 minutes before giving up. this allows the user to "reconfigure" the app in the case where
|
||||
// an app has an external domain and cloudron is migrated to custom domain.
|
||||
var isNakedDomain = tld.getDomain(app.altDomain) === app.altDomain;
|
||||
if (isNakedDomain) { // check naked domains with A record since CNAME records don't work there
|
||||
sysinfo.getPublicIp(function (error, ip) {
|
||||
if (error) return callback(error);
|
||||
|
||||
domains.waitForDNSRecord(app.altDomain, tld.getDomain(app.altDomain), ip, 'A', { interval: 10000, times: 60 }, callback);
|
||||
});
|
||||
} else {
|
||||
domains.waitForDNSRecord(app.altDomain, tld.getDomain(app.altDomain), app.intrinsicFqdn + '.', 'CNAME', { interval: 10000, times: 60 }, callback);
|
||||
}
|
||||
}
|
||||
|
||||
// Ordering is based on the following rationale:
|
||||
// - configure nginx, icon, oauth
|
||||
// - register subdomain.
|
||||
@@ -427,6 +446,9 @@ function install(app, callback) {
|
||||
updateApp.bind(null, app, { installationProgress: '85, Waiting for DNS propagation' }),
|
||||
exports._waitForDnsPropagation.bind(null, app),
|
||||
|
||||
updateApp.bind(null, app, { installationProgress: '90, Waiting for External Domain setup' }),
|
||||
exports._waitForAltDomainDnsPropagation.bind(null, app), // required when restoring and !restoreConfig
|
||||
|
||||
updateApp.bind(null, app, { installationProgress: '95, Configuring reverse proxy' }),
|
||||
configureReverseProxy.bind(null, app),
|
||||
|
||||
@@ -472,7 +494,7 @@ function configure(app, callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
// oldConfig can be null during an infra update
|
||||
var locationChanged = app.oldConfig && (app.oldConfig.fqdn !== app.fqdn);
|
||||
var locationChanged = app.oldConfig && (app.oldConfig.intrinsicFqdn !== app.intrinsicFqdn);
|
||||
|
||||
async.series([
|
||||
updateApp.bind(null, app, { installationProgress: '10, Cleaning up old install' }),
|
||||
@@ -519,6 +541,9 @@ function configure(app, callback) {
|
||||
updateApp.bind(null, app, { installationProgress: '80, Waiting for DNS propagation' }),
|
||||
exports._waitForDnsPropagation.bind(null, app),
|
||||
|
||||
updateApp.bind(null, app, { installationProgress: '85, Waiting for External Domain setup' }),
|
||||
exports._waitForAltDomainDnsPropagation.bind(null, app),
|
||||
|
||||
updateApp.bind(null, app, { installationProgress: '90, Configuring reverse proxy' }),
|
||||
configureReverseProxy.bind(null, app),
|
||||
|
||||
|
||||
+16
-19
@@ -68,9 +68,10 @@ var NOOP_CALLBACK = function (error) { if (error) debug(error); };
|
||||
var BACKUPTASK_CMD = path.join(__dirname, 'backuptask.js');
|
||||
|
||||
function debugApp(app) {
|
||||
assert(typeof app === 'object');
|
||||
assert(!app || typeof app === 'object');
|
||||
|
||||
debug(app.fqdn + ' ' + util.format.apply(util, Array.prototype.slice.call(arguments, 1)));
|
||||
var prefix = app ? app.intrinsicFqdn : '(no app)';
|
||||
debug(prefix + ' ' + util.format.apply(util, Array.prototype.slice.call(arguments, 1)));
|
||||
}
|
||||
|
||||
function BackupsError(reason, errorOrMessage) {
|
||||
@@ -231,11 +232,6 @@ function sync(backupConfig, backupId, dataDir, callback) {
|
||||
assert.strictEqual(typeof dataDir, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
function setBackupProgress(message) {
|
||||
debug(message);
|
||||
safe.fs.writeFileSync(paths.BACKUP_RESULT_FILE, message);
|
||||
}
|
||||
|
||||
syncer.sync(dataDir, function processTask(task, iteratorCallback) {
|
||||
debug('sync: processing task: %j', task);
|
||||
var backupFilePath = path.join(getBackupFilePath(backupConfig, backupId, backupConfig.format), task.path);
|
||||
@@ -243,23 +239,24 @@ function sync(backupConfig, backupId, dataDir, callback) {
|
||||
if (task.operation === 'removedir') {
|
||||
safe.fs.writeFileSync(paths.BACKUP_RESULT_FILE, `Removing directory ${task.path}`);
|
||||
return api(backupConfig.provider).removeDir(backupConfig, backupFilePath)
|
||||
.on('progress', setBackupProgress)
|
||||
.on('progress', function (detail) {
|
||||
debug(`sync: ${detail}`);
|
||||
safe.fs.writeFileSync(paths.BACKUP_RESULT_FILE, detail);
|
||||
})
|
||||
.on('done', iteratorCallback);
|
||||
} else if (task.operation === 'remove') {
|
||||
setBackupProgress(`Removing ${task.path}`);
|
||||
safe.fs.writeFileSync(paths.BACKUP_RESULT_FILE, `Removing ${task.path}`);
|
||||
return api(backupConfig.provider).remove(backupConfig, backupFilePath, iteratorCallback);
|
||||
}
|
||||
|
||||
var retryCount = 0;
|
||||
async.retry({ times: 5, interval: 20000 }, function (retryCallback) {
|
||||
retryCallback = once(retryCallback); // protect again upload() erroring much later after read stream error
|
||||
|
||||
++retryCount;
|
||||
debug(`${task.operation} ${task.path} try ${retryCount}`);
|
||||
if (task.operation === 'add') {
|
||||
setBackupProgress(`Adding ${task.path}`);
|
||||
safe.fs.writeFileSync(paths.BACKUP_RESULT_FILE, `Adding ${task.path}`);
|
||||
var stream = fs.createReadStream(path.join(dataDir, task.path));
|
||||
stream.on('error', function (error) { setBackupProgress(`read stream error for ${task.path}: ${error.message}`); retryCallback(); }); // ignore error if file disappears
|
||||
stream.on('error', function () { return retryCallback(); }); // ignore error if file disappears
|
||||
api(backupConfig.provider).upload(backupConfig, backupFilePath, stream, retryCallback);
|
||||
}
|
||||
}, iteratorCallback);
|
||||
@@ -297,6 +294,8 @@ function upload(backupId, format, dataDir, callback) {
|
||||
assert.strictEqual(typeof dataDir, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
callback = once(callback);
|
||||
|
||||
debug('upload: id %s format %s dataDir %s', backupId, format, dataDir);
|
||||
|
||||
settings.getBackupConfig(function (error, backupConfig) {
|
||||
@@ -304,8 +303,6 @@ function upload(backupId, format, dataDir, callback) {
|
||||
|
||||
if (format === 'tgz') {
|
||||
async.retry({ times: 5, interval: 20000 }, function (retryCallback) {
|
||||
retryCallback = once(retryCallback); // protect again upload() erroring much later after tar stream error
|
||||
|
||||
var tarStream = createTarPackStream(dataDir, backupConfig.key || null);
|
||||
tarStream.on('error', retryCallback); // already returns BackupsError
|
||||
|
||||
@@ -723,7 +720,7 @@ function backupApp(app, callback) {
|
||||
const timestamp = (new Date()).toISOString().replace(/[T.]/g, '-').replace(/[:Z]/g,'');
|
||||
safe.fs.unlinkSync(paths.BACKUP_LOG_FILE); // start fresh log file
|
||||
|
||||
progress.set(progress.BACKUP, 10, 'Backing up ' + app.fqdn);
|
||||
progress.set(progress.BACKUP, 10, 'Backing up ' + (app.altDomain || app.intrinsicFqdn));
|
||||
|
||||
backupAppWithTimestamp(app, timestamp, function (error) {
|
||||
progress.set(progress.BACKUP, 100, error ? error.message : '');
|
||||
@@ -750,12 +747,12 @@ function backupBoxAndApps(auditSource, callback) {
|
||||
var step = 100/(allApps.length+2);
|
||||
|
||||
async.mapSeries(allApps, function iterator(app, iteratorCallback) {
|
||||
progress.set(progress.BACKUP, step * processed, 'Backing up ' + app.fqdn);
|
||||
progress.set(progress.BACKUP, step * processed, 'Backing up ' + (app.altDomain || app.intrinsicFqdn));
|
||||
|
||||
++processed;
|
||||
|
||||
if (!app.enableBackup) {
|
||||
progress.set(progress.BACKUP, step * processed, 'Skipped backup ' + app.fqdn);
|
||||
progress.set(progress.BACKUP, step * processed, 'Skipped backup ' + (app.altDomain || app.intrinsicFqdn));
|
||||
return iteratorCallback(null, null); // nothing to backup
|
||||
}
|
||||
|
||||
@@ -765,7 +762,7 @@ function backupBoxAndApps(auditSource, callback) {
|
||||
return iteratorCallback(error);
|
||||
}
|
||||
|
||||
progress.set(progress.BACKUP, step * processed, 'Backed up ' + app.fqdn);
|
||||
progress.set(progress.BACKUP, step * processed, 'Backed up ' + (app.altDomain || app.intrinsicFqdn));
|
||||
|
||||
iteratorCallback(null, backupId || null); // clear backupId if is in BAD_STATE and never backed up
|
||||
});
|
||||
|
||||
+1
-1
@@ -191,7 +191,7 @@ function getAll(callback) {
|
||||
if (record.type === exports.TYPE_PROXY) record.name = result.manifest.title + ' Website Proxy';
|
||||
if (record.type === exports.TYPE_OAUTH) record.name = result.manifest.title + ' OAuth';
|
||||
|
||||
record.domain = result.fqdn;
|
||||
record.domain = result.altDomain || result.intrinsicFqdn;
|
||||
|
||||
tmp.push(record);
|
||||
|
||||
|
||||
+1
-1
@@ -294,7 +294,7 @@ function doUpdate(boxUpdateInfo, callback) {
|
||||
|
||||
debug('updating box %s %j', boxUpdateInfo.sourceTarballUrl, _.omit(data, 'tlsCert', 'tlsKey', 'token', 'appstore', 'caas'));
|
||||
|
||||
progress.set(progress.UPDATE, 5, 'Downloading and installing new version');
|
||||
progress.set(progress.UPDATE, 5, 'Downloading and extracting new version');
|
||||
|
||||
shell.sudo('update', [ UPDATE_CMD, boxUpdateInfo.sourceTarballUrl, JSON.stringify(data) ], function (error) {
|
||||
if (error) return updateError(error);
|
||||
|
||||
+11
-37
@@ -22,12 +22,12 @@ var apps = require('./apps.js'),
|
||||
reverseProxy = require('./reverseproxy.js'),
|
||||
scheduler = require('./scheduler.js'),
|
||||
settings = require('./settings.js'),
|
||||
semver = require('semver'),
|
||||
updateChecker = require('./updatechecker.js');
|
||||
|
||||
var gJobs = {
|
||||
alive: null, // send periodic stats
|
||||
appAutoUpdater: null,
|
||||
boxAutoUpdater: null,
|
||||
autoUpdater: null,
|
||||
appUpdateChecker: null,
|
||||
backup: null,
|
||||
boxUpdateChecker: null,
|
||||
@@ -78,16 +78,14 @@ function initialize(callback) {
|
||||
});
|
||||
|
||||
settings.events.on(settings.TIME_ZONE_KEY, recreateJobs);
|
||||
settings.events.on(settings.APP_AUTOUPDATE_PATTERN_KEY, appAutoupdatePatternChanged);
|
||||
settings.events.on(settings.BOX_AUTOUPDATE_PATTERN_KEY, boxAutoupdatePatternChanged);
|
||||
settings.events.on(settings.AUTOUPDATE_PATTERN_KEY, autoupdatePatternChanged);
|
||||
settings.events.on(settings.DYNAMIC_DNS_KEY, dynamicDnsChanged);
|
||||
|
||||
settings.getAll(function (error, allSettings) {
|
||||
if (error) return callback(error);
|
||||
|
||||
recreateJobs(allSettings[settings.TIME_ZONE_KEY]);
|
||||
appAutoupdatePatternChanged(allSettings[settings.APP_AUTOUPDATE_PATTERN_KEY]);
|
||||
boxAutoupdatePatternChanged(allSettings[settings.BOX_AUTOUPDATE_PATTERN_KEY]);
|
||||
autoupdatePatternChanged(allSettings[settings.AUTOUPDATE_PATTERN_KEY]);
|
||||
dynamicDnsChanged(allSettings[settings.DYNAMIC_DNS_KEY]);
|
||||
|
||||
callback();
|
||||
@@ -191,51 +189,28 @@ function recreateJobs(tz) {
|
||||
});
|
||||
}
|
||||
|
||||
function boxAutoupdatePatternChanged(pattern) {
|
||||
function autoupdatePatternChanged(pattern) {
|
||||
assert.strictEqual(typeof pattern, 'string');
|
||||
assert(gJobs.boxUpdateCheckerJob);
|
||||
|
||||
debug('Box auto update pattern changed to %s', pattern);
|
||||
debug('Auto update pattern changed to %s', pattern);
|
||||
|
||||
if (gJobs.boxAutoUpdater) gJobs.boxAutoUpdater.stop();
|
||||
if (gJobs.autoUpdater) gJobs.autoUpdater.stop();
|
||||
|
||||
if (pattern === constants.AUTOUPDATE_PATTERN_NEVER) return;
|
||||
|
||||
gJobs.boxAutoUpdater = new CronJob({
|
||||
gJobs.autoUpdater = new CronJob({
|
||||
cronTime: pattern,
|
||||
onTick: function() {
|
||||
var updateInfo = updateChecker.getUpdateInfo();
|
||||
if (updateInfo.box) {
|
||||
debug('Starting autoupdate to %j', updateInfo.box);
|
||||
cloudron.updateToLatest(AUDIT_SOURCE, NOOP_CALLBACK);
|
||||
} else {
|
||||
debug('No box auto updates available');
|
||||
}
|
||||
},
|
||||
start: true,
|
||||
timeZone: gJobs.boxUpdateCheckerJob.cronTime.zone // hack
|
||||
});
|
||||
}
|
||||
|
||||
function appAutoupdatePatternChanged(pattern) {
|
||||
assert.strictEqual(typeof pattern, 'string');
|
||||
assert(gJobs.boxUpdateCheckerJob);
|
||||
|
||||
debug('Apps auto update pattern changed to %s', pattern);
|
||||
|
||||
if (gJobs.appAutoUpdater) gJobs.appAutoUpdater.stop();
|
||||
|
||||
if (pattern === constants.AUTOUPDATE_PATTERN_NEVER) return;
|
||||
|
||||
gJobs.appAutoUpdater = new CronJob({
|
||||
cronTime: pattern,
|
||||
onTick: function() {
|
||||
var updateInfo = updateChecker.getUpdateInfo();
|
||||
if (updateInfo.apps) {
|
||||
} else if (updateInfo.apps) {
|
||||
debug('Starting app update to %j', updateInfo.apps);
|
||||
apps.autoupdateApps(updateInfo.apps, AUDIT_SOURCE, NOOP_CALLBACK);
|
||||
} else {
|
||||
debug('No app auto updates available');
|
||||
debug('No auto updates available');
|
||||
}
|
||||
},
|
||||
start: true,
|
||||
@@ -266,8 +241,7 @@ function uninitialize(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
settings.events.removeListener(settings.TIME_ZONE_KEY, recreateJobs);
|
||||
settings.events.removeListener(settings.APP_AUTOUPDATE_PATTERN_KEY, appAutoupdatePatternChanged);
|
||||
settings.events.removeListener(settings.BOX_AUTOUPDATE_PATTERN_KEY, boxAutoupdatePatternChanged);
|
||||
settings.events.removeListener(settings.AUTOUPDATE_PATTERN_KEY, autoupdatePatternChanged);
|
||||
settings.events.removeListener(settings.DYNAMIC_DNS_KEY, dynamicDnsChanged);
|
||||
|
||||
for (var job in gJobs) {
|
||||
|
||||
+5
-6
@@ -13,7 +13,6 @@ var assert = require('assert'),
|
||||
constants = require('./constants.js'),
|
||||
eventlog = require('./eventlog.js'),
|
||||
tokendb = require('./tokendb.js'),
|
||||
user = require('./user.js'),
|
||||
util = require('util');
|
||||
|
||||
function DeveloperError(reason, errorOrMessage) {
|
||||
@@ -38,19 +37,19 @@ util.inherits(DeveloperError, Error);
|
||||
DeveloperError.INTERNAL_ERROR = 'Internal Error';
|
||||
DeveloperError.EXTERNAL_ERROR = 'External Error';
|
||||
|
||||
function issueDeveloperToken(userObject, ip, callback) {
|
||||
assert.strictEqual(typeof userObject, 'object');
|
||||
assert.strictEqual(typeof ip, 'string');
|
||||
function issueDeveloperToken(user, auditSource, callback) {
|
||||
assert.strictEqual(typeof user, 'object');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
var token = tokendb.generateToken();
|
||||
var expiresAt = Date.now() + constants.DEFAULT_TOKEN_EXPIRATION;
|
||||
var scopes = '*,' + clients.SCOPE_ROLE_SDK;
|
||||
|
||||
tokendb.add(token, userObject.id, 'cid-cli', expiresAt, scopes, function (error) {
|
||||
tokendb.add(token, user.id, 'cid-cli', expiresAt, scopes, function (error) {
|
||||
if (error) return callback(new DeveloperError(DeveloperError.INTERNAL_ERROR, error));
|
||||
|
||||
eventlog.add(eventlog.ACTION_USER_LOGIN, { authType: 'cli', ip: ip }, { userId: userObject.id, user: user.removePrivateFields(userObject) });
|
||||
eventlog.add(eventlog.ACTION_USER_LOGIN, auditSource, { authType: 'cli', userId: user.id, username: user.username });
|
||||
|
||||
callback(null, { token: token, expiresAt: new Date(expiresAt).toISOString() });
|
||||
});
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
'use strict';
|
||||
|
||||
exports = module.exports = {
|
||||
resolve: resolve
|
||||
};
|
||||
|
||||
var assert = require('assert'),
|
||||
child_process = require('child_process'),
|
||||
debug = require('debug')('box:dig');
|
||||
|
||||
function resolve(domain, type, options, callback) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof type, 'string');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
// dig @server cloudron.io TXT +short
|
||||
var args = [ ];
|
||||
if (options.server) args.push('@' + options.server);
|
||||
if (type === 'PTR') {
|
||||
args.push('-x', domain);
|
||||
} else {
|
||||
args.push(domain, type);
|
||||
}
|
||||
args.push('+short');
|
||||
|
||||
child_process.execFile('/usr/bin/dig', args, { encoding: 'utf8', killSignal: 'SIGKILL', timeout: options.timeout || 0 }, function (error, stdout, stderr) {
|
||||
if (error && error.killed) error.code = 'ETIMEDOUT';
|
||||
|
||||
if (error || stderr) debug('resolve error (%j): %j %s %s', args, error, stdout, stderr);
|
||||
if (error) return callback(error);
|
||||
|
||||
debug('resolve (%j): %s', args, stdout);
|
||||
|
||||
if (!stdout) return callback(); // timeout or no result
|
||||
|
||||
var lines = stdout.trim().split('\n');
|
||||
if (type === 'MX') {
|
||||
lines = lines.map(function (line) {
|
||||
var parts = line.split(' ');
|
||||
return { priority: parts[0], exchange: parts[1] };
|
||||
});
|
||||
}
|
||||
return callback(null, lines);
|
||||
});
|
||||
}
|
||||
@@ -11,7 +11,7 @@ exports = module.exports = {
|
||||
var assert = require('assert'),
|
||||
async = require('async'),
|
||||
debug = require('debug')('box:dns/cloudflare'),
|
||||
dns = require('../native-dns.js'),
|
||||
dns = require('dns'),
|
||||
DomainError = require('../domains.js').DomainError,
|
||||
superagent = require('superagent'),
|
||||
util = require('util'),
|
||||
@@ -58,7 +58,7 @@ function getZoneByName(dnsConfig, zoneName, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function getDnsRecordsByZoneId(dnsConfig, zoneId, zoneName, subdomain, type, callback) {
|
||||
function getDNSRecordsByZoneId(dnsConfig, zoneId, zoneName, subdomain, type, callback) {
|
||||
assert.strictEqual(typeof dnsConfig, 'object');
|
||||
assert.strictEqual(typeof zoneId, 'string');
|
||||
assert.strictEqual(typeof zoneName, 'string');
|
||||
@@ -100,7 +100,7 @@ function upsert(dnsConfig, zoneName, subdomain, type, values, callback) {
|
||||
|
||||
var zoneId = result.id;
|
||||
|
||||
getDnsRecordsByZoneId(dnsConfig, zoneId, zoneName, subdomain, type, function (error, result) {
|
||||
getDNSRecordsByZoneId(dnsConfig, zoneId, zoneName, subdomain, type, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
var dnsRecords = result;
|
||||
@@ -171,7 +171,7 @@ function get(dnsConfig, zoneName, subdomain, type, callback) {
|
||||
getZoneByName(dnsConfig, zoneName, function(error, result){
|
||||
if (error) return callback(error);
|
||||
|
||||
getDnsRecordsByZoneId(dnsConfig, result.id, zoneName, subdomain, type, function(error, result) {
|
||||
getDNSRecordsByZoneId(dnsConfig, result.id, zoneName, subdomain, type, function(error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
var tmp = result.map(function (record) { return record.content; });
|
||||
@@ -193,7 +193,7 @@ function del(dnsConfig, zoneName, subdomain, type, values, callback) {
|
||||
getZoneByName(dnsConfig, zoneName, function(error, result){
|
||||
if (error) return callback(error);
|
||||
|
||||
getDnsRecordsByZoneId(dnsConfig, result.id, zoneName, subdomain, type, function(error, result) {
|
||||
getDNSRecordsByZoneId(dnsConfig, result.id, zoneName, subdomain, type, function(error, result) {
|
||||
if (error) return callback(error);
|
||||
if (result.length === 0) return callback(null);
|
||||
|
||||
@@ -243,7 +243,7 @@ function verifyDnsConfig(dnsConfig, fqdn, zoneName, ip, callback) {
|
||||
|
||||
if (process.env.BOX_ENV === 'test') return callback(null, credentials); // this shouldn't be here
|
||||
|
||||
dns.resolve(zoneName, 'NS', { timeout: 5000 }, function (error, nameservers) {
|
||||
dns.resolveNs(zoneName, function (error, nameservers) {
|
||||
if (error && error.code === 'ENOTFOUND') return callback(new DomainError(DomainError.BAD_FIELD, 'Unable to resolve nameservers for this domain'));
|
||||
if (error || !nameservers) return callback(new DomainError(DomainError.BAD_FIELD, error ? error.message : 'Unable to get nameservers'));
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ exports = module.exports = {
|
||||
var assert = require('assert'),
|
||||
async = require('async'),
|
||||
debug = require('debug')('box:dns/digitalocean'),
|
||||
dns = require('../native-dns.js'),
|
||||
dns = require('dns'),
|
||||
DomainError = require('../domains.js').DomainError,
|
||||
safe = require('safetydance'),
|
||||
superagent = require('superagent'),
|
||||
@@ -210,7 +210,7 @@ function verifyDnsConfig(dnsConfig, fqdn, zoneName, ip, callback) {
|
||||
|
||||
if (process.env.BOX_ENV === 'test') return callback(null, credentials); // this shouldn't be here
|
||||
|
||||
dns.resolve(zoneName, 'NS', { timeout: 5000 }, function (error, nameservers) {
|
||||
dns.resolveNs(zoneName, function (error, nameservers) {
|
||||
if (error && error.code === 'ENOTFOUND') return callback(new DomainError(DomainError.BAD_FIELD, 'Unable to resolve nameservers for this domain'));
|
||||
if (error || !nameservers) return callback(new DomainError(DomainError.BAD_FIELD, error ? error.message : 'Unable to get nameservers'));
|
||||
|
||||
|
||||
+2
-2
@@ -10,7 +10,7 @@ exports = module.exports = {
|
||||
|
||||
var assert = require('assert'),
|
||||
debug = require('debug')('box:dns/gcdns'),
|
||||
dns = require('../native-dns.js'),
|
||||
dns = require('dns'),
|
||||
DomainError = require('../domains.js').DomainError,
|
||||
GCDNS = require('@google-cloud/dns'),
|
||||
util = require('util'),
|
||||
@@ -172,7 +172,7 @@ function verifyDnsConfig(dnsConfig, fqdn, zoneName, ip, callback) {
|
||||
var credentials = getDnsCredentials(dnsConfig);
|
||||
if (process.env.BOX_ENV === 'test') return callback(null, credentials); // this shouldn't be here
|
||||
|
||||
dns.resolve(zoneName, 'NS', { timeout: 5000 }, function (error, nameservers) {
|
||||
dns.resolveNs(zoneName, function (error, resolvedNS) {
|
||||
if (error && error.code === 'ENOTFOUND') return callback(new DomainError(DomainError.BAD_FIELD, 'Unable to resolve nameservers for this domain'));
|
||||
if (error || !resolvedNS) return callback(new DomainError(DomainError.BAD_FIELD, error ? error.message : 'Unable to get nameservers'));
|
||||
|
||||
|
||||
+2
-2
@@ -10,7 +10,7 @@ exports = module.exports = {
|
||||
|
||||
var assert = require('assert'),
|
||||
debug = require('debug')('box:dns/manual'),
|
||||
dns = require('../native-dns.js'),
|
||||
dns = require('dns'),
|
||||
DomainError = require('../domains.js').DomainError,
|
||||
util = require('util');
|
||||
|
||||
@@ -56,7 +56,7 @@ function verifyDnsConfig(dnsConfig, domain, zoneName, ip, callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
// Very basic check if the nameservers can be fetched
|
||||
dns.resolve(zoneName, 'NS', { timeout: 5000 }, function (error, nameservers) {
|
||||
dns.resolveNs(zoneName, function (error, nameservers) {
|
||||
if (error || !nameservers) return callback(new DomainError(DomainError.BAD_FIELD, 'Unable to get nameservers'));
|
||||
|
||||
callback(null, { wildcard: !!dnsConfig.wildcard });
|
||||
|
||||
+3
-2
@@ -46,10 +46,11 @@ function del(dnsConfig, zoneName, subdomain, type, values, callback) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
function waitForDns(domain, zoneName, value, options, callback) {
|
||||
function waitForDns(domain, zoneName, value, type, options, callback) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof zoneName, 'string');
|
||||
assert.strictEqual(typeof value, 'string');
|
||||
assert(typeof value === 'string' || util.isRegExp(value));
|
||||
assert(type === 'A' || type === 'CNAME' || type === 'TXT');
|
||||
assert(options && typeof options === 'object'); // { interval: 5000, times: 50000 }
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
|
||||
+4
-3
@@ -13,8 +13,9 @@ exports = module.exports = {
|
||||
|
||||
var assert = require('assert'),
|
||||
AWS = require('aws-sdk'),
|
||||
config = require('../config.js'),
|
||||
debug = require('debug')('box:dns/route53'),
|
||||
dns = require('../native-dns.js'),
|
||||
dns = require('dns'),
|
||||
DomainError = require('../domains.js').DomainError,
|
||||
util = require('util'),
|
||||
_ = require('underscore');
|
||||
@@ -192,7 +193,7 @@ function del(dnsConfig, zoneName, subdomain, type, values, callback) {
|
||||
};
|
||||
|
||||
var route53 = new AWS.Route53(getDnsCredentials(dnsConfig));
|
||||
route53.changeResourceRecordSets(params, function(error) {
|
||||
route53.changeResourceRecordSets(params, function(error, result) {
|
||||
if (error && error.code === 'AccessDenied') return callback(new DomainError(DomainError.ACCESS_DENIED, error.message));
|
||||
if (error && error.code === 'InvalidClientTokenId') return callback(new DomainError(DomainError.ACCESS_DENIED, error.message));
|
||||
if (error && error.message && error.message.indexOf('it was not found') !== -1) {
|
||||
@@ -233,7 +234,7 @@ function verifyDnsConfig(dnsConfig, fqdn, zoneName, ip, callback) {
|
||||
|
||||
if (process.env.BOX_ENV === 'test') return callback(null, credentials); // this shouldn't be here
|
||||
|
||||
dns.resolve(zoneName, 'NS', { timeout: 5000 }, function (error, nameservers) {
|
||||
dns.resolveNs(zoneName, function (error, nameservers) {
|
||||
if (error && error.code === 'ENOTFOUND') return callback(new DomainError(DomainError.BAD_FIELD, 'Unable to resolve nameservers for this domain'));
|
||||
if (error || !nameservers) return callback(new DomainError(DomainError.BAD_FIELD, error ? error.message : 'Unable to get nameservers'));
|
||||
|
||||
|
||||
+44
-45
@@ -5,59 +5,53 @@ exports = module.exports = waitForDns;
|
||||
var assert = require('assert'),
|
||||
async = require('async'),
|
||||
debug = require('debug')('box:dns/waitfordns'),
|
||||
dns = require('../native-dns.js'),
|
||||
DomainError = require('../domains.js').DomainError;
|
||||
dig = require('../dig.js'),
|
||||
dns = require('dns'),
|
||||
DomainError = require('../domains.js').DomainError,
|
||||
util = require('util');
|
||||
|
||||
function resolveIp(hostname, options, callback) {
|
||||
assert.strictEqual(typeof hostname, 'string');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
// try A record at authoritative server
|
||||
debug(`resolveIp: Checking if ${hostname} has A record at ${options.server}`);
|
||||
dns.resolve(hostname, 'A', options, function (error, results) {
|
||||
if (!error && results.length !== 0) return callback(null, results);
|
||||
|
||||
// try CNAME record at authoritative server
|
||||
debug(`resolveIp: Checking if ${hostname} has CNAME record at ${options.server}`);
|
||||
dns.resolve(hostname, 'CNAME', options, function (error, results) {
|
||||
if (error || results.length === 0) return callback(error, results);
|
||||
|
||||
// recurse lookup the CNAME record
|
||||
debug(`resolveIp: Resolving ${hostname}'s CNAME record ${results[0]}`);
|
||||
dns.resolve(results[0], 'A', { server: '127.0.0.1', timeout: options.timeout }, callback);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function isChangeSynced(domain, value, nameserver, callback) {
|
||||
function isChangeSynced(domain, value, type, nameserver, callback) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof value, 'string');
|
||||
assert(util.isRegExp(value));
|
||||
assert.strictEqual(typeof type, 'string');
|
||||
assert.strictEqual(typeof nameserver, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
// ns records cannot have cname
|
||||
dns.resolve(nameserver, 'A', { timeout: 5000 }, function (error, nsIps) {
|
||||
dns.resolve4(nameserver, function (error, nsIps) {
|
||||
if (error || !nsIps || nsIps.length === 0) {
|
||||
debug(`isChangeSynced: cannot resolve NS ${nameserver}`); // it's fine if one or more ns are dead
|
||||
return callback(null, true);
|
||||
debug('nameserver %s does not resolve. assuming it stays bad.', nameserver); // it's fine if one or more ns are dead
|
||||
return callback(true);
|
||||
}
|
||||
|
||||
async.every(nsIps, function (nsIp, iteratorCallback) {
|
||||
resolveIp(domain, { server: nsIp, timeout: 5000 }, function (error, answer) {
|
||||
if (error && error.code === 'TIMEOUT') {
|
||||
debug(`isChangeSynced: NS ${nameserver} (${nsIp}) timed out when resolving ${domain}`);
|
||||
dig.resolve(domain, type, { server: nsIp, timeout: 5000 }, function (error, answer) {
|
||||
if (error && error.code === 'ETIMEDOUT') {
|
||||
debug('nameserver %s (%s) timed out when trying to resolve %s', nameserver, nsIp, domain);
|
||||
return iteratorCallback(null, true); // should be ok if dns server is down
|
||||
}
|
||||
|
||||
if (error) {
|
||||
debug(`isChangeSynced: NS ${nameserver} (${nsIp}) errored when resolve ${domain}: ${error}`);
|
||||
debug('nameserver %s (%s) returned error trying to resolve %s: %s', nameserver, nsIp, domain, error);
|
||||
return iteratorCallback(null, false);
|
||||
}
|
||||
|
||||
debug(`isChangeSynced: ${domain} was resolved to ${answer} at NS ${nameserver} (${nsIp}). Expecting ${value}`);
|
||||
if (!answer || answer.length === 0) {
|
||||
debug('bad answer from nameserver %s (%s) resolving %s (%s)', nameserver, nsIp, domain, type);
|
||||
return iteratorCallback(null, false);
|
||||
}
|
||||
|
||||
iteratorCallback(null, answer.length === 1 && answer[0] === value);
|
||||
debug('isChangeSynced: ns: %s (%s), name:%s Actual:%j Expecting:%s', nameserver, nsIp, domain, answer, value);
|
||||
|
||||
var match = answer.some(function (a) {
|
||||
return ((type === 'A' && value.test(a)) ||
|
||||
(type === 'CNAME' && value.test(a)) ||
|
||||
(type === 'TXT' && value.test(a)));
|
||||
});
|
||||
|
||||
if (match) return iteratorCallback(null, true); // done!
|
||||
|
||||
iteratorCallback(null, false);
|
||||
});
|
||||
}, callback);
|
||||
|
||||
@@ -65,25 +59,30 @@ function isChangeSynced(domain, value, nameserver, callback) {
|
||||
}
|
||||
|
||||
// check if IP change has propagated to every nameserver
|
||||
function waitForDns(domain, zoneName, value, options, callback) {
|
||||
function waitForDns(domain, zoneName, value, type, options, callback) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof zoneName, 'string');
|
||||
assert.strictEqual(typeof value, 'string');
|
||||
assert(typeof value === 'string' || util.isRegExp(value));
|
||||
assert(type === 'A' || type === 'CNAME' || type === 'TXT');
|
||||
assert(options && typeof options === 'object'); // { interval: 5000, times: 50000 }
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
debug('waitForDns: domain %s to be %s in zone %s.', domain, value, zoneName);
|
||||
if (typeof value === 'string') {
|
||||
// http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
|
||||
value = new RegExp('^' + value.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') + '$');
|
||||
}
|
||||
|
||||
var attempt = 0;
|
||||
debug('waitForIp: domain %s to be %s in zone %s.', domain, value, zoneName);
|
||||
|
||||
var attempt = 1;
|
||||
async.retry(options, function (retryCallback) {
|
||||
++attempt;
|
||||
debug(`waitForDns (try ${attempt}): ${domain} to be ${value} in zone ${zoneName}`);
|
||||
debug('waitForDNS: %s (zone: %s) attempt %s.', domain, zoneName, attempt++);
|
||||
|
||||
dns.resolve(zoneName, 'NS', { timeout: 5000 }, function (error, nameservers) {
|
||||
dns.resolveNs(zoneName, function (error, nameservers) {
|
||||
if (error || !nameservers) return retryCallback(error || new DomainError(DomainError.EXTERNAL_ERROR, 'Unable to get nameservers'));
|
||||
|
||||
async.every(nameservers, isChangeSynced.bind(null, domain, value), function (error, synced) {
|
||||
debug('waitForDns: %s %s ns: %j', domain, synced ? 'done' : 'not done', nameservers);
|
||||
async.every(nameservers, isChangeSynced.bind(null, domain, value, type), function (error, synced) {
|
||||
debug('waitForIp: %s %s ns: %j', domain, synced ? 'done' : 'not done', nameservers);
|
||||
|
||||
retryCallback(synced ? null : new DomainError(DomainError.EXTERNAL_ERROR, 'ETRYAGAIN'));
|
||||
});
|
||||
@@ -91,7 +90,7 @@ function waitForDns(domain, zoneName, value, options, callback) {
|
||||
}, function retryDone(error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
debug(`waitForDns: ${domain} has propagated`);
|
||||
debug('waitForDNS: %s done.', domain);
|
||||
|
||||
callback(null);
|
||||
});
|
||||
|
||||
+5
-9
@@ -15,7 +15,6 @@ exports = module.exports = {
|
||||
createSubcontainer: createSubcontainer,
|
||||
getContainerIdByIp: getContainerIdByIp,
|
||||
inspect: inspect,
|
||||
inspectByName: inspect,
|
||||
execContainer: execContainer
|
||||
};
|
||||
|
||||
@@ -50,9 +49,10 @@ var addons = require('./addons.js'),
|
||||
_ = require('underscore');
|
||||
|
||||
function debugApp(app, args) {
|
||||
assert(typeof app === 'object');
|
||||
assert(!app || typeof app === 'object');
|
||||
|
||||
debug(app.fqdn + ' ' + util.format.apply(util, Array.prototype.slice.call(arguments, 1)));
|
||||
var prefix = app ? app.intrinsicFqdn : '(no app)';
|
||||
debug(prefix + ' ' + util.format.apply(util, Array.prototype.slice.call(arguments, 1)));
|
||||
}
|
||||
|
||||
function pullImage(manifest, callback) {
|
||||
@@ -129,7 +129,7 @@ function createSubcontainer(app, name, cmd, options, callback) {
|
||||
|
||||
var manifest = app.manifest;
|
||||
var exposedPorts = {}, dockerPortBindings = { };
|
||||
var domain = app.fqdn;
|
||||
var domain = app.altDomain || app.intrinsicFqdn;
|
||||
var stdEnv = [
|
||||
'CLOUDRON=1',
|
||||
'WEBADMIN_ORIGIN=' + config.adminOrigin(),
|
||||
@@ -163,10 +163,6 @@ function createSubcontainer(app, name, cmd, options, callback) {
|
||||
memoryLimit = constants.DEFAULT_MEMORY_LIMIT;
|
||||
}
|
||||
|
||||
// give scheduler tasks twice the memory limit since background jobs take more memory
|
||||
// if required, we can make this a manifest and runtime argument later
|
||||
if (!isAppContainer) memoryLimit *= 2;
|
||||
|
||||
// apparmor is disabled on few servers
|
||||
var enableSecurityOpt = config.CLOUDRON && safe(function () { return child_process.spawnSync('aa-enabled').status === 0; }, false);
|
||||
|
||||
@@ -190,7 +186,7 @@ function createSubcontainer(app, name, cmd, options, callback) {
|
||||
'/run': {}
|
||||
},
|
||||
Labels: {
|
||||
'fqdn': app.fqdn,
|
||||
'fqdn': app.intrinsicFqdn,
|
||||
'appId': app.id,
|
||||
'isSubcontainer': String(!isAppContainer)
|
||||
},
|
||||
|
||||
+19
-15
@@ -10,11 +10,11 @@ module.exports = exports = {
|
||||
fqdn: fqdn,
|
||||
setAdmin: setAdmin,
|
||||
|
||||
getDnsRecords: getDnsRecords,
|
||||
upsertDnsRecords: upsertDnsRecords,
|
||||
removeDnsRecords: removeDnsRecords,
|
||||
getDNSRecords: getDNSRecords,
|
||||
upsertDNSRecords: upsertDNSRecords,
|
||||
removeDNSRecords: removeDNSRecords,
|
||||
|
||||
waitForDnsRecord: waitForDnsRecord,
|
||||
waitForDNSRecord: waitForDNSRecord,
|
||||
|
||||
DomainError: DomainError
|
||||
};
|
||||
@@ -116,7 +116,7 @@ function add(domain, zoneName, provider, config, fallbackCertificate, tlsConfig,
|
||||
}
|
||||
|
||||
if (fallbackCertificate) {
|
||||
let error = reverseProxy.validateCertificate(`test.${domain}`, fallbackCertificate.cert, fallbackCertificate.key);
|
||||
let error = reverseProxy.validateCertificate(fallbackCertificate.cert, fallbackCertificate.key, domain);
|
||||
if (error) return callback(new DomainError(DomainError.BAD_FIELD, error.message));
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ function get(domain, callback) {
|
||||
var cert = safe.fs.readFileSync(bundle.certFilePath, 'utf-8');
|
||||
var key = safe.fs.readFileSync(bundle.keyFilePath, 'utf-8');
|
||||
|
||||
if (!cert || !key) return callback(new DomainError(DomainError.INTERNAL_ERROR, 'unable to read certificates from disk'));
|
||||
if (!cert || !key) return callback(new DomainError(DomainError.INTERNAL_ERROR, error));
|
||||
|
||||
result.fallbackCertificate = { cert: cert, key: key };
|
||||
|
||||
@@ -196,7 +196,7 @@ function update(domain, provider, config, fallbackCertificate, tlsConfig, callba
|
||||
if (error) return callback(new DomainError(DomainError.INTERNAL_ERROR, error));
|
||||
|
||||
if (fallbackCertificate) {
|
||||
let error = reverseProxy.validateCertificate(`test.${domain}`, fallbackCertificate.cert, fallbackCertificate.key);
|
||||
let error = reverseProxy.validateCertificate(fallbackCertificate.cert, fallbackCertificate.key, domain);
|
||||
if (error) return callback(new DomainError(DomainError.BAD_FIELD, error.message));
|
||||
}
|
||||
|
||||
@@ -256,7 +256,7 @@ function getName(domain, subdomain) {
|
||||
return subdomain === '' ? part : subdomain + '.' + part;
|
||||
}
|
||||
|
||||
function getDnsRecords(subdomain, domain, type, callback) {
|
||||
function getDNSRecords(subdomain, domain, type, callback) {
|
||||
assert.strictEqual(typeof subdomain, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof type, 'string');
|
||||
@@ -273,7 +273,7 @@ function getDnsRecords(subdomain, domain, type, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function upsertDnsRecords(subdomain, domain, type, values, callback) {
|
||||
function upsertDNSRecords(subdomain, domain, type, values, callback) {
|
||||
assert.strictEqual(typeof subdomain, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof type, 'string');
|
||||
@@ -293,7 +293,7 @@ function upsertDnsRecords(subdomain, domain, type, values, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function removeDnsRecords(subdomain, domain, type, values, callback) {
|
||||
function removeDNSRecords(subdomain, domain, type, values, callback) {
|
||||
assert.strictEqual(typeof subdomain, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof type, 'string');
|
||||
@@ -313,18 +313,22 @@ function removeDnsRecords(subdomain, domain, type, values, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
// only wait for A record
|
||||
function waitForDnsRecord(fqdn, domain, value, options, callback) {
|
||||
function waitForDNSRecord(fqdn, domain, value, type, options, callback) {
|
||||
assert.strictEqual(typeof fqdn, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof value, 'string');
|
||||
assert(typeof value === 'string' || util.isRegExp(value));
|
||||
assert(type === 'A' || type === 'CNAME' || type === 'TXT');
|
||||
assert(options && typeof options === 'object'); // { interval: 5000, times: 50000 }
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
get(domain, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
// domain can be not found when waiting for altDomain. When we migrate altDomain, this can never happen
|
||||
if (error && error.reason !== DomainError.NOT_FOUND) return callback(new DomainError(DomainError.INTERNAL_ERROR, error));
|
||||
|
||||
api(result.provider).waitForDns(fqdn, result ? result.zoneName : domain, value, options, callback);
|
||||
// hack for lack of provider with altDomain. When we migrate altDomain, this will be automatically "manual"
|
||||
const provider = result ? result.provider : 'manual';
|
||||
|
||||
api(provider).waitForDns(fqdn, result ? result.zoneName : domain, value, type, options, callback);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -23,7 +23,7 @@ function sync(callback) {
|
||||
|
||||
debug('refreshDNS: current ip %s', ip);
|
||||
|
||||
domains.upsertDnsRecords(config.adminLocation(), config.adminDomain(), 'A', [ ip ], function (error) {
|
||||
domains.upsertDNSRecords(config.adminLocation(), config.adminDomain(), 'A', [ ip ], function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
debug('refreshDNS: done for admin location');
|
||||
@@ -35,7 +35,7 @@ function sync(callback) {
|
||||
// do not change state of installing apps since apptask will error if dns record already exists
|
||||
if (app.installationState !== appdb.ISTATE_INSTALLED) return callback();
|
||||
|
||||
domains.upsertDnsRecords(app.location, app.domain, 'A', [ ip ], callback);
|
||||
domains.upsertDNSRecords(app.location, app.domain, 'A', [ ip ], callback);
|
||||
}, function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
|
||||
+4
-3
@@ -22,6 +22,7 @@ exports = module.exports = {
|
||||
ACTION_BACKUP_START: 'backup.start',
|
||||
ACTION_BACKUP_CLEANUP: 'backup.cleanup',
|
||||
ACTION_CERTIFICATE_RENEWAL: 'certificate.renew',
|
||||
ACTION_CLI_MODE: 'settings.climode',
|
||||
ACTION_START: 'cloudron.start',
|
||||
ACTION_UPDATE: 'cloudron.update',
|
||||
ACTION_USER_ADD: 'user.add',
|
||||
@@ -90,14 +91,14 @@ function get(id, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function getAllPaged(actions, search, page, perPage, callback) {
|
||||
assert(Array.isArray(actions));
|
||||
function getAllPaged(action, search, page, perPage, callback) {
|
||||
assert(typeof action === 'string' || action === null);
|
||||
assert(typeof search === 'string' || search === null);
|
||||
assert.strictEqual(typeof page, 'number');
|
||||
assert.strictEqual(typeof perPage, 'number');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
eventlogdb.getAllPaged(actions, search, page, perPage, function (error, events) {
|
||||
eventlogdb.getAllPaged(action, search, page, perPage, function (error, events) {
|
||||
if (error) return callback(new EventLogError(EventLogError.INTERNAL_ERROR, error));
|
||||
|
||||
callback(null, events);
|
||||
|
||||
+8
-9
@@ -40,8 +40,8 @@ function get(eventId, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function getAllPaged(actions, search, page, perPage, callback) {
|
||||
assert(Array.isArray(actions));
|
||||
function getAllPaged(action, search, page, perPage, callback) {
|
||||
assert(typeof action === 'string' || action === null);
|
||||
assert(typeof search === 'string' || search === null);
|
||||
assert.strictEqual(typeof page, 'number');
|
||||
assert.strictEqual(typeof perPage, 'number');
|
||||
@@ -50,15 +50,14 @@ function getAllPaged(actions, search, page, perPage, callback) {
|
||||
var data = [];
|
||||
var query = 'SELECT ' + EVENTLOGS_FIELDS + ' FROM eventlog';
|
||||
|
||||
if (actions.length || search) query += ' WHERE';
|
||||
if (action || search) query += ' WHERE';
|
||||
if (search) query += ' (source LIKE ' + mysql.escape('%' + search + '%') + ' OR data LIKE ' + mysql.escape('%' + search + '%') + ')';
|
||||
if (action && search) query += ' AND ';
|
||||
|
||||
if (actions.length && search) query += ' AND ( ';
|
||||
actions.forEach(function (action, i) {
|
||||
query += ' (action LIKE ' + mysql.escape(`%${action}%`) + ') ';
|
||||
if (i < actions.length-1) query += ' OR ';
|
||||
});
|
||||
if (actions.length && search) query += ' ) ';
|
||||
if (action) {
|
||||
query += ' action=?';
|
||||
data.push(action);
|
||||
}
|
||||
|
||||
query += ' ORDER BY creationTime DESC LIMIT ?,?';
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ exports = module.exports = {
|
||||
'postgresql': { repo: 'cloudron/postgresql', tag: 'cloudron/postgresql:1.0.0' },
|
||||
'mongodb': { repo: 'cloudron/mongodb', tag: 'cloudron/mongodb:1.0.1' },
|
||||
'redis': { repo: 'cloudron/redis', tag: 'cloudron/redis:1.0.0' },
|
||||
'mail': { repo: 'cloudron/mail', tag: 'cloudron/mail:1.2.0' },
|
||||
'mail': { repo: 'cloudron/mail', tag: 'cloudron/mail:1.1.0' },
|
||||
'graphite': { repo: 'cloudron/graphite', tag: 'cloudron/graphite:1.0.0' }
|
||||
}
|
||||
};
|
||||
|
||||
+3
-5
@@ -410,7 +410,7 @@ function authorizeUserForApp(req, res, next) {
|
||||
// we return no such object, to avoid leakage of a users existence
|
||||
if (!result) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
|
||||
eventlog.add(eventlog.ACTION_USER_LOGIN, { authType: 'ldap', appId: app.id, app: app }, { userId: req.user.id, user: user.removePrivateFields(req.user) });
|
||||
eventlog.add(eventlog.ACTION_USER_LOGIN, { authType: 'ldap', appId: app.id }, { userId: req.user.id });
|
||||
|
||||
res.end();
|
||||
});
|
||||
@@ -418,8 +418,6 @@ function authorizeUserForApp(req, res, next) {
|
||||
}
|
||||
|
||||
function authenticateMailbox(req, res, next) {
|
||||
debug('mailbox auth: %s (from %s)', req.dn.toString(), req.connection.ldap.id);
|
||||
|
||||
if (!req.dn.rdns[0].attrs.cn) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
|
||||
var email = req.dn.rdns[0].attrs.cn.value.toLowerCase();
|
||||
@@ -451,12 +449,12 @@ function authenticateMailbox(req, res, next) {
|
||||
} else if (mailbox.ownerType === mailboxdb.TYPE_USER) {
|
||||
if (!domain.enabled) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
|
||||
user.verifyWithUsername(parts[0], req.credentials || '', function (error, result) {
|
||||
user.verifyWithUsername(parts[0], req.credentials || '', function (error, user) {
|
||||
if (error && error.reason === UserError.NOT_FOUND) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
if (error && error.reason === UserError.WRONG_PASSWORD) return next(new ldap.InvalidCredentialsError(req.dn.toString()));
|
||||
if (error) return next(new ldap.OperationsError(error.message));
|
||||
|
||||
eventlog.add(eventlog.ACTION_USER_LOGIN, { authType: 'ldap', mailboxId: email }, { userId: result.id, user: user.removePrivateFields(result) });
|
||||
eventlog.add(eventlog.ACTION_USER_LOGIN, { authType: 'ldap', mailboxId: email }, { userId: user.username });
|
||||
res.end();
|
||||
});
|
||||
} else {
|
||||
|
||||
+156
-210
@@ -8,9 +8,6 @@ exports = module.exports = {
|
||||
|
||||
add: add,
|
||||
del: del,
|
||||
update: update,
|
||||
|
||||
addDnsRecords: addDnsRecords,
|
||||
|
||||
setMailFromValidation: setMailFromValidation,
|
||||
setCatchAllAddress: setCatchAllAddress,
|
||||
@@ -22,7 +19,6 @@ exports = module.exports = {
|
||||
sendTestMail: sendTestMail,
|
||||
|
||||
getMailboxes: getMailboxes,
|
||||
removeMailboxes: removeMailboxes,
|
||||
getUserMailbox: getUserMailbox,
|
||||
enableUserMailbox: enableUserMailbox,
|
||||
disableUserMailbox: disableUserMailbox,
|
||||
@@ -46,7 +42,7 @@ var assert = require('assert'),
|
||||
constants = require('./constants.js'),
|
||||
DatabaseError = require('./databaseerror.js'),
|
||||
debug = require('debug')('box:mail'),
|
||||
dns = require('./native-dns.js'),
|
||||
dig = require('./dig.js'),
|
||||
domains = require('./domains.js'),
|
||||
groups = require('./groups.js'),
|
||||
GroupError = groups.GroupError,
|
||||
@@ -69,7 +65,7 @@ var assert = require('assert'),
|
||||
util = require('util'),
|
||||
_ = require('underscore');
|
||||
|
||||
const DNS_OPTIONS = { server: '127.0.0.1', timeout: 5000 }; // unbound runs on 127.0.0.1
|
||||
const digOptions = { server: '127.0.0.1', port: 53, timeout: 5000 };
|
||||
var NOOP_CALLBACK = function (error) { if (error) debug(error); };
|
||||
|
||||
function MailError(reason, errorOrMessage) {
|
||||
@@ -95,7 +91,6 @@ MailError.INTERNAL_ERROR = 'Internal Error';
|
||||
MailError.BAD_FIELD = 'Bad Field';
|
||||
MailError.ALREADY_EXISTS = 'Already Exists';
|
||||
MailError.NOT_FOUND = 'Not Found';
|
||||
MailError.IN_USE = 'In Use';
|
||||
|
||||
function validateAlias(alias) {
|
||||
assert.strictEqual(typeof alias, 'string');
|
||||
@@ -123,7 +118,7 @@ function checkOutboundPort25(callback) {
|
||||
'smtp.mail.yahoo.com',
|
||||
'smtp.o2.ie',
|
||||
'smtp.comcast.net',
|
||||
'smtp.1und1.de',
|
||||
'outgoing.verizon.net'
|
||||
]);
|
||||
|
||||
var relay = {
|
||||
@@ -184,11 +179,9 @@ function verifyRelay(relay, callback) {
|
||||
assert.strictEqual(typeof relay, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
// we used to verify cloudron-smtp with checkOutboundPort25 but that is unreliable given that we just
|
||||
// randomly select some smtp server
|
||||
if (relay.provider === 'cloudron-smtp') return callback();
|
||||
var verifier = relay.provider === 'cloudron-smtp' ? checkOutboundPort25 : checkSmtpRelay.bind(null, relay);
|
||||
|
||||
checkSmtpRelay(relay, function (error) {
|
||||
verifier(function (error) {
|
||||
if (error) return callback(new MailError(MailError.BAD_FIELD, error.message));
|
||||
|
||||
callback();
|
||||
@@ -207,13 +200,14 @@ function checkDkim(domain, callback) {
|
||||
var dkimKey = readDkimPublicKeySync(domain);
|
||||
if (!dkimKey) return callback(new Error('Failed to read dkim public key'), dkim);
|
||||
|
||||
dkim.expected = 'v=DKIM1; t=s; p=' + dkimKey;
|
||||
dkim.expected = '"v=DKIM1; t=s; p=' + dkimKey + '"';
|
||||
|
||||
dns.resolve(dkim.domain, dkim.type, DNS_OPTIONS, function (error, txtRecords) {
|
||||
dig.resolve(dkim.domain, dkim.type, digOptions, function (error, txtRecords) {
|
||||
if (error && error.code === 'ENOTFOUND') return callback(null, dkim); // not setup
|
||||
if (error) return callback(error, dkim);
|
||||
|
||||
if (txtRecords.length !== 0) {
|
||||
dkim.value = txtRecords[0].join('');
|
||||
if (Array.isArray(txtRecords) && txtRecords.length !== 0) {
|
||||
dkim.value = txtRecords[0];
|
||||
dkim.status = (dkim.value === dkim.expected);
|
||||
}
|
||||
|
||||
@@ -226,18 +220,21 @@ function checkSpf(domain, callback) {
|
||||
domain: domain,
|
||||
type: 'TXT',
|
||||
value: null,
|
||||
expected: 'v=spf1 a:' + config.mailFqdn() + ' ~all',
|
||||
expected: '"v=spf1 a:' + config.mailFqdn() + ' ~all"',
|
||||
status: false
|
||||
};
|
||||
|
||||
dns.resolve(spf.domain, spf.type, DNS_OPTIONS, function (error, txtRecords) {
|
||||
// https://agari.zendesk.com/hc/en-us/articles/202952749-How-long-can-my-SPF-record-be-
|
||||
dig.resolve(spf.domain, spf.type, digOptions, function (error, txtRecords) {
|
||||
if (error && error.code === 'ENOTFOUND') return callback(null, spf); // not setup
|
||||
if (error) return callback(error, spf);
|
||||
|
||||
if (!Array.isArray(txtRecords)) return callback(null, spf);
|
||||
|
||||
var i;
|
||||
for (i = 0; i < txtRecords.length; i++) {
|
||||
let txtRecord = txtRecords[i].join(''); // https://agari.zendesk.com/hc/en-us/articles/202952749-How-long-can-my-SPF-record-be-
|
||||
if (txtRecord.indexOf('v=spf1 ') !== 0) continue; // not SPF
|
||||
spf.value = txtRecord;
|
||||
if (txtRecords[i].indexOf('"v=spf1 ') !== 0) continue; // not SPF
|
||||
spf.value = txtRecords[i];
|
||||
spf.status = spf.value.indexOf(' a:' + config.adminFqdn()) !== -1;
|
||||
break;
|
||||
}
|
||||
@@ -245,7 +242,7 @@ function checkSpf(domain, callback) {
|
||||
if (spf.status) {
|
||||
spf.expected = spf.value;
|
||||
} else if (i !== txtRecords.length) {
|
||||
spf.expected = 'v=spf1 a:' + config.adminFqdn() + ' ' + spf.value.slice('v=spf1 '.length);
|
||||
spf.expected = '"v=spf1 a:' + config.adminFqdn() + ' ' + spf.value.slice('"v=spf1 '.length);
|
||||
}
|
||||
|
||||
callback(null, spf);
|
||||
@@ -261,12 +258,13 @@ function checkMx(domain, callback) {
|
||||
status: false
|
||||
};
|
||||
|
||||
dns.resolve(mx.domain, mx.type, DNS_OPTIONS, function (error, mxRecords) {
|
||||
dig.resolve(mx.domain, mx.type, digOptions, function (error, mxRecords) {
|
||||
if (error && error.code === 'ENOTFOUND') return callback(null, mx); // not setup
|
||||
if (error) return callback(error, mx);
|
||||
|
||||
if (mxRecords.length !== 0) {
|
||||
mx.status = mxRecords.length == 1 && mxRecords[0].exchange === config.mailFqdn();
|
||||
mx.value = mxRecords.map(function (r) { return r.priority + ' ' + r.exchange + '.'; }).join(' ');
|
||||
if (Array.isArray(mxRecords) && mxRecords.length !== 0) {
|
||||
mx.status = mxRecords.length == 1 && mxRecords[0].exchange === (config.mailFqdn() + '.');
|
||||
mx.value = mxRecords.map(function (r) { return r.priority + ' ' + r.exchange; }).join(' ');
|
||||
}
|
||||
|
||||
callback(null, mx);
|
||||
@@ -278,15 +276,16 @@ function checkDmarc(domain, callback) {
|
||||
domain: '_dmarc.' + domain,
|
||||
type: 'TXT',
|
||||
value: null,
|
||||
expected: 'v=DMARC1; p=reject; pct=100',
|
||||
expected: '"v=DMARC1; p=reject; pct=100"',
|
||||
status: false
|
||||
};
|
||||
|
||||
dns.resolve(dmarc.domain, dmarc.type, DNS_OPTIONS, function (error, txtRecords) {
|
||||
dig.resolve(dmarc.domain, dmarc.type, digOptions, function (error, txtRecords) {
|
||||
if (error && error.code === 'ENOTFOUND') return callback(null, dmarc); // not setup
|
||||
if (error) return callback(error, dmarc);
|
||||
|
||||
if (txtRecords.length !== 0) {
|
||||
dmarc.value = txtRecords[0].join('');
|
||||
if (Array.isArray(txtRecords) && txtRecords.length !== 0) {
|
||||
dmarc.value = txtRecords[0];
|
||||
dmarc.status = (dmarc.value === dmarc.expected);
|
||||
}
|
||||
|
||||
@@ -299,7 +298,7 @@ function checkPtr(callback) {
|
||||
domain: null,
|
||||
type: 'PTR',
|
||||
value: null,
|
||||
expected: config.mailFqdn(), // any trailing '.' is added by client software (https://lists.gt.net/spf/devel/7918)
|
||||
expected: config.mailFqdn() + '.',
|
||||
status: false
|
||||
};
|
||||
|
||||
@@ -308,10 +307,11 @@ function checkPtr(callback) {
|
||||
|
||||
ptr.domain = ip.split('.').reverse().join('.') + '.in-addr.arpa';
|
||||
|
||||
dns.resolve(ptr.domain, 'PTR', DNS_OPTIONS, function (error, ptrRecords) {
|
||||
dig.resolve(ip, 'PTR', digOptions, function (error, ptrRecords) {
|
||||
if (error && error.code === 'ENOTFOUND') return callback(null, ptr); // not setup
|
||||
if (error) return callback(error, ptr);
|
||||
|
||||
if (ptrRecords.length !== 0) {
|
||||
if (Array.isArray(ptrRecords) && ptrRecords.length !== 0) {
|
||||
ptr.value = ptrRecords.join(' ');
|
||||
ptr.status = ptrRecords.some(function (v) { return v === ptr.expected; });
|
||||
}
|
||||
@@ -323,31 +323,15 @@ function checkPtr(callback) {
|
||||
|
||||
// https://raw.githubusercontent.com/jawsome/node-dnsbl/master/list.json
|
||||
const RBL_LIST = [
|
||||
{
|
||||
'name': 'Abuse.ch',
|
||||
'dns': 'spam.abuse.ch',
|
||||
'site': 'http://abuse.ch/'
|
||||
},
|
||||
|
||||
{
|
||||
'name': 'Barracuda',
|
||||
'dns': 'b.barracudacentral.org',
|
||||
'site': 'http://www.barracudacentral.org/rbl/removal-request'
|
||||
},
|
||||
{
|
||||
'name': 'Composite Blocking List',
|
||||
'dns': 'cbl.abuseat.org',
|
||||
'site': 'http://www.abuseat.org'
|
||||
},
|
||||
{
|
||||
'name': 'Multi SURBL',
|
||||
'dns': 'multi.surbl.org',
|
||||
'site': 'http://www.surbl.org'
|
||||
},
|
||||
{
|
||||
'name': 'Passive Spam Block List',
|
||||
'dns': 'psbl.surriel.com',
|
||||
'site': 'https://psbl.org'
|
||||
'name': 'SpamCop',
|
||||
'dns': 'bl.spamcop.net',
|
||||
'site': 'http://spamcop.net'
|
||||
},
|
||||
{
|
||||
'name': 'Sorbs Aggregate Zone',
|
||||
@@ -360,20 +344,30 @@ const RBL_LIST = [
|
||||
'site': 'http://sorbs.net'
|
||||
},
|
||||
{
|
||||
'name': 'Spam Cannibal',
|
||||
'dns': 'bl.spamcannibal.org',
|
||||
'site': 'http://www.spamcannibal.org/cannibal.cgi'
|
||||
},
|
||||
{
|
||||
'name': 'SpamCop',
|
||||
'dns': 'bl.spamcop.net',
|
||||
'site': 'http://spamcop.net'
|
||||
'name': 'Composite Blocking List',
|
||||
'dns': 'cbl.abuseat.org',
|
||||
'site': 'http://www.abuseat.org'
|
||||
},
|
||||
{
|
||||
'name': 'SpamHaus Zen',
|
||||
'dns': 'zen.spamhaus.org',
|
||||
'site': 'http://spamhaus.org'
|
||||
},
|
||||
{
|
||||
'name': 'Multi SURBL',
|
||||
'dns': 'multi.surbl.org',
|
||||
'site': 'http://www.surbl.org'
|
||||
},
|
||||
{
|
||||
'name': 'Spam Cannibal',
|
||||
'dns': 'bl.spamcannibal.org',
|
||||
'site': 'http://www.spamcannibal.org/cannibal.cgi'
|
||||
},
|
||||
{
|
||||
'name': 'dnsbl.abuse.ch',
|
||||
'dns': 'spam.abuse.ch',
|
||||
'site': 'http://dnsbl.abuse.ch/'
|
||||
},
|
||||
{
|
||||
'name': 'The Unsubscribe Blacklist(UBL)',
|
||||
'dns': 'ubl.unsubscore.com ',
|
||||
@@ -397,15 +391,15 @@ function checkRblStatus(domain, callback) {
|
||||
|
||||
// https://tools.ietf.org/html/rfc5782
|
||||
async.map(RBL_LIST, function (rblServer, iteratorDone) {
|
||||
dns.resolve(flippedIp + '.' + rblServer.dns, 'A', DNS_OPTIONS, function (error, records) {
|
||||
dig.resolve(flippedIp + '.' + rblServer.dns, 'A', digOptions, function (error, records) {
|
||||
if (error || !records) return iteratorDone(null, null); // not listed
|
||||
|
||||
debug('checkRblStatus: %s (ip: %s) is in the blacklist of %j', domain, flippedIp, rblServer);
|
||||
|
||||
var result = _.extend({ }, rblServer);
|
||||
|
||||
dns.resolve(flippedIp + '.' + rblServer.dns, 'TXT', DNS_OPTIONS, function (error, txtRecords) {
|
||||
result.txtRecords = error || !txtRecords ? 'No txt record' : txtRecords.map(x => x.join(''));
|
||||
dig.resolve(flippedIp + '.' + rblServer.dns, 'TXT', digOptions, function (error, txtRecords) {
|
||||
result.txtRecords = error || !txtRecords ? 'No txt record' : txtRecords;
|
||||
|
||||
debug('checkRblStatus: %s (error: %s) (txtRecords: %j)', domain, error, txtRecords);
|
||||
|
||||
@@ -475,59 +469,45 @@ function getStatus(domain, callback) {
|
||||
function createMailConfig(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
const mailFqdn = config.mailFqdn();
|
||||
|
||||
debug('createMailConfig: generating mail config');
|
||||
|
||||
maildb.getAll(function (error, mailDomains) {
|
||||
maildb.getAll(function (error, mailOutDomains) {
|
||||
if (error) return callback(error);
|
||||
|
||||
user.getOwner(function (error, owner) {
|
||||
const mailFqdn = config.mailFqdn();
|
||||
const defaultDomain = config.adminDomain();
|
||||
const alertsFrom = `no-reply@${defaultDomain}`;
|
||||
var mailDomain = mailOutDomains[0]; // mail container can only handle one domain at this point
|
||||
|
||||
const alertsFrom = `no-reply@${mailDomain.domain}`;
|
||||
|
||||
user.getOwner(function (error, owner) {
|
||||
const alertsTo = config.provider() === 'caas' ? [ 'support@cloudron.io' ] : [ ];
|
||||
alertsTo.concat(error ? [] : owner.email).join(','); // owner may not exist yet
|
||||
|
||||
const mailOutDomains = mailDomains.map(function (d) { return d.domain; }).join(',');
|
||||
const mailInDomains = mailDomains.filter(function (d) { return d.enabled; }).map(function (d) { return d.domain; }).join(',');
|
||||
const mailOutDomain = mailDomain.domain;
|
||||
const mailInDomain = mailDomain.enabled ? mailDomain.domain : '';
|
||||
const catchAll = mailDomain.catchAll.map(function (c) { return `${c}@${mailDomain.domain}`; }).join(',');
|
||||
const mailFromValidation = mailDomain.mailFromValidation;
|
||||
|
||||
if (!safe.fs.writeFileSync(path.join(paths.ADDON_CONFIG_DIR, 'mail/mail.ini'),
|
||||
`mail_in_domains=${mailInDomains}\nmail_out_domains=${mailOutDomains}\nmail_default_domain=${defaultDomain}\nmail_server_name=${mailFqdn}\nalerts_from=${alertsFrom}\nalerts_to=${alertsTo}\n\n`, 'utf8')) {
|
||||
if (!safe.fs.writeFileSync(paths.ADDON_CONFIG_DIR + '/mail/mail.ini',
|
||||
`mail_in_domains=${mailInDomain}\nmail_out_domains=${mailOutDomain}\nmail_default_domain=${mailDomain.domain}\nmail_server_name=${mailFqdn}\nalerts_from=${alertsFrom}\nalerts_to=${alertsTo}\ncatch_all=${catchAll}\nmail_from_validation=${mailFromValidation}\n`, 'utf8')) {
|
||||
return callback(new Error('Could not create mail var file:' + safe.error.message));
|
||||
}
|
||||
|
||||
// enable_outbound makes plugin forward email for relayed mail. non-relayed mail always hits LMTP plugin first
|
||||
if (!safe.fs.writeFileSync(path.join(paths.ADDON_CONFIG_DIR, 'mail/smtp_forward.ini'), 'enable_outbound=false\ndomain_selector=mail_from\n', 'utf8')) {
|
||||
return callback(new Error('Could not create smtp forward file:' + safe.error.message));
|
||||
var relay = mailDomain.relay;
|
||||
|
||||
const enabled = relay.provider !== 'cloudron-smtp' ? true : false,
|
||||
host = relay.host || '',
|
||||
port = relay.port || 25,
|
||||
username = relay.username || '',
|
||||
password = relay.password || '';
|
||||
|
||||
if (!safe.fs.writeFileSync(paths.ADDON_CONFIG_DIR + '/mail/smtp_forward.ini',
|
||||
`enable_outbound=${enabled}\nhost=${host}\nport=${port}\nenable_tls=true\nauth_type=plain\nauth_user=${username}\nauth_pass=${password}`, 'utf8')) {
|
||||
return callback(new Error('Could not create mail var file:' + safe.error.message));
|
||||
}
|
||||
|
||||
// create sections for per-domain configuration
|
||||
mailDomains.forEach(function (domain) {
|
||||
const catchAll = domain.catchAll.map(function (c) { return `${c}@${domain.domain}`; }).join(',');
|
||||
const mailFromValidation = domain.mailFromValidation;
|
||||
|
||||
if (!safe.fs.appendFileSync(path.join(paths.ADDON_CONFIG_DIR, 'mail/mail.ini'),
|
||||
`[${domain.domain}]\ncatch_all=${catchAll}\nmail_from_validation=${mailFromValidation}\n\n`, 'utf8')) {
|
||||
return callback(new Error('Could not create mail var file:' + safe.error.message));
|
||||
}
|
||||
|
||||
const relay = domain.relay;
|
||||
|
||||
const enableRelay = relay.provider !== 'cloudron-smtp',
|
||||
host = relay.host || '',
|
||||
port = relay.port || 25,
|
||||
username = relay.username || '',
|
||||
password = relay.password || '';
|
||||
|
||||
if (!enableRelay) return;
|
||||
|
||||
if (!safe.fs.appendFileSync(paths.ADDON_CONFIG_DIR + '/mail/smtp_forward.ini',
|
||||
`[${domain.domain}]\nenable_outbound=true\nhost=${host}\nport=${port}\nenable_tls=true\nauth_type=plain\nauth_user=${username}\nauth_pass=${password}\n\n`, 'utf8')) {
|
||||
return callback(new Error('Could not create mail var file:' + safe.error.message));
|
||||
}
|
||||
});
|
||||
|
||||
callback(null, mailInDomains.length !== 0 /* allowInbound */);
|
||||
callback(null, mailInDomain.length !== 0);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -544,7 +524,7 @@ function restartMail(callback) {
|
||||
const memoryLimit = Math.max((1 + Math.round(os.totalmem()/(1024*1024*1024)/4)) * 128, 256);
|
||||
|
||||
// admin and mail share the same certificate
|
||||
reverseProxy.getCertificate({ fqdn: config.adminFqdn(), domain: config.adminDomain() }, function (error, bundle) {
|
||||
reverseProxy.getCertificate({ intrinsicFqdn: config.adminFqdn(), domain: config.adminDomain() }, function (error, bundle) {
|
||||
if (error) return callback(error);
|
||||
|
||||
// the setup script copies dhparams.pem to /addons/mail
|
||||
@@ -602,57 +582,16 @@ function getAll(callback) {
|
||||
});
|
||||
}
|
||||
|
||||
// https://agari.zendesk.com/hc/en-us/articles/202952749-How-long-can-my-SPF-record-be-
|
||||
function txtRecordsWithSpf(domain, callback) {
|
||||
function ensureDkimKey(domain, callback) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
domains.getDnsRecords('', domain, 'TXT', function (error, txtRecords) {
|
||||
if (error) return callback(error);
|
||||
var dkimPath = path.join(paths.MAIL_DATA_DIR, `dkim/${domain}`);
|
||||
var dkimPrivateKeyFile = path.join(dkimPath, 'private');
|
||||
var dkimPublicKeyFile = path.join(dkimPath, 'public');
|
||||
var dkimSelectorFile = path.join(dkimPath, 'selector');
|
||||
|
||||
debug('txtRecordsWithSpf: current txt records - %j', txtRecords);
|
||||
|
||||
var i, matches, validSpf;
|
||||
|
||||
for (i = 0; i < txtRecords.length; i++) {
|
||||
matches = txtRecords[i].match(/^("?v=spf1) /); // DO backend may return without quotes
|
||||
if (matches === null) continue;
|
||||
|
||||
// this won't work if the entry is arbitrarily "split" across quoted strings
|
||||
validSpf = txtRecords[i].indexOf('a:' + config.mailFqdn()) !== -1;
|
||||
break; // there can only be one SPF record
|
||||
}
|
||||
|
||||
if (validSpf) return callback(null, null);
|
||||
|
||||
if (!matches) { // no spf record was found, create one
|
||||
txtRecords.push('"v=spf1 a:' + config.mailFqdn() + ' ~all"');
|
||||
debug('txtRecordsWithSpf: adding txt record');
|
||||
} else { // just add ourself
|
||||
txtRecords[i] = matches[1] + ' a:' + config.mailFqdn() + txtRecords[i].slice(matches[1].length);
|
||||
debug('txtRecordsWithSpf: inserting txt record');
|
||||
}
|
||||
|
||||
return callback(null, txtRecords);
|
||||
});
|
||||
}
|
||||
|
||||
function ensureDkimKeySync(domain) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
|
||||
const dkimPath = path.join(paths.MAIL_DATA_DIR, `dkim/${domain}`);
|
||||
const dkimPrivateKeyFile = path.join(dkimPath, 'private');
|
||||
const dkimPublicKeyFile = path.join(dkimPath, 'public');
|
||||
const dkimSelectorFile = path.join(dkimPath, 'selector');
|
||||
|
||||
if (safe.fs.existsSync(dkimPublicKeyFile) &&
|
||||
safe.fs.existsSync(dkimPublicKeyFile) &&
|
||||
safe.fs.existsSync(dkimPublicKeyFile)) {
|
||||
debug(`Reusing existing DKIM keys for ${domain}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
debug(`Generating new DKIM keys for ${domain}`);
|
||||
debug('Generating new DKIM keys');
|
||||
|
||||
if (!safe.fs.mkdirSync(dkimPath) && safe.error.code !== 'EEXIST') {
|
||||
debug('Error creating dkim.', safe.error);
|
||||
@@ -664,7 +603,41 @@ function ensureDkimKeySync(domain) {
|
||||
|
||||
if (!safe.fs.writeFileSync(dkimSelectorFile, config.dkimSelector(), 'utf8')) return new MailError(MailError.INTERNAL_ERROR, safe.error);
|
||||
|
||||
return null;
|
||||
callback();
|
||||
}
|
||||
|
||||
// https://agari.zendesk.com/hc/en-us/articles/202952749-How-long-can-my-SPF-record-be-
|
||||
function txtRecordsWithSpf(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
domains.getDNSRecords('', config.adminDomain(), 'TXT', function (error, txtRecords) {
|
||||
if (error) return callback(error);
|
||||
|
||||
debug('txtRecordsWithSpf: current txt records - %j', txtRecords);
|
||||
|
||||
var i, matches, validSpf;
|
||||
|
||||
for (i = 0; i < txtRecords.length; i++) {
|
||||
matches = txtRecords[i].match(/^("?v=spf1) /); // DO backend may return without quotes
|
||||
if (matches === null) continue;
|
||||
|
||||
// this won't work if the entry is arbitrarily "split" across quoted strings
|
||||
validSpf = txtRecords[i].indexOf('a:' + config.adminFqdn()) !== -1;
|
||||
break; // there can only be one SPF record
|
||||
}
|
||||
|
||||
if (validSpf) return callback(null, null);
|
||||
|
||||
if (!matches) { // no spf record was found, create one
|
||||
txtRecords.push('"v=spf1 a:' + config.adminFqdn() + ' ~all"');
|
||||
debug('txtRecordsWithSpf: adding txt record');
|
||||
} else { // just add ourself
|
||||
txtRecords[i] = matches[1] + ' a:' + config.adminFqdn() + txtRecords[i].slice(matches[1].length);
|
||||
debug('txtRecordsWithSpf: inserting txt record');
|
||||
}
|
||||
|
||||
return callback(null, txtRecords);
|
||||
});
|
||||
}
|
||||
|
||||
function readDkimPublicKeySync(domain) {
|
||||
@@ -690,9 +663,6 @@ function addDnsRecords(domain, callback) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
var error = ensureDkimKeySync(domain);
|
||||
if (error) return callback(error);
|
||||
|
||||
if (process.env.BOX_ENV === 'test') return callback();
|
||||
|
||||
var dkimKey = readDkimPublicKeySync(domain);
|
||||
@@ -706,21 +676,28 @@ function addDnsRecords(domain, callback) {
|
||||
|
||||
debug('addDnsRecords: %j', records);
|
||||
|
||||
txtRecordsWithSpf(domain, function (error, txtRecords) {
|
||||
if (error) return callback(error);
|
||||
async.retry({ times: 10, interval: 20000 }, function (retryCallback) {
|
||||
txtRecordsWithSpf(function (error, txtRecords) {
|
||||
if (error) return retryCallback(error);
|
||||
|
||||
if (txtRecords) records.push({ subdomain: '', domain: domain, type: 'TXT', values: txtRecords });
|
||||
if (txtRecords) records.push({ subdomain: '', domain: domain, type: 'TXT', values: txtRecords });
|
||||
|
||||
debug('addDnsRecords: will update %j', records);
|
||||
debug('addDnsRecords: will update %j', records);
|
||||
|
||||
async.mapSeries(records, function (record, iteratorCallback) {
|
||||
domains.upsertDnsRecords(record.subdomain, record.domain, record.type, record.values, iteratorCallback);
|
||||
}, function (error, changeIds) {
|
||||
if (error) debug('addDnsRecords: failed to update : %s. will retry', error);
|
||||
else debug('addDnsRecords: records %j added with changeIds %j', records, changeIds);
|
||||
async.mapSeries(records, function (record, iteratorCallback) {
|
||||
domains.upsertDNSRecords(record.subdomain, record.domain, record.type, record.values, iteratorCallback);
|
||||
}, function (error, changeIds) {
|
||||
if (error) debug('addDnsRecords: failed to update : %s. will retry', error);
|
||||
else debug('addDnsRecords: records %j added with changeIds %j', records, changeIds);
|
||||
|
||||
callback(error);
|
||||
retryCallback(error);
|
||||
});
|
||||
});
|
||||
}, function (error) {
|
||||
if (error) debug('addDnsRecords: done updating records with error:', error);
|
||||
else debug('addDnsRecords: done');
|
||||
|
||||
callback(error);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -728,28 +705,18 @@ function add(domain, callback) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
maildb.add(domain, function (error) {
|
||||
if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(new MailError(MailError.ALREADY_EXISTS, 'Domain already exists'));
|
||||
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new MailError(MailError.NOT_FOUND, 'No such domain'));
|
||||
ensureDkimKey(domain, function (error) {
|
||||
if (error) return callback(new MailError(MailError.INTERNAL_ERROR, error));
|
||||
|
||||
addDnsRecords(domain, NOOP_CALLBACK); // add the required dns records asynchronously
|
||||
maildb.add(domain, function (error) {
|
||||
if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(new MailError(MailError.ALREADY_EXISTS, 'Domain already exists'));
|
||||
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new MailError(MailError.NOT_FOUND, 'No such domain'));
|
||||
if (error) return callback(new MailError(MailError.INTERNAL_ERROR, error));
|
||||
|
||||
callback();
|
||||
});
|
||||
}
|
||||
addDnsRecords(domain, NOOP_CALLBACK); // add the required dns records asynchronously
|
||||
|
||||
// this is just a way to resync the mail "dns" records via the UI
|
||||
function update(domain, callback) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
get(domain, function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
addDnsRecords(domain, NOOP_CALLBACK);
|
||||
|
||||
callback();
|
||||
callback();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -757,18 +724,11 @@ function del(domain, callback) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
get(domain, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
maildb.del(domain, function (error) {
|
||||
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new MailError(MailError.NOT_FOUND, error.message));
|
||||
if (error) return callback(new MailError(MailError.INTERNAL_ERROR, error));
|
||||
|
||||
maildb.del(domain, function (error) {
|
||||
if (error && error.reason === DatabaseError.IN_USE) return callback(new MailError(MailError.IN_USE));
|
||||
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new MailError(MailError.NOT_FOUND, error.message));
|
||||
if (error) return callback(new MailError(MailError.INTERNAL_ERROR, error));
|
||||
|
||||
if (result && result.enabled) restartMail(NOOP_CALLBACK);
|
||||
|
||||
callback();
|
||||
});
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -840,7 +800,7 @@ function setMailEnabled(domain, enabled, callback) {
|
||||
];
|
||||
|
||||
async.mapSeries(records, function (record, iteratorCallback) {
|
||||
domains.upsertDnsRecords(record.subdomain, domain, record.type, record.values, iteratorCallback);
|
||||
domains.upsertDNSRecords(record.subdomain, domain, record.type, record.values, iteratorCallback);
|
||||
}, NOOP_CALLBACK);
|
||||
|
||||
callback(null);
|
||||
@@ -872,17 +832,6 @@ function getMailboxes(domain, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function removeMailboxes(domain, callback) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
mailboxdb.delByDomain(domain, function (error) {
|
||||
if (error) return callback(new MailError(MailError.INTERNAL_ERROR, error));
|
||||
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
function getUserMailbox(domain, userId, callback) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof userId, 'string');
|
||||
@@ -891,7 +840,6 @@ function getUserMailbox(domain, userId, callback) {
|
||||
user.get(userId, function (error, result) {
|
||||
if (error && error.reason === UserError.NOT_FOUND) return callback(new MailError(MailError.NOT_FOUND, 'no such user'));
|
||||
if (error) return callback(new MailError(MailError.INTERNAL_ERROR, error));
|
||||
if (!result.username) return callback(new MailError(MailError.NOT_FOUND, 'no such mailbox'));
|
||||
|
||||
mailboxdb.getMailbox(result.username, domain, function (error, result) {
|
||||
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new MailError(MailError.NOT_FOUND, 'no such mailbox'));
|
||||
@@ -910,7 +858,6 @@ function enableUserMailbox(domain, userId, callback) {
|
||||
user.get(userId, function (error, result) {
|
||||
if (error && error.reason === UserError.NOT_FOUND) return callback(new MailError(MailError.NOT_FOUND, 'no such user'));
|
||||
if (error) return callback(new MailError(MailError.INTERNAL_ERROR));
|
||||
if (!result.username) return callback(new MailError(MailError.NOT_FOUND, 'user has no username'));
|
||||
|
||||
mailboxdb.add(result.username, domain, userId, mailboxdb.TYPE_USER, function (error) {
|
||||
if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(new MailError(MailError.ALREADY_EXISTS, 'mailbox already exists'));
|
||||
@@ -929,7 +876,6 @@ function disableUserMailbox(domain, userId, callback) {
|
||||
user.get(userId, function (error, result) {
|
||||
if (error && error.reason === UserError.NOT_FOUND) return callback(new MailError(MailError.NOT_FOUND, 'no such user'));
|
||||
if (error) return callback(new MailError(MailError.INTERNAL_ERROR, error));
|
||||
if (!result.username) return callback(new MailError(MailError.NOT_FOUND, 'user has no username'));
|
||||
|
||||
mailboxdb.del(result.username, domain, function (error) {
|
||||
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new MailError(MailError.NOT_FOUND, 'no such mailbox'));
|
||||
@@ -948,7 +894,8 @@ function getAliases(domain, userId, callback) {
|
||||
user.get(userId, function (error, result) {
|
||||
if (error && error.reason === UserError.NOT_FOUND) return callback(new MailError(MailError.NOT_FOUND, 'no such user'));
|
||||
if (error) return callback(new MailError(MailError.INTERNAL_ERROR, error));
|
||||
if (!result.username) return callback(new MailError(MailError.NOT_FOUND, 'user has no username'));
|
||||
|
||||
if (!result.username) return callback(null, []);
|
||||
|
||||
mailboxdb.getAliasesForName(result.username, domain, function (error, aliases) {
|
||||
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new MailError(MailError.NOT_FOUND, 'no such mailbox'));
|
||||
@@ -975,7 +922,6 @@ function setAliases(domain, userId, aliases, callback) {
|
||||
user.get(userId, function (error, result) {
|
||||
if (error && error.reason === UserError.NOT_FOUND) return callback(new MailError(MailError.NOT_FOUND, 'no such user'));
|
||||
if (error) return callback(new MailError(MailError.INTERNAL_ERROR, error));
|
||||
if (!result.username) return callback(new MailError(MailError.NOT_FOUND, 'user has no username'));
|
||||
|
||||
mailboxdb.setAliasesForName(result.username, domain, aliases, function (error) {
|
||||
if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(new MailError(MailError.ALREADY_EXISTS, error.message));
|
||||
|
||||
@@ -9,7 +9,7 @@ This is most likely a problem in the application.
|
||||
To resolve this, you can try the following:
|
||||
* Restart the app in the app configuration dialog
|
||||
* Restore the app to the latest backup
|
||||
* Contact us via support@cloudron.io or https://forum.cloudron.io
|
||||
* Contact us via support@cloudron.io or https://chat.cloudron.io
|
||||
|
||||
|
||||
Powered by https://cloudron.io
|
||||
|
||||
@@ -17,7 +17,6 @@ exports = module.exports = {
|
||||
|
||||
getByOwnerId: getByOwnerId,
|
||||
delByOwnerId: delByOwnerId,
|
||||
delByDomain: delByDomain,
|
||||
|
||||
updateName: updateName,
|
||||
|
||||
@@ -73,18 +72,6 @@ function del(name, domain, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function delByDomain(domain, callback) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
// deletes aliases as well
|
||||
database.query('DELETE FROM mailboxes WHERE domain = ?', [ domain ], function (error) {
|
||||
if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error));
|
||||
|
||||
callback(null);
|
||||
});
|
||||
}
|
||||
|
||||
function delByOwnerId(id, callback) {
|
||||
assert.strictEqual(typeof id, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
@@ -61,7 +61,6 @@ function del(domain, callback) {
|
||||
|
||||
// deletes aliases as well
|
||||
database.query('DELETE FROM mail WHERE domain=?', [ domain ], function (error, result) {
|
||||
if (error && error.code === 'ER_ROW_IS_REFERENCED_2') return callback(new DatabaseError(DatabaseError.IN_USE));
|
||||
if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error));
|
||||
if (result.affectedRows === 0) return callback(new DatabaseError(DatabaseError.NOT_FOUND));
|
||||
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
exports = module.exports = {
|
||||
resolve: resolve
|
||||
};
|
||||
|
||||
var assert = require('assert'),
|
||||
dns = require('dns');
|
||||
|
||||
// a note on TXT records. It doesn't have quotes ("") at the DNS level. Those quotes
|
||||
// are added for DNS server software to enclose spaces. Such quotes may also be returned
|
||||
// by the DNS REST API of some providers
|
||||
function resolve(hostname, rrtype, options, callback) {
|
||||
assert.strictEqual(typeof hostname, 'string');
|
||||
assert.strictEqual(typeof rrtype, 'string');
|
||||
assert(options && typeof options === 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
const resolver = new dns.Resolver();
|
||||
if (options.server) resolver.setServers([ options.server ]);
|
||||
|
||||
// should callback with ECANCELLED but looks like we might hit https://github.com/nodejs/node/issues/14814
|
||||
const timerId = setTimeout(resolver.cancel.bind(resolver), options.timeout || 5000);
|
||||
|
||||
resolver.resolve(hostname, rrtype, function (error, result) {
|
||||
clearTimeout(timerId);
|
||||
|
||||
if (error && error.code === 'ECANCELLED') error.code = 'TIMEOUT';
|
||||
|
||||
// result is an empty array if there was no error but there is no record. when you query a random
|
||||
// domain, it errors with ENOTFOUND. But if you query an existing domain (A record) but with different
|
||||
// type (CNAME) it is not an error and empty array
|
||||
callback(error, result);
|
||||
});
|
||||
}
|
||||
@@ -33,7 +33,7 @@
|
||||
</div>
|
||||
<input class="btn btn-primary btn-outline pull-right" type="submit" value="Sign in"/>
|
||||
</form>
|
||||
<a href="/api/v1/session/password/resetRequest.html">Reset password</a>
|
||||
<a href="/api/v1/session/password/resetRequest.html">Reset your password</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<div class="layout-content">
|
||||
|
||||
<center>
|
||||
<h2>Reset password</h2>
|
||||
<h2>Reset your password</h2>
|
||||
</center>
|
||||
|
||||
<br/>
|
||||
|
||||
+38
-28
@@ -32,7 +32,6 @@ var acme = require('./cert/acme.js'),
|
||||
caas = require('./cert/caas.js'),
|
||||
config = require('./config.js'),
|
||||
constants = require('./constants.js'),
|
||||
crypto = require('crypto'),
|
||||
debug = require('debug')('box:certificates'),
|
||||
domains = require('./domains.js'),
|
||||
ejs = require('ejs'),
|
||||
@@ -40,12 +39,12 @@ var acme = require('./cert/acme.js'),
|
||||
fallback = require('./cert/fallback.js'),
|
||||
fs = require('fs'),
|
||||
mailer = require('./mailer.js'),
|
||||
os = require('os'),
|
||||
path = require('path'),
|
||||
paths = require('./paths.js'),
|
||||
platform = require('./platform.js'),
|
||||
safe = require('safetydance'),
|
||||
shell = require('./shell.js'),
|
||||
tld = require('tldjs'),
|
||||
user = require('./user.js'),
|
||||
util = require('util');
|
||||
|
||||
@@ -85,11 +84,12 @@ function getApi(app, callback) {
|
||||
|
||||
if (domain.tlsConfig.provider === 'fallback') return callback(null, fallback, {});
|
||||
|
||||
var api = domain.tlsConfig.provider === 'caas' ? caas : acme;
|
||||
// use acme if we have altDomain or the tlsConfig is not caas
|
||||
var api = (app.altDomain || domain.tlsConfig.provider !== 'caas') ? acme : caas;
|
||||
|
||||
var options = { };
|
||||
if (domain.tlsConfig.provider === 'caas') {
|
||||
options.prod = true;
|
||||
options.prod = true; // with altDomain, we will choose acme setting based on this
|
||||
} else { // acme
|
||||
options.prod = domain.tlsConfig.provider.match(/.*-prod/) !== null; // matches 'le-prod' or 'letsencrypt-prod'
|
||||
}
|
||||
@@ -138,11 +138,22 @@ function validateCertificate(domain, cert, key) {
|
||||
if (!cert && key) return new ReverseProxyError(ReverseProxyError.INVALID_CERT, 'missing cert');
|
||||
if (cert && !key) return new ReverseProxyError(ReverseProxyError.INVALID_CERT, 'missing key');
|
||||
|
||||
// -checkhost checks for SAN or CN exclusively. SAN takes precedence and if present, ignores the CN.
|
||||
var result = safe.child_process.execSync(`openssl x509 -noout -checkhost "${domain}"`, { encoding: 'utf8', input: cert });
|
||||
var result = safe.child_process.execSync('openssl x509 -noout -checkhost "' + domain + '"', { encoding: 'utf8', input: cert });
|
||||
if (!result) return new ReverseProxyError(ReverseProxyError.INVALID_CERT, 'Unable to get certificate subject.');
|
||||
|
||||
if (result.indexOf('does match certificate') === -1) return new ReverseProxyError(ReverseProxyError.INVALID_CERT, `Certificate is not valid for this domain. Expecting ${domain}`);
|
||||
// if no match, check alt names
|
||||
if (result.indexOf('does match certificate') === -1) {
|
||||
// https://github.com/drwetter/testssl.sh/pull/383
|
||||
var cmd = 'openssl x509 -noout -text | grep -A3 "Subject Alternative Name" | \
|
||||
grep "DNS:" | \
|
||||
sed -e "s/DNS://g" -e "s/ //g" -e "s/,/ /g" -e "s/othername:<unsupported>//g"';
|
||||
result = safe.child_process.execSync(cmd, { encoding: 'utf8', input: cert });
|
||||
var altNames = result ? [ ] : result.trim().split(' '); // might fail if cert has no SAN
|
||||
debug('validateCertificate: detected altNames as %j', altNames);
|
||||
|
||||
// check altNames
|
||||
if (!altNames.some(matchesDomain)) return new ReverseProxyError(ReverseProxyError.INVALID_CERT, util.format('Certificate is not valid for this domain. Expecting %s in %j', domain, altNames));
|
||||
}
|
||||
|
||||
// http://httpd.apache.org/docs/2.0/ssl/ssl_faq.html#verify
|
||||
var certModulus = safe.child_process.execSync('openssl x509 -noout -modulus', { encoding: 'utf8', input: cert });
|
||||
@@ -175,14 +186,8 @@ function setFallbackCertificate(domain, fallback, callback) {
|
||||
if (!safe.fs.writeFileSync(path.join(paths.APP_CERTS_DIR, `${domain}.host.cert`), fallback.cert)) return callback(new ReverseProxyError(ReverseProxyError.INTERNAL_ERROR, safe.error.message));
|
||||
if (!safe.fs.writeFileSync(path.join(paths.APP_CERTS_DIR, `${domain}.host.key`), fallback.key)) return callback(new ReverseProxyError(ReverseProxyError.INTERNAL_ERROR, safe.error.message));
|
||||
} else if (!fs.existsSync(certFilePath) || !fs.existsSync(keyFilePath)) { // generate it
|
||||
let opensslConf = safe.fs.readFileSync('/etc/ssl/openssl.cnf', 'utf8');
|
||||
// SAN must contain all the domains since CN check is based on implementation if SAN is found. -checkhost also checks only SAN if present!
|
||||
let opensslConfWithSan = `${opensslConf}\n[SAN]\nsubjectAltName=DNS:${domain},DNS:*.${domain}\n`;
|
||||
let configFile = path.join(os.tmpdir(), 'openssl-' + crypto.randomBytes(4).readUInt32LE(0) + '.conf');
|
||||
safe.fs.writeFileSync(configFile, opensslConfWithSan, 'utf8');
|
||||
let certCommand = util.format(`openssl req -x509 -newkey rsa:2048 -keyout ${keyFilePath} -out ${certFilePath} -days 3650 -subj /CN=*.${domain} -extensions SAN -config ${configFile} -nodes`);
|
||||
var certCommand = util.format('openssl req -x509 -newkey rsa:2048 -keyout %s -out %s -days 3650 -subj /CN=*.%s -nodes', keyFilePath, certFilePath, domain);
|
||||
if (!safe.child_process.execSync(certCommand)) return callback(new ReverseProxyError(ReverseProxyError.INTERNAL_ERROR, safe.error.message));
|
||||
safe.fs.unlinkSync(configFile);
|
||||
}
|
||||
|
||||
platform.handleCertChanged('*.' + domain);
|
||||
@@ -215,13 +220,15 @@ function getCertificate(app, callback) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
var certFilePath = path.join(paths.APP_CERTS_DIR, `${app.fqdn}.user.cert`);
|
||||
var keyFilePath = path.join(paths.APP_CERTS_DIR, `${app.fqdn}.user.key`);
|
||||
var vhost = app.altDomain || app.intrinsicFqdn;
|
||||
|
||||
var certFilePath = path.join(paths.APP_CERTS_DIR, `${vhost}.user.cert`);
|
||||
var keyFilePath = path.join(paths.APP_CERTS_DIR, `${vhost}.user.key`);
|
||||
|
||||
if (fs.existsSync(certFilePath) && fs.existsSync(keyFilePath)) return callback(null, { certFilePath, keyFilePath });
|
||||
|
||||
certFilePath = path.join(paths.APP_CERTS_DIR, `${app.fqdn}.cert`);
|
||||
keyFilePath = path.join(paths.APP_CERTS_DIR, `${app.fqdn}.key`);
|
||||
certFilePath = path.join(paths.APP_CERTS_DIR, `${vhost}.cert`);
|
||||
keyFilePath = path.join(paths.APP_CERTS_DIR, `${vhost}.key`);
|
||||
|
||||
if (fs.existsSync(certFilePath) && fs.existsSync(keyFilePath)) return callback(null, { certFilePath, keyFilePath });
|
||||
|
||||
@@ -233,7 +240,7 @@ function ensureCertificate(app, auditSource, callback) {
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
const vhost = app.fqdn;
|
||||
var vhost = app.altDomain || app.intrinsicFqdn;
|
||||
|
||||
var certFilePath = path.join(paths.APP_CERTS_DIR, `${vhost}.user.cert`);
|
||||
var keyFilePath = path.join(paths.APP_CERTS_DIR, `${vhost}.user.key`);
|
||||
@@ -271,7 +278,7 @@ function ensureCertificate(app, auditSource, callback) {
|
||||
eventlog.add(eventlog.ACTION_CERTIFICATE_RENEWAL, auditSource, { domain: vhost, errorMessage: errorMessage });
|
||||
|
||||
// if no cert was returned use fallback. the fallback/caas provider will not provide any for example
|
||||
if (!certFilePath || !keyFilePath) return getFallbackCertificate(app.domain, callback);
|
||||
if (!certFilePath || !keyFilePath) return getFallbackCertificate(app.altDomain ? tld.getDomain(app.altDomain) : app.domain, callback);
|
||||
|
||||
callback(null, { certFilePath, keyFilePath, reason: 'new-le' });
|
||||
});
|
||||
@@ -307,7 +314,7 @@ function configureAdmin(auditSource, callback) {
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
var adminApp = { domain: config.adminDomain(), fqdn: config.adminFqdn() };
|
||||
var adminApp = { domain: config.adminDomain(), intrinsicFqdn: config.adminFqdn() };
|
||||
ensureCertificate(adminApp, auditSource, function (error, bundle) {
|
||||
if (error) return callback(error);
|
||||
|
||||
@@ -322,11 +329,12 @@ function configureAppInternal(app, bundle, callback) {
|
||||
|
||||
var sourceDir = path.resolve(__dirname, '..');
|
||||
var endpoint = 'app';
|
||||
var vhost = app.altDomain || app.intrinsicFqdn;
|
||||
|
||||
var data = {
|
||||
sourceDir: sourceDir,
|
||||
adminOrigin: config.adminOrigin(),
|
||||
vhost: app.fqdn,
|
||||
vhost: vhost,
|
||||
hasIPv6: config.hasIPv6(),
|
||||
port: app.httpPort,
|
||||
endpoint: endpoint,
|
||||
@@ -338,10 +346,10 @@ function configureAppInternal(app, bundle, callback) {
|
||||
var nginxConf = ejs.render(NGINX_APPCONFIG_EJS, data);
|
||||
|
||||
var nginxConfigFilename = path.join(paths.NGINX_APPCONFIG_DIR, app.id + '.conf');
|
||||
debug('writing config for "%s" to %s with options %j', app.fqdn, nginxConfigFilename, data);
|
||||
debug('writing config for "%s" to %s with options %j', vhost, nginxConfigFilename, data);
|
||||
|
||||
if (!safe.fs.writeFileSync(nginxConfigFilename, nginxConf)) {
|
||||
debug('Error creating nginx config for "%s" : %s', app.fqdn, safe.error.message);
|
||||
debug('Error creating nginx config for "%s" : %s', vhost, safe.error.message);
|
||||
return callback(safe.error);
|
||||
}
|
||||
|
||||
@@ -364,9 +372,11 @@ function unconfigureApp(app, callback) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
var vhost = app.altDomain || app.intrinsicFqdn;
|
||||
|
||||
var nginxConfigFilename = path.join(paths.NGINX_APPCONFIG_DIR, app.id + '.conf');
|
||||
if (!safe.fs.unlinkSync(nginxConfigFilename)) {
|
||||
if (safe.error.code !== 'ENOENT') debug('Error removing nginx configuration of "%s": %s', app.fqdn, safe.error.message);
|
||||
if (safe.error.code !== 'ENOENT') debug('Error removing nginx configuration of "%s": %s', vhost, safe.error.message);
|
||||
return callback(null);
|
||||
}
|
||||
|
||||
@@ -382,21 +392,21 @@ function renewAll(auditSource, callback) {
|
||||
apps.getAll(function (error, allApps) {
|
||||
if (error) return callback(error);
|
||||
|
||||
allApps.push({ domain: config.adminDomain(), fqdn: config.adminFqdn() }); // inject fake webadmin app
|
||||
allApps.push({ domain: config.adminDomain(), intrinsicFqdn: config.adminFqdn() }); // inject fake webadmin app
|
||||
|
||||
async.eachSeries(allApps, function (app, iteratorCallback) {
|
||||
ensureCertificate(app, auditSource, function (error, bundle) {
|
||||
if (bundle.reason !== 'new-le' && bundle.reason !== 'fallback') return iteratorCallback();
|
||||
|
||||
// reconfigure for the case where we got a renewed cert after fallback
|
||||
var configureFunc = app.fqdn === config.adminFqdn() ?
|
||||
var configureFunc = app.intrinsicFqdn === config.adminFqdn() ?
|
||||
configureAdminInternal.bind(null, bundle, constants.NGINX_ADMIN_CONFIG_FILE_NAME, config.adminFqdn())
|
||||
: configureAppInternal.bind(null, app, bundle);
|
||||
|
||||
configureFunc(function (ignoredError) {
|
||||
if (ignoredError) debug('fallbackExpiredCertificates: error reconfiguring app', ignoredError);
|
||||
|
||||
platform.handleCertChanged(app.fqdn);
|
||||
platform.handleCertChanged(app.intrinsicFqdn);
|
||||
|
||||
iteratorCallback(); // move to next app
|
||||
});
|
||||
|
||||
+6
-4
@@ -58,6 +58,8 @@ function removeInternalAppFields(app) {
|
||||
iconUrl: app.iconUrl,
|
||||
fqdn: app.fqdn,
|
||||
memoryLimit: app.memoryLimit,
|
||||
altDomain: app.altDomain,
|
||||
cnameTarget: app.cnameTarget,
|
||||
xFrameOptions: app.xFrameOptions,
|
||||
sso: app.sso,
|
||||
debugMode: app.debugMode,
|
||||
@@ -132,6 +134,9 @@ function installApp(req, res, next) {
|
||||
|
||||
if ('memoryLimit' in data && typeof data.memoryLimit !== 'number') return next(new HttpError(400, 'memoryLimit is not a number'));
|
||||
|
||||
// falsy value in altDomain unsets it
|
||||
if (data.altDomain && typeof data.altDomain !== 'string') return next(new HttpError(400, 'altDomain must be a string'));
|
||||
|
||||
if (data.xFrameOptions && typeof data.xFrameOptions !== 'string') return next(new HttpError(400, 'xFrameOptions must be a string'));
|
||||
|
||||
if ('sso' in data && typeof data.sso !== 'boolean') return next(new HttpError(400, 'sso must be a boolean'));
|
||||
@@ -176,6 +181,7 @@ function configureApp(req, res, next) {
|
||||
if (!data.cert && data.key) return next(new HttpError(400, 'cert must be provided'));
|
||||
|
||||
if ('memoryLimit' in data && typeof data.memoryLimit !== 'number') return next(new HttpError(400, 'memoryLimit is not a number'));
|
||||
if (data.altDomain && typeof data.altDomain !== 'string') return next(new HttpError(400, 'altDomain must be a string'));
|
||||
if (data.xFrameOptions && typeof data.xFrameOptions !== 'string') return next(new HttpError(400, 'xFrameOptions must be a string'));
|
||||
|
||||
if ('enableBackup' in data && typeof data.enableBackup !== 'boolean') return next(new HttpError(400, 'enableBackup must be a boolean'));
|
||||
@@ -237,13 +243,9 @@ function cloneApp(req, res, next) {
|
||||
|
||||
apps.clone(req.params.id, data, auditSource(req), function (error, result) {
|
||||
if (error && error.reason === AppsError.NOT_FOUND) return next(new HttpError(404, 'No such app'));
|
||||
if (error && error.reason === AppsError.PORT_RESERVED) return next(new HttpError(409, 'Port ' + error.message + ' is reserved.'));
|
||||
if (error && error.reason === AppsError.PORT_CONFLICT) return next(new HttpError(409, 'Port ' + error.message + ' is already in use.'));
|
||||
if (error && error.reason === AppsError.ALREADY_EXISTS) return next(new HttpError(409, error.message));
|
||||
if (error && error.reason === AppsError.BAD_FIELD) return next(new HttpError(400, error.message));
|
||||
if (error && error.reason === AppsError.BAD_STATE) return next(new HttpError(409, error.message));
|
||||
if (error && error.reason === AppsError.BILLING_REQUIRED) return next(new HttpError(402, 'Billing required'));
|
||||
if (error && error.reason === AppsError.BAD_CERTIFICATE) return next(new HttpError(400, error.message));
|
||||
if (error && error.reason === AppsError.EXTERNAL_ERROR) return next(new HttpError(424, error.message));
|
||||
if (error) return next(new HttpError(500, error));
|
||||
|
||||
|
||||
@@ -89,7 +89,6 @@ function feedback(req, res, next) {
|
||||
if (VALID_TYPES.indexOf(req.body.type) === -1) return next(new HttpError(400, 'unknown type'));
|
||||
if (typeof req.body.subject !== 'string' || !req.body.subject) return next(new HttpError(400, 'subject must be string'));
|
||||
if (typeof req.body.description !== 'string' || !req.body.description) return next(new HttpError(400, 'description must be string'));
|
||||
if (req.body.appId && typeof req.body.appId !== 'string') return next(new HttpError(400, 'appId must be string'));
|
||||
|
||||
appstore.sendFeedback(_.extend(req.body, { email: req.user.email, displayName: req.user.displayName }), function (error) {
|
||||
if (error && error.reason === AppstoreError.BILLING_REQUIRED) return next(new HttpError(402, 'Login to App Store to create support tickets. You can also email support@cloudron.io'));
|
||||
|
||||
@@ -9,14 +9,17 @@ var developer = require('../developer.js'),
|
||||
HttpError = require('connect-lastmile').HttpError,
|
||||
HttpSuccess = require('connect-lastmile').HttpSuccess;
|
||||
|
||||
function auditSource(req) {
|
||||
var ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress || null;
|
||||
return { ip: ip, username: req.user ? req.user.username : null, userId: req.user ? req.user.id : null };
|
||||
}
|
||||
|
||||
function login(req, res, next) {
|
||||
passport.authenticate('local', function (error, user) {
|
||||
if (error) return next(new HttpError(500, error));
|
||||
if (!user) return next(new HttpError(401, 'Invalid credentials'));
|
||||
|
||||
var ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress || null;
|
||||
|
||||
developer.issueDeveloperToken(user, ip, function (error, result) {
|
||||
developer.issueDeveloperToken(user, auditSource(req), function (error, result) {
|
||||
if (error) return next(new HttpError(500, error));
|
||||
|
||||
next(new HttpSuccess(200, { token: result.token, expiresAt: result.expiresAt }));
|
||||
|
||||
@@ -29,9 +29,6 @@ function add(req, res, next) {
|
||||
if ('tlsConfig' in req.body && typeof req.body.tlsConfig !== 'object') return next(new HttpError(400, 'tlsConfig must be a object with a provider string property'));
|
||||
if (req.body.tlsConfig && (!req.body.tlsConfig.provider || typeof req.body.tlsConfig.provider !== 'string')) return next(new HttpError(400, 'tlsConfig.provider must be a string'));
|
||||
|
||||
// some DNS providers like DigitalOcean take a really long time to verify credentials (https://github.com/expressjs/timeout/issues/26)
|
||||
req.clearTimeout();
|
||||
|
||||
domains.add(req.body.domain, req.body.zoneName || '', req.body.provider, req.body.config, req.body.fallbackCertificate || null, req.body.tlsConfig || { provider: 'letsencrypt-prod' }, function (error) {
|
||||
if (error && error.reason === DomainError.ALREADY_EXISTS) return next(new HttpError(409, error.message));
|
||||
if (error && error.reason === DomainError.BAD_FIELD) return next(new HttpError(400, error.message));
|
||||
@@ -75,9 +72,6 @@ function update(req, res, next) {
|
||||
if ('tlsConfig' in req.body && typeof req.body.tlsConfig !== 'object') return next(new HttpError(400, 'tlsConfig must be a object with a provider string property'));
|
||||
if (req.body.tlsConfig && (!req.body.tlsConfig.provider || typeof req.body.tlsConfig.provider !== 'string')) return next(new HttpError(400, 'tlsConfig.provider must be a string'));
|
||||
|
||||
// some DNS providers like DigitalOcean take a really long time to verify credentials (https://github.com/expressjs/timeout/issues/26)
|
||||
req.clearTimeout();
|
||||
|
||||
domains.update(req.params.domain, req.body.provider, req.body.config, req.body.fallbackCertificate || null, req.body.tlsConfig || { provider: 'letsencrypt-prod' }, function (error) {
|
||||
if (error && error.reason === DomainError.NOT_FOUND) return next(new HttpError(404, error.message));
|
||||
if (error && error.reason === DomainError.BAD_FIELD) return next(new HttpError(400, error.message));
|
||||
@@ -93,7 +87,7 @@ function del(req, res, next) {
|
||||
|
||||
domains.del(req.params.domain, function (error) {
|
||||
if (error && error.reason === DomainError.NOT_FOUND) return next(new HttpError(404, error.message));
|
||||
if (error && error.reason === DomainError.IN_USE) return next(new HttpError(409, 'Domain is still in use. Remove all apps using this domain'));
|
||||
if (error && error.reason === DomainError.IN_USE) return next(new HttpError(409, 'Domain is still in use'));
|
||||
if (error) return next(new HttpError(500, error));
|
||||
|
||||
next(new HttpSuccess(204));
|
||||
|
||||
@@ -15,14 +15,10 @@ function get(req, res, next) {
|
||||
var perPage = typeof req.query.per_page !== 'undefined'? parseInt(req.query.per_page) : 25;
|
||||
if (!perPage || perPage < 0) return next(new HttpError(400, 'per_page query param has to be a postive number'));
|
||||
|
||||
if (req.query.actions && typeof req.query.actions !== 'string') return next(new HttpError(400, 'actions must be a comma separated string'));
|
||||
if (req.query.action && typeof req.query.action !== 'string') return next(new HttpError(400, 'action must be a string'));
|
||||
if (req.query.search && typeof req.query.search !== 'string') return next(new HttpError(400, 'search must be a string'));
|
||||
|
||||
var actions = req.query.actions ? req.query.actions.split(',').map(function (s) { return s.trim(); }) : [];
|
||||
if (req.query.action) actions.push(req.query.action);
|
||||
|
||||
eventlog.getAllPaged(actions, req.query.search || null, page, perPage, function (error, result) {
|
||||
eventlog.getAllPaged(req.query.action || null, req.query.search || null, page, perPage, function (error, result) {
|
||||
if (error) return next(new HttpError(500, error));
|
||||
|
||||
next(new HttpSuccess(200, { eventlogs: result }));
|
||||
|
||||
@@ -4,7 +4,6 @@ exports = module.exports = {
|
||||
get: get,
|
||||
|
||||
add: add,
|
||||
update: update,
|
||||
del: del,
|
||||
|
||||
getStatus: getStatus,
|
||||
@@ -61,24 +60,11 @@ function add(req, res, next) {
|
||||
});
|
||||
}
|
||||
|
||||
function update(req, res, next) {
|
||||
assert.strictEqual(typeof req.body, 'object');
|
||||
assert.strictEqual(typeof req.params.domain, 'string');
|
||||
|
||||
mail.update(req.params.domain, function (error) {
|
||||
if (error && error.reason === MailError.NOT_FOUND) return next(new HttpError(404, error.message));
|
||||
if (error) return next(new HttpError(500, error));
|
||||
|
||||
next(new HttpSuccess(202));
|
||||
});
|
||||
}
|
||||
|
||||
function del(req, res, next) {
|
||||
assert.strictEqual(typeof req.params.domain, 'string');
|
||||
|
||||
mail.del(req.params.domain, function (error) {
|
||||
if (error && error.reason === MailError.NOT_FOUND) return next(new HttpError(404, error.message));
|
||||
if (error && error.reason === MailError.IN_USE) return next(new HttpError(409, 'Mail domain is still in use. Remove existing mailboxes'));
|
||||
if (error) return next(new HttpError(500, error));
|
||||
|
||||
next(new HttpSuccess(204));
|
||||
@@ -88,9 +74,6 @@ function del(req, res, next) {
|
||||
function getStatus(req, res, next) {
|
||||
assert.strictEqual(typeof req.params.domain, 'string');
|
||||
|
||||
// can take a while to query all the DNS entries
|
||||
req.clearTimeout();
|
||||
|
||||
mail.getStatus(req.params.domain, function (error, records) {
|
||||
if (error && error.reason === MailError.NOT_FOUND) return next(new HttpError(404, error.message));
|
||||
if (error) return next(new HttpError(500, error));
|
||||
|
||||
@@ -26,10 +26,9 @@ var apps = require('../apps'),
|
||||
util = require('util'),
|
||||
_ = require('underscore');
|
||||
|
||||
// appObject is optional here
|
||||
function auditSource(req, appId, appObject) {
|
||||
function auditSource(req, appId) {
|
||||
var ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress || null;
|
||||
return { authType: 'oauth', ip: ip, appId: appId, app: appObject };
|
||||
return { authType: 'oauth', ip: ip, appId: appId };
|
||||
}
|
||||
|
||||
// create OAuth 2.0 server
|
||||
@@ -240,7 +239,7 @@ function loginForm(req, res) {
|
||||
apps.get(result.appId, function (error, result) {
|
||||
if (error) return sendErrorPageOrRedirect(req, res, 'Unknown Application for those OAuth credentials');
|
||||
|
||||
var applicationName = result.fqdn;
|
||||
var applicationName = result.altDomain || result.intrinsicFqdn;
|
||||
render(applicationName, '/api/v1/apps/' + result.id + '/icon');
|
||||
});
|
||||
});
|
||||
@@ -448,7 +447,7 @@ var authorization = [
|
||||
var type = req.oauth2.client.type;
|
||||
|
||||
if (type === clients.TYPE_EXTERNAL || type === clients.TYPE_BUILT_IN) {
|
||||
eventlog.add(eventlog.ACTION_USER_LOGIN, auditSource(req, req.oauth2.client.appId), { userId: req.oauth2.user.id, user: user.removePrivateFields(req.oauth2.user) });
|
||||
eventlog.add(eventlog.ACTION_USER_LOGIN, auditSource(req, req.oauth2.client.appId), { userId: req.oauth2.user.id });
|
||||
return next();
|
||||
}
|
||||
|
||||
@@ -459,7 +458,7 @@ var authorization = [
|
||||
if (error) return sendError(req, res, 'Internal error');
|
||||
if (!access) return sendErrorPageOrRedirect(req, res, 'No access to this app.');
|
||||
|
||||
eventlog.add(eventlog.ACTION_USER_LOGIN, auditSource(req, appObject.id, appObject), { userId: req.oauth2.user.id, user: user.removePrivateFields(req.oauth2.user) });
|
||||
eventlog.add(eventlog.ACTION_USER_LOGIN, auditSource(req, appObject.id), { userId: req.oauth2.user.id });
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
+6
-30
@@ -1,11 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
exports = module.exports = {
|
||||
getAppAutoupdatePattern: getAppAutoupdatePattern,
|
||||
setAppAutoupdatePattern: setAppAutoupdatePattern,
|
||||
|
||||
getBoxAutoupdatePattern: getBoxAutoupdatePattern,
|
||||
setBoxAutoupdatePattern: setBoxAutoupdatePattern,
|
||||
getAutoupdatePattern: getAutoupdatePattern,
|
||||
setAutoupdatePattern: setAutoupdatePattern,
|
||||
|
||||
getCloudronName: getCloudronName,
|
||||
setCloudronName: setCloudronName,
|
||||
@@ -30,41 +27,20 @@ var assert = require('assert'),
|
||||
settings = require('../settings.js'),
|
||||
SettingsError = settings.SettingsError;
|
||||
|
||||
function getAppAutoupdatePattern(req, res, next) {
|
||||
settings.getAppAutoupdatePattern(function (error, pattern) {
|
||||
function getAutoupdatePattern(req, res, next) {
|
||||
settings.getAutoupdatePattern(function (error, pattern) {
|
||||
if (error) return next(new HttpError(500, error));
|
||||
|
||||
next(new HttpSuccess(200, { pattern: pattern }));
|
||||
});
|
||||
}
|
||||
|
||||
function setAppAutoupdatePattern(req, res, next) {
|
||||
function setAutoupdatePattern(req, res, next) {
|
||||
assert.strictEqual(typeof req.body, 'object');
|
||||
|
||||
if (typeof req.body.pattern !== 'string') return next(new HttpError(400, 'pattern is required'));
|
||||
|
||||
settings.setAppAutoupdatePattern(req.body.pattern, function (error) {
|
||||
if (error && error.reason === SettingsError.BAD_FIELD) return next(new HttpError(400, error.message));
|
||||
if (error) return next(new HttpError(500, error));
|
||||
|
||||
next(new HttpSuccess(200));
|
||||
});
|
||||
}
|
||||
|
||||
function getBoxAutoupdatePattern(req, res, next) {
|
||||
settings.getBoxAutoupdatePattern(function (error, pattern) {
|
||||
if (error) return next(new HttpError(500, error));
|
||||
|
||||
next(new HttpSuccess(200, { pattern: pattern }));
|
||||
});
|
||||
}
|
||||
|
||||
function setBoxAutoupdatePattern(req, res, next) {
|
||||
assert.strictEqual(typeof req.body, 'object');
|
||||
|
||||
if (typeof req.body.pattern !== 'string') return next(new HttpError(400, 'pattern is required'));
|
||||
|
||||
settings.setBoxAutoupdatePattern(req.body.pattern, function (error) {
|
||||
settings.setAutoupdatePattern(req.body.pattern, function (error) {
|
||||
if (error && error.reason === SettingsError.BAD_FIELD) return next(new HttpError(400, error.message));
|
||||
if (error) return next(new HttpError(500, error));
|
||||
|
||||
|
||||
@@ -9,23 +9,25 @@
|
||||
var appdb = require('../../appdb.js'),
|
||||
apps = require('../../apps.js'),
|
||||
assert = require('assert'),
|
||||
path = require('path'),
|
||||
async = require('async'),
|
||||
child_process = require('child_process'),
|
||||
clients = require('../../clients.js'),
|
||||
config = require('../../config.js'),
|
||||
constants = require('../../constants.js'),
|
||||
apphealthmonitor = require('../../apphealthmonitor.js'),
|
||||
database = require('../../database.js'),
|
||||
docker = require('../../docker.js').connection,
|
||||
domains = require('../../domains.js'),
|
||||
expect = require('expect.js'),
|
||||
fs = require('fs'),
|
||||
hock = require('hock'),
|
||||
http = require('http'),
|
||||
https = require('https'),
|
||||
js2xml = require('js2xmlparser').parse,
|
||||
ldap = require('../../ldap.js'),
|
||||
mail = require('../../mail.js'),
|
||||
net = require('net'),
|
||||
nock = require('nock'),
|
||||
path = require('path'),
|
||||
paths = require('../../paths.js'),
|
||||
safe = require('safetydance'),
|
||||
server = require('../../server.js'),
|
||||
@@ -41,8 +43,9 @@ var SERVER_URL = 'http://localhost:' + config.get('port');
|
||||
|
||||
// Test image information
|
||||
var TEST_IMAGE_REPO = 'cloudron/test';
|
||||
var TEST_IMAGE_TAG = '25.4.0';
|
||||
var TEST_IMAGE_TAG = '25.2.0';
|
||||
var TEST_IMAGE = TEST_IMAGE_REPO + ':' + TEST_IMAGE_TAG;
|
||||
// var TEST_IMAGE_ID = child_process.execSync('docker inspect --format={{.Id}} ' + TEST_IMAGE).toString('utf8').trim();
|
||||
|
||||
const DOMAIN_0 = {
|
||||
domain: 'example-apps-test.com',
|
||||
@@ -57,6 +60,7 @@ const CLOUDRON_ID = 'somecloudronid';
|
||||
|
||||
var APP_STORE_ID = 'test', APP_ID;
|
||||
var APP_LOCATION = 'appslocation';
|
||||
var APP_DOMAIN = 'example-apps-test.com';
|
||||
var APP_LOCATION_2 = 'appslocationtwo';
|
||||
var APP_LOCATION_NEW = 'appslocationnew';
|
||||
|
||||
@@ -66,19 +70,14 @@ APP_MANIFEST.dockerImage = TEST_IMAGE;
|
||||
var APP_MANIFEST_1 = JSON.parse(fs.readFileSync(__dirname + '/../../../../test-app/CloudronManifest.json', 'utf8'));
|
||||
APP_MANIFEST_1.dockerImage = TEST_IMAGE;
|
||||
|
||||
const USERNAME = 'superadmin';
|
||||
const PASSWORD = 'Foobar?1337';
|
||||
const EMAIL ='admin@me.com';
|
||||
|
||||
var USERNAME = 'superadmin', PASSWORD = 'Foobar?1337', EMAIL ='admin@me.com';
|
||||
var USER_1_ID = null, USERNAME_1 = 'user', EMAIL_1 ='user@me.com';
|
||||
const USER_1_APPSTORE_TOKEN = 'appstoretoken';
|
||||
const USERNAME_1 = 'user';
|
||||
const EMAIL_1 ='user@me.com';
|
||||
var user_1_id = null;
|
||||
|
||||
// authentication token
|
||||
var token = null;
|
||||
var token = null; // authentication token
|
||||
var token_1 = null;
|
||||
|
||||
var awsHostedZones;
|
||||
|
||||
function startDockerProxy(interceptor, callback) {
|
||||
assert.strictEqual(typeof interceptor, 'function');
|
||||
|
||||
@@ -120,7 +119,6 @@ function checkAddons(appEntry, done) {
|
||||
|
||||
delete body.recvmail; // unclear why dovecot mail delivery won't work
|
||||
delete body.stdenv; // cannot access APP_ORIGIN
|
||||
delete body.email; // sieve will fail not sure why yet
|
||||
|
||||
for (var key in body) {
|
||||
if (body[key] !== 'OK') return callback('Not done yet: ' + JSON.stringify(body));
|
||||
@@ -166,6 +164,21 @@ function startBox(done) {
|
||||
safe.fs.unlinkSync(paths.INFRA_VERSION_FILE);
|
||||
child_process.execSync('docker ps -qa | xargs --no-run-if-empty docker rm -f');
|
||||
|
||||
// awsHostedZones = {
|
||||
// HostedZones: [{
|
||||
// Id: '/hostedzone/ZONEID',
|
||||
// Name: config.zoneName() + '.',
|
||||
// CallerReference: '305AFD59-9D73-4502-B020-F4E6F889CB30',
|
||||
// ResourceRecordSetCount: 2,
|
||||
// ChangeInfo: {
|
||||
// Id: '/change/CKRTFJA0ANHXB',
|
||||
// Status: 'INSYNC'
|
||||
// }
|
||||
// }],
|
||||
// IsTruncated: false,
|
||||
// MaxItems: '100'
|
||||
// };
|
||||
|
||||
async.series([
|
||||
// first clear, then start server. otherwise, taskmanager spins up tasks for obsolete appIds
|
||||
database.initialize,
|
||||
@@ -205,7 +218,7 @@ function startBox(done) {
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(201);
|
||||
|
||||
user_1_id = res.body.id;
|
||||
USER_1_ID = res.body.id;
|
||||
|
||||
callback(null);
|
||||
});
|
||||
@@ -215,7 +228,7 @@ function startBox(done) {
|
||||
token_1 = tokendb.generateToken();
|
||||
|
||||
// HACK to get a token for second user (passwords are generated and the user should have gotten a password setup link...)
|
||||
tokendb.add(token_1, user_1_id, 'test-client-id', Date.now() + 1000000, '*', callback);
|
||||
tokendb.add(token_1, USER_1_ID, 'test-client-id', Date.now() + 100000, '*', callback);
|
||||
},
|
||||
|
||||
function (callback) {
|
||||
@@ -250,7 +263,6 @@ function stopBox(done) {
|
||||
|
||||
// db is not cleaned up here since it's too late to call it after server.stop. if called before server.stop taskmanager apptasks are unhappy :/
|
||||
async.series([
|
||||
apphealthmonitor.stop,
|
||||
taskmanager.stopPendingTasks,
|
||||
taskmanager.waitForPendingTasks,
|
||||
appdb._clear,
|
||||
@@ -444,11 +456,11 @@ describe('App API', function () {
|
||||
});
|
||||
|
||||
it('app install succeeds with purchase', function (done) {
|
||||
var fake1 = nock(config.apiServerOrigin()).post(function (uri) { return uri.indexOf('/api/v1/users/' + user_1_id + '/cloudrons') >= 0; }, { 'domain': DOMAIN_0.domain }).reply(201, { cloudron: { id: CLOUDRON_ID } });
|
||||
var fake1 = nock(config.apiServerOrigin()).post(function (uri) { return uri.indexOf('/api/v1/users/' + USER_1_ID + '/cloudrons') >= 0; }, { 'domain': DOMAIN_0.domain }).reply(201, { cloudron: { id: CLOUDRON_ID } });
|
||||
var fake2 = nock(config.apiServerOrigin()).get('/api/v1/apps/' + APP_STORE_ID).reply(200, { manifest: APP_MANIFEST });
|
||||
var fake3 = nock(config.apiServerOrigin()).post(function (uri) { return uri.indexOf('/api/v1/users/' + user_1_id + '/cloudrons/' + CLOUDRON_ID + '/apps/') >= 0; }, { 'appstoreId': APP_STORE_ID }).reply(201, { });
|
||||
var fake3 = nock(config.apiServerOrigin()).post(function (uri) { return uri.indexOf('/api/v1/users/' + USER_1_ID + '/cloudrons/' + CLOUDRON_ID + '/apps/') >= 0; }, { 'appstoreId': APP_STORE_ID }).reply(201, { });
|
||||
|
||||
settings.setAppstoreConfig({ userId: user_1_id, token: USER_1_APPSTORE_TOKEN }, function (error) {
|
||||
settings.setAppstoreConfig({ userId: USER_1_ID, token: USER_1_APPSTORE_TOKEN }, function (error) {
|
||||
if (error) return done(error);
|
||||
|
||||
expect(fake1.isDone()).to.be.ok();
|
||||
@@ -560,8 +572,8 @@ describe('App API', function () {
|
||||
});
|
||||
|
||||
it('can uninstall app', function (done) {
|
||||
var fake1 = nock(config.apiServerOrigin()).get(function (uri) { return uri.indexOf('/api/v1/users/' + user_1_id + '/cloudrons/' + CLOUDRON_ID + '/apps/') >= 0; }).reply(200, { });
|
||||
var fake2 = nock(config.apiServerOrigin()).delete(function (uri) { return uri.indexOf('/api/v1/users/' + user_1_id + '/cloudrons/' + CLOUDRON_ID + '/apps/') >= 0; }).reply(204, { });
|
||||
var fake1 = nock(config.apiServerOrigin()).get(function (uri) { return uri.indexOf('/api/v1/users/' + USER_1_ID + '/cloudrons/' + CLOUDRON_ID + '/apps/') >= 0; }).reply(200, { });
|
||||
var fake2 = nock(config.apiServerOrigin()).delete(function (uri) { return uri.indexOf('/api/v1/users/' + USER_1_ID + '/cloudrons/' + CLOUDRON_ID + '/apps/') >= 0; }).reply(204, { });
|
||||
|
||||
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/uninstall')
|
||||
.send({ password: PASSWORD })
|
||||
@@ -576,7 +588,7 @@ describe('App API', function () {
|
||||
|
||||
it('app install succeeds again', function (done) {
|
||||
var fake1 = nock(config.apiServerOrigin()).get('/api/v1/apps/' + APP_STORE_ID).reply(200, { manifest: APP_MANIFEST });
|
||||
var fake2 = nock(config.apiServerOrigin()).post(function (uri) { return uri.indexOf('/api/v1/users/' + user_1_id + '/cloudrons/' + CLOUDRON_ID + '/apps/') >= 0; }, { 'appstoreId': APP_STORE_ID }).reply(201, { });
|
||||
var fake2 = nock(config.apiServerOrigin()).post(function (uri) { return uri.indexOf('/api/v1/users/' + USER_1_ID + '/cloudrons/' + CLOUDRON_ID + '/apps/') >= 0; }, { 'appstoreId': APP_STORE_ID }).reply(201, { });
|
||||
|
||||
superagent.post(SERVER_URL + '/api/v1/apps/install')
|
||||
.query({ access_token: token })
|
||||
@@ -625,15 +637,17 @@ describe('App API', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('App installation', function () {
|
||||
xdescribe('App installation', function () {
|
||||
this.timeout(100000);
|
||||
|
||||
var apiHockInstance = hock.createHock({ throwOnUnmatched: false }), apiHockServer;
|
||||
var awsHockInstance = hock.createHock({ throwOnUnmatched: false }), awsHockServer;
|
||||
|
||||
// *.foobar.com
|
||||
var validCert1, validKey1;
|
||||
|
||||
before(function (done) {
|
||||
child_process.execSync('openssl req -subj "/CN=*.' + DOMAIN_0.domain + '/O=My Company Name LTD./C=US" -new -newkey rsa:2048 -days 365 -nodes -x509 -keyout /tmp/server.key -out /tmp/server.crt');
|
||||
child_process.execSync('openssl req -subj "/CN=*.foobar.com/O=My Company Name LTD./C=US" -new -newkey rsa:2048 -days 365 -nodes -x509 -keyout /tmp/server.key -out /tmp/server.crt');
|
||||
validKey1 = fs.readFileSync('/tmp/server.key', 'utf8');
|
||||
validCert1 = fs.readFileSync('/tmp/server.crt', 'utf8');
|
||||
|
||||
@@ -641,37 +655,53 @@ describe('App installation', function () {
|
||||
|
||||
async.series([
|
||||
startBox,
|
||||
apphealthmonitor.start,
|
||||
|
||||
function (callback) {
|
||||
apiHockInstance
|
||||
.get('/api/v1/apps/' + APP_STORE_ID + '/versions/' + APP_MANIFEST.version + '/icon')
|
||||
.replyWithFile(200, path.resolve(__dirname, '../../../assets/avatar.png'));
|
||||
.replyWithFile(200, path.resolve(__dirname, '../../../webadmin/src/img/appicon_fallback.png'));
|
||||
|
||||
var port = parseInt(url.parse(config.apiServerOrigin()).port, 10);
|
||||
apiHockServer = http.createServer(apiHockInstance.handler).listen(port, callback);
|
||||
},
|
||||
|
||||
settings.setDnsConfig.bind(null, { provider: 'route53', accessKeyId: 'accessKeyId', secretAccessKey: 'secretAccessKey', endpoint: 'http://localhost:5353' }, config.adminDomain(), config.zoneName()),
|
||||
|
||||
settings.setTlsConfig.bind(null, { provider: 'caas' }),
|
||||
|
||||
function (callback) {
|
||||
var fake1 = nock(config.apiServerOrigin()).post(function (uri) { return uri.indexOf('/api/v1/users/' + user_1_id + '/cloudrons') >= 0; }, { 'domain': DOMAIN_0.domain }).reply(201, { cloudron: { id: CLOUDRON_ID } });
|
||||
settings.setAppstoreConfig({ userId: user_1_id, token: USER_1_APPSTORE_TOKEN }, function (error) {
|
||||
if (error) return callback(error);
|
||||
awsHockInstance
|
||||
.get('/2013-04-01/hostedzone')
|
||||
.max(Infinity)
|
||||
.reply(200, js2xml('ListHostedZonesResponse', awsHostedZones, { wrapHandlers: { HostedZones: () => 'HostedZone'} }), { 'Content-Type': 'application/xml' })
|
||||
.filteringPathRegEx(/name=[^&]*/, 'name=location')
|
||||
.get('/2013-04-01/hostedzone/ZONEID/rrset?maxitems=1&name=location&type=A')
|
||||
.max(Infinity)
|
||||
.reply(200, js2xml('ListResourceRecordSetsResponse', { ResourceRecordSets: [ ] }, { 'Content-Type': 'application/xml' }))
|
||||
.filteringRequestBody(function (unusedBody) { return ''; }) // strip out body
|
||||
.post('/2013-04-01/hostedzone/ZONEID/rrset/')
|
||||
.max(Infinity)
|
||||
.reply(200, js2xml('ChangeResourceRecordSetsResponse', { ChangeInfo: { Id: 'dnsrecordid', Status: 'INSYNC' } }), { 'Content-Type': 'application/xml' });
|
||||
|
||||
expect(fake1.isDone()).to.be.ok();
|
||||
|
||||
callback();
|
||||
});
|
||||
awsHockServer = http.createServer(awsHockInstance.handler).listen(5353, callback);
|
||||
}
|
||||
], done);
|
||||
});
|
||||
|
||||
after(stopBox);
|
||||
after(function (done) {
|
||||
APP_ID = null;
|
||||
async.series([
|
||||
apiHockServer.close.bind(apiHockServer),
|
||||
awsHockServer.close.bind(awsHockServer),
|
||||
stopBox
|
||||
], done);
|
||||
});
|
||||
|
||||
var appResult = null, appEntry = null;
|
||||
|
||||
it('can install test app', function (done) {
|
||||
var fake1 = nock(config.apiServerOrigin()).get('/api/v1/apps/' + APP_STORE_ID).reply(200, { manifest: APP_MANIFEST });
|
||||
var fake2 = nock(config.apiServerOrigin()).post(function (uri) { return uri.indexOf('/api/v1/users/' + user_1_id + '/cloudrons/' + CLOUDRON_ID + '/apps/') >= 0; }, { 'appstoreId': APP_STORE_ID }).reply(201, { });
|
||||
var fake3 = nock(config.apiServerOrigin()).post(function (uri) { return uri.indexOf('/api/v1/users/USER_ID/cloudrons/CLOUDRON_ID/apps/') >= 0; }, { 'appstoreId': APP_STORE_ID }).reply(201, { });
|
||||
|
||||
var count = 0;
|
||||
function checkInstallStatus() {
|
||||
@@ -688,11 +718,10 @@ describe('App installation', function () {
|
||||
|
||||
superagent.post(SERVER_URL + '/api/v1/apps/install')
|
||||
.query({ access_token: token })
|
||||
.send({ appStoreId: APP_STORE_ID, location: APP_LOCATION, domain: DOMAIN_0.domain, portBindings: { ECHO_SERVER_PORT: 7171 }, accessRestriction: null })
|
||||
.send({ appStoreId: APP_STORE_ID, location: APP_LOCATION, portBindings: { ECHO_SERVER_PORT: 7171 }, accessRestriction: null })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(202);
|
||||
expect(fake1.isDone()).to.be.ok();
|
||||
expect(fake2.isDone()).to.be.ok();
|
||||
APP_ID = res.body.id;
|
||||
checkInstallStatus();
|
||||
});
|
||||
@@ -721,8 +750,8 @@ describe('App installation', function () {
|
||||
expect(data.Config.Env).to.contain('WEBADMIN_ORIGIN=' + config.adminOrigin());
|
||||
expect(data.Config.Env).to.contain('API_ORIGIN=' + config.adminOrigin());
|
||||
expect(data.Config.Env).to.contain('CLOUDRON=1');
|
||||
expect(data.Config.Env).to.contain('APP_ORIGIN=https://' + APP_LOCATION + '.' + DOMAIN_0.domain);
|
||||
expect(data.Config.Env).to.contain('APP_DOMAIN=' + APP_LOCATION + '.' + DOMAIN_0.domain);
|
||||
expect(data.Config.Env).to.contain('APP_ORIGIN=https://' + APP_LOCATION + '.' + APP_DOMAIN);
|
||||
expect(data.Config.Env).to.contain('APP_DOMAIN=' + APP_LOCATION + '.' + APP_DOMAIN);
|
||||
// Hostname must not be set of app fqdn or app location!
|
||||
expect(data.Config.Hostname).to.not.contain(APP_LOCATION);
|
||||
expect(data.Config.Env).to.contain('ECHO_SERVER_PORT=7171');
|
||||
@@ -1100,8 +1129,8 @@ describe('App installation', function () {
|
||||
});
|
||||
|
||||
it('can uninstall app', function (done) {
|
||||
var fake1 = nock(config.apiServerOrigin()).get(function (uri) { return uri.indexOf('/api/v1/users/' + user_1_id + '/cloudrons/' + CLOUDRON_ID + '/apps/') >= 0; }).reply(200, { });
|
||||
var fake2 = nock(config.apiServerOrigin()).delete(function (uri) { return uri.indexOf('/api/v1/users/' + user_1_id + '/cloudrons/' + CLOUDRON_ID + '/apps/') >= 0; }).reply(204, { });
|
||||
var fake2 = nock(config.apiServerOrigin()).get(function (uri) { return uri.indexOf('/api/v1/users/USER_ID/cloudrons/CLOUDRON_ID/apps/') >= 0; }).reply(200, { });
|
||||
var fake3 = nock(config.apiServerOrigin()).delete(function (uri) { return uri.indexOf('/api/v1/users/USER_ID/cloudrons/CLOUDRON_ID/apps/') >= 0; }).reply(204, { });
|
||||
|
||||
var count = 0;
|
||||
function checkUninstallStatus() {
|
||||
@@ -1119,10 +1148,6 @@ describe('App installation', function () {
|
||||
.query({ access_token: token })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(202);
|
||||
|
||||
expect(fake1.isDone()).to.be.ok();
|
||||
expect(fake2.isDone()).to.be.ok();
|
||||
|
||||
checkUninstallStatus();
|
||||
});
|
||||
});
|
||||
@@ -1148,7 +1173,11 @@ describe('App installation', function () {
|
||||
it('uninstalled - unregistered subdomain', function (done) {
|
||||
apiHockInstance.done(function (error) { // checks if all the apiHockServer APIs were called
|
||||
expect(!error).to.be.ok();
|
||||
done();
|
||||
|
||||
awsHockInstance.done(function (error) {
|
||||
expect(!error).to.be.ok();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -6,16 +6,11 @@
|
||||
/* global after:false */
|
||||
|
||||
var async = require('async'),
|
||||
child_process = require('child_process'),
|
||||
config = require('../../config.js'),
|
||||
database = require('../../database.js'),
|
||||
expect = require('expect.js'),
|
||||
fs = require('fs'),
|
||||
path = require('path'),
|
||||
paths = require('../../paths.js'),
|
||||
superagent = require('superagent'),
|
||||
server = require('../../server.js'),
|
||||
_ = require('underscore');
|
||||
server = require('../../server.js');
|
||||
|
||||
var SERVER_URL = 'http://localhost:' + config.get('port');
|
||||
|
||||
@@ -133,17 +128,6 @@ describe('Domains API', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('fails without token', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/domains')
|
||||
.query({ })
|
||||
.send(DOMAIN_0)
|
||||
.end(function (error, result) {
|
||||
expect(result.statusCode).to.equal(401);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('succeeds', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/domains')
|
||||
.query({ access_token: token })
|
||||
@@ -268,116 +252,4 @@ describe('Domains API', function () {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Certificates API', function () {
|
||||
var validCert0, validKey0, // example.com
|
||||
validCert1, validKey1; // *.example.com
|
||||
|
||||
before(function (done) {
|
||||
child_process.execSync(`openssl req -subj "/CN=${DOMAIN_0.domain}/O=My Company Name LTD./C=US" -new -newkey rsa:2048 -days 365 -nodes -x509 -keyout /tmp/server.key -out /tmp/server.crt`);
|
||||
validKey0 = fs.readFileSync('/tmp/server.key', 'utf8');
|
||||
validCert0 = fs.readFileSync('/tmp/server.crt', 'utf8');
|
||||
|
||||
child_process.execSync(`openssl req -subj "/CN=*.${DOMAIN_0.domain}/O=My Company Name LTD./C=US" -new -newkey rsa:2048 -days 365 -nodes -x509 -keyout /tmp/server.key -out /tmp/server.crt`);
|
||||
validKey1 = fs.readFileSync('/tmp/server.key', 'utf8');
|
||||
validCert1 = fs.readFileSync('/tmp/server.crt', 'utf8');
|
||||
|
||||
superagent.post(SERVER_URL + '/api/v1/domains')
|
||||
.query({ access_token: token })
|
||||
.send(DOMAIN_0)
|
||||
.end(function (error, result) {
|
||||
expect(result.statusCode).to.equal(201);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('cannot set certificate without certificate', function (done) {
|
||||
var d = _.extend({}, DOMAIN_0);
|
||||
d.fallbackCertificate = { key: validKey1 };
|
||||
|
||||
superagent.put(`${SERVER_URL}/api/v1/domains/${DOMAIN_0.domain}`)
|
||||
.query({ access_token: token })
|
||||
.send(d)
|
||||
.end(function (error, result) {
|
||||
expect(result.statusCode).to.equal(400);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('cannot set certificate without key', function (done) {
|
||||
var d = _.extend({}, DOMAIN_0);
|
||||
d.fallbackCertificate = { cert: validCert1 };
|
||||
|
||||
superagent.put(`${SERVER_URL}/api/v1/domains/${DOMAIN_0.domain}`)
|
||||
.query({ access_token: token })
|
||||
.send(d)
|
||||
.end(function (error, result) {
|
||||
expect(result.statusCode).to.equal(400);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('cannot set certificate with cert not being a string', function (done) {
|
||||
var d = _.extend({}, DOMAIN_0);
|
||||
d.fallbackCertificate = { cert: 1234, key: validKey1 };
|
||||
|
||||
superagent.put(`${SERVER_URL}/api/v1/domains/${DOMAIN_0.domain}`)
|
||||
.query({ access_token: token })
|
||||
.send(d)
|
||||
.end(function (error, result) {
|
||||
expect(result.statusCode).to.equal(400);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('cannot set certificate with key not being a string', function (done) {
|
||||
var d = _.extend({}, DOMAIN_0);
|
||||
d.fallbackCertificate = { cert: validCert1, key: true };
|
||||
|
||||
superagent.put(`${SERVER_URL}/api/v1/domains/${DOMAIN_0.domain}`)
|
||||
.query({ access_token: token })
|
||||
.send(d)
|
||||
.end(function (error, result) {
|
||||
expect(result.statusCode).to.equal(400);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('cannot set non-fallback certificate', function (done) {
|
||||
var d = _.extend({}, DOMAIN_0);
|
||||
d.fallbackCertificate = { cert: validCert0, key: validKey0 };
|
||||
|
||||
superagent.put(`${SERVER_URL}/api/v1/domains/${DOMAIN_0.domain}`)
|
||||
.query({ access_token: token })
|
||||
.send(d)
|
||||
.end(function (error, result) {
|
||||
expect(result.statusCode).to.equal(400);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can set fallback certificate', function (done) {
|
||||
var d = _.extend({}, DOMAIN_0);
|
||||
d.fallbackCertificate = { cert: validCert1, key: validKey1 };
|
||||
|
||||
superagent.put(`${SERVER_URL}/api/v1/domains/${DOMAIN_0.domain}`)
|
||||
.query({ access_token: token })
|
||||
.send(d)
|
||||
.end(function (error, result) {
|
||||
expect(result.statusCode).to.equal(204);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('did set the certificate', function (done) {
|
||||
var cert = fs.readFileSync(path.join(paths.APP_CERTS_DIR, `${DOMAIN_0.domain}.host.cert`), 'utf-8');
|
||||
expect(cert).to.eql(validCert1);
|
||||
|
||||
var key = fs.readFileSync(path.join(paths.APP_CERTS_DIR, `${DOMAIN_0.domain}.host.key`), 'utf-8');
|
||||
expect(key).to.eql(validKey1);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -77,8 +77,6 @@ function cleanup(done) {
|
||||
}
|
||||
|
||||
describe('Eventlog API', function () {
|
||||
this.timeout(10000);
|
||||
|
||||
before(setup);
|
||||
after(cleanup);
|
||||
|
||||
@@ -113,7 +111,7 @@ describe('Eventlog API', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('succeeds with deprecated action', function (done) {
|
||||
it('succeeds with action', function (done) {
|
||||
superagent.get(SERVER_URL + '/api/v1/cloudron/eventlog')
|
||||
.query({ access_token: token, page: 1, per_page: 10, action: 'cloudron.activate' })
|
||||
.end(function (error, result) {
|
||||
@@ -124,17 +122,6 @@ describe('Eventlog API', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('succeeds with actions', function (done) {
|
||||
superagent.get(SERVER_URL + '/api/v1/cloudron/eventlog')
|
||||
.query({ access_token: token, page: 1, per_page: 10, actions: 'cloudron.activate, user.add' })
|
||||
.end(function (error, result) {
|
||||
expect(result.statusCode).to.equal(200);
|
||||
expect(result.body.eventlogs.length).to.equal(3);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('succeeds with search', function (done) {
|
||||
superagent.get(SERVER_URL + '/api/v1/cloudron/eventlog')
|
||||
.query({ access_token: token, page: 1, per_page: 10, search: EMAIL })
|
||||
@@ -148,7 +135,7 @@ describe('Eventlog API', function () {
|
||||
|
||||
it('succeeds with search', function (done) {
|
||||
superagent.get(SERVER_URL + '/api/v1/cloudron/eventlog')
|
||||
.query({ access_token: token, page: 1, per_page: 10, search: EMAIL, actions: 'cloudron.activate' })
|
||||
.query({ access_token: token, page: 1, per_page: 10, search: EMAIL, action: 'cloudron.activate' })
|
||||
.end(function (error, result) {
|
||||
expect(result.statusCode).to.equal(200);
|
||||
expect(result.body.eventlogs.length).to.equal(0);
|
||||
|
||||
@@ -198,18 +198,16 @@ describe('Mail API', function () {
|
||||
this.timeout(10000);
|
||||
|
||||
before(function (done) {
|
||||
var dns = require('../../native-dns.js');
|
||||
var dig = require('../../dig.js');
|
||||
|
||||
// replace dns resolveTxt()
|
||||
resolve = dns.resolve;
|
||||
dns.resolve = function (hostname, type, options, callback) {
|
||||
resolve = dig.resolve;
|
||||
dig.resolve = function (hostname, type, options, callback) {
|
||||
expect(hostname).to.be.a('string');
|
||||
expect(callback).to.be.a('function');
|
||||
|
||||
if (!dnsAnswerQueue[hostname] || !(type in dnsAnswerQueue[hostname])) return callback(new Error('no mock answer'));
|
||||
|
||||
if (dnsAnswerQueue[hostname][type] === null) return callback(new Error({ code: 'ENODATA'} ));
|
||||
|
||||
callback(null, dnsAnswerQueue[hostname][type]);
|
||||
};
|
||||
|
||||
@@ -224,13 +222,13 @@ describe('Mail API', function () {
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(201);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
after(function (done) {
|
||||
var dns = require('../../native-dns.js');
|
||||
var dig = require('../../dig.js');
|
||||
|
||||
dns.resolve = resolve;
|
||||
dig.resolve = resolve;
|
||||
|
||||
superagent.del(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain)
|
||||
.send({ password: PASSWORD })
|
||||
@@ -270,20 +268,20 @@ describe('Mail API', function () {
|
||||
expect(res.body.dns.dkim.domain).to.eql(dkimDomain);
|
||||
expect(res.body.dns.dkim.type).to.eql('TXT');
|
||||
expect(res.body.dns.dkim.value).to.eql(null);
|
||||
expect(res.body.dns.dkim.expected).to.eql('v=DKIM1; t=s; p=' + mail._readDkimPublicKeySync(DOMAIN_0.domain));
|
||||
expect(res.body.dns.dkim.expected).to.eql('"v=DKIM1; t=s; p=' + mail._readDkimPublicKeySync(DOMAIN_0.domain) + '"');
|
||||
expect(res.body.dns.dkim.status).to.eql(false);
|
||||
|
||||
expect(res.body.dns.spf).to.be.an('object');
|
||||
expect(res.body.dns.spf.domain).to.eql(spfDomain);
|
||||
expect(res.body.dns.spf.type).to.eql('TXT');
|
||||
expect(res.body.dns.spf.value).to.eql(null);
|
||||
expect(res.body.dns.spf.expected).to.eql('v=spf1 a:' + config.adminFqdn() + ' ~all');
|
||||
expect(res.body.dns.spf.expected).to.eql('"v=spf1 a:' + config.adminFqdn() + ' ~all"');
|
||||
expect(res.body.dns.spf.status).to.eql(false);
|
||||
|
||||
expect(res.body.dns.dmarc).to.be.an('object');
|
||||
expect(res.body.dns.dmarc.type).to.eql('TXT');
|
||||
expect(res.body.dns.dmarc.value).to.eql(null);
|
||||
expect(res.body.dns.dmarc.expected).to.eql('v=DMARC1; p=reject; pct=100');
|
||||
expect(res.body.dns.dmarc.expected).to.eql('"v=DMARC1; p=reject; pct=100"');
|
||||
expect(res.body.dns.dmarc.status).to.eql(false);
|
||||
|
||||
expect(res.body.dns.mx).to.be.an('object');
|
||||
@@ -295,7 +293,7 @@ describe('Mail API', function () {
|
||||
expect(res.body.dns.ptr).to.be.an('object');
|
||||
expect(res.body.dns.ptr.type).to.eql('PTR');
|
||||
// expect(res.body.ptr.value).to.eql(null); this will be anything random
|
||||
expect(res.body.dns.ptr.expected).to.eql(config.mailFqdn());
|
||||
expect(res.body.dns.ptr.expected).to.eql(config.mailFqdn() + '.');
|
||||
expect(res.body.dns.ptr.status).to.eql(false);
|
||||
|
||||
done();
|
||||
@@ -316,17 +314,17 @@ describe('Mail API', function () {
|
||||
expect(res.statusCode).to.equal(200);
|
||||
|
||||
expect(res.body.dns.spf).to.be.an('object');
|
||||
expect(res.body.dns.spf.expected).to.eql('v=spf1 a:' + config.adminFqdn() + ' ~all');
|
||||
expect(res.body.dns.spf.expected).to.eql('"v=spf1 a:' + config.adminFqdn() + ' ~all"');
|
||||
expect(res.body.dns.spf.status).to.eql(false);
|
||||
expect(res.body.dns.spf.value).to.eql(null);
|
||||
|
||||
expect(res.body.dns.dkim).to.be.an('object');
|
||||
expect(res.body.dns.dkim.expected).to.eql('v=DKIM1; t=s; p=' + mail._readDkimPublicKeySync(DOMAIN_0.domain));
|
||||
expect(res.body.dns.dkim.expected).to.eql('"v=DKIM1; t=s; p=' + mail._readDkimPublicKeySync(DOMAIN_0.domain) + '"');
|
||||
expect(res.body.dns.dkim.status).to.eql(false);
|
||||
expect(res.body.dns.dkim.value).to.eql(null);
|
||||
|
||||
expect(res.body.dns.dmarc).to.be.an('object');
|
||||
expect(res.body.dns.dmarc.expected).to.eql('v=DMARC1; p=reject; pct=100');
|
||||
expect(res.body.dns.dmarc.expected).to.eql('"v=DMARC1; p=reject; pct=100"');
|
||||
expect(res.body.dns.dmarc.status).to.eql(false);
|
||||
expect(res.body.dns.dmarc.value).to.eql(null);
|
||||
|
||||
@@ -336,7 +334,7 @@ describe('Mail API', function () {
|
||||
expect(res.body.dns.mx.value).to.eql(null);
|
||||
|
||||
expect(res.body.dns.ptr).to.be.an('object');
|
||||
expect(res.body.dns.ptr.expected).to.eql(config.mailFqdn());
|
||||
expect(res.body.dns.ptr.expected).to.eql(config.mailFqdn() + '.');
|
||||
expect(res.body.dns.ptr.status).to.eql(false);
|
||||
// expect(res.body.ptr.value).to.eql(null); this will be anything random
|
||||
|
||||
@@ -347,10 +345,10 @@ describe('Mail API', function () {
|
||||
it('succeeds with all different spf, dkim, dmarc, mx, ptr records', function (done) {
|
||||
clearDnsAnswerQueue();
|
||||
|
||||
dnsAnswerQueue[mxDomain].MX = [ { priority: '20', exchange: config.mailFqdn() }, { priority: '30', exchange: config.mailFqdn() } ];
|
||||
dnsAnswerQueue[dmarcDomain].TXT = [['v=DMARC2; p=reject; pct=100']];
|
||||
dnsAnswerQueue[dkimDomain].TXT = [['v=DKIM2; t=s; p=' + mail._readDkimPublicKeySync(DOMAIN_0.domain)]];
|
||||
dnsAnswerQueue[spfDomain].TXT = [['v=spf1 a:random.com ~all']];
|
||||
dnsAnswerQueue[mxDomain].MX = [ { priority: '20', exchange: config.mailFqdn() + '.' }, { priority: '30', exchange: config.mailFqdn() + '.'} ];
|
||||
dnsAnswerQueue[dmarcDomain].TXT = ['"v=DMARC2; p=reject; pct=100"'];
|
||||
dnsAnswerQueue[dkimDomain].TXT = ['"v=DKIM2; t=s; p=' + mail._readDkimPublicKeySync(DOMAIN_0.domain) + '"'];
|
||||
dnsAnswerQueue[spfDomain].TXT = ['"v=spf1 a:random.com ~all"'];
|
||||
|
||||
superagent.get(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/status')
|
||||
.query({ access_token: token })
|
||||
@@ -358,19 +356,19 @@ describe('Mail API', function () {
|
||||
expect(res.statusCode).to.equal(200);
|
||||
|
||||
expect(res.body.dns.spf).to.be.an('object');
|
||||
expect(res.body.dns.spf.expected).to.eql('v=spf1 a:' + config.adminFqdn() + ' a:random.com ~all');
|
||||
expect(res.body.dns.spf.expected).to.eql('"v=spf1 a:' + config.adminFqdn() + ' a:random.com ~all"');
|
||||
expect(res.body.dns.spf.status).to.eql(false);
|
||||
expect(res.body.dns.spf.value).to.eql('v=spf1 a:random.com ~all');
|
||||
expect(res.body.dns.spf.value).to.eql('"v=spf1 a:random.com ~all"');
|
||||
|
||||
expect(res.body.dns.dkim).to.be.an('object');
|
||||
expect(res.body.dns.dkim.expected).to.eql('v=DKIM1; t=s; p=' + mail._readDkimPublicKeySync(DOMAIN_0.domain));
|
||||
expect(res.body.dns.dkim.expected).to.eql('"v=DKIM1; t=s; p=' + mail._readDkimPublicKeySync(DOMAIN_0.domain) + '"');
|
||||
expect(res.body.dns.dkim.status).to.eql(false);
|
||||
expect(res.body.dns.dkim.value).to.eql('v=DKIM2; t=s; p=' + mail._readDkimPublicKeySync(DOMAIN_0.domain));
|
||||
expect(res.body.dns.dkim.value).to.eql('"v=DKIM2; t=s; p=' + mail._readDkimPublicKeySync(DOMAIN_0.domain) + '"');
|
||||
|
||||
expect(res.body.dns.dmarc).to.be.an('object');
|
||||
expect(res.body.dns.dmarc.expected).to.eql('v=DMARC1; p=reject; pct=100');
|
||||
expect(res.body.dns.dmarc.expected).to.eql('"v=DMARC1; p=reject; pct=100"');
|
||||
expect(res.body.dns.dmarc.status).to.eql(false);
|
||||
expect(res.body.dns.dmarc.value).to.eql('v=DMARC2; p=reject; pct=100');
|
||||
expect(res.body.dns.dmarc.value).to.eql('"v=DMARC2; p=reject; pct=100"');
|
||||
|
||||
expect(res.body.dns.mx).to.be.an('object');
|
||||
expect(res.body.dns.mx.status).to.eql(false);
|
||||
@@ -378,7 +376,7 @@ describe('Mail API', function () {
|
||||
expect(res.body.dns.mx.value).to.eql('20 ' + config.mailFqdn() + '. 30 ' + config.mailFqdn() + '.');
|
||||
|
||||
expect(res.body.dns.ptr).to.be.an('object');
|
||||
expect(res.body.dns.ptr.expected).to.eql(config.mailFqdn());
|
||||
expect(res.body.dns.ptr.expected).to.eql(config.mailFqdn() + '.');
|
||||
expect(res.body.dns.ptr.status).to.eql(false);
|
||||
// expect(res.body.ptr.value).to.eql(null); this will be anything random
|
||||
|
||||
@@ -391,7 +389,7 @@ describe('Mail API', function () {
|
||||
it('succeeds with existing embedded spf', function (done) {
|
||||
clearDnsAnswerQueue();
|
||||
|
||||
dnsAnswerQueue[spfDomain].TXT = [['v=spf1 a:example.com a:' + config.mailFqdn() + ' ~all']];
|
||||
dnsAnswerQueue[spfDomain].TXT = ['"v=spf1 a:example.com a:' + config.mailFqdn() + ' ~all"'];
|
||||
|
||||
superagent.get(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/status')
|
||||
.query({ access_token: token })
|
||||
@@ -401,8 +399,8 @@ describe('Mail API', function () {
|
||||
expect(res.body.dns.spf).to.be.an('object');
|
||||
expect(res.body.dns.spf.domain).to.eql(spfDomain);
|
||||
expect(res.body.dns.spf.type).to.eql('TXT');
|
||||
expect(res.body.dns.spf.value).to.eql('v=spf1 a:example.com a:' + config.mailFqdn() + ' ~all');
|
||||
expect(res.body.dns.spf.expected).to.eql('v=spf1 a:example.com a:' + config.mailFqdn() + ' ~all');
|
||||
expect(res.body.dns.spf.value).to.eql('"v=spf1 a:example.com a:' + config.mailFqdn() + ' ~all"');
|
||||
expect(res.body.dns.spf.expected).to.eql('"v=spf1 a:example.com a:' + config.mailFqdn() + ' ~all"');
|
||||
expect(res.body.dns.spf.status).to.eql(true);
|
||||
|
||||
done();
|
||||
@@ -412,10 +410,10 @@ describe('Mail API', function () {
|
||||
it('succeeds with all correct records', function (done) {
|
||||
clearDnsAnswerQueue();
|
||||
|
||||
dnsAnswerQueue[mxDomain].MX = [ { priority: '10', exchange: config.mailFqdn() } ];
|
||||
dnsAnswerQueue[dmarcDomain].TXT = [['v=DMARC1; p=reject; pct=100']];
|
||||
dnsAnswerQueue[dkimDomain].TXT = [['v=DKIM1; t=s; p=', mail._readDkimPublicKeySync(DOMAIN_0.domain) ]];
|
||||
dnsAnswerQueue[spfDomain].TXT = [['v=spf1 a:' + config.adminFqdn() + ' ~all']];
|
||||
dnsAnswerQueue[mxDomain].MX = [ { priority: '10', exchange: config.mailFqdn() + '.' } ];
|
||||
dnsAnswerQueue[dmarcDomain].TXT = ['"v=DMARC1; p=reject; pct=100"'];
|
||||
dnsAnswerQueue[dkimDomain].TXT = ['"v=DKIM1; t=s; p=' + mail._readDkimPublicKeySync(DOMAIN_0.domain) + '"'];
|
||||
dnsAnswerQueue[spfDomain].TXT = ['"v=spf1 a:' + config.adminFqdn() + ' ~all"'];
|
||||
|
||||
superagent.get(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/status')
|
||||
.query({ access_token: token })
|
||||
@@ -425,21 +423,21 @@ describe('Mail API', function () {
|
||||
expect(res.body.dns.dkim).to.be.an('object');
|
||||
expect(res.body.dns.dkim.domain).to.eql(dkimDomain);
|
||||
expect(res.body.dns.dkim.type).to.eql('TXT');
|
||||
expect(res.body.dns.dkim.value).to.eql('v=DKIM1; t=s; p=' + mail._readDkimPublicKeySync(DOMAIN_0.domain));
|
||||
expect(res.body.dns.dkim.expected).to.eql('v=DKIM1; t=s; p=' + mail._readDkimPublicKeySync(DOMAIN_0.domain));
|
||||
expect(res.body.dns.dkim.value).to.eql('"v=DKIM1; t=s; p=' + mail._readDkimPublicKeySync(DOMAIN_0.domain) + '"');
|
||||
expect(res.body.dns.dkim.expected).to.eql('"v=DKIM1; t=s; p=' + mail._readDkimPublicKeySync(DOMAIN_0.domain) + '"');
|
||||
expect(res.body.dns.dkim.status).to.eql(true);
|
||||
|
||||
expect(res.body.dns.spf).to.be.an('object');
|
||||
expect(res.body.dns.spf.domain).to.eql(spfDomain);
|
||||
expect(res.body.dns.spf.type).to.eql('TXT');
|
||||
expect(res.body.dns.spf.value).to.eql('v=spf1 a:' + config.adminFqdn() + ' ~all');
|
||||
expect(res.body.dns.spf.expected).to.eql('v=spf1 a:' + config.adminFqdn() + ' ~all');
|
||||
expect(res.body.dns.spf.value).to.eql('"v=spf1 a:' + config.adminFqdn() + ' ~all"');
|
||||
expect(res.body.dns.spf.expected).to.eql('"v=spf1 a:' + config.adminFqdn() + ' ~all"');
|
||||
expect(res.body.dns.spf.status).to.eql(true);
|
||||
|
||||
expect(res.body.dns.dmarc).to.be.an('object');
|
||||
expect(res.body.dns.dmarc.expected).to.eql('v=DMARC1; p=reject; pct=100');
|
||||
expect(res.body.dns.dmarc.expected).to.eql('"v=DMARC1; p=reject; pct=100"');
|
||||
expect(res.body.dns.dmarc.status).to.eql(true);
|
||||
expect(res.body.dns.dmarc.value).to.eql('v=DMARC1; p=reject; pct=100');
|
||||
expect(res.body.dns.dmarc.value).to.eql('"v=DMARC1; p=reject; pct=100"');
|
||||
|
||||
expect(res.body.dns.mx).to.be.an('object');
|
||||
expect(res.body.dns.mx.status).to.eql(true);
|
||||
@@ -459,7 +457,7 @@ describe('Mail API', function () {
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(201);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
after(function (done) {
|
||||
@@ -511,7 +509,7 @@ describe('Mail API', function () {
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(201);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
after(function (done) {
|
||||
@@ -582,7 +580,7 @@ describe('Mail API', function () {
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(201);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
after(function (done) {
|
||||
@@ -660,7 +658,7 @@ describe('Mail API', function () {
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(201);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
after(function (done) {
|
||||
@@ -772,21 +770,17 @@ describe('Mail API', function () {
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(201);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
after(function (done) {
|
||||
mail.removeMailboxes(DOMAIN_0.domain, function (error) {
|
||||
if (error) return done(error);
|
||||
|
||||
superagent.del(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain)
|
||||
.send({ password: PASSWORD })
|
||||
.query({ access_token: token })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(204);
|
||||
done();
|
||||
});
|
||||
});
|
||||
superagent.del(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain)
|
||||
.send({ password: PASSWORD })
|
||||
.query({ access_token: token })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(204);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('set fails if aliases is missing', function (done) {
|
||||
@@ -902,17 +896,13 @@ describe('Mail API', function () {
|
||||
});
|
||||
|
||||
after(function (done) {
|
||||
mail.removeMailboxes(DOMAIN_0.domain, function (error) {
|
||||
if (error) return done(error);
|
||||
|
||||
superagent.del(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain)
|
||||
.send({ password: PASSWORD })
|
||||
.query({ access_token: token })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(204);
|
||||
done();
|
||||
});
|
||||
});
|
||||
superagent.del(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain)
|
||||
.send({ password: PASSWORD })
|
||||
.query({ access_token: token })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(204);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('add fails without groupId', function (done) {
|
||||
|
||||
@@ -167,7 +167,8 @@ describe('OAuth2', function () {
|
||||
domain: DOMAIN_0.domain,
|
||||
portBindings: {},
|
||||
accessRestriction: null,
|
||||
memoryLimit: 0
|
||||
memoryLimit: 0,
|
||||
altDomain: null
|
||||
};
|
||||
|
||||
var APP_1 = {
|
||||
@@ -178,7 +179,8 @@ describe('OAuth2', function () {
|
||||
domain: DOMAIN_0.domain,
|
||||
portBindings: {},
|
||||
accessRestriction: { users: [ 'foobar' ] },
|
||||
memoryLimit: 0
|
||||
memoryLimit: 0,
|
||||
altDomain: null
|
||||
};
|
||||
|
||||
var APP_2 = {
|
||||
@@ -189,7 +191,8 @@ describe('OAuth2', function () {
|
||||
domain: DOMAIN_0.domain,
|
||||
portBindings: {},
|
||||
accessRestriction: { users: [ USER_0.id ] },
|
||||
memoryLimit: 0
|
||||
memoryLimit: 0,
|
||||
altDomain: null
|
||||
};
|
||||
|
||||
var APP_3 = {
|
||||
@@ -200,7 +203,8 @@ describe('OAuth2', function () {
|
||||
domain: DOMAIN_0.domain,
|
||||
portBindings: {},
|
||||
accessRestriction: { groups: [ 'someothergroup', 'admin', 'anothergroup' ] },
|
||||
memoryLimit: 0
|
||||
memoryLimit: 0,
|
||||
altDomain: null
|
||||
};
|
||||
|
||||
// unknown app
|
||||
|
||||
+25
-238
@@ -14,262 +14,49 @@ var async = require('async'),
|
||||
server = require('../../server.js');
|
||||
|
||||
var SERVER_URL = 'http://localhost:' + config.get('port');
|
||||
var DOMAIN = 'example-server-test.com';
|
||||
|
||||
var USERNAME = 'superadmin', PASSWORD = 'Foobar?1337', EMAIL ='silly@me.com';
|
||||
var token = null;
|
||||
|
||||
function setup(done) {
|
||||
config._reset();
|
||||
config.setFqdn('example-server-test.com');
|
||||
config.setVersion('1.2.3');
|
||||
|
||||
async.series([
|
||||
server.start,
|
||||
database._clear
|
||||
server.start.bind(server),
|
||||
|
||||
database._clear,
|
||||
|
||||
function createAdmin(callback) {
|
||||
superagent.post(SERVER_URL + '/api/v1/cloudron/activate')
|
||||
.query({ setupToken: 'somesetuptoken' })
|
||||
.send({ username: USERNAME, password: PASSWORD, email: EMAIL })
|
||||
.end(function (error, result) {
|
||||
expect(result).to.be.ok();
|
||||
expect(result.statusCode).to.eql(201);
|
||||
|
||||
// stash token for further use
|
||||
token = result.body.token;
|
||||
|
||||
callback();
|
||||
});
|
||||
}
|
||||
], done);
|
||||
}
|
||||
|
||||
function cleanup(done) {
|
||||
async.series([
|
||||
database._clear,
|
||||
server.stop
|
||||
], done);
|
||||
database._clear(function (error) {
|
||||
expect(!error).to.be.ok();
|
||||
|
||||
server.stop(done);
|
||||
});
|
||||
}
|
||||
|
||||
describe('REST API', function () {
|
||||
before(setup);
|
||||
after(cleanup);
|
||||
|
||||
it('dns setup fails without provider', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/cloudron/dns_setup')
|
||||
.send({ domain: DOMAIN, adminFqdn: 'my.' + DOMAIN, config: {} })
|
||||
.end(function (error, result) {
|
||||
expect(result).to.be.ok();
|
||||
expect(result.statusCode).to.eql(400);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('dns setup fails with invalid provider', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/cloudron/dns_setup')
|
||||
.send({ provider: 'foobar', domain: DOMAIN, adminFqdn: 'my.' + DOMAIN, config: {} })
|
||||
.end(function (error, result) {
|
||||
expect(result).to.be.ok();
|
||||
expect(result.statusCode).to.eql(400);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('dns setup fails with missing domain', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/cloudron/dns_setup')
|
||||
.send({ provider: 'noop', adminFqdn: 'my.' + DOMAIN, config: {} })
|
||||
.end(function (error, result) {
|
||||
expect(result).to.be.ok();
|
||||
expect(result.statusCode).to.eql(400);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('dns setup fails with invalid domain', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/cloudron/dns_setup')
|
||||
.send({ provider: 'noop', domain: '.foo', adminFqdn: 'my.' + DOMAIN, config: {} })
|
||||
.end(function (error, result) {
|
||||
expect(result).to.be.ok();
|
||||
expect(result.statusCode).to.eql(400);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('dns setup fails with missing adminFqdn', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/cloudron/dns_setup')
|
||||
.send({ provider: 'noop', domain: DOMAIN, config: {} })
|
||||
.end(function (error, result) {
|
||||
expect(result).to.be.ok();
|
||||
expect(result.statusCode).to.eql(400);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('dns setup fails with invalid adminFqdn', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/cloudron/dns_setup')
|
||||
.send({ provider: 'noop', domain: DOMAIN, adminFqdn: 'my', config: {} })
|
||||
.end(function (error, result) {
|
||||
expect(result).to.be.ok();
|
||||
expect(result.statusCode).to.eql(400);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('dns setup fails with invalid config', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/cloudron/dns_setup')
|
||||
.send({ provider: 'noop', domain: DOMAIN, adminFqdn: 'my' + DOMAIN, config: 'not an object' })
|
||||
.end(function (error, result) {
|
||||
expect(result).to.be.ok();
|
||||
expect(result.statusCode).to.eql(400);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('dns setup fails with invalid zoneName', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/cloudron/dns_setup')
|
||||
.send({ provider: 'noop', domain: DOMAIN, adminFqdn: 'my' + DOMAIN, config: {}, zoneName: 1337 })
|
||||
.end(function (error, result) {
|
||||
expect(result).to.be.ok();
|
||||
expect(result.statusCode).to.eql(400);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('dns setup fails with invalid tlsConfig', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/cloudron/dns_setup')
|
||||
.send({ provider: 'noop', domain: DOMAIN, adminFqdn: 'my' + DOMAIN, config: {}, tlsConfig: 'foobar' })
|
||||
.end(function (error, result) {
|
||||
expect(result).to.be.ok();
|
||||
expect(result.statusCode).to.eql(400);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('dns setup fails with invalid tlsConfig provider', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/cloudron/dns_setup')
|
||||
.send({ provider: 'noop', domain: DOMAIN, adminFqdn: 'my' + DOMAIN, config: {}, tlsConfig: { provider: 1337 } })
|
||||
.end(function (error, result) {
|
||||
expect(result).to.be.ok();
|
||||
expect(result.statusCode).to.eql(400);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('dns setup succeeds', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/cloudron/dns_setup')
|
||||
.send({ provider: 'noop', domain: DOMAIN, adminFqdn: 'my.' + DOMAIN, config: {} })
|
||||
.end(function (error, result) {
|
||||
expect(result).to.be.ok();
|
||||
expect(result.statusCode).to.eql(200);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('dns setup twice fails', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/cloudron/dns_setup')
|
||||
.send({ provider: 'noop', domain: DOMAIN, adminFqdn: 'my.' + DOMAIN, config: {} })
|
||||
.end(function (error, result) {
|
||||
expect(result).to.be.ok();
|
||||
expect(result.statusCode).to.eql(409);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('activation fails without username', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/cloudron/activate')
|
||||
.query({ setupToken: 'somesetuptoken' })
|
||||
.send({ password: PASSWORD, email: EMAIL })
|
||||
.end(function (error, result) {
|
||||
expect(result).to.be.ok();
|
||||
expect(result.statusCode).to.eql(400);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('activation fails with invalid username', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/cloudron/activate')
|
||||
.query({ setupToken: 'somesetuptoken' })
|
||||
.send({ username: '?this.is-not!valid', password: PASSWORD, email: EMAIL })
|
||||
.end(function (error, result) {
|
||||
expect(result).to.be.ok();
|
||||
expect(result.statusCode).to.eql(400);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('activation fails without email', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/cloudron/activate')
|
||||
.query({ setupToken: 'somesetuptoken' })
|
||||
.send({ username: USERNAME, password: PASSWORD })
|
||||
.end(function (error, result) {
|
||||
expect(result).to.be.ok();
|
||||
expect(result.statusCode).to.eql(400);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('activation fails with invalid email', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/cloudron/activate')
|
||||
.query({ setupToken: 'somesetuptoken' })
|
||||
.send({ username: USERNAME, password: PASSWORD, email: 'notanemail' })
|
||||
.end(function (error, result) {
|
||||
expect(result).to.be.ok();
|
||||
expect(result.statusCode).to.eql(400);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('activation fails without password', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/cloudron/activate')
|
||||
.query({ setupToken: 'somesetuptoken' })
|
||||
.send({ username: USERNAME, email: EMAIL })
|
||||
.end(function (error, result) {
|
||||
expect(result).to.be.ok();
|
||||
expect(result.statusCode).to.eql(400);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('activation fails with invalid password', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/cloudron/activate')
|
||||
.query({ setupToken: 'somesetuptoken' })
|
||||
.send({ username: USERNAME, password: 'short', email: EMAIL })
|
||||
.end(function (error, result) {
|
||||
expect(result).to.be.ok();
|
||||
expect(result.statusCode).to.eql(400);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('activation succeeds', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/cloudron/activate')
|
||||
.query({ setupToken: 'somesetuptoken' })
|
||||
.send({ username: USERNAME, password: PASSWORD, email: EMAIL })
|
||||
.end(function (error, result) {
|
||||
expect(result).to.be.ok();
|
||||
expect(result.statusCode).to.eql(201);
|
||||
|
||||
// stash token for further use
|
||||
token = result.body.token;
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('activating twice fails', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/cloudron/activate')
|
||||
.query({ setupToken: 'somesetuptoken' })
|
||||
.send({ username: USERNAME, password: PASSWORD, email: EMAIL })
|
||||
.end(function (error, result) {
|
||||
expect(result).to.be.ok();
|
||||
expect(result.statusCode).to.eql(409);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('does not crash with invalid JSON', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/users')
|
||||
.query({ access_token: token })
|
||||
|
||||
@@ -2,16 +2,19 @@
|
||||
|
||||
/* global it:false */
|
||||
/* global describe:false */
|
||||
/* global xdescribe:false */
|
||||
/* global before:false */
|
||||
/* global after:false */
|
||||
|
||||
var async = require('async'),
|
||||
child_process = require('child_process'),
|
||||
config = require('../../config.js'),
|
||||
constants = require('../../constants.js'),
|
||||
database = require('../../database.js'),
|
||||
expect = require('expect.js'),
|
||||
fs = require('fs'),
|
||||
nock = require('nock'),
|
||||
path = require('path'),
|
||||
paths = require('../../paths.js'),
|
||||
server = require('../../server.js'),
|
||||
settings = require('../../settings.js'),
|
||||
@@ -60,9 +63,9 @@ describe('Settings API', function () {
|
||||
before(setup);
|
||||
after(cleanup);
|
||||
|
||||
describe('app_autoupdate_pattern', function () {
|
||||
it('can get app auto update pattern (default)', function (done) {
|
||||
superagent.get(SERVER_URL + '/api/v1/settings/app_autoupdate_pattern')
|
||||
describe('autoupdate_pattern', function () {
|
||||
it('can get auto update pattern (default)', function (done) {
|
||||
superagent.get(SERVER_URL + '/api/v1/settings/autoupdate_pattern')
|
||||
.query({ access_token: token })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(200);
|
||||
@@ -71,8 +74,8 @@ describe('Settings API', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('cannot set app_autoupdate_pattern without pattern', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/settings/app_autoupdate_pattern')
|
||||
it('cannot set autoupdate_pattern without pattern', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/settings/autoupdate_pattern')
|
||||
.query({ access_token: token })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(400);
|
||||
@@ -80,13 +83,13 @@ describe('Settings API', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('can set app_autoupdate_pattern', function (done) {
|
||||
it('can set autoupdate_pattern', function (done) {
|
||||
var eventPattern = null;
|
||||
settings.events.on(settings.APP_AUTOUPDATE_PATTERN_KEY, function (pattern) {
|
||||
settings.events.on(settings.AUTOUPDATE_PATTERN_KEY, function (pattern) {
|
||||
eventPattern = pattern;
|
||||
});
|
||||
|
||||
superagent.post(SERVER_URL + '/api/v1/settings/app_autoupdate_pattern')
|
||||
superagent.post(SERVER_URL + '/api/v1/settings/autoupdate_pattern')
|
||||
.query({ access_token: token })
|
||||
.send({ pattern: '00 30 11 * * 1-5' })
|
||||
.end(function (err, res) {
|
||||
@@ -96,13 +99,13 @@ describe('Settings API', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('can set app_autoupdate_pattern to never', function (done) {
|
||||
it('can set autoupdate_pattern to never', function (done) {
|
||||
var eventPattern = null;
|
||||
settings.events.on(settings.APP_AUTOUPDATE_PATTERN_KEY, function (pattern) {
|
||||
settings.events.on(settings.AUTOUPDATE_PATTERN_KEY, function (pattern) {
|
||||
eventPattern = pattern;
|
||||
});
|
||||
|
||||
superagent.post(SERVER_URL + '/api/v1/settings/app_autoupdate_pattern')
|
||||
superagent.post(SERVER_URL + '/api/v1/settings/autoupdate_pattern')
|
||||
.query({ access_token: token })
|
||||
.send({ pattern: constants.AUTOUPDATE_PATTERN_NEVER })
|
||||
.end(function (err, res) {
|
||||
@@ -112,71 +115,8 @@ describe('Settings API', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('cannot set invalid app_autoupdate_pattern', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/settings/app_autoupdate_pattern')
|
||||
.query({ access_token: token })
|
||||
.send({ pattern: '1 3 x 5 6' })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(400);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('box_autoupdate_pattern', function () {
|
||||
it('can get app auto update pattern (default)', function (done) {
|
||||
superagent.get(SERVER_URL + '/api/v1/settings/box_autoupdate_pattern')
|
||||
.query({ access_token: token })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(200);
|
||||
expect(res.body.pattern).to.be.ok();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('cannot set box_autoupdate_pattern without pattern', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/settings/box_autoupdate_pattern')
|
||||
.query({ access_token: token })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(400);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can set box_autoupdate_pattern', function (done) {
|
||||
var eventPattern = null;
|
||||
settings.events.on(settings.BOX_AUTOUPDATE_PATTERN_KEY, function (pattern) {
|
||||
eventPattern = pattern;
|
||||
});
|
||||
|
||||
superagent.post(SERVER_URL + '/api/v1/settings/box_autoupdate_pattern')
|
||||
.query({ access_token: token })
|
||||
.send({ pattern: '00 30 11 * * 1-5' })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(200);
|
||||
expect(eventPattern === '00 30 11 * * 1-5').to.be.ok();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can set box_autoupdate_pattern to never', function (done) {
|
||||
var eventPattern = null;
|
||||
settings.events.on(settings.BOX_AUTOUPDATE_PATTERN_KEY, function (pattern) {
|
||||
eventPattern = pattern;
|
||||
});
|
||||
|
||||
superagent.post(SERVER_URL + '/api/v1/settings/box_autoupdate_pattern')
|
||||
.query({ access_token: token })
|
||||
.send({ pattern: constants.AUTOUPDATE_PATTERN_NEVER })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(200);
|
||||
expect(eventPattern).to.eql(constants.AUTOUPDATE_PATTERN_NEVER);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('cannot set invalid box_autoupdate_pattern', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/settings/box_autoupdate_pattern')
|
||||
it('cannot set invalid autoupdate_pattern', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/settings/autoupdate_pattern')
|
||||
.query({ access_token: token })
|
||||
.send({ pattern: '1 3 x 5 6' })
|
||||
.end(function (err, res) {
|
||||
@@ -280,6 +220,99 @@ describe('Settings API', function () {
|
||||
});
|
||||
});
|
||||
|
||||
xdescribe('Certificates API', function () {
|
||||
var validCert0, validKey0, // example.com
|
||||
validCert1, validKey1; // *.example.com
|
||||
|
||||
before(function () {
|
||||
child_process.execSync('openssl req -subj "/CN=example.com/O=My Company Name LTD./C=US" -new -newkey rsa:2048 -days 365 -nodes -x509 -keyout /tmp/server.key -out /tmp/server.crt');
|
||||
validKey0 = fs.readFileSync('/tmp/server.key', 'utf8');
|
||||
validCert0 = fs.readFileSync('/tmp/server.crt', 'utf8');
|
||||
|
||||
child_process.execSync('openssl req -subj "/CN=*.example.com/O=My Company Name LTD./C=US" -new -newkey rsa:2048 -days 365 -nodes -x509 -keyout /tmp/server.key -out /tmp/server.crt');
|
||||
validKey1 = fs.readFileSync('/tmp/server.key', 'utf8');
|
||||
validCert1 = fs.readFileSync('/tmp/server.crt', 'utf8');
|
||||
});
|
||||
|
||||
it('cannot set certificate without token', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/settings/certificate')
|
||||
.end(function (error, result) {
|
||||
expect(result.statusCode).to.equal(401);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('cannot set certificate without certificate', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/settings/certificate')
|
||||
.query({ access_token: token })
|
||||
.send({ key: validKey1 })
|
||||
.end(function (error, result) {
|
||||
expect(result.statusCode).to.equal(400);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('cannot set certificate without key', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/settings/certificate')
|
||||
.query({ access_token: token })
|
||||
.send({ cert: validCert1 })
|
||||
.end(function (error, result) {
|
||||
expect(result.statusCode).to.equal(400);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('cannot set certificate with cert not being a string', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/settings/certificate')
|
||||
.query({ access_token: token })
|
||||
.send({ cert: 1234, key: validKey1 })
|
||||
.end(function (error, result) {
|
||||
expect(result.statusCode).to.equal(400);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('cannot set certificate with key not being a string', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/settings/certificate')
|
||||
.query({ access_token: token })
|
||||
.send({ cert: validCert1, key: true })
|
||||
.end(function (error, result) {
|
||||
expect(result.statusCode).to.equal(400);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('cannot set non wildcard certificate', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/settings/certificate')
|
||||
.query({ access_token: token })
|
||||
.send({ cert: validCert0, key: validKey0 })
|
||||
.end(function (error, result) {
|
||||
expect(result.statusCode).to.equal(400);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can set certificate', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/settings/certificate')
|
||||
.query({ access_token: token })
|
||||
.send({ cert: validCert1, key: validKey1 })
|
||||
.end(function (error, result) {
|
||||
expect(result.statusCode).to.equal(202);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('did set the certificate', function (done) {
|
||||
var cert = fs.readFileSync(path.join(paths.NGINX_CERT_DIR, 'host.cert'), 'utf-8');
|
||||
expect(cert).to.eql(validCert1);
|
||||
|
||||
var key = fs.readFileSync(path.join(paths.NGINX_CERT_DIR, 'host.key'), 'utf-8');
|
||||
expect(key).to.eql(validKey1);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('time_zone', function () {
|
||||
it('succeeds', function (done) {
|
||||
superagent.get(SERVER_URL + '/api/v1/settings/time_zone')
|
||||
|
||||
+14
-3
@@ -20,7 +20,8 @@ var assert = require('assert'),
|
||||
HttpSuccess = require('connect-lastmile').HttpSuccess,
|
||||
oauth2 = require('./oauth2.js'),
|
||||
user = require('../user.js'),
|
||||
UserError = user.UserError;
|
||||
UserError = user.UserError,
|
||||
_ = require('underscore');
|
||||
|
||||
function auditSource(req) {
|
||||
var ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress || null;
|
||||
@@ -87,7 +88,9 @@ function list(req, res, next) {
|
||||
user.list(function (error, results) {
|
||||
if (error) return next(new HttpError(500, error));
|
||||
|
||||
var users = results.map(user.removePrivateFields);
|
||||
var users = results.map(function (result) {
|
||||
return _.pick(result, 'id', 'username', 'email', 'fallbackEmail', 'displayName', 'groupIds', 'admin');
|
||||
});
|
||||
|
||||
next(new HttpSuccess(200, { users: users }));
|
||||
});
|
||||
@@ -103,7 +106,15 @@ function get(req, res, next) {
|
||||
if (error && error.reason === UserError.NOT_FOUND) return next(new HttpError(404, 'No such user'));
|
||||
if (error) return next(new HttpError(500, error));
|
||||
|
||||
next(new HttpSuccess(200, user.removePrivateFields(result)));
|
||||
next(new HttpSuccess(200, {
|
||||
id: result.id,
|
||||
username: result.username,
|
||||
displayName: result.displayName,
|
||||
email: result.email,
|
||||
fallbackEmail: result.fallbackEmail,
|
||||
admin: result.admin,
|
||||
groupIds: result.groupIds
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+27
-44
@@ -24,7 +24,7 @@ function sync(callback) {
|
||||
|
||||
callback = callback || NOOP_CALLBACK;
|
||||
|
||||
debug('sync: synchronizing global state with installed app state');
|
||||
debug('Syncing');
|
||||
|
||||
apps.getAll(function (error, allApps) {
|
||||
if (error) return callback(error);
|
||||
@@ -36,10 +36,11 @@ function sync(callback) {
|
||||
async.eachSeries(removedAppIds, function (appId, iteratorDone) {
|
||||
stopJobs(appId, gState[appId], iteratorDone);
|
||||
}, function (error) {
|
||||
if (error) debug('sync: error stopping jobs of removed apps', error);
|
||||
if (error) debug('Error stopping jobs of removed apps', error);
|
||||
|
||||
gState = _.omit(gState, removedAppIds);
|
||||
|
||||
debug('sync: checking apps %j', allAppIds);
|
||||
async.eachSeries(allApps, function (app, iteratorDone) {
|
||||
var appState = gState[app.id] || null;
|
||||
var schedulerConfig = app.manifest.addons ? app.manifest.addons.scheduler : null;
|
||||
@@ -50,10 +51,9 @@ function sync(callback) {
|
||||
return iteratorDone(); // nothing changed
|
||||
}
|
||||
|
||||
debug(`sync: app ${app.fqdn} changed`);
|
||||
|
||||
debug('sync: app %s changed', app.id);
|
||||
stopJobs(app.id, appState, function (error) {
|
||||
if (error) debug(`sync: error stopping jobs of ${app.fqdn} : ${error.message}`);
|
||||
if (error) debug('Error stopping jobs for %s : %s', app.id, error.message);
|
||||
|
||||
if (!schedulerConfig) {
|
||||
delete gState[app.id];
|
||||
@@ -62,21 +62,20 @@ function sync(callback) {
|
||||
|
||||
gState[app.id] = {
|
||||
schedulerConfig: schedulerConfig,
|
||||
cronJobs: createCronJobs(app, schedulerConfig)
|
||||
cronJobs: createCronJobs(app.id, schedulerConfig)
|
||||
};
|
||||
|
||||
iteratorDone();
|
||||
});
|
||||
});
|
||||
|
||||
debug('sync: done');
|
||||
debug('Done syncing');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function killContainer(containerName, callback) {
|
||||
assert.strictEqual(typeof containerName, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
if (!containerName) return callback();
|
||||
|
||||
async.series([
|
||||
docker.stopContainerByName.bind(null, containerName),
|
||||
@@ -93,7 +92,7 @@ function stopJobs(appId, appState, callback) {
|
||||
assert.strictEqual(typeof appState, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
debug(`stopJobs: stopping jobs of ${appId}`);
|
||||
debug('stopJobs for %s', appId);
|
||||
|
||||
if (!appState) return callback();
|
||||
|
||||
@@ -102,30 +101,29 @@ function stopJobs(appId, appState, callback) {
|
||||
appState.cronJobs[taskName].stop();
|
||||
}
|
||||
|
||||
killContainer(`${appId}-${taskName}`, iteratorDone);
|
||||
var containerName = appId + '-' + taskName;
|
||||
killContainer(containerName, iteratorDone);
|
||||
}, callback);
|
||||
}
|
||||
|
||||
function createCronJobs(app, schedulerConfig) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
function createCronJobs(appId, schedulerConfig) {
|
||||
assert.strictEqual(typeof appId, 'string');
|
||||
assert(schedulerConfig && typeof schedulerConfig === 'object');
|
||||
|
||||
debug(`createCronJobs: creating cron jobs for app ${app.fqdn}`);
|
||||
debug('creating cron jobs for app %s', appId);
|
||||
|
||||
var jobs = { };
|
||||
|
||||
Object.keys(schedulerConfig).forEach(function (taskName) {
|
||||
var task = schedulerConfig[taskName];
|
||||
|
||||
const randomSecond = Math.floor(60*Math.random()); // don't start all crons to decrease memory pressure
|
||||
var cronTime = (config.TEST ? '*/5 ' : '00 ') + task.schedule; // time ticks faster in tests
|
||||
|
||||
var cronTime = (config.TEST ? '*/5 ' : `${randomSecond} `) + task.schedule; // time ticks faster in tests
|
||||
|
||||
debug(`createCronJobs: ${app.fqdn} task ${taskName} scheduled at ${cronTime} with cmd ${task.command}`);
|
||||
debug('scheduling task for %s/%s @ %s : %s', appId, taskName, cronTime, task.command);
|
||||
|
||||
var cronJob = new CronJob({
|
||||
cronTime: cronTime, // at this point, the pattern has been validated
|
||||
onTick: runTask.bind(null, app.id, taskName), // put the app id in closure, so we don't use the outdated app object by mistake
|
||||
onTick: doTask.bind(null, appId, taskName),
|
||||
start: true
|
||||
});
|
||||
|
||||
@@ -135,50 +133,35 @@ function createCronJobs(app, schedulerConfig) {
|
||||
return jobs;
|
||||
}
|
||||
|
||||
function runTask(appId, taskName, callback) {
|
||||
function doTask(appId, taskName, callback) {
|
||||
assert.strictEqual(typeof appId, 'string');
|
||||
assert.strictEqual(typeof taskName, 'string');
|
||||
assert(!callback || typeof callback === 'function');
|
||||
|
||||
const JOB_MAX_TIME = 30 * 60 * 1000; // 30 minutes
|
||||
|
||||
callback = callback || NOOP_CALLBACK;
|
||||
|
||||
debug(`runTask: running task ${taskName} of ${appId}`);
|
||||
debug('Executing task %s/%s', appId, taskName);
|
||||
|
||||
apps.get(appId, function (error, app) {
|
||||
if (error) return callback(error);
|
||||
|
||||
if (app.installationState !== appdb.ISTATE_INSTALLED || app.runState !== appdb.RSTATE_RUNNING || app.health !== appdb.HEALTH_HEALTHY) {
|
||||
debug(`runTask: skipped task ${taskName} because app ${app.fqdn} has run state ${app.installationState}`);
|
||||
debug('task %s skipped. app %s is not installed/running/healthy', taskName, app.id);
|
||||
return callback();
|
||||
}
|
||||
|
||||
const containerName = `${app.id}-${taskName}`;
|
||||
var containerName = app.id + '-' + taskName;
|
||||
|
||||
docker.inspectByName(containerName, function (err, data) {
|
||||
if (!err && data && data.State.Running === true) {
|
||||
const jobStartTime = new Date(data.State.StartedAt); // iso 8601
|
||||
if (new Date() - jobStartTime < JOB_MAX_TIME) {
|
||||
debug(`runTask: skipped task ${taskName} of app ${app.fqdn} since it was started at ${jobStartTime}`);
|
||||
return callback();
|
||||
}
|
||||
}
|
||||
killContainer(containerName, function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
debug(`runTask: removing any old task ${taskName} of app ${app.fqdn}`);
|
||||
debug('Creating subcontainer for %s/%s : %s', app.id, taskName, gState[appId].schedulerConfig[taskName].command);
|
||||
|
||||
killContainer(containerName, function (error) {
|
||||
// NOTE: if you change container name here, fix addons.js to return correct container names
|
||||
docker.createSubcontainer(app, containerName, [ '/bin/sh', '-c', gState[appId].schedulerConfig[taskName].command ], { } /* options */, function (error, container) {
|
||||
if (error) return callback(error);
|
||||
const cmd = gState[appId].schedulerConfig[taskName].command;
|
||||
|
||||
debug(`runTask: starting task ${taskName} of app ${app.fqdn} with cmd ${cmd}`);
|
||||
|
||||
// NOTE: if you change container name here, fix addons.js to return correct container names
|
||||
docker.createSubcontainer(app, containerName, [ '/bin/sh', '-c', cmd ], { } /* options */, function (error, container) {
|
||||
if (error) return callback(error);
|
||||
|
||||
docker.startContainer(container.id, callback);
|
||||
});
|
||||
docker.startContainer(container.id, callback);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+3
-8
@@ -60,9 +60,7 @@ function initializeExpressSync() {
|
||||
router.del = router.delete; // amend router.del for readability further on
|
||||
|
||||
app
|
||||
// the timeout middleware will respond with a 503. the request itself cannot be 'aborted' and will continue
|
||||
// search for req.clearTimeout in route handlers to see places where this timeout is reset
|
||||
.use(middleware.timeout(REQUEST_TIMEOUT, { respond: true }))
|
||||
.use(middleware.timeout(REQUEST_TIMEOUT))
|
||||
.use(json)
|
||||
.use(urlencoded)
|
||||
.use(middleware.cookieParser())
|
||||
@@ -197,10 +195,8 @@ function initializeExpressSync() {
|
||||
router.post('/api/v1/apps/:id/upload', appsScope, routes.user.requireAdmin, multipart, routes.apps.uploadFile);
|
||||
|
||||
// settings routes (these are for the settings tab - avatar & name have public routes for normal users. see above)
|
||||
router.get ('/api/v1/settings/app_autoupdate_pattern', settingsScope, routes.user.requireAdmin, routes.settings.getAppAutoupdatePattern);
|
||||
router.post('/api/v1/settings/app_autoupdate_pattern', settingsScope, routes.user.requireAdmin, routes.settings.setAppAutoupdatePattern);
|
||||
router.get ('/api/v1/settings/box_autoupdate_pattern', settingsScope, routes.user.requireAdmin, routes.settings.getBoxAutoupdatePattern);
|
||||
router.post('/api/v1/settings/box_autoupdate_pattern', settingsScope, routes.user.requireAdmin, routes.settings.setBoxAutoupdatePattern);
|
||||
router.get ('/api/v1/settings/autoupdate_pattern', settingsScope, routes.user.requireAdmin, routes.settings.getAutoupdatePattern);
|
||||
router.post('/api/v1/settings/autoupdate_pattern', settingsScope, routes.user.requireAdmin, routes.settings.setAutoupdatePattern);
|
||||
router.get ('/api/v1/settings/cloudron_name', settingsScope, routes.user.requireAdmin, routes.settings.getCloudronName);
|
||||
router.post('/api/v1/settings/cloudron_name', settingsScope, routes.user.requireAdmin, routes.settings.setCloudronName);
|
||||
router.get ('/api/v1/settings/cloudron_avatar', settingsScope, routes.user.requireAdmin, routes.settings.getCloudronAvatar);
|
||||
@@ -215,7 +211,6 @@ function initializeExpressSync() {
|
||||
|
||||
// email routes
|
||||
router.get ('/api/v1/mail/:domain', settingsScope, routes.user.requireAdmin, routes.mail.get);
|
||||
router.post('/api/v1/mail/:domain', settingsScope, routes.user.requireAdmin, routes.mail.update);
|
||||
router.post('/api/v1/mail', settingsScope, routes.user.requireAdmin, routes.mail.add);
|
||||
router.del ('/api/v1/mail/:domain', settingsScope, routes.user.requireAdmin, routes.user.verifyPassword, routes.mail.del);
|
||||
router.get ('/api/v1/mail/:domain/status', settingsScope, routes.user.requireAdmin, routes.mail.getStatus);
|
||||
|
||||
+10
-44
@@ -6,11 +6,8 @@ exports = module.exports = {
|
||||
initialize: initialize,
|
||||
uninitialize: uninitialize,
|
||||
|
||||
getAppAutoupdatePattern: getAppAutoupdatePattern,
|
||||
setAppAutoupdatePattern: setAppAutoupdatePattern,
|
||||
|
||||
getBoxAutoupdatePattern: getBoxAutoupdatePattern,
|
||||
setBoxAutoupdatePattern: setBoxAutoupdatePattern,
|
||||
getAutoupdatePattern: getAutoupdatePattern,
|
||||
setAutoupdatePattern: setAutoupdatePattern,
|
||||
|
||||
getTimeZone: getTimeZone,
|
||||
setTimeZone: setTimeZone,
|
||||
@@ -48,8 +45,7 @@ exports = module.exports = {
|
||||
CAAS_CONFIG_KEY: 'caas_config',
|
||||
|
||||
// strings
|
||||
APP_AUTOUPDATE_PATTERN_KEY: 'app_autoupdate_pattern',
|
||||
BOX_AUTOUPDATE_PATTERN_KEY: 'box_autoupdate_pattern',
|
||||
AUTOUPDATE_PATTERN_KEY: 'autoupdate_pattern',
|
||||
TIME_ZONE_KEY: 'time_zone',
|
||||
CLOUDRON_NAME_KEY: 'cloudron_name',
|
||||
|
||||
@@ -73,8 +69,7 @@ var assert = require('assert'),
|
||||
|
||||
var gDefaults = (function () {
|
||||
var result = { };
|
||||
result[exports.APP_AUTOUPDATE_PATTERN_KEY] = '00 30 1,3,5,23 * * *';
|
||||
result[exports.BOX_AUTOUPDATE_PATTERN_KEY] = '00 00 1,3,5,23 * * *';
|
||||
result[exports.AUTOUPDATE_PATTERN_KEY] = '00 00 1,3,5,23 * * *';
|
||||
result[exports.TIME_ZONE_KEY] = 'America/Los_Angeles';
|
||||
result[exports.CLOUDRON_NAME_KEY] = 'Cloudron';
|
||||
result[exports.DYNAMIC_DNS_KEY] = false;
|
||||
@@ -130,7 +125,7 @@ function uninitialize(callback) {
|
||||
callback();
|
||||
}
|
||||
|
||||
function setAppAutoupdatePattern(pattern, callback) {
|
||||
function setAutoupdatePattern(pattern, callback) {
|
||||
assert.strictEqual(typeof pattern, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
@@ -139,49 +134,20 @@ function setAppAutoupdatePattern(pattern, callback) {
|
||||
if (!job) return callback(new SettingsError(SettingsError.BAD_FIELD, 'Invalid pattern'));
|
||||
}
|
||||
|
||||
settingsdb.set(exports.APP_AUTOUPDATE_PATTERN_KEY, pattern, function (error) {
|
||||
settingsdb.set(exports.AUTOUPDATE_PATTERN_KEY, pattern, function (error) {
|
||||
if (error) return callback(new SettingsError(SettingsError.INTERNAL_ERROR, error));
|
||||
|
||||
exports.events.emit(exports.APP_AUTOUPDATE_PATTERN_KEY, pattern);
|
||||
exports.events.emit(exports.AUTOUPDATE_PATTERN_KEY, pattern);
|
||||
|
||||
return callback(null);
|
||||
});
|
||||
}
|
||||
|
||||
function getAppAutoupdatePattern(callback) {
|
||||
function getAutoupdatePattern(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
settingsdb.get(exports.APP_AUTOUPDATE_PATTERN_KEY, function (error, pattern) {
|
||||
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(null, gDefaults[exports.APP_AUTOUPDATE_PATTERN_KEY]);
|
||||
if (error) return callback(new SettingsError(SettingsError.INTERNAL_ERROR, error));
|
||||
|
||||
callback(null, pattern);
|
||||
});
|
||||
}
|
||||
|
||||
function setBoxAutoupdatePattern(pattern, callback) {
|
||||
assert.strictEqual(typeof pattern, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
if (pattern !== constants.AUTOUPDATE_PATTERN_NEVER) { // check if pattern is valid
|
||||
var job = safe.safeCall(function () { return new CronJob(pattern); });
|
||||
if (!job) return callback(new SettingsError(SettingsError.BAD_FIELD, 'Invalid pattern'));
|
||||
}
|
||||
|
||||
settingsdb.set(exports.BOX_AUTOUPDATE_PATTERN_KEY, pattern, function (error) {
|
||||
if (error) return callback(new SettingsError(SettingsError.INTERNAL_ERROR, error));
|
||||
|
||||
exports.events.emit(exports.BOX_AUTOUPDATE_PATTERN_KEY, pattern);
|
||||
|
||||
return callback(null);
|
||||
});
|
||||
}
|
||||
|
||||
function getBoxAutoupdatePattern(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
settingsdb.get(exports.BOX_AUTOUPDATE_PATTERN_KEY, function (error, pattern) {
|
||||
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(null, gDefaults[exports.BOX_AUTOUPDATE_PATTERN_KEY]);
|
||||
settingsdb.get(exports.AUTOUPDATE_PATTERN_KEY, function (error, pattern) {
|
||||
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(null, gDefaults[exports.AUTOUPDATE_PATTERN_KEY]);
|
||||
if (error) return callback(new SettingsError(SettingsError.INTERNAL_ERROR, error));
|
||||
|
||||
callback(null, pattern);
|
||||
|
||||
+37
-21
@@ -119,18 +119,38 @@ function configureWebadmin(callback) {
|
||||
|
||||
gWebadminStatus.configuring = true; // re-entracy guard
|
||||
|
||||
function done(error) {
|
||||
gWebadminStatus.configuring = false;
|
||||
debug('configureWebadmin: done error: %j', error || {});
|
||||
callback(error);
|
||||
}
|
||||
|
||||
function configureReverseProxy(error) {
|
||||
debug('configureReverseProxy: error %j', error || null);
|
||||
|
||||
reverseProxy.configureAdmin({ userId: null, username: 'setup' }, function (error) {
|
||||
debug('configureWebadmin: done error: %j', error || {});
|
||||
gWebadminStatus.configuring = false;
|
||||
|
||||
if (error) return callback(error);
|
||||
if (error) return done(error);
|
||||
|
||||
gWebadminStatus.tls = true;
|
||||
|
||||
callback();
|
||||
done();
|
||||
});
|
||||
}
|
||||
|
||||
function addWebadminDnsRecord(ip, domain, callback) {
|
||||
assert.strictEqual(typeof ip, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
if (process.env.BOX_ENV === 'test') return callback();
|
||||
|
||||
async.retry({ times: 10, interval: 20000 }, function (retryCallback) {
|
||||
domains.upsertDNSRecords(config.adminLocation(), domain, 'A', [ ip ], retryCallback);
|
||||
}, function (error) {
|
||||
if (error) debug('addWebadminDnsRecord: done updating records with error:', error);
|
||||
else debug('addWebadminDnsRecord: done');
|
||||
|
||||
callback(error);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -139,11 +159,10 @@ function configureWebadmin(callback) {
|
||||
sysinfo.getPublicIp(function (error, ip) {
|
||||
if (error) return configureReverseProxy(error);
|
||||
|
||||
domains.upsertDnsRecords(config.adminLocation(), config.adminDomain(), 'A', [ ip ], function (error) {
|
||||
debug('addWebadminDnsRecord: updated records with error:', error);
|
||||
addWebadminDnsRecord(ip, config.adminDomain(), function (error) {
|
||||
if (error) return configureReverseProxy(error);
|
||||
|
||||
domains.waitForDnsRecord(config.adminFqdn(), config.adminDomain(), ip, { interval: 30000, times: 50000 }, function (error) {
|
||||
domains.waitForDNSRecord(config.adminFqdn(), config.adminDomain(), ip, 'A', { interval: 30000, times: 50000 }, function (error) {
|
||||
if (error) return configureReverseProxy(error);
|
||||
|
||||
gWebadminStatus.dns = true;
|
||||
@@ -167,11 +186,9 @@ function dnsSetup(adminFqdn, domain, zoneName, provider, dnsConfig, tlsConfig, c
|
||||
|
||||
if (gWebadminStatus.configuring || gWebadminStatus.restoring) return callback(new SetupError(SetupError.BAD_STATE, 'Already restoring or configuring'));
|
||||
|
||||
if (!tld.isValid(adminFqdn) || !adminFqdn.endsWith(domain)) return callback(new SetupError(SetupError.BAD_FIELD, 'adminFqdn must be a subdomain of domain'));
|
||||
|
||||
if (!zoneName) zoneName = tld.getDomain(domain) || domain;
|
||||
|
||||
debug(`dnsSetup: Setting up Cloudron with domain ${domain} and zone ${zoneName} using admin fqdn ${adminFqdn}`);
|
||||
debug('dnsSetup: Setting up Cloudron with domain %s and zone %s', domain, zoneName);
|
||||
|
||||
function done(error) {
|
||||
if (error && error.reason === DomainError.BAD_FIELD) return callback(new SetupError(SetupError.BAD_FIELD, error.message));
|
||||
@@ -193,12 +210,14 @@ function dnsSetup(adminFqdn, domain, zoneName, provider, dnsConfig, tlsConfig, c
|
||||
domains.get(domain, function (error, result) {
|
||||
if (error && error.reason !== DomainError.NOT_FOUND) return callback(new SettingsError(SettingsError.INTERNAL_ERROR, error));
|
||||
|
||||
if (result) return callback(new SettingsError(SettingsError.ALREADY_EXISTS, 'domain already exists'));
|
||||
|
||||
async.series([
|
||||
domains.add.bind(null, domain, zoneName, provider, dnsConfig, null /* cert */, tlsConfig),
|
||||
mail.add.bind(null, domain)
|
||||
], done);
|
||||
if (!result) {
|
||||
async.series([
|
||||
domains.add.bind(null, domain, zoneName, provider, dnsConfig, null /* cert */, tlsConfig),
|
||||
mail.add.bind(null, domain)
|
||||
], done);
|
||||
} else {
|
||||
domains.update(domain, provider, dnsConfig, null /* cert */, tlsConfig, done);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -285,7 +304,7 @@ function restore(backupConfig, backupId, version, callback) {
|
||||
if (error && error.reason === BackupsError.EXTERNAL_ERROR) return callback(new SetupError(SetupError.EXTERNAL_ERROR, error.message));
|
||||
if (error) return callback(new SetupError(SetupError.INTERNAL_ERROR, error));
|
||||
|
||||
debug(`restore: restoring from ${backupId} from provider ${backupConfig.provider} with format ${backupConfig.format}`);
|
||||
debug(`restore: restoring from ${backupId} from provider ${backupConfig.provider}`);
|
||||
|
||||
gWebadminStatus.restoring = true;
|
||||
|
||||
@@ -294,9 +313,6 @@ function restore(backupConfig, backupId, version, callback) {
|
||||
async.series([
|
||||
backups.restore.bind(null, backupConfig, backupId),
|
||||
autoprovision,
|
||||
// currently, our suggested restore flow is after a dnsSetup. This re-creates DKIM keys and updates the DNS
|
||||
// for this reason, we have to re-setup DNS after a restore. Once we have a 100% IP based restore, we can skip this
|
||||
mail.addDnsRecords.bind(null, config.adminDomain()),
|
||||
shell.sudo.bind(null, 'restart', [ RESTART_CMD ])
|
||||
], function (error) {
|
||||
debug('restore:', error);
|
||||
|
||||
+1
-1
@@ -204,7 +204,7 @@ function copy(apiConfig, oldFilePath, newFilePath) {
|
||||
var relativePath = path.relative(oldFilePath, file.name);
|
||||
|
||||
file.copy(path.join(newFilePath, relativePath), function(error) {
|
||||
if (error && error.code === 404) return iteratorCallback(new BackupsError(BackupsError.NOT_FOUND, 'Old backup not found'));
|
||||
if (error && error.code == 404) return iteratorCallback(new BackupsError(BackupsError.NOT_FOUND, 'Old backup not found'));
|
||||
if (error) {
|
||||
debug('copyBackup: gcs copy error', error);
|
||||
return iteratorCallback(new BackupsError(BackupsError.EXTERNAL_ERROR, error.message));
|
||||
|
||||
@@ -6,13 +6,8 @@
|
||||
// New backends can start from here
|
||||
// -------------------------------------------
|
||||
|
||||
// Implementation note:
|
||||
// retry logic for upload() comes from the syncer since it is stream based
|
||||
// for the other API calls we leave it to the backend to retry. this allows
|
||||
// them to tune the concurrency based on failures/rate limits accordingly
|
||||
exports = module.exports = {
|
||||
upload: upload,
|
||||
|
||||
download: download,
|
||||
downloadDir: downloadDir,
|
||||
copy: copy,
|
||||
@@ -35,7 +30,6 @@ function upload(apiConfig, backupFilePath, sourceStream, callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
// Result: none
|
||||
// sourceStream errors are handled upstream
|
||||
|
||||
callback(new Error('not implemented'));
|
||||
}
|
||||
|
||||
+52
-55
@@ -72,10 +72,7 @@ function getCaasConfig(apiConfig, callback) {
|
||||
region: apiConfig.region || 'us-east-1',
|
||||
maxRetries: 5,
|
||||
retryDelayOptions: {
|
||||
customBackoff: () => 20000 // constant backoff - https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Config.html#retryDelayOptions-property
|
||||
},
|
||||
httpOptions: {
|
||||
connectTimeout: 10000 // https://github.com/aws/aws-sdk-js/pull/1446
|
||||
base: 20000 // 2^5 * 20 seconds
|
||||
}
|
||||
};
|
||||
|
||||
@@ -104,17 +101,16 @@ function getS3Config(apiConfig, callback) {
|
||||
region: apiConfig.region || 'us-east-1',
|
||||
maxRetries: 5,
|
||||
retryDelayOptions: {
|
||||
customBackoff: () => 20000 // constant backoff - https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Config.html#retryDelayOptions-property
|
||||
},
|
||||
httpOptions: {
|
||||
connectTimeout: 10000 // https://github.com/aws/aws-sdk-js/pull/1446
|
||||
base: 20000 // 2^5 * 20 seconds
|
||||
}
|
||||
};
|
||||
|
||||
if (apiConfig.endpoint) credentials.endpoint = apiConfig.endpoint;
|
||||
|
||||
if (apiConfig.acceptSelfSignedCerts === true && credentials.endpoint && credentials.endpoint.startsWith('https://')) {
|
||||
credentials.httpOptions.agent = new https.Agent({ rejectUnauthorized: false });
|
||||
credentials.httpOptions.agent = {
|
||||
agent: new https.Agent({ rejectUnauthorized: false })
|
||||
};
|
||||
}
|
||||
callback(null, credentials);
|
||||
}
|
||||
@@ -126,6 +122,15 @@ function upload(apiConfig, backupFilePath, sourceStream, callback) {
|
||||
assert.strictEqual(typeof sourceStream, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
function done(error) {
|
||||
if (error) {
|
||||
debug('[%s] upload: s3 upload error.', backupFilePath, error);
|
||||
return callback(new BackupsError(BackupsError.EXTERNAL_ERROR, `Error uploading ${backupFilePath}. Message: ${error.message} HTTP Code: ${error.code}`));
|
||||
}
|
||||
|
||||
callback(null);
|
||||
}
|
||||
|
||||
getS3Config(apiConfig, function (error, credentials) {
|
||||
if (error) return callback(error);
|
||||
|
||||
@@ -139,14 +144,7 @@ function upload(apiConfig, backupFilePath, sourceStream, callback) {
|
||||
|
||||
// s3.upload automatically does a multi-part upload. we set queueSize to 1 to reduce memory usage
|
||||
// uploader will buffer at most queueSize * partSize bytes into memory at any given time.
|
||||
s3.upload(params, { partSize: 10 * 1024 * 1024, queueSize: 1 }, function (error) {
|
||||
if (error) {
|
||||
debug('[%s] upload: s3 upload error.', backupFilePath, error);
|
||||
return callback(new BackupsError(BackupsError.EXTERNAL_ERROR, `Error uploading ${backupFilePath}. Message: ${error.message} HTTP Code: ${error.code}`));
|
||||
}
|
||||
|
||||
callback(null);
|
||||
});
|
||||
return s3.upload(params, { partSize: 10 * 1024 * 1024, queueSize: 1 }, done);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -183,14 +181,14 @@ function download(apiConfig, backupFilePath, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function listDir(apiConfig, dir, iteratorCallback, callback) {
|
||||
function listDir(apiConfig, backupFilePath, iteratorCallback, callback) {
|
||||
getS3Config(apiConfig, function (error, credentials) {
|
||||
if (error) return callback(error);
|
||||
|
||||
var s3 = new AWS.S3(credentials);
|
||||
var listParams = {
|
||||
Bucket: apiConfig.bucket,
|
||||
Prefix: dir
|
||||
Prefix: backupFilePath
|
||||
};
|
||||
|
||||
async.forever(function listAndDownload(foreverCallback) {
|
||||
@@ -309,28 +307,24 @@ function copy(apiConfig, oldFilePath, newFilePath) {
|
||||
};
|
||||
|
||||
// S3 copyObject has a file size limit of 5GB so if we have larger files, we do a multipart copy
|
||||
// Exoscale takes too long to copy 5GB
|
||||
const largeFileLimit = apiConfig.provider === 'exoscale-sos' ? 1024 * 1024 * 1024 : 5 * 1024 * 1024 * 1024;
|
||||
|
||||
if (content.Size < largeFileLimit) {
|
||||
events.emit('progress', `Copying ${relativePath || oldFilePath}`);
|
||||
if (content.Size < 5 * 1024 * 1024 * 1024 || apiConfig.provider === 'digitalocean-spaces') { // DO has not implemented this yet
|
||||
events.emit('progress', `Copying ${relativePath}`);
|
||||
|
||||
copyParams.CopySource = encodeCopySource(apiConfig.bucket, content.Key);
|
||||
s3.copyObject(copyParams, done).on('retry', function (response) {
|
||||
++retryCount;
|
||||
events.emit('progress', `Retrying (${response.retryCount+1}) copy of ${relativePath || oldFilePath}. Status code: ${response.httpResponse.statusCode}`);
|
||||
events.emit('progress', `Retrying (${response.retryCount+1}) copy of ${relativePath}. Status code: ${response.httpResponse.statusCode}`);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
events.emit('progress', `Copying (multipart) ${relativePath || oldFilePath}`);
|
||||
events.emit('progress', `Copying (multipart) ${relativePath}`);
|
||||
|
||||
s3.createMultipartUpload(copyParams, function (error, result) {
|
||||
if (error) return done(error);
|
||||
|
||||
// Exoscale (96M) was suggested by exoscale. 1GB - rather random size for others
|
||||
const chunkSize = apiConfig.provider === 'exoscale-sos' ? 96 * 1024 * 1024 : 1024 * 1024 * 1024;
|
||||
const CHUNK_SIZE = 1024 * 1024 * 1024; // 1GB - rather random size
|
||||
var uploadId = result.UploadId;
|
||||
var uploadedParts = [];
|
||||
var partNumber = 1;
|
||||
@@ -339,7 +333,7 @@ function copy(apiConfig, oldFilePath, newFilePath) {
|
||||
var size = content.Size-1;
|
||||
|
||||
function copyNextChunk() {
|
||||
endBytes = startBytes + chunkSize;
|
||||
endBytes = startBytes + CHUNK_SIZE;
|
||||
if (endBytes > size) endBytes = size;
|
||||
|
||||
var params = {
|
||||
@@ -372,7 +366,7 @@ function copy(apiConfig, oldFilePath, newFilePath) {
|
||||
s3.completeMultipartUpload(params, done);
|
||||
}).on('retry', function (response) {
|
||||
++retryCount;
|
||||
events.emit('progress', `Retrying (${response.retryCount+1}) multipart copy of ${relativePath || oldFilePath}. Status code: ${response.httpResponse.statusCode}`);
|
||||
events.emit('progress', `Retrying (${response.retryCount+1}) multipart copy of ${relativePath}. Status code: ${response.httpResponse.statusCode}`);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -382,11 +376,11 @@ function copy(apiConfig, oldFilePath, newFilePath) {
|
||||
|
||||
var total = 0, concurrency = 4;
|
||||
|
||||
listDir(apiConfig, oldFilePath, function listDirIterator(s3, objects, done) {
|
||||
listDir(apiConfig, oldFilePath, function (s3, objects, done) {
|
||||
total += objects.length;
|
||||
|
||||
if (retryCount === 0) concurrency = Math.min(concurrency + 1, 10); else concurrency = Math.max(concurrency - 1, 5);
|
||||
events.emit('progress', `${retryCount} errors so far. concurrency set to ${concurrency}`);
|
||||
events.emit('progress', `${retryCount} errors. concurrency set to ${concurrency}`);
|
||||
retryCount = 0;
|
||||
|
||||
async.eachLimit(objects, concurrency, copyFile.bind(null, s3), done);
|
||||
@@ -416,11 +410,10 @@ function remove(apiConfig, filename, callback) {
|
||||
}
|
||||
};
|
||||
|
||||
// deleteObjects does not return error if key is not found
|
||||
s3.deleteObjects(deleteParams, function (error) {
|
||||
if (error) debug(`remove: Unable to remove ${deleteParams.Key}. error: ${error.message}`);
|
||||
if (error) debug('remove: Unable to remove %s. Not fatal.', deleteParams.Key, error);
|
||||
|
||||
callback(error);
|
||||
callback(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -432,29 +425,33 @@ function removeDir(apiConfig, pathPrefix) {
|
||||
var events = new EventEmitter();
|
||||
var total = 0;
|
||||
|
||||
listDir(apiConfig, pathPrefix, function listDirIterator(s3, objects, done) {
|
||||
function deleteFiles(s3, contents, iteratorCallback) {
|
||||
var deleteParams = {
|
||||
Bucket: apiConfig.bucket,
|
||||
Delete: {
|
||||
Objects: contents.map(function (c) { return { Key: c.Key }; })
|
||||
}
|
||||
};
|
||||
|
||||
events.emit('progress', `Removing ${contents.length} files from ${contents[0].Key} to ${contents[contents.length-1].Key}`);
|
||||
|
||||
s3.deleteObjects(deleteParams, function (error /*, deleteData */) {
|
||||
if (error) {
|
||||
events.emit('progress', `Unable to remove ${deleteParams.Key} ${error.message}`);
|
||||
return iteratorCallback(error);
|
||||
}
|
||||
|
||||
iteratorCallback();
|
||||
});
|
||||
}
|
||||
|
||||
listDir(apiConfig, pathPrefix, function (s3, objects, done) {
|
||||
total += objects.length;
|
||||
|
||||
const chunkSize = apiConfig.provider !== 'digitalocean-spaces' ? 1000 : 100; // throttle objects in each request
|
||||
var chunks = chunk(objects, chunkSize);
|
||||
const batchSize = apiConfig.provider !== 'digitalocean-spaces' ? 1000 : 100; // throttle objects in each request
|
||||
var chunks = batchSize === 1 ? objects : chunk(objects, batchSize);
|
||||
|
||||
async.eachSeries(chunks, function deleteFiles(contents, iteratorCallback) {
|
||||
var deleteParams = {
|
||||
Bucket: apiConfig.bucket,
|
||||
Delete: {
|
||||
Objects: contents.map(function (c) { return { Key: c.Key }; })
|
||||
}
|
||||
};
|
||||
|
||||
events.emit('progress', `Removing ${contents.length} files from ${contents[0].Key} to ${contents[contents.length-1].Key}`);
|
||||
|
||||
// deleteObjects does not return error if key is not found
|
||||
s3.deleteObjects(deleteParams, function (error /*, deleteData */) {
|
||||
if (error) events.emit('progress', `Unable to remove ${deleteParams.Key} ${error.message}`);
|
||||
|
||||
iteratorCallback(error);
|
||||
});
|
||||
}, done);
|
||||
async.eachSeries(chunks, deleteFiles.bind(null, s3), done);
|
||||
}, function (error) {
|
||||
events.emit('progress', `Removed ${total} files`);
|
||||
|
||||
|
||||
+2
-1
@@ -16,6 +16,7 @@ var appdb = require('./appdb.js'),
|
||||
assert = require('assert'),
|
||||
async = require('async'),
|
||||
child_process = require('child_process'),
|
||||
config = require('./config.js'),
|
||||
debug = require('debug')('box:taskmanager'),
|
||||
locker = require('./locker.js'),
|
||||
sendFailureLogs = require('./logcollector.js').sendFailureLogs,
|
||||
@@ -47,7 +48,7 @@ function resumeTasks(callback) {
|
||||
|
||||
if (app.installationState === appdb.ISTATE_ERROR) return;
|
||||
|
||||
debug('Creating process for %s (%s) with state %s', app.fqdn, app.id, app.installationState);
|
||||
debug('Creating process for %s (%s) with state %s', app.intrinsicFqdn, app.id, app.installationState);
|
||||
restartAppTask(app.id, NOOP_CALLBACK); // restart because the auto-installer could have queued up tasks already
|
||||
});
|
||||
|
||||
|
||||
@@ -1,176 +0,0 @@
|
||||
/* jslint node:true */
|
||||
/* global it:false */
|
||||
/* global describe:false */
|
||||
/* global before:false */
|
||||
/* global after:false */
|
||||
/* global beforeEach:false */
|
||||
|
||||
'use strict';
|
||||
|
||||
var async = require('async'),
|
||||
appstore = require('../appstore.js'),
|
||||
AppstoreError = appstore.AppstoreError,
|
||||
config = require('../config.js'),
|
||||
database = require('../database.js'),
|
||||
expect = require('expect.js'),
|
||||
nock = require('nock'),
|
||||
settings = require('../settings.js');
|
||||
|
||||
const DOMAIN = 'example-appstore-test.com';
|
||||
const APPSTORE_USER_ID = 'appstoreuserid';
|
||||
const APPSTORE_TOKEN = 'appstoretoken';
|
||||
const CLOUDRON_ID = 'cloudronid';
|
||||
const APP_ID = 'appid';
|
||||
const APPSTORE_APP_ID = 'appstoreappid';
|
||||
|
||||
function setup(done) {
|
||||
nock.cleanAll();
|
||||
config.setFqdn(DOMAIN);
|
||||
config.setAdminFqdn('my.' + DOMAIN);
|
||||
|
||||
async.series([
|
||||
database.initialize,
|
||||
database._clear,
|
||||
settings.initialize
|
||||
], done);
|
||||
}
|
||||
|
||||
function cleanup(done) {
|
||||
nock.cleanAll();
|
||||
|
||||
async.series([
|
||||
settings.uninitialize,
|
||||
database._clear,
|
||||
database.uninitialize
|
||||
], done);
|
||||
}
|
||||
|
||||
describe('Appstore', function () {
|
||||
before(setup);
|
||||
after(cleanup);
|
||||
|
||||
beforeEach(nock.cleanAll);
|
||||
|
||||
it('cannot send alive status without appstore config', function (done) {
|
||||
appstore.sendAliveStatus(function (error) {
|
||||
expect(error).to.be.ok();
|
||||
expect(error.reason).to.equal(AppstoreError.BILLING_REQUIRED);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can set appstore config', function (done) {
|
||||
var scope = nock('http://localhost:6060')
|
||||
.post(`/api/v1/users/${APPSTORE_USER_ID}/cloudrons?accessToken=${APPSTORE_TOKEN}`, function () { return true; })
|
||||
.reply(201, { cloudron: { id: CLOUDRON_ID }});
|
||||
|
||||
settings.setAppstoreConfig({ userId: APPSTORE_USER_ID, token: APPSTORE_TOKEN }, function (error) {
|
||||
expect(error).to.not.be.ok();
|
||||
expect(scope.isDone()).to.be.ok();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can send alive status', function (done) {
|
||||
var scope = nock('http://localhost:6060')
|
||||
.post(`/api/v1/users/${APPSTORE_USER_ID}/cloudrons/${CLOUDRON_ID}/alive?accessToken=${APPSTORE_TOKEN}`, function (body) {
|
||||
expect(body.version).to.be.a('string');
|
||||
expect(body.adminFqdn).to.be.a('string');
|
||||
expect(body.provider).to.be.a('string');
|
||||
expect(body.backendSettings).to.be.an('object');
|
||||
expect(body.backendSettings.backupConfig).to.be.an('object');
|
||||
expect(body.backendSettings.backupConfig.provider).to.be.a('string');
|
||||
expect(body.backendSettings.backupConfig.hardlinks).to.be.a('boolean');
|
||||
expect(body.backendSettings.domainConfig).to.be.an('object');
|
||||
expect(body.backendSettings.domainConfig.count).to.be.a('number');
|
||||
expect(body.backendSettings.domainConfig.domains).to.be.an('array');
|
||||
expect(body.backendSettings.mailConfig).to.be.an('object');
|
||||
expect(body.backendSettings.mailConfig.outboundCount).to.be.a('number');
|
||||
expect(body.backendSettings.mailConfig.inboundCount).to.be.a('number');
|
||||
expect(body.backendSettings.mailConfig.catchAllCount).to.be.a('number');
|
||||
expect(body.backendSettings.mailConfig.relayProviders).to.be.an('array');
|
||||
expect(body.backendSettings.appAutoupdatePattern).to.be.a('string');
|
||||
expect(body.backendSettings.boxAutoupdatePattern).to.be.a('string');
|
||||
expect(body.backendSettings.timeZone).to.be.a('string');
|
||||
expect(body.machine).to.be.an('object');
|
||||
expect(body.machine.cpus).to.be.an('array');
|
||||
expect(body.machine.totalmem).to.be.an('number');
|
||||
expect(body.events).to.be.an('object');
|
||||
expect(body.events.lastLogin).to.be.an('number');
|
||||
|
||||
return true;
|
||||
})
|
||||
.reply(201, { cloudron: { id: CLOUDRON_ID }});
|
||||
|
||||
appstore.sendAliveStatus(function (error) {
|
||||
expect(error).to.not.be.ok();
|
||||
expect(scope.isDone()).to.be.ok();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can get account', function (done) {
|
||||
var scope = nock('http://localhost:6060')
|
||||
.get(`/api/v1/users/${APPSTORE_USER_ID}?accessToken=${APPSTORE_TOKEN}`)
|
||||
.reply(200, { profile: { id: APPSTORE_USER_ID }});
|
||||
|
||||
appstore.getAccount(function (error, result) {
|
||||
expect(error).to.not.be.ok();
|
||||
expect(scope.isDone()).to.be.ok();
|
||||
expect(result.id).to.equal(APPSTORE_USER_ID);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can purchase an app', function (done) {
|
||||
var scope = nock('http://localhost:6060')
|
||||
.post(`/api/v1/users/${APPSTORE_USER_ID}/cloudrons/${CLOUDRON_ID}/apps/${APP_ID}?accessToken=${APPSTORE_TOKEN}`, function () { return true; })
|
||||
.reply(201, {});
|
||||
|
||||
appstore.purchase(APP_ID, APPSTORE_APP_ID, function (error) {
|
||||
expect(error).to.not.be.ok();
|
||||
expect(scope.isDone()).to.be.ok();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('unpurchase succeeds if app was never purchased', function (done) {
|
||||
var scope1 = nock('http://localhost:6060')
|
||||
.get(`/api/v1/users/${APPSTORE_USER_ID}/cloudrons/${CLOUDRON_ID}/apps/${APP_ID}?accessToken=${APPSTORE_TOKEN}`)
|
||||
.reply(404, {});
|
||||
|
||||
var scope2 = nock('http://localhost:6060')
|
||||
.delete(`/api/v1/users/${APPSTORE_USER_ID}/cloudrons/${CLOUDRON_ID}/apps/${APP_ID}?accessToken=${APPSTORE_TOKEN}`, function () { return true; })
|
||||
.reply(204, {});
|
||||
|
||||
appstore.unpurchase(APP_ID, APPSTORE_APP_ID, function (error) {
|
||||
expect(error).to.not.be.ok();
|
||||
expect(scope1.isDone()).to.be.ok();
|
||||
expect(scope2.isDone()).to.not.be.ok();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can unpurchase an app', function (done) {
|
||||
var scope1 = nock('http://localhost:6060')
|
||||
.get(`/api/v1/users/${APPSTORE_USER_ID}/cloudrons/${CLOUDRON_ID}/apps/${APP_ID}?accessToken=${APPSTORE_TOKEN}`)
|
||||
.reply(200, {});
|
||||
|
||||
var scope2 = nock('http://localhost:6060')
|
||||
.delete(`/api/v1/users/${APPSTORE_USER_ID}/cloudrons/${CLOUDRON_ID}/apps/${APP_ID}?accessToken=${APPSTORE_TOKEN}`, function () { return true; })
|
||||
.reply(204, {});
|
||||
|
||||
appstore.unpurchase(APP_ID, APPSTORE_APP_ID, function (error) {
|
||||
expect(error).to.not.be.ok();
|
||||
expect(scope1.isDone()).to.be.ok();
|
||||
expect(scope2.isDone()).to.be.ok();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -68,6 +68,7 @@ var APP = {
|
||||
runState: null,
|
||||
location: 'applocation',
|
||||
domain: DOMAIN_0.domain,
|
||||
intrinsicFqdn: DOMAIN_0.domain + '.' + 'applocation',
|
||||
fqdn: DOMAIN_0.domain + '.' + 'applocation',
|
||||
manifest: MANIFEST,
|
||||
containerId: null,
|
||||
|
||||
@@ -210,6 +210,7 @@ describe('database', function () {
|
||||
oldConfig: null,
|
||||
newConfig: null,
|
||||
memoryLimit: 4294967296,
|
||||
altDomain: null,
|
||||
xFrameOptions: 'DENY',
|
||||
sso: true,
|
||||
debugMode: null,
|
||||
@@ -715,6 +716,7 @@ describe('database', function () {
|
||||
oldConfig: null,
|
||||
updateConfig: null,
|
||||
memoryLimit: 4294967296,
|
||||
altDomain: null,
|
||||
xFrameOptions: 'DENY',
|
||||
sso: true,
|
||||
debugMode: null,
|
||||
@@ -741,6 +743,7 @@ describe('database', function () {
|
||||
oldConfig: null,
|
||||
updateConfig: null,
|
||||
memoryLimit: 0,
|
||||
altDomain: null,
|
||||
xFrameOptions: 'SAMEORIGIN',
|
||||
sso: true,
|
||||
debugMode: null,
|
||||
@@ -1362,7 +1365,7 @@ describe('database', function () {
|
||||
});
|
||||
|
||||
it('getAllPaged succeeds', function (done) {
|
||||
eventlogdb.getAllPaged([], null, 1, 1, function (error, results) {
|
||||
eventlogdb.getAllPaged(null, null, 1, 1, function (error, results) {
|
||||
expect(error).to.be(null);
|
||||
expect(results).to.be.an(Array);
|
||||
expect(results.length).to.be(1);
|
||||
@@ -1377,7 +1380,7 @@ describe('database', function () {
|
||||
});
|
||||
|
||||
it('getAllPaged succeeds with source search', function (done) {
|
||||
eventlogdb.getAllPaged([], '1.2.3.4', 1, 1, function (error, results) {
|
||||
eventlogdb.getAllPaged(null, '1.2.3.4', 1, 1, function (error, results) {
|
||||
expect(error).to.be(null);
|
||||
expect(results).to.be.an(Array);
|
||||
expect(results.length).to.be(1);
|
||||
@@ -1392,7 +1395,7 @@ describe('database', function () {
|
||||
});
|
||||
|
||||
it('getAllPaged succeeds with data search', function (done) {
|
||||
eventlogdb.getAllPaged([], 'thatapp', 1, 1, function (error, results) {
|
||||
eventlogdb.getAllPaged(null, 'thatapp', 1, 1, function (error, results) {
|
||||
expect(error).to.be(null);
|
||||
expect(results).to.be.an(Array);
|
||||
expect(results.length).to.be(1);
|
||||
@@ -1417,7 +1420,7 @@ describe('database', function () {
|
||||
eventlogdb.delByCreationTime(new Date(), actions, function (error) {
|
||||
expect(error).to.be(null);
|
||||
|
||||
eventlogdb.getAllPaged([], null, 1, 100, function (error, results) {
|
||||
eventlogdb.getAllPaged(null, null, 1, 100, function (error, results) {
|
||||
expect(error).to.be(null);
|
||||
expect(results.length).to.be(2);
|
||||
results = results.sort(function (x, y) { return x.action > y.action; }); // because equal timestamp gives random ordering
|
||||
@@ -1590,10 +1593,7 @@ describe('database', function () {
|
||||
|
||||
describe('mailboxes', function () {
|
||||
before(function (done) {
|
||||
async.series([
|
||||
domaindb.add.bind(null, DOMAIN_0.domain, { zoneName: DOMAIN_0.zoneName, provider: DOMAIN_0.provider, config: DOMAIN_0.config, tlsConfig: DOMAIN_0.tlsConfig }),
|
||||
maildb.add.bind(null, DOMAIN_0.domain)
|
||||
], done);
|
||||
domaindb.add(DOMAIN_0.domain, { zoneName: DOMAIN_0.zoneName, provider: DOMAIN_0.provider, config: DOMAIN_0.config, tlsConfig: DOMAIN_0.tlsConfig }, done);
|
||||
});
|
||||
|
||||
after(function (done) {
|
||||
|
||||
+18
-18
@@ -55,7 +55,7 @@ describe('dns provider', function () {
|
||||
});
|
||||
|
||||
it('upsert succeeds', function (done) {
|
||||
domains.upsertDnsRecords('test', DOMAIN_0.domain, 'A', [ '1.2.3.4' ], function (error, result) {
|
||||
domains.upsertDNSRecords('test', DOMAIN_0.domain, 'A', [ '1.2.3.4' ], function (error, result) {
|
||||
expect(error).to.eql(null);
|
||||
expect(result).to.eql('noop-record-id');
|
||||
|
||||
@@ -64,7 +64,7 @@ describe('dns provider', function () {
|
||||
});
|
||||
|
||||
it('get succeeds', function (done) {
|
||||
domains.getDnsRecords('test', DOMAIN_0.domain, 'A', function (error, result) {
|
||||
domains.getDNSRecords('test', DOMAIN_0.domain, 'A', function (error, result) {
|
||||
expect(error).to.eql(null);
|
||||
expect(result).to.be.an(Array);
|
||||
expect(result.length).to.eql(0);
|
||||
@@ -74,7 +74,7 @@ describe('dns provider', function () {
|
||||
});
|
||||
|
||||
it('del succeeds', function (done) {
|
||||
domains.removeDnsRecords('test', DOMAIN_0.domain, 'A', [ '1.2.3.4' ], function (error) {
|
||||
domains.removeDNSRecords('test', DOMAIN_0.domain, 'A', [ '1.2.3.4' ], function (error) {
|
||||
expect(error).to.eql(null);
|
||||
|
||||
done();
|
||||
@@ -115,7 +115,7 @@ describe('dns provider', function () {
|
||||
.post('/v2/domains/' + DOMAIN_0.zoneName + '/records')
|
||||
.reply(201, { domain_record: DOMAIN_RECORD_0 });
|
||||
|
||||
domains.upsertDnsRecords('test', DOMAIN_0.domain, 'A', [ '1.2.3.4' ], function (error, result) {
|
||||
domains.upsertDNSRecords('test', DOMAIN_0.domain, 'A', [ '1.2.3.4' ], function (error, result) {
|
||||
expect(error).to.eql(null);
|
||||
expect(result).to.eql('3352892');
|
||||
expect(req1.isDone()).to.be.ok();
|
||||
@@ -165,7 +165,7 @@ describe('dns provider', function () {
|
||||
.put('/v2/domains/' + DOMAIN_0.zoneName + '/records/' + DOMAIN_RECORD_1.id)
|
||||
.reply(200, { domain_record: DOMAIN_RECORD_1_NEW });
|
||||
|
||||
domains.upsertDnsRecords('test', DOMAIN_0.domain, 'A', [ DOMAIN_RECORD_1_NEW.data ], function (error, result) {
|
||||
domains.upsertDNSRecords('test', DOMAIN_0.domain, 'A', [ DOMAIN_RECORD_1_NEW.data ], function (error, result) {
|
||||
expect(error).to.eql(null);
|
||||
expect(result).to.eql('3352893');
|
||||
expect(req1.isDone()).to.be.ok();
|
||||
@@ -251,7 +251,7 @@ describe('dns provider', function () {
|
||||
.post('/v2/domains/' + DOMAIN_0.zoneName + '/records')
|
||||
.reply(201, { domain_record: DOMAIN_RECORD_2_NEW });
|
||||
|
||||
domains.upsertDnsRecords('', DOMAIN_0.domain, 'TXT', [ DOMAIN_RECORD_2_NEW.data, DOMAIN_RECORD_1_NEW.data, DOMAIN_RECORD_3_NEW.data ], function (error, result) {
|
||||
domains.upsertDNSRecords('', DOMAIN_0.domain, 'TXT', [ DOMAIN_RECORD_2_NEW.data, DOMAIN_RECORD_1_NEW.data, DOMAIN_RECORD_3_NEW.data ], function (error, result) {
|
||||
expect(error).to.eql(null);
|
||||
expect(result).to.eql('3352893');
|
||||
expect(req1.isDone()).to.be.ok();
|
||||
@@ -290,7 +290,7 @@ describe('dns provider', function () {
|
||||
.get('/v2/domains/' + DOMAIN_0.zoneName + '/records')
|
||||
.reply(200, { domain_records: [ DOMAIN_RECORD_0, DOMAIN_RECORD_1 ] });
|
||||
|
||||
domains.getDnsRecords('test', DOMAIN_0.domain, 'A', function (error, result) {
|
||||
domains.getDNSRecords('test', DOMAIN_0.domain, 'A', function (error, result) {
|
||||
expect(error).to.eql(null);
|
||||
expect(result).to.be.an(Array);
|
||||
expect(result.length).to.eql(1);
|
||||
@@ -331,7 +331,7 @@ describe('dns provider', function () {
|
||||
.delete('/v2/domains/' + DOMAIN_0.zoneName + '/records/' + DOMAIN_RECORD_1.id)
|
||||
.reply(204, {});
|
||||
|
||||
domains.removeDnsRecords('test', DOMAIN_0.domain, 'A', ['1.2.3.4'], function (error) {
|
||||
domains.removeDNSRecords('test', DOMAIN_0.domain, 'A', ['1.2.3.4'], function (error) {
|
||||
expect(error).to.eql(null);
|
||||
expect(req1.isDone()).to.be.ok();
|
||||
expect(req2.isDone()).to.be.ok();
|
||||
@@ -437,7 +437,7 @@ describe('dns provider', function () {
|
||||
}
|
||||
}]);
|
||||
|
||||
domains.upsertDnsRecords('test', DOMAIN_0.domain, 'A', [ '1.2.3.4' ], function (error, result) {
|
||||
domains.upsertDNSRecords('test', DOMAIN_0.domain, 'A', [ '1.2.3.4' ], function (error, result) {
|
||||
expect(error).to.eql(null);
|
||||
expect(result).to.eql('/change/C2QLKQIWEI0BZF');
|
||||
expect(awsAnswerQueue.length).to.eql(0);
|
||||
@@ -456,7 +456,7 @@ describe('dns provider', function () {
|
||||
}
|
||||
}]);
|
||||
|
||||
domains.upsertDnsRecords('test', DOMAIN_0.domain, 'A', [ '1.2.3.4' ], function (error, result) {
|
||||
domains.upsertDNSRecords('test', DOMAIN_0.domain, 'A', [ '1.2.3.4' ], function (error, result) {
|
||||
expect(error).to.eql(null);
|
||||
expect(result).to.eql('/change/C2QLKQIWEI0BZF');
|
||||
expect(awsAnswerQueue.length).to.eql(0);
|
||||
@@ -475,7 +475,7 @@ describe('dns provider', function () {
|
||||
}
|
||||
}]);
|
||||
|
||||
domains.upsertDnsRecords('', DOMAIN_0.domain, 'TXT', [ 'first', 'second', 'third' ], function (error, result) {
|
||||
domains.upsertDNSRecords('', DOMAIN_0.domain, 'TXT', [ 'first', 'second', 'third' ], function (error, result) {
|
||||
expect(error).to.eql(null);
|
||||
expect(result).to.eql('/change/C2QLKQIWEI0BZF');
|
||||
expect(awsAnswerQueue.length).to.eql(0);
|
||||
@@ -496,7 +496,7 @@ describe('dns provider', function () {
|
||||
}]
|
||||
}]);
|
||||
|
||||
domains.getDnsRecords('test', DOMAIN_0.domain, 'A', function (error, result) {
|
||||
domains.getDNSRecords('test', DOMAIN_0.domain, 'A', function (error, result) {
|
||||
expect(error).to.eql(null);
|
||||
expect(result).to.be.an(Array);
|
||||
expect(result.length).to.eql(1);
|
||||
@@ -517,7 +517,7 @@ describe('dns provider', function () {
|
||||
}
|
||||
}]);
|
||||
|
||||
domains.removeDnsRecords('test', DOMAIN_0.domain, 'A', ['1.2.3.4'], function (error) {
|
||||
domains.removeDNSRecords('test', DOMAIN_0.domain, 'A', ['1.2.3.4'], function (error) {
|
||||
expect(error).to.eql(null);
|
||||
expect(awsAnswerQueue.length).to.eql(0);
|
||||
|
||||
@@ -588,7 +588,7 @@ describe('dns provider', function () {
|
||||
zoneQueue.push([null, [ ]]); // getRecords
|
||||
zoneQueue.push([null, {id: '1'}]);
|
||||
|
||||
domains.upsertDnsRecords('test', DOMAIN_0.domain, 'A', [ '1.2.3.4' ], function (error, result) {
|
||||
domains.upsertDNSRecords('test', DOMAIN_0.domain, 'A', [ '1.2.3.4' ], function (error, result) {
|
||||
expect(error).to.eql(null);
|
||||
expect(result).to.eql('1');
|
||||
expect(zoneQueue.length).to.eql(0);
|
||||
@@ -602,7 +602,7 @@ describe('dns provider', function () {
|
||||
zoneQueue.push([null, [GCDNS().zone('test').record('A', {'name': 'test', data:['5.6.7.8'], ttl: 1})]]);
|
||||
zoneQueue.push([null, {id: '2'}]);
|
||||
|
||||
domains.upsertDnsRecords('test', DOMAIN_0.domain, 'A', [ '1.2.3.4' ], function (error, result) {
|
||||
domains.upsertDNSRecords('test', DOMAIN_0.domain, 'A', [ '1.2.3.4' ], function (error, result) {
|
||||
expect(error).to.eql(null);
|
||||
expect(result).to.eql('2');
|
||||
expect(zoneQueue.length).to.eql(0);
|
||||
@@ -616,7 +616,7 @@ describe('dns provider', function () {
|
||||
zoneQueue.push([null, [ ]]); // getRecords
|
||||
zoneQueue.push([null, {id: '3'}]);
|
||||
|
||||
domains.upsertDnsRecords('', DOMAIN_0.domain, 'TXT', [ 'first', 'second', 'third' ], function (error, result) {
|
||||
domains.upsertDNSRecords('', DOMAIN_0.domain, 'TXT', [ 'first', 'second', 'third' ], function (error, result) {
|
||||
expect(error).to.eql(null);
|
||||
expect(result).to.eql('3');
|
||||
expect(zoneQueue.length).to.eql(0);
|
||||
@@ -629,7 +629,7 @@ describe('dns provider', function () {
|
||||
zoneQueue.push([null, HOSTED_ZONES]);
|
||||
zoneQueue.push([null, [GCDNS().zone('test').record('A', {'name': 'test', data:['1.2.3.4', '5.6.7.8'], ttl: 1})]]);
|
||||
|
||||
domains.getDnsRecords('test', DOMAIN_0.domain, 'A', function (error, result) {
|
||||
domains.getDNSRecords('test', DOMAIN_0.domain, 'A', function (error, result) {
|
||||
expect(error).to.eql(null);
|
||||
expect(result).to.be.an(Array);
|
||||
expect(result.length).to.eql(2);
|
||||
@@ -645,7 +645,7 @@ describe('dns provider', function () {
|
||||
zoneQueue.push([null, [GCDNS().zone('test').record('A', {'name': 'test', data:['5.6.7.8'], ttl: 1})]]);
|
||||
zoneQueue.push([null, {id: '5'}]);
|
||||
|
||||
domains.removeDnsRecords('test', DOMAIN_0.domain, 'A', ['1.2.3.4'], function (error) {
|
||||
domains.removeDNSRecords('test', DOMAIN_0.domain, 'A', ['1.2.3.4'], function (error) {
|
||||
expect(error).to.eql(null);
|
||||
expect(zoneQueue.length).to.eql(0);
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ describe('Eventlog', function () {
|
||||
});
|
||||
|
||||
it('getAllPaged succeeds', function (done) {
|
||||
eventlog.getAllPaged([], null, 1, 1, function (error, results) {
|
||||
eventlog.getAllPaged(null, null, 1, 1, function (error, results) {
|
||||
expect(error).to.be(null);
|
||||
expect(results).to.be.an(Array);
|
||||
expect(results.length).to.be(1);
|
||||
|
||||
@@ -58,12 +58,6 @@ describe('Certificates', function () {
|
||||
var validCert2 = '-----BEGIN CERTIFICATE-----\nMIIBwjCCAWwCCQCZjm6jL50XfTANBgkqhkiG9w0BAQsFADBoMQswCQYDVQQGEwJE\nRTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xEDAOBgNVBAoMB05l\nYnVsb24xDDAKBgNVBAsMA0NUTzEXMBUGA1UEAwwOYmF6LmZvb2Jhci5jb20wHhcN\nMTYxMTA4MDgyMDE1WhcNMjAxMTA3MDgyMDE1WjBoMQswCQYDVQQGEwJERTEPMA0G\nA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xEDAOBgNVBAoMB05lYnVsb24x\nDDAKBgNVBAsMA0NUTzEXMBUGA1UEAwwOYmF6LmZvb2Jhci5jb20wXDANBgkqhkiG\n9w0BAQEFAANLADBIAkEAtKoyTPrf2DjKbnW7Xr1HbRvV+quHTcGmUq5anDI7G4w/\nabqDXGYyakHHlPyZxYp7FWQxCm83rHUuDT1LiLIBZQIDAQABMA0GCSqGSIb3DQEB\nCwUAA0EAVaD2Q6bF9hcUUBev5NyjaMdDYURuWfjuwWUkb8W50O2ed3O+MATKrDdS\nyVaBy8W02KJ4Y1ym4je/MF8nilPurA==\n-----END CERTIFICATE-----';
|
||||
var validKey2 = '-----BEGIN RSA PRIVATE KEY-----\nMIIBPQIBAAJBALSqMkz639g4ym51u169R20b1fqrh03BplKuWpwyOxuMP2m6g1xm\nMmpBx5T8mcWKexVkMQpvN6x1Lg09S4iyAWUCAwEAAQJBAJXu7YHPbjfuoalcUZzF\nbuKRCFtZQRf5z0Os6QvZ8A3iR0SzYJzx+c2ibp7WdifMXp3XaKm4tHSOfumrjUIq\nt10CIQDrs9Xo7bq0zuNjUV5IshNfaiYKZRfQciRVW2O8xBP9VwIhAMQ5CCEDZy+u\nsaF9RtmB0bjbe6XonBlAzoflfH/MAwWjAiEA50hL+ohr0MfCMM7DKaozgEj0kvan\n645VQLywnaX5x3kCIQDCwjinS9FnKmV0e/uOd6PJb0/S5IXLKt/TUpu33K5DMQIh\nAM9peu3B5t9pO59MmeUGZwI+bEJfEb+h03WTptBxS3pO\n-----END RSA PRIVATE KEY-----';
|
||||
|
||||
// cp /etc/ssl/openssl.cnf /tmp/openssl.cnf
|
||||
// echo -e "[SAN]\nsubjectAltName=DNS:amazing.com,DNS:*.amazing.com\n" >> /tmp/openssl.cnf
|
||||
// openssl req -x509 -newkey rsa:2048 -keyout amazing.key -out amazing.crt -days 3650 -subj /CN=*.amazing.com -nodes -extensions SAN -config /tmp/openssl.cnf
|
||||
var validCert3 = '-----BEGIN CERTIFICATE-----\nMIIC3DCCAcSgAwIBAgIJALcStAD5sDWEMA0GCSqGSIb3DQEBCwUAMBgxFjAUBgNV\nBAMMDSouYW1hemluZy5jb20wHhcNMTgwMjA5MjIxMzM2WhcNMjgwMjA3MjIxMzM2\nWjAYMRYwFAYDVQQDDA0qLmFtYXppbmcuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOC\nAQ8AMIIBCgKCAQEAvp8dk13u4vmAfKfRNOO8+rVQ8q+vyR8scc9Euj0pTodLBflM\n2K6Zk0isirRzCL/jd4n1A6QrPeJ+r2J4xtHk2j+pavt8Sa2Go2MzpAe3OTuIqYJf\nUt7Im3f2Lb67itTPrpA2TR3A/dDFlazju+eBd3t3496Do8aBPpXAdOabfPsrv3nE\nx97vrr4tzeK3kG9u7GYuod5gyiwF2t5wSeMWbFk2oqkOCtHRXE77JDKVxIGiepnU\nTnkW9b7jIkiBQ1x0xHG4soewV2ymGHS2XrUHZ45FFMG7yVYpytKT9Iz9ty/z5VcL\nZ6NzgU/pKfQaIe8MpoDpVf5UNeB2DOAAEoJKKwIDAQABoykwJzAlBgNVHREEHjAc\nggthbWF6aW5nLmNvbYINKi5hbWF6aW5nLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEA\nMULk6B9XrVPAole8W66o3WUUOrC7NVjbwZjr+Kp5oQTSo84qacaZS2C3ox/j/TZY\nUuNvoE6gIOHi+inN+G4P76K7NEvm8+Y1CeAyaPq01H4Qy2lk9F5wFMtPqvBZnF9C\nx1MvV30FruHXe5pDfnG1npKECpn2SgE3k6FRHM55u8rTMEm/O4TtsDq+fPqUvyWa\nZuRjPv4qVGGkoPyxA6iffxclpOAXs3JUgLcYoM2vxKC0YSOjHEa0p4uffX063Jgg\nybuy3OKvm+8L6moycX7J+LZK81dDTFDtF7PwrnRbpS4re0i/LSk23jDQvDOLnrAa\nSawRR8+1QHTENBo7dnP+NA==\n-----END CERTIFICATE-----';
|
||||
var validKey3 = '-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC+nx2TXe7i+YB8\np9E047z6tVDyr6/JHyxxz0S6PSlOh0sF+UzYrpmTSKyKtHMIv+N3ifUDpCs94n6v\nYnjG0eTaP6lq+3xJrYajYzOkB7c5O4ipgl9S3sibd/YtvruK1M+ukDZNHcD90MWV\nrOO754F3e3fj3oOjxoE+lcB05pt8+yu/ecTH3u+uvi3N4reQb27sZi6h3mDKLAXa\n3nBJ4xZsWTaiqQ4K0dFcTvskMpXEgaJ6mdROeRb1vuMiSIFDXHTEcbiyh7BXbKYY\ndLZetQdnjkUUwbvJVinK0pP0jP23L/PlVwtno3OBT+kp9Boh7wymgOlV/lQ14HYM\n4AASgkorAgMBAAECggEAdVSVLMcNqlGuv4vAHtDq2lpOaAKxrZbtkWPlxsisqzRl\nfljT7y+RQfHimkG16LXL+iFFWadsIlxOY/+1nZNGTPwQeNQwzVzs2ZbPC3DgW28E\nkGm56NVOHzu4oLGc2DhjWOxVMCRXTSN66sUPK/K0YunxgqXM2zrtBKvCWXI0VLlo\nN/UWAwHf4i0GWRl8u8PvxgMXlSW9p9l6gSsivWRMag9ADwRQ/NSKrRYkiOoRe3vz\nLxXARBvzeZXvOPVLGVRX4SIR7OmS8cC6Ol/rp1/ZFFID7aN+wdzphPSL1UNUriw4\nDv1mxz73SNakgeYSFBoWRS5BsJI01JoCoILsnhVCiQKBgQDyW+k5+j4K17fzwsmi\nyxZ0Nz/ncpkqxVrWYZM3pn7OVkb2NDArimEk53kmJ0hrT84kKJUYDx55R2TpnzpV\nMLmjxgs9TUrzZzsL/DP2ppkfE3OrPS+06OGa5GbURxD6KPvqDtOmU3oFyJ3f4YJR\nVK7RW+zO4sXEpHIxwdBXbYov1QKBgQDJWbt+W5M0sA2D5LrUBNMTvMdNnKH0syc2\nZlcIOdj6HuUIveYpBRq64Jn9VJpXMxQanwE+IUjCpPTa8wF0OA6MZPy6cfovqb8a\ni1/M/lvCoYVS3KHLcTOvTGD3xej0EUj13xWGNu8y3i7Z9/Bl21hEyjd0q0I5OqJx\no9Qa5TGR/wKBgBPfkYpdiMTe14i3ik09FgRFm4nhDcpCEKbPrYC8uF03Ge6KbQDF\nAh5ClN6aDggurRqt8Tvd0YPkZNP7aI8fxbk2PimystiuuFrNPX2WP6warjt2cvkE\nt6s522zAvxWkUrPor1ZONg1PXBLFrSf6J7OnNA3q7oina23FFM52fwRZAoGAZ7l7\nFffU2IKNI9HT0N7/YZ6RSVEUOXuFCsgjs5AhT5BUynERPTZs87I6gb9wltUwWRpq\nSHhbBDJ4FMa0jAtIq1hmvSF0EdOvJ9x+qJqr6JLOnMYd7zDMwFRna5yfigPRgx+9\n9dsc1CaTGiRYyg/5484MTWTgA51KC6Kq5IQHSj8CgYBr9rWgqM8hVCKSt1cMguQV\nTPaV97+u3kV2jFd/aVgDtCDIVvp5TPuqfskE1v3MsSjJ8hfHdYvyxZB8h8T4LlTD\n2HdxwCjVh2qirAvkar2b1mfA6R8msmVaIxBu4MqDcIPqR823klF7A8jSD3MGzYcU\nbnnxMdwgWQkmx0/6/90ZCg==\n-----END PRIVATE KEY-----\n';
|
||||
|
||||
it('does not allow empty string for cert', function () {
|
||||
expect(reverseProxy.validateCertificate('foobar.com', '', 'key')).to.be.an(Error);
|
||||
});
|
||||
@@ -108,11 +102,6 @@ describe('Certificates', function () {
|
||||
it('does not allow invalid cert/key tuple', function () {
|
||||
expect(reverseProxy.validateCertificate('foobar.com', validCert0, validKey1)).to.be.an(Error);
|
||||
});
|
||||
|
||||
it('picks certificate in SAN', function () {
|
||||
expect(reverseProxy.validateCertificate('amazing.com', validCert3, validKey3)).to.be(null);
|
||||
expect(reverseProxy.validateCertificate('subdomain.amazing.com', validCert3, validKey3)).to.be(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getApi - caas', function () {
|
||||
@@ -144,6 +133,24 @@ describe('Certificates', function () {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('returns prod-acme with altDomain in prod cloudron', function (done) {
|
||||
reverseProxy._getApi({ domain: DOMAIN_0.domain, altDomain: 'foo.something.com' }, function (error, api, options) {
|
||||
expect(error).to.be(null);
|
||||
expect(api._name).to.be('acme');
|
||||
expect(options.prod).to.be(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('returns prod acme with altDomain in dev cloudron', function (done) {
|
||||
reverseProxy._getApi({ domain: DOMAIN_0.domain, altDomain: 'foo.something.com' }, function (error, api, options) {
|
||||
expect(error).to.be(null);
|
||||
expect(api._name).to.be('acme');
|
||||
expect(options.prod).to.be(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getApi - letsencrypt-prod', function () {
|
||||
@@ -167,6 +174,15 @@ describe('Certificates', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns prod acme with altDomain in prod cloudron', function (done) {
|
||||
reverseProxy._getApi({ domain: DOMAIN_0.domain, altDomain: 'foo.bar.com' }, function (error, api, options) {
|
||||
expect(error).to.be(null);
|
||||
expect(api._name).to.be('acme');
|
||||
expect(options.prod).to.be(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('returns prod acme in dev cloudron', function (done) {
|
||||
reverseProxy._getApi({ domain: DOMAIN_0.domain }, function (error, api, options) {
|
||||
expect(error).to.be(null);
|
||||
@@ -206,5 +222,14 @@ describe('Certificates', function () {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('returns staging acme with altDomain in prod cloudron', function (done) {
|
||||
reverseProxy._getApi({ domain: DOMAIN_0.domain, altDomain: 'foo.bar.com' }, function (error, api, options) {
|
||||
expect(error).to.be(null);
|
||||
expect(api._name).to.be('acme');
|
||||
expect(options.prod).to.be(false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
var async = require('async'),
|
||||
config = require('../config.js'),
|
||||
constants = require('../constants.js'),
|
||||
database = require('../database.js'),
|
||||
expect = require('expect.js'),
|
||||
MockS3 = require('mock-aws-s3'),
|
||||
@@ -68,16 +67,8 @@ describe('Settings', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('can get default app_autoupdate_pattern', function (done) {
|
||||
settings.getAppAutoupdatePattern(function (error, pattern) {
|
||||
expect(error).to.be(null);
|
||||
expect(pattern).to.be('00 30 1,3,5,23 * * *');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can get default box_autoupdate_pattern', function (done) {
|
||||
settings.getBoxAutoupdatePattern(function (error, pattern) {
|
||||
it('can get default autoupdate_pattern', function (done) {
|
||||
settings.getAutoupdatePattern(function (error, pattern) {
|
||||
expect(error).to.be(null);
|
||||
expect(pattern).to.be('00 00 1,3,5,23 * * *');
|
||||
done();
|
||||
@@ -139,8 +130,7 @@ describe('Settings', function () {
|
||||
settings.getAll(function (error, allSettings) {
|
||||
expect(error).to.be(null);
|
||||
expect(allSettings[settings.TIME_ZONE_KEY]).to.be.a('string');
|
||||
expect(allSettings[settings.APP_AUTOUPDATE_PATTERN_KEY]).to.be.a('string');
|
||||
expect(allSettings[settings.BOX_AUTOUPDATE_PATTERN_KEY]).to.be.a('string');
|
||||
expect(allSettings[settings.AUTOUPDATE_PATTERN_KEY]).to.be.a('string');
|
||||
expect(allSettings[settings.CLOUDRON_NAME_KEY]).to.be.a('string');
|
||||
done();
|
||||
});
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ mkdir -p boxdata/appicons boxdata/mail boxdata/certs boxdata/mail/dkim/localhost
|
||||
mkdir -p platformdata/addons/mail platformdata/nginx/cert platformdata/nginx/applications platformdata/collectd/collectd.conf.d platformdata/addons platformdata/logrotate.d platformdata/backup
|
||||
|
||||
# put cert
|
||||
openssl req -x509 -newkey rsa:2048 -keyout platformdata/nginx/cert/host.key -out platformdata/nginx/cert/host.cert -days 3650 -subj '/CN=localhost' -nodes -config <(cat /etc/ssl/openssl.cnf <(printf "\n[SAN]\nsubjectAltName=DNS:*.localhost"))
|
||||
openssl req -x509 -newkey rsa:2048 -keyout platformdata/nginx/cert/host.key -out platformdata/nginx/cert/host.cert -days 3650 -subj '/CN=localhost' -nodes
|
||||
|
||||
# create docker network (while the infra code does this, most tests skip infra setup)
|
||||
docker network create --subnet=172.18.0.0/16 cloudron || true
|
||||
|
||||
@@ -78,7 +78,7 @@ describe('updatechecker - box - manual (email)', function () {
|
||||
domains.add.bind(null, DOMAIN_0.domain, DOMAIN_0.zoneName, DOMAIN_0.provider, DOMAIN_0.config, DOMAIN_0.fallbackCertificate, DOMAIN_0.tlsConfig),
|
||||
mail.add.bind(null, DOMAIN_0.domain),
|
||||
user.createOwner.bind(null, USER_0.username, USER_0.password, USER_0.email, USER_0.displayName, AUDIT_SOURCE),
|
||||
settings.setBoxAutoupdatePattern.bind(null, constants.AUTOUPDATE_PATTERN_NEVER),
|
||||
settings.setAutoupdatePattern.bind(null, constants.AUTOUPDATE_PATTERN_NEVER),
|
||||
settingsdb.set.bind(null, settings.APPSTORE_CONFIG_KEY, JSON.stringify({ userId: 'uid', cloudronId: 'cid', token: 'token' })),
|
||||
mailer._clearMailQueue
|
||||
], done);
|
||||
@@ -296,7 +296,7 @@ describe('updatechecker - app - manual (email)', function () {
|
||||
mailer._clearMailQueue,
|
||||
appdb.add.bind(null, APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, APP_0.portBindings, APP_0),
|
||||
user.createOwner.bind(null, USER_0.username, USER_0.password, USER_0.email, USER_0.displayName, AUDIT_SOURCE),
|
||||
settings.setAppAutoupdatePattern.bind(null, constants.AUTOUPDATE_PATTERN_NEVER),
|
||||
settings.setAutoupdatePattern.bind(null, constants.AUTOUPDATE_PATTERN_NEVER),
|
||||
settingsdb.set.bind(null, settings.APPSTORE_CONFIG_KEY, JSON.stringify({ userId: 'uid', cloudronId: 'cid', token: 'token' }))
|
||||
], done);
|
||||
});
|
||||
@@ -412,7 +412,6 @@ describe('updatechecker - app - automatic (no email)', function () {
|
||||
mailer._clearMailQueue,
|
||||
appdb.add.bind(null, APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, APP_0.portBindings, APP_0),
|
||||
user.createOwner.bind(null, USER_0.username, USER_0.password, USER_0.email, USER_0.displayName, AUDIT_SOURCE),
|
||||
settings.setAppAutoupdatePattern.bind(null, '00 00 1,3,5,23 * * *'),
|
||||
settingsdb.set.bind(null, settings.APPSTORE_CONFIG_KEY, JSON.stringify({ userId: 'uid', cloudronId: 'cid', token: 'token' }))
|
||||
], done);
|
||||
});
|
||||
@@ -478,7 +477,6 @@ describe('updatechecker - app - automatic free (email)', function () {
|
||||
mailer._clearMailQueue,
|
||||
appdb.add.bind(null, APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, APP_0.portBindings, APP_0),
|
||||
user.createOwner.bind(null, USER_0.username, USER_0.password, USER_0.email, USER_0.displayName, AUDIT_SOURCE),
|
||||
settings.setAppAutoupdatePattern.bind(null, '00 00 1,3,5,23 * * *'),
|
||||
settingsdb.set.bind(null, settings.APPSTORE_CONFIG_KEY, JSON.stringify({ userId: 'uid', cloudronId: 'cid', token: 'token' }))
|
||||
], done);
|
||||
});
|
||||
|
||||
@@ -112,7 +112,7 @@ function checkAppUpdates(callback) {
|
||||
}
|
||||
|
||||
// only send notifications if update pattern is 'never'
|
||||
settings.getAppAutoupdatePattern(function (error, result) {
|
||||
settings.getAutoupdatePattern(function (error, result) {
|
||||
if (error) {
|
||||
debug(error);
|
||||
} else if (result === constants.AUTOUPDATE_PATTERN_NEVER) {
|
||||
@@ -168,7 +168,7 @@ function checkBoxUpdates(callback) {
|
||||
}
|
||||
|
||||
// only send notifications if update pattern is 'never'
|
||||
settings.getBoxAutoupdatePattern(function (error, result) {
|
||||
settings.getAutoupdatePattern(function (error, result) {
|
||||
if (error) debug(error);
|
||||
else if (result === constants.AUTOUPDATE_PATTERN_NEVER) mailer.boxUpdateAvailable(true /* hasSubscription */, updateInfo.version, updateInfo.changelog);
|
||||
|
||||
|
||||
+6
-16
@@ -3,8 +3,6 @@
|
||||
exports = module.exports = {
|
||||
UserError: UserError,
|
||||
|
||||
removePrivateFields: removePrivateFields,
|
||||
|
||||
list: listUsers,
|
||||
create: createUser,
|
||||
count: count,
|
||||
@@ -120,10 +118,6 @@ function validateDisplayName(name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function removePrivateFields(user) {
|
||||
return _.pick(user, 'id', 'username', 'email', 'fallbackEmail', 'displayName', 'groupIds', 'admin');
|
||||
}
|
||||
|
||||
function createUser(username, password, email, displayName, auditSource, options, callback) {
|
||||
assert(username === null || typeof username === 'string');
|
||||
assert.strictEqual(typeof password, 'string');
|
||||
@@ -184,7 +178,7 @@ function createUser(username, password, email, displayName, auditSource, options
|
||||
|
||||
callback(null, user);
|
||||
|
||||
eventlog.add(eventlog.ACTION_USER_ADD, auditSource, { userId: user.id, email: user.email, user: removePrivateFields(user) });
|
||||
eventlog.add(eventlog.ACTION_USER_ADD, auditSource, { userId: user.id, email: user.email });
|
||||
|
||||
if (!owner) mailer.userAdded(user, sendInvite);
|
||||
if (sendInvite) mailer.sendInvite(user, invitor);
|
||||
@@ -198,7 +192,7 @@ function verifyGhost(username, password) {
|
||||
assert.strictEqual(typeof username, 'string');
|
||||
assert.strictEqual(typeof password, 'string');
|
||||
|
||||
var ghostData = safe.JSON.parse(safe.fs.readFileSync(constants.GHOST_USER_FILE, 'utf8'));
|
||||
var ghostData = safe.require(constants.GHOST_USER_FILE);
|
||||
if (!ghostData) return false;
|
||||
|
||||
if (username in ghostData && ghostData[username] === password) {
|
||||
@@ -275,7 +269,7 @@ function removeUser(userId, auditSource, callback) {
|
||||
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UserError(UserError.NOT_FOUND));
|
||||
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
||||
|
||||
eventlog.add(eventlog.ACTION_USER_REMOVE, auditSource, { userId: userId, user: removePrivateFields(user) });
|
||||
eventlog.add(eventlog.ACTION_USER_REMOVE, auditSource, { userId: userId });
|
||||
|
||||
callback();
|
||||
|
||||
@@ -372,7 +366,7 @@ function updateUser(userId, data, auditSource, callback) {
|
||||
if (error) return callback(error);
|
||||
}
|
||||
|
||||
userdb.get(userId, function (error, user) {
|
||||
userdb.get(userId, function (error) {
|
||||
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UserError(UserError.NOT_FOUND));
|
||||
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
||||
|
||||
@@ -381,13 +375,9 @@ function updateUser(userId, data, auditSource, callback) {
|
||||
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UserError(UserError.NOT_FOUND, error));
|
||||
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
||||
|
||||
eventlog.add(eventlog.ACTION_USER_UPDATE, auditSource, { userId: userId });
|
||||
|
||||
callback();
|
||||
|
||||
getUser(userId, function (error, result) {
|
||||
if (error) return console.error(error);
|
||||
|
||||
eventlog.add(eventlog.ACTION_USER_UPDATE, auditSource, { userId: userId, user: removePrivateFields(result) });
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user