Compare commits
72 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a00b7281a7 | |||
| ddeee0c970 | |||
| 8aad71efd0 | |||
| 2028f6b984 | |||
| bff4999d27 | |||
| d429015f83 | |||
| e2628e2d43 | |||
| 05dcbee7e3 | |||
| a81919262e | |||
| b14b5f141b | |||
| 1259d11173 | |||
| 0a7b132be8 | |||
| ed9210eede | |||
| 9ee6aa54c6 | |||
| 7cfc455cd3 | |||
| a481ceac8c | |||
| 8c7eff4e24 | |||
| c6c584ff74 | |||
| ba50eb121d | |||
| aa8ebbd7ea | |||
| 64bc9c6dbe | |||
| bba9963b7c | |||
| 6ea2aa4a54 | |||
| 3c3f81365b | |||
| 3adeed381b | |||
| 0f5b7278b8 | |||
| f94ff49fb9 | |||
| d512a9c30d | |||
| 0c5113ed5b | |||
| 2469f4cdff | |||
| 9c53bfb7fb | |||
| 8b8144588d | |||
| 77553da4c1 | |||
| cbcf943691 | |||
| 725a19e5b5 | |||
| f9115f902a | |||
| e4faf26d74 | |||
| 1c96fbb533 | |||
| 3dc163c33d | |||
| edae94cf2e | |||
| d1ff8e9d6b | |||
| 70743bd285 | |||
| 493f1505f0 | |||
| 007e3b5eef | |||
| d9bf6c0933 | |||
| 324344d118 | |||
| 5cb71e9443 | |||
| cca19f00c5 | |||
| 6648f41f3d | |||
| c1e6b47fd6 | |||
| 0f103ccce1 | |||
| bc6e652293 | |||
| 85b4f2dbdd | |||
| d47b83a63b | |||
| b2e9fa7e0d | |||
| a9fb444622 | |||
| 33ba22a021 | |||
| 57de0282cd | |||
| 8568fd26d8 | |||
| 84f41e08cf | |||
| a96da20536 | |||
| 5199a9342e | |||
| 893ecec0fa | |||
| e3da6419f5 | |||
| 0750d2ba50 | |||
| f1fcb65fbe | |||
| 215aa65d5a | |||
| 85f67c13da | |||
| 6dcc478aeb | |||
| 3f2496db6f | |||
| 612f79f9e0 | |||
| 90fb1cd735 |
@@ -1965,6 +1965,7 @@
|
||||
* better nginx config for higher loads
|
||||
* backups: add CIFS storage provider
|
||||
* backups: add SSHFS storage provider
|
||||
* backups: add NFS storage provider
|
||||
* s3: use vhost style
|
||||
* Fix crash when redis config was set
|
||||
* Update schedule was unselected in the UI
|
||||
@@ -1978,4 +1979,53 @@
|
||||
* mail: make authentication case insensitive
|
||||
* Fix timeout issues in postgresql and mysql addon
|
||||
* Do not count stopped apps for memory use
|
||||
* LDAP group synchronization
|
||||
|
||||
[5.3.1]
|
||||
* better nginx config for higher loads
|
||||
* backups: add CIFS storage provider
|
||||
* backups: add SSHFS storage provider
|
||||
* backups: add NFS storage provider
|
||||
* s3: use vhost style
|
||||
* Fix crash when redis config was set
|
||||
* Update schedule was unselected in the UI
|
||||
* cloudron-setup: --provider is now optional
|
||||
* show warning for unstable updates
|
||||
* add forumUrl to app manifest
|
||||
* postgresql: add unaccent extension for peertube
|
||||
* mail: Add Auto-Submitted header to NDRs
|
||||
* backups: ensure that the latest backup of installed apps is always preserved
|
||||
* add nginx logs
|
||||
* mail: make authentication case insensitive
|
||||
* Fix timeout issues in postgresql and mysql addon
|
||||
* Do not count stopped apps for memory use
|
||||
* LDAP group synchronization
|
||||
|
||||
[5.3.2]
|
||||
* Do not install sshfs package
|
||||
* 'provider' is not required anymore in various API calls
|
||||
* redis: Set maxmemory and maxmemory-policy
|
||||
* Add mlock capability to manifest (for vault app)
|
||||
|
||||
[5.3.3]
|
||||
* Fix issue where some postinstall messages where causing angular to infinite loop
|
||||
|
||||
[5.3.4]
|
||||
* Fix issue in database error handling
|
||||
|
||||
[5.4.0]
|
||||
* Update nginx to 1.18 for various security fixes
|
||||
* Add ping capability (for statping app)
|
||||
* Fix bug where aliases were displayed incorrectly in SOGo
|
||||
* Add univention as LDAP provider
|
||||
* Bump max_connection for postgres addon to 200
|
||||
* mail: Add pagination to mailing list API
|
||||
* Allow admin to lock email and display name of users
|
||||
* Allow admin to ensure all users have 2FA setup
|
||||
* ami: fix regression where we didn't send provider as part of get status call
|
||||
* nginx: hide version
|
||||
* backups: add b2 provider
|
||||
* Add filemanager webinterface
|
||||
* Add darkmode
|
||||
* Add note that password reset and invite links expire in 24 hours
|
||||
|
||||
|
||||
@@ -13,6 +13,9 @@ function die {
|
||||
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
readonly ubuntu_codename=$(lsb_release -cs)
|
||||
readonly ubuntu_version=$(lsb_release -rs)
|
||||
|
||||
# hold grub since updating it breaks on some VPS providers. also, dist-upgrade will trigger it
|
||||
apt-mark hold grub* >/dev/null
|
||||
apt-get -o Dpkg::Options::="--force-confdef" update -y
|
||||
@@ -26,8 +29,6 @@ debconf-set-selections <<< 'mysql-server mysql-server/root_password_again passwo
|
||||
|
||||
# this enables automatic security upgrades (https://help.ubuntu.com/community/AutomaticSecurityUpdates)
|
||||
# resolvconf is needed for unbound to work property after disabling systemd-resolved in 18.04
|
||||
ubuntu_version=$(lsb_release -rs)
|
||||
ubuntu_codename=$(lsb_release -cs)
|
||||
gpg_package=$([[ "${ubuntu_version}" == "16.04" ]] && echo "gnupg" || echo "gpg")
|
||||
apt-get -y install \
|
||||
acl \
|
||||
@@ -52,16 +53,11 @@ apt-get -y install \
|
||||
unbound \
|
||||
xfsprogs
|
||||
|
||||
if [[ "${ubuntu_version}" == "16.04" ]]; then
|
||||
echo "==> installing nginx for xenial for TLSv3 support"
|
||||
|
||||
curl -sL http://nginx.org/packages/ubuntu/pool/nginx/n/nginx/nginx_1.14.0-1~xenial_amd64.deb -o /tmp/nginx.deb
|
||||
# apt install with install deps (as opposed to dpkg -i)
|
||||
apt install -y /tmp/nginx.deb
|
||||
rm /tmp/nginx.deb
|
||||
else
|
||||
apt install -y nginx-full
|
||||
fi
|
||||
echo "==> installing nginx for xenial for TLSv3 support"
|
||||
curl -sL http://nginx.org/packages/ubuntu/pool/nginx/n/nginx/nginx_1.18.0-1~${ubuntu_codename}_amd64.deb -o /tmp/nginx.deb
|
||||
# apt install with install deps (as opposed to dpkg -i)
|
||||
apt install -y /tmp/nginx.deb
|
||||
rm /tmp/nginx.deb
|
||||
|
||||
# on some providers like scaleway the sudo file is changed and we want to keep the old one
|
||||
apt-get -o Dpkg::Options::="--force-confold" install -y sudo
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.runSql('ALTER TABLE users ADD COLUMN ts TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP', function (error) {
|
||||
if (error) console.error(error);
|
||||
|
||||
db.runSql('ALTER TABLE users DROP COLUMN modifiedAt', callback);
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function(db, callback) {
|
||||
db.runSql('ALTER TABLE users DROP COLUMN ts', function (error) {
|
||||
if (error) console.error(error);
|
||||
callback(error);
|
||||
});
|
||||
};
|
||||
@@ -21,7 +21,7 @@ CREATE TABLE IF NOT EXISTS users(
|
||||
password VARCHAR(1024) NOT NULL,
|
||||
salt VARCHAR(512) NOT NULL,
|
||||
createdAt VARCHAR(512) NOT NULL,
|
||||
modifiedAt VARCHAR(512) NOT NULL,
|
||||
ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
displayName VARCHAR(512) DEFAULT "",
|
||||
fallbackEmail VARCHAR(512) DEFAULT "",
|
||||
twoFactorAuthenticationSecret VARCHAR(128) DEFAULT "",
|
||||
@@ -88,6 +88,7 @@ CREATE TABLE IF NOT EXISTS apps(
|
||||
taskId INTEGER, // current task
|
||||
errorJson TEXT,
|
||||
bindsJson TEXT, // bind mounts
|
||||
servicesConfigJson TEXT, // app services configuration
|
||||
|
||||
FOREIGN KEY(mailboxDomain) REFERENCES domains(domain),
|
||||
FOREIGN KEY(taskId) REFERENCES tasks(id),
|
||||
|
||||
Generated
+46
-46
@@ -343,7 +343,7 @@
|
||||
},
|
||||
"amdefine": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=",
|
||||
"dev": true
|
||||
},
|
||||
@@ -416,7 +416,7 @@
|
||||
},
|
||||
"assert-plus": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
|
||||
},
|
||||
"assertion-error": {
|
||||
@@ -488,7 +488,7 @@
|
||||
},
|
||||
"backoff": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/backoff/-/backoff-2.5.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-9hbtqdPktmuMp/ynn2lXIsX44m8=",
|
||||
"requires": {
|
||||
"precond": "0.2"
|
||||
@@ -626,7 +626,7 @@
|
||||
},
|
||||
"buffer-equal-constant-time": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk="
|
||||
},
|
||||
"buffer-fill": {
|
||||
@@ -641,7 +641,7 @@
|
||||
},
|
||||
"bunyan": {
|
||||
"version": "1.8.12",
|
||||
"resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.12.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-8VDw9nSKvdcq6uhPBEA74u8RN5c=",
|
||||
"requires": {
|
||||
"dtrace-provider": "~0.8",
|
||||
@@ -741,9 +741,9 @@
|
||||
}
|
||||
},
|
||||
"cloudron-manifestformat": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/cloudron-manifestformat/-/cloudron-manifestformat-5.3.0.tgz",
|
||||
"integrity": "sha512-KMHTtR/oRnMzqTUzY1706xpYA1PrvmwIenC8HrigJhOYQdeMLv7egg1Eg0QIB1bfxKIhb+1Zc2i48HKE7MQGkg==",
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/cloudron-manifestformat/-/cloudron-manifestformat-5.5.0.tgz",
|
||||
"integrity": "sha512-Xf1vOwCFT5h1MZQ9fC8EyfL2jfpVlShg5r7est/ZA+vSzcbvk2nQxPmpk4q4e6iDfr19B7iUw2b2X7mw5c1Dlg==",
|
||||
"requires": {
|
||||
"cron": "^1.8.2",
|
||||
"java-packagename-regex": "^1.0.0",
|
||||
@@ -772,7 +772,7 @@
|
||||
},
|
||||
"code-point-at": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
|
||||
"dev": true
|
||||
},
|
||||
@@ -817,7 +817,7 @@
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
|
||||
},
|
||||
"concat-stream": {
|
||||
@@ -1001,7 +1001,7 @@
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
|
||||
},
|
||||
"cron": {
|
||||
@@ -1043,7 +1043,7 @@
|
||||
},
|
||||
"dashdash": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
|
||||
"requires": {
|
||||
"assert-plus": "^1.0.0"
|
||||
@@ -1249,7 +1249,7 @@
|
||||
},
|
||||
"decamelize": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
|
||||
},
|
||||
"deep-eql": {
|
||||
@@ -1529,7 +1529,7 @@
|
||||
},
|
||||
"ent": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0="
|
||||
},
|
||||
"error-ex": {
|
||||
@@ -1633,7 +1633,7 @@
|
||||
},
|
||||
"expect.js": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/expect.js/-/expect.js-0.3.1.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-sKWaDS7/VDdUTr8M6qYBWEHQm1s=",
|
||||
"dev": true
|
||||
},
|
||||
@@ -2420,7 +2420,7 @@
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
||||
"requires": {
|
||||
"once": "^1.3.0",
|
||||
@@ -2450,7 +2450,7 @@
|
||||
},
|
||||
"is-arrayish": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
|
||||
"dev": true
|
||||
},
|
||||
@@ -2529,12 +2529,12 @@
|
||||
},
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
},
|
||||
"isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
|
||||
},
|
||||
"isstream": {
|
||||
@@ -2661,7 +2661,7 @@
|
||||
},
|
||||
"ldap-filter": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/ldap-filter/-/ldap-filter-0.2.2.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-8rhCvguG2jNSeYUFsx68rlkNd9A=",
|
||||
"requires": {
|
||||
"assert-plus": "0.1.5"
|
||||
@@ -2669,7 +2669,7 @@
|
||||
"dependencies": {
|
||||
"assert-plus": {
|
||||
"version": "0.1.5",
|
||||
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-7nQAlBMALYTOxyGcasgRgS5yMWA="
|
||||
}
|
||||
}
|
||||
@@ -2868,19 +2868,19 @@
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=",
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
@@ -3093,7 +3093,7 @@
|
||||
},
|
||||
"mv": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
@@ -3104,7 +3104,7 @@
|
||||
"dependencies": {
|
||||
"glob": {
|
||||
"version": "6.0.4",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
@@ -3117,13 +3117,13 @@
|
||||
},
|
||||
"ncp": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=",
|
||||
"optional": true
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "2.4.5",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
@@ -3411,7 +3411,7 @@
|
||||
},
|
||||
"nopt": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -3460,7 +3460,7 @@
|
||||
},
|
||||
"number-is-nan": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
|
||||
"dev": true
|
||||
},
|
||||
@@ -3611,7 +3611,7 @@
|
||||
},
|
||||
"parse-json": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -3635,7 +3635,7 @@
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
|
||||
},
|
||||
"path-key": {
|
||||
@@ -3725,7 +3725,7 @@
|
||||
},
|
||||
"precond": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/precond/-/precond-0.2.3.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-qpWRvKokkj8eD0hJ0kD0fvwQdaw="
|
||||
},
|
||||
"pretty-bytes": {
|
||||
@@ -3793,7 +3793,7 @@
|
||||
},
|
||||
"pseudomap": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
|
||||
"dev": true
|
||||
},
|
||||
@@ -4061,7 +4061,7 @@
|
||||
},
|
||||
"require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
|
||||
},
|
||||
"require-main-filename": {
|
||||
@@ -4247,7 +4247,7 @@
|
||||
},
|
||||
"set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
|
||||
},
|
||||
"setprototypeof": {
|
||||
@@ -4307,7 +4307,7 @@
|
||||
},
|
||||
"signal-exit": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
|
||||
},
|
||||
"smtp-connection": {
|
||||
@@ -4401,7 +4401,7 @@
|
||||
},
|
||||
"sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
|
||||
},
|
||||
"sqlstring": {
|
||||
@@ -4568,7 +4568,7 @@
|
||||
},
|
||||
"stubs": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-6NK6H6nJBXAwPAMLaQD31fiavls="
|
||||
},
|
||||
"superagent": {
|
||||
@@ -4876,7 +4876,7 @@
|
||||
},
|
||||
"typedarray": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
|
||||
},
|
||||
"uid-safe": {
|
||||
@@ -4948,7 +4948,7 @@
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
|
||||
},
|
||||
"utile": {
|
||||
@@ -5003,7 +5003,7 @@
|
||||
},
|
||||
"vasync": {
|
||||
"version": "1.6.4",
|
||||
"resolved": "https://registry.npmjs.org/vasync/-/vasync-1.6.4.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-3+k2Fq0OeugBszKp2Iv8XNyOHR8=",
|
||||
"requires": {
|
||||
"verror": "1.6.0"
|
||||
@@ -5011,7 +5011,7 @@
|
||||
"dependencies": {
|
||||
"verror": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/verror/-/verror-1.6.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-fROyex+swuLakEBetepuW90lLqU=",
|
||||
"requires": {
|
||||
"extsprintf": "1.2.0"
|
||||
@@ -5021,7 +5021,7 @@
|
||||
},
|
||||
"verror": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
|
||||
"requires": {
|
||||
"assert-plus": "^1.0.0",
|
||||
@@ -5044,7 +5044,7 @@
|
||||
},
|
||||
"which-module": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
|
||||
},
|
||||
"wide-align": {
|
||||
@@ -5131,7 +5131,7 @@
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||
},
|
||||
"write-file-atomic": {
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@
|
||||
"async": "^2.6.3",
|
||||
"aws-sdk": "^2.685.0",
|
||||
"body-parser": "^1.19.0",
|
||||
"cloudron-manifestformat": "^5.3.0",
|
||||
"cloudron-manifestformat": "^5.5.0",
|
||||
"connect": "^3.7.0",
|
||||
"connect-lastmile": "^2.0.0",
|
||||
"connect-timeout": "^1.9.0",
|
||||
|
||||
@@ -106,12 +106,6 @@ if [[ "${initBaseImage}" == "true" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=> Ensure required apt sources"
|
||||
if ! add-apt-repository universe &>> "${LOG_FILE}"; then
|
||||
echo "Could not add required apt sources (for nginx-full). See ${LOG_FILE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=> Updating apt and installing script dependencies"
|
||||
if ! apt-get update &>> "${LOG_FILE}"; then
|
||||
echo "Could not update package repositories. See ${LOG_FILE}"
|
||||
@@ -159,7 +153,7 @@ if [[ "${initBaseImage}" == "true" ]]; then
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# NOTE: this install script only supports 4.2 and above
|
||||
# The provider flag is still used for marketplace images
|
||||
echo "=> Installing version ${version} (this takes some time) ..."
|
||||
mkdir -p /etc/cloudron
|
||||
echo "${provider}" > /etc/cloudron/PROVIDER
|
||||
|
||||
@@ -112,7 +112,7 @@ if [[ "${enableSSH}" == "true" ]]; then
|
||||
permit_root_login=$(grep -q ^PermitRootLogin.*yes /etc/ssh/sshd_config && echo "yes" || echo "no")
|
||||
|
||||
# support.js uses similar logic
|
||||
if $(grep -q "ec2\|lightsail\|ami" /etc/cloudron/PROVIDER); then
|
||||
if [[ -d /home/ubuntu ]]; then
|
||||
ssh_user="ubuntu"
|
||||
keys_file="/home/ubuntu/.ssh/authorized_keys"
|
||||
else
|
||||
|
||||
@@ -58,9 +58,9 @@ if [[ $(docker version --format {{.Client.Version}}) != "18.09.2" ]]; then
|
||||
fi
|
||||
|
||||
readonly nginx_version=$(nginx -v)
|
||||
if [[ "${nginx_version}" != *"1.14."* && "${ubuntu_version}" == "16.04" ]]; then
|
||||
echo "==> installer: installing nginx for xenial for TLSv3 support"
|
||||
curl -sL http://nginx.org/packages/ubuntu/pool/nginx/n/nginx/nginx_1.14.0-1~xenial_amd64.deb -o /tmp/nginx.deb
|
||||
if [[ "${nginx_version}" != *"1.18."* ]]; then
|
||||
echo "==> installer: installing nginx 1.18"
|
||||
curl -sL http://nginx.org/packages/ubuntu/pool/nginx/n/nginx/nginx_1.18.0-1~${ubuntu_codename}_amd64.deb -o /tmp/nginx.deb
|
||||
# apt install with install deps (as opposed to dpkg -i)
|
||||
apt install -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" --force-yes /tmp/nginx.deb
|
||||
rm /tmp/nginx.deb
|
||||
@@ -92,9 +92,6 @@ if [[ ${try} -eq 10 ]]; then
|
||||
exit 4
|
||||
fi
|
||||
|
||||
echo "==> installer: ensure sshfs is installed"
|
||||
apt install -y sshfs
|
||||
|
||||
echo "==> installer: downloading new addon images"
|
||||
images=$(node -e "var i = require('${box_src_tmp_dir}/src/infra_version.js'); console.log(i.baseImages.map(function (x) { return x.tag; }).join(' '), Object.keys(i.images).map(function (x) { return i.images[x].tag; }).join(' '));")
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ WorkingDirectory=/home/yellowtent/box
|
||||
Restart=always
|
||||
; Systemd does not append logs when logging to files, we spawn a shell first and exec to replace it after setting up the pipes
|
||||
ExecStart=/bin/sh -c 'echo "Logging to /home/yellowtent/platformdata/logs/box.log"; exec /usr/bin/node /home/yellowtent/box/box.js >> /home/yellowtent/platformdata/logs/box.log 2>&1'
|
||||
Environment="HOME=/home/yellowtent" "USER=yellowtent" "DEBUG=box*,connect-lastmile" "BOX_ENV=cloudron" "NODE_ENV=production"
|
||||
Environment="HOME=/home/yellowtent" "USER=yellowtent" "DEBUG=box:*,connect-lastmile,-box:ldap" "BOX_ENV=cloudron" "NODE_ENV=production"
|
||||
; kill apptask processes as well
|
||||
KillMode=control-group
|
||||
; Do not kill this process on OOM. Children inherit this score. Do not set it to -1000 so that MemoryMax can keep working
|
||||
|
||||
+1
-1
@@ -427,7 +427,7 @@ function removeInternalFields(app) {
|
||||
function removeRestrictedFields(app) {
|
||||
return _.pick(app,
|
||||
'id', 'appStoreId', 'installationState', 'error', 'runState', 'health', 'taskId', 'alternateDomains', 'sso',
|
||||
'location', 'domain', 'fqdn', 'manifest', 'portBindings', 'iconUrl', 'creationTime', 'ts', 'tags', 'label');
|
||||
'location', 'domain', 'fqdn', 'manifest', 'portBindings', 'iconUrl', 'creationTime', 'ts', 'tags', 'label', 'enableBackup');
|
||||
}
|
||||
|
||||
function getIconUrlSync(app) {
|
||||
|
||||
+7
-136
@@ -11,7 +11,6 @@ exports = module.exports = {
|
||||
trackFinishedSetup: trackFinishedSetup,
|
||||
|
||||
registerWithLoginCredentials: registerWithLoginCredentials,
|
||||
registerWithLicense: registerWithLicense,
|
||||
|
||||
purchaseApp: purchaseApp,
|
||||
unpurchaseApp: unpurchaseApp,
|
||||
@@ -20,8 +19,6 @@ exports = module.exports = {
|
||||
getSubscription: getSubscription,
|
||||
isFreePlan: isFreePlan,
|
||||
|
||||
sendAliveStatus: sendAliveStatus,
|
||||
|
||||
getAppUpdate: getAppUpdate,
|
||||
getBoxUpdate: getBoxUpdate,
|
||||
|
||||
@@ -34,21 +31,14 @@ var apps = require('./apps.js'),
|
||||
BoxError = require('./boxerror.js'),
|
||||
constants = require('./constants.js'),
|
||||
debug = require('debug')('box:appstore'),
|
||||
domains = require('./domains.js'),
|
||||
eventlog = require('./eventlog.js'),
|
||||
groups = require('./groups.js'),
|
||||
mail = require('./mail.js'),
|
||||
os = require('os'),
|
||||
paths = require('./paths.js'),
|
||||
safe = require('safetydance'),
|
||||
semver = require('semver'),
|
||||
settings = require('./settings.js'),
|
||||
superagent = require('superagent'),
|
||||
users = require('./users.js'),
|
||||
util = require('util');
|
||||
|
||||
const NOOP_CALLBACK = function (error) { if (error) debug(error); };
|
||||
|
||||
// These are the default options and will be adjusted once a subscription state is obtained
|
||||
// Keep in sync with appstore/routes/cloudrons.js
|
||||
let gFeatures = {
|
||||
@@ -57,7 +47,10 @@ let gFeatures = {
|
||||
externalLdap: false,
|
||||
privateDockerRegistry: false,
|
||||
branding: false,
|
||||
support: false
|
||||
support: false,
|
||||
directoryConfig: false,
|
||||
mailboxMaxCount: 5,
|
||||
emailPremium: false
|
||||
};
|
||||
|
||||
// attempt to load feature cache in case appstore would be down
|
||||
@@ -233,111 +226,6 @@ function unpurchaseApp(appId, data, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function sendAliveStatus(callback) {
|
||||
callback = callback || NOOP_CALLBACK;
|
||||
|
||||
let allSettings, allDomains, mailDomains, loginEvents, userCount, groupCount;
|
||||
|
||||
async.series([
|
||||
function (callback) {
|
||||
settings.getAll(function (error, result) {
|
||||
if (error) return callback(error);
|
||||
allSettings = result;
|
||||
callback();
|
||||
});
|
||||
},
|
||||
function (callback) {
|
||||
domains.getAll(function (error, result) {
|
||||
if (error) return callback(error);
|
||||
allDomains = result;
|
||||
callback();
|
||||
});
|
||||
},
|
||||
function (callback) {
|
||||
mail.getDomains(function (error, result) {
|
||||
if (error) return callback(error);
|
||||
mailDomains = result;
|
||||
callback();
|
||||
});
|
||||
},
|
||||
function (callback) {
|
||||
eventlog.getAllPaged([ eventlog.ACTION_USER_LOGIN ], null, 1, 1, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
loginEvents = result;
|
||||
callback();
|
||||
});
|
||||
},
|
||||
function (callback) {
|
||||
users.count(function (error, result) {
|
||||
if (error) return callback(error);
|
||||
userCount = result;
|
||||
callback();
|
||||
});
|
||||
},
|
||||
function (callback) {
|
||||
groups.count(function (error, result) {
|
||||
if (error) return callback(error);
|
||||
groupCount = 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; })))
|
||||
},
|
||||
userCount: userCount,
|
||||
groupCount: groupCount,
|
||||
appAutoupdatePattern: allSettings[settings.APP_AUTOUPDATE_PATTERN_KEY],
|
||||
boxAutoupdatePattern: allSettings[settings.BOX_AUTOUPDATE_PATTERN_KEY],
|
||||
timeZone: allSettings[settings.TIME_ZONE_KEY],
|
||||
sysinfoProvider: allSettings[settings.SYSINFO_CONFIG_KEY].provider
|
||||
};
|
||||
|
||||
var data = {
|
||||
version: constants.VERSION,
|
||||
adminFqdn: settings.adminFqdn(),
|
||||
provider: settings.provider(),
|
||||
backendSettings: backendSettings,
|
||||
machine: {
|
||||
cpus: os.cpus(),
|
||||
totalmem: os.totalmem()
|
||||
},
|
||||
events: {
|
||||
lastLogin: loginEvents[0] ? (new Date(loginEvents[0].creationTime).getTime()) : 0
|
||||
}
|
||||
};
|
||||
|
||||
getCloudronToken(function (error, token) {
|
||||
if (error) return callback(error);
|
||||
|
||||
const url = `${settings.apiServerOrigin()}/api/v1/alive`;
|
||||
superagent.post(url).send(data).query({ accessToken: token }).timeout(30 * 1000).end(function (error, result) {
|
||||
if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error));
|
||||
if (result.statusCode === 404) return callback(new BoxError(BoxError.NOT_FOUND));
|
||||
if (result.statusCode === 401) return callback(new BoxError(BoxError.INVALID_CREDENTIALS));
|
||||
if (result.statusCode === 422) return callback(new BoxError(BoxError.LICENSE_ERROR, result.body.message));
|
||||
if (result.statusCode !== 201) return callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('Sending alive status failed. %s %j', result.status, result.body)));
|
||||
|
||||
callback(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getBoxUpdate(options, callback) {
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
@@ -453,16 +341,14 @@ function registerCloudron(data, callback) {
|
||||
|
||||
// This works without a Cloudron token as this Cloudron was not yet registered
|
||||
let gBeginSetupAlreadyTracked = false;
|
||||
function trackBeginSetup(provider) {
|
||||
assert.strictEqual(typeof provider, 'string');
|
||||
|
||||
function trackBeginSetup() {
|
||||
// avoid browser reload double tracking, not perfect since box might restart, but covers most cases and is simple
|
||||
if (gBeginSetupAlreadyTracked) return;
|
||||
gBeginSetupAlreadyTracked = true;
|
||||
|
||||
const url = `${settings.apiServerOrigin()}/api/v1/helper/setup_begin`;
|
||||
|
||||
superagent.post(url).send({ provider }).timeout(30 * 1000).end(function (error, result) {
|
||||
superagent.post(url).send({}).timeout(30 * 1000).end(function (error, result) {
|
||||
if (error && !error.response) return console.error(error.message);
|
||||
if (result.statusCode !== 200) return console.error(error.message);
|
||||
});
|
||||
@@ -480,21 +366,6 @@ function trackFinishedSetup(domain) {
|
||||
});
|
||||
}
|
||||
|
||||
function registerWithLicense(license, domain, callback) {
|
||||
assert.strictEqual(typeof license, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
getCloudronToken(function (error, token) {
|
||||
if (token) return callback(new BoxError(BoxError.CONFLICT, 'Cloudron is already registered'));
|
||||
|
||||
const provider = settings.provider();
|
||||
const version = constants.VERSION;
|
||||
|
||||
registerCloudron({ license, domain, provider, version }, callback);
|
||||
});
|
||||
}
|
||||
|
||||
function registerWithLoginCredentials(options, callback) {
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
@@ -514,7 +385,7 @@ function registerWithLoginCredentials(options, callback) {
|
||||
login(options.email, options.password, options.totpToken || '', function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
registerCloudron({ domain: settings.adminDomain(), accessToken: result.accessToken, provider: settings.provider(), version: constants.VERSION, purpose: options.purpose || '' }, callback);
|
||||
registerCloudron({ domain: settings.adminDomain(), accessToken: result.accessToken, version: constants.VERSION, purpose: options.purpose || '' }, callback);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -105,6 +105,7 @@ function api(provider) {
|
||||
case 'exoscale-sos': return require('./storage/s3.js');
|
||||
case 'wasabi': return require('./storage/s3.js');
|
||||
case 'scaleway-objectstorage': return require('./storage/s3.js');
|
||||
case 'backblaze-b2': return require('./storage/s3.js');
|
||||
case 'linode-objectstorage': return require('./storage/s3.js');
|
||||
case 'ovh-objectstorage': return require('./storage/s3.js');
|
||||
case 'noop': return require('./storage/noop.js');
|
||||
|
||||
+3
-2
@@ -139,10 +139,11 @@ function getConfig(callback) {
|
||||
mailFqdn: settings.mailFqdn(),
|
||||
version: constants.VERSION,
|
||||
isDemo: settings.isDemo(),
|
||||
provider: settings.provider(),
|
||||
cloudronName: allSettings[settings.CLOUDRON_NAME_KEY],
|
||||
footer: allSettings[settings.FOOTER_KEY] || constants.FOOTER,
|
||||
features: appstore.getFeatures()
|
||||
features: appstore.getFeatures(),
|
||||
profileLocked: allSettings[settings.DIRECTORY_CONFIG_KEY].lockUserProfiles,
|
||||
mandatory2FA: allSettings[settings.DIRECTORY_CONFIG_KEY].mandatory2FA
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ exports = module.exports = {
|
||||
|
||||
var appHealthMonitor = require('./apphealthmonitor.js'),
|
||||
apps = require('./apps.js'),
|
||||
appstore = require('./appstore.js'),
|
||||
assert = require('assert'),
|
||||
async = require('async'),
|
||||
auditSource = require('./auditsource.js'),
|
||||
@@ -41,7 +40,6 @@ var appHealthMonitor = require('./apphealthmonitor.js'),
|
||||
updateChecker = require('./updatechecker.js');
|
||||
|
||||
var gJobs = {
|
||||
alive: null, // send periodic stats
|
||||
appAutoUpdater: null,
|
||||
boxAutoUpdater: null,
|
||||
appUpdateChecker: null,
|
||||
@@ -73,12 +71,6 @@ function startJobs(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
const randomMinute = Math.floor(60*Math.random());
|
||||
gJobs.alive = new CronJob({
|
||||
cronTime: '00 ' + randomMinute + ' * * * *', // every hour on a random minute
|
||||
onTick: appstore.sendAliveStatus,
|
||||
start: true
|
||||
});
|
||||
|
||||
gJobs.systemChecks = new CronJob({
|
||||
cronTime: '00 30 * * * *', // every 30 minutes. if you change this interval, change the notification messages with correct duration
|
||||
onTick: () => cloudron.runSystemChecks(NOOP_CALLBACK),
|
||||
|
||||
+32
-82
@@ -22,8 +22,7 @@ var assert = require('assert'),
|
||||
once = require('once'),
|
||||
util = require('util');
|
||||
|
||||
var gConnectionPool = null,
|
||||
gDefaultConnection = null;
|
||||
var gConnectionPool = null;
|
||||
|
||||
const gDatabase = {
|
||||
hostname: '127.0.0.1',
|
||||
@@ -43,66 +42,37 @@ function initialize(callback) {
|
||||
gDatabase.hostname = require('child_process').execSync('docker inspect -f "{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}" mysql-server').toString().trim();
|
||||
}
|
||||
|
||||
// https://github.com/mysqljs/mysql#pool-options
|
||||
gConnectionPool = mysql.createPool({
|
||||
connectionLimit: 5, // this has to be > 1 since we store one connection as 'default'. the rest for transactions
|
||||
connectionLimit: 5,
|
||||
host: gDatabase.hostname,
|
||||
user: gDatabase.username,
|
||||
password: gDatabase.password,
|
||||
port: gDatabase.port,
|
||||
database: gDatabase.name,
|
||||
multipleStatements: false,
|
||||
waitForConnections: true, // getConnection() will wait until a connection is avaiable
|
||||
ssl: false,
|
||||
timezone: 'Z' // mysql follows the SYSTEM timezone. on Cloudron, this is UTC
|
||||
});
|
||||
|
||||
gConnectionPool.on('connection', function (connection) {
|
||||
// connection objects are re-used. so we have to attach to the event here (once) to prevent crash
|
||||
// note the pool also has an 'acquire' event but that is called whenever we do a getConnection()
|
||||
connection.on('error', (error) => debug(`Connection ${connection.threadId} error: ${error.message} ${error.code}`));
|
||||
|
||||
connection.query('USE ' + gDatabase.name);
|
||||
connection.query('SET SESSION sql_mode = \'strict_all_tables\'');
|
||||
});
|
||||
|
||||
reconnect(callback);
|
||||
callback(null);
|
||||
}
|
||||
|
||||
function uninitialize(callback) {
|
||||
if (gConnectionPool) {
|
||||
gConnectionPool.end(callback);
|
||||
gDefaultConnection = null;
|
||||
gConnectionPool = null;
|
||||
} else {
|
||||
callback(null);
|
||||
}
|
||||
}
|
||||
if (!gConnectionPool) return callback(null);
|
||||
|
||||
function reconnect(callback) {
|
||||
callback = callback ? once(callback) : function () {};
|
||||
|
||||
debug('reconnect: connecting to database');
|
||||
|
||||
gConnectionPool.getConnection(function (error, connection) {
|
||||
if (error) {
|
||||
debug(`reconnect: db connection error. ${error.message} fatal:${error.fatal} code:${error.code}. Will retry in 10 seconds`);
|
||||
return setTimeout(reconnect.bind(null, callback), 10000);
|
||||
}
|
||||
|
||||
debug('reconnect: connected to database');
|
||||
|
||||
connection.on('error', function (error) {
|
||||
// by design, we catch all normal errors by providing callbacks.
|
||||
// this function should be invoked only when we have no callbacks pending and we have a fatal error
|
||||
assert(error.fatal, 'Non-fatal error on connection object');
|
||||
|
||||
debug(`reconnect: db connection error. ${error.message} fatal:${error.fatal} code:${error.code}. Will retry in 10 seconds`);
|
||||
|
||||
gDefaultConnection = null;
|
||||
|
||||
// This is most likely an issue an can cause double callbacks from reconnect()
|
||||
setTimeout(reconnect.bind(null, callback), 10000);
|
||||
});
|
||||
|
||||
gDefaultConnection = connection;
|
||||
|
||||
callback(null);
|
||||
});
|
||||
gConnectionPool.end(callback);
|
||||
gConnectionPool = null;
|
||||
}
|
||||
|
||||
function clear(callback) {
|
||||
@@ -115,39 +85,14 @@ function clear(callback) {
|
||||
child_process.exec(cmd, callback);
|
||||
}
|
||||
|
||||
function beginTransaction(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
if (gConnectionPool === null) return callback(new BoxError(BoxError.DATABASE_ERROR, 'No database connection pool.'));
|
||||
|
||||
gConnectionPool.getConnection(function (error, connection) {
|
||||
if (error) return callback(error);
|
||||
|
||||
connection.beginTransaction(function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
return callback(null, connection);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function query() {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
var callback = args[args.length - 1];
|
||||
const args = Array.prototype.slice.call(arguments);
|
||||
const callback = args[args.length - 1];
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
if (gDefaultConnection === null) return callback(new BoxError(BoxError.DATABASE_ERROR, 'No connection to database'));
|
||||
if (constants.TEST && !gConnectionPool) return callback(new BoxError(BoxError.DATABASE_ERROR, 'database.js not initialized'));
|
||||
|
||||
gDefaultConnection.query.apply(gDefaultConnection, args);
|
||||
}
|
||||
|
||||
function rollback(connection, transactionError, callback) {
|
||||
connection.rollback(function (error) {
|
||||
if (error) debug('rollback: error when rolling back', error);
|
||||
|
||||
connection.release();
|
||||
callback(transactionError);
|
||||
});
|
||||
gConnectionPool.query.apply(gConnectionPool, args); // this is same as getConnection/query/release
|
||||
}
|
||||
|
||||
function transaction(queries, callback) {
|
||||
@@ -156,21 +101,26 @@ function transaction(queries, callback) {
|
||||
|
||||
callback = once(callback);
|
||||
|
||||
beginTransaction(function (error, connection) {
|
||||
gConnectionPool.getConnection(function (error, connection) {
|
||||
if (error) return callback(error);
|
||||
|
||||
connection.on('error', callback);
|
||||
const releaseConnection = (error) => { connection.release(); callback(error); };
|
||||
|
||||
async.mapSeries(queries, function iterator(query, done) {
|
||||
connection.query(query.query, query.args, done);
|
||||
}, function seriesDone(error, results) {
|
||||
if (error) return rollback(connection, error, callback);
|
||||
connection.beginTransaction(function (error) {
|
||||
if (error) return releaseConnection(error);
|
||||
|
||||
connection.commit(function (error) {
|
||||
if (error) return rollback(connection, error, callback);
|
||||
async.mapSeries(queries, function iterator(query, done) {
|
||||
connection.query(query.query, query.args, done);
|
||||
}, function seriesDone(error, results) {
|
||||
if (error) return connection.rollback(() => releaseConnection(error));
|
||||
|
||||
connection.release();
|
||||
callback(null, results);
|
||||
connection.commit(function (error) {
|
||||
if (error) return connection.rollback(() => releaseConnection(error));
|
||||
|
||||
connection.release();
|
||||
|
||||
callback(null, results);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -194,7 +144,7 @@ function exportToFile(file, callback) {
|
||||
|
||||
// latest mysqldump enables column stats by default which is not present in MySQL 5.7 server
|
||||
// this option must not be set in production cloudrons which still use the old mysqldump
|
||||
const disableColStats = (constants.TEST && process.env.DESKTOP_SESSION !== 'ubuntu') ? '--column-statistics=0' : '';
|
||||
const disableColStats = (constants.TEST && require('fs').readFileSync('/etc/lsb-release', 'utf-8').includes('20.04')) ? '--column-statistics=0' : '';
|
||||
|
||||
var cmd = `/usr/bin/mysqldump -h "${gDatabase.hostname}" -u root -p${gDatabase.password} ${disableColStats} --single-transaction --routines --triggers ${gDatabase.name} > "${file}"`;
|
||||
|
||||
|
||||
+7
-6
@@ -306,7 +306,8 @@ function createSubcontainer(app, name, cmd, options, callback) {
|
||||
Dns: ['172.18.0.1'], // use internal dns
|
||||
DnsSearch: ['.'], // use internal dns
|
||||
SecurityOpt: [ 'apparmor=docker-cloudron-app' ],
|
||||
CapDrop: [ 'NET_RAW' ] // https://docs-stage.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities
|
||||
CapAdd: [],
|
||||
CapDrop: []
|
||||
},
|
||||
NetworkingConfig: {
|
||||
EndpointsConfig: {
|
||||
@@ -318,11 +319,11 @@ function createSubcontainer(app, name, cmd, options, callback) {
|
||||
};
|
||||
|
||||
var capabilities = manifest.capabilities || [];
|
||||
if (capabilities.includes('net_admin')) {
|
||||
containerOptions.HostConfig.CapAdd = [
|
||||
'NET_ADMIN', 'NET_RAW'
|
||||
];
|
||||
}
|
||||
|
||||
// https://docs-stage.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities
|
||||
if (capabilities.includes('net_admin')) containerOptions.HostConfig.CapAdd.push('NET_ADMIN', 'NET_RAW');
|
||||
if (capabilities.includes('mlock')) containerOptions.HostConfig.CapAdd.push('IPC_LOCK'); // mlock prevents swapping
|
||||
if (!capabilities.includes('ping')) containerOptions.HostConfig.CapDrop.push('NET_RAW'); // NET_RAW is included by default by Docker
|
||||
|
||||
containerOptions = _.extend(containerOptions, options);
|
||||
|
||||
|
||||
+38
-16
@@ -22,6 +22,7 @@ var assert = require('assert'),
|
||||
debug = require('debug')('box:externalldap'),
|
||||
groups = require('./groups.js'),
|
||||
ldap = require('ldapjs'),
|
||||
once = require('once'),
|
||||
settings = require('./settings.js'),
|
||||
tasks = require('./tasks.js'),
|
||||
users = require('./users.js');
|
||||
@@ -41,14 +42,14 @@ function translateUser(ldapConfig, ldapUser) {
|
||||
|
||||
return {
|
||||
username: ldapUser[ldapConfig.usernameField],
|
||||
email: ldapUser.mail,
|
||||
email: ldapUser.mail || ldapUser.mailPrimaryAddress,
|
||||
displayName: ldapUser.cn // user.giveName + ' ' + user.sn
|
||||
};
|
||||
}
|
||||
|
||||
function validUserRequirements(user) {
|
||||
if (!user.username || !user.email || !user.displayName) {
|
||||
debug(`[LDAP user empty username/email/displayName] username=${user.username} email=${user.email} displayName=${user.displayName}`);
|
||||
debug(`[Invalid LDAP user] username=${user.username} email=${user.email} displayName=${user.displayName}`);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
@@ -56,29 +57,46 @@ function validUserRequirements(user) {
|
||||
}
|
||||
|
||||
// performs service bind if required
|
||||
function getClient(externalLdapConfig, callback) {
|
||||
function getClient(externalLdapConfig, doBindAuth, callback) {
|
||||
assert.strictEqual(typeof externalLdapConfig, 'object');
|
||||
assert.strictEqual(typeof doBindAuth, 'boolean');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
// ensure we only callback once since we also have to listen to client.error events
|
||||
callback = once(callback);
|
||||
|
||||
// basic validation to not crash
|
||||
try { ldap.parseDN(externalLdapConfig.baseDn); } catch (e) { return callback(new BoxError(BoxError.BAD_FIELD, 'invalid baseDn')); }
|
||||
try { ldap.parseFilter(externalLdapConfig.filter); } catch (e) { return callback(new BoxError(BoxError.BAD_FIELD, 'invalid filter')); }
|
||||
|
||||
var config = {
|
||||
url: externalLdapConfig.url,
|
||||
tlsOptions: {
|
||||
rejectUnauthorized: externalLdapConfig.acceptSelfSignedCerts ? false : true
|
||||
}
|
||||
};
|
||||
|
||||
var client;
|
||||
try {
|
||||
client = ldap.createClient({ url: externalLdapConfig.url });
|
||||
client = ldap.createClient(config);
|
||||
} catch (e) {
|
||||
if (e instanceof ldap.ProtocolError) return callback(new BoxError(BoxError.BAD_FIELD, 'url protocol is invalid'));
|
||||
return callback(new BoxError(BoxError.INTERNAL_ERROR, e));
|
||||
}
|
||||
|
||||
if (!externalLdapConfig.bindDn) return callback(null, client);
|
||||
// ensure we don't just crash
|
||||
client.on('error', function (error) {
|
||||
callback(new BoxError(BoxError.EXTERNAL_ERROR, error));
|
||||
});
|
||||
|
||||
// skip bind auth if none exist or if not wanted
|
||||
if (!externalLdapConfig.bindDn || !doBindAuth) return callback(null, client);
|
||||
|
||||
client.bind(externalLdapConfig.bindDn, externalLdapConfig.bindPassword, function (error) {
|
||||
if (error instanceof ldap.InvalidCredentialsError) return callback(new BoxError(BoxError.INVALID_CREDENTIALS));
|
||||
if (error) return callback(new BoxError(BoxError.EXTERNAL_ERROR, error));
|
||||
|
||||
callback(null, client, externalLdapConfig);
|
||||
callback(null, client);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -87,7 +105,7 @@ function ldapGetByDN(externalLdapConfig, dn, callback) {
|
||||
assert.strictEqual(typeof dn, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
getClient(externalLdapConfig, function (error, client) {
|
||||
getClient(externalLdapConfig, true, function (error, client) {
|
||||
if (error) return callback(error);
|
||||
|
||||
let searchOptions = {
|
||||
@@ -124,7 +142,7 @@ function ldapUserSearch(externalLdapConfig, options, callback) {
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
getClient(externalLdapConfig, function (error, client) {
|
||||
getClient(externalLdapConfig, true, function (error, client) {
|
||||
if (error) return callback(error);
|
||||
|
||||
let searchOptions = {
|
||||
@@ -165,7 +183,7 @@ function ldapGroupSearch(externalLdapConfig, options, callback) {
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
getClient(externalLdapConfig, function (error, client) {
|
||||
getClient(externalLdapConfig, true, function (error, client) {
|
||||
if (error) return callback(error);
|
||||
|
||||
let searchOptions = {
|
||||
@@ -220,6 +238,7 @@ function testConfig(config, callback) {
|
||||
try { ldap.parseFilter(config.filter); } catch (e) { return callback(new BoxError(BoxError.BAD_FIELD, 'invalid filter')); }
|
||||
|
||||
if ('syncGroups' in config && typeof config.syncGroups !== 'boolean') return callback(new BoxError(BoxError.BAD_FIELD, 'syncGroups must be a boolean'));
|
||||
if ('acceptSelfSignedCerts' in config && typeof config.acceptSelfSignedCerts !== 'boolean') return callback(new BoxError(BoxError.BAD_FIELD, 'acceptSelfSignedCerts must be a boolean'));
|
||||
|
||||
if (config.syncGroups) {
|
||||
if (!config.groupBaseDn) return callback(new BoxError(BoxError.BAD_FIELD, 'groupBaseDn must not be empty'));
|
||||
@@ -231,7 +250,7 @@ function testConfig(config, callback) {
|
||||
if (!config.groupnameField || typeof config.groupnameField !== 'string') return callback(new BoxError(BoxError.BAD_FIELD, 'groupFilter must not be empty'));
|
||||
}
|
||||
|
||||
getClient(config, function (error, client) {
|
||||
getClient(config, true, function (error, client) {
|
||||
if (error) return callback(error);
|
||||
|
||||
var opts = {
|
||||
@@ -315,12 +334,15 @@ function verifyPassword(user, password, callback) {
|
||||
if (ldapUsers.length === 0) return callback(new BoxError(BoxError.NOT_FOUND));
|
||||
if (ldapUsers.length > 1) return callback(new BoxError(BoxError.CONFLICT));
|
||||
|
||||
let client = ldap.createClient({ url: externalLdapConfig.url });
|
||||
client.bind(ldapUsers[0].dn, password, function (error) {
|
||||
if (error instanceof ldap.InvalidCredentialsError) return callback(new BoxError(BoxError.INVALID_CREDENTIALS));
|
||||
if (error) return callback(new BoxError(BoxError.EXTERNAL_ERROR, error));
|
||||
getClient(externalLdapConfig, false, function (error, client) {
|
||||
if (error) return callback(error);
|
||||
|
||||
callback(null, translateUser(externalLdapConfig, ldapUsers[0]));
|
||||
client.bind(ldapUsers[0].dn, password, function (error) {
|
||||
if (error instanceof ldap.InvalidCredentialsError) return callback(new BoxError(BoxError.INVALID_CREDENTIALS));
|
||||
if (error) return callback(new BoxError(BoxError.EXTERNAL_ERROR, error));
|
||||
|
||||
callback(null, translateUser(externalLdapConfig, ldapUsers[0]));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -496,7 +518,7 @@ function syncGroupUsers(externalLdapConfig, progressCallback, callback) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
var ldapGroupMembers = found.member || [];
|
||||
var ldapGroupMembers = found.member || found.uniqueMember || [];
|
||||
|
||||
debug(`Group ${group.name} has ${ldapGroupMembers.length} members.`);
|
||||
|
||||
|
||||
@@ -17,11 +17,11 @@ exports = module.exports = {
|
||||
'images': {
|
||||
'turn': { repo: 'cloudron/turn', tag: 'cloudron/turn:1.1.0@sha256:e1dd22aa6eef5beb7339834b200a8bb787ffc2264ce11139857a054108fefb4f' },
|
||||
'mysql': { repo: 'cloudron/mysql', tag: 'cloudron/mysql:2.3.1@sha256:c1145d43c8a912fe6f5a5629a4052454a4aa6f23391c1efbffeec9d12d72a256' },
|
||||
'postgresql': { repo: 'cloudron/postgresql', tag: 'cloudron/postgresql:2.2.1@sha256:430f3e8b700327d4afa03a7b4e10a8b5544f171e0946ead8cdc5b67ee32db8e4' },
|
||||
'postgresql': { repo: 'cloudron/postgresql', tag: 'cloudron/postgresql:2.3.0@sha256:4112cc31a09b465bdfbd715fedab4bc86898246b4615d789dfb1d2cb728e3872' },
|
||||
'mongodb': { repo: 'cloudron/mongodb', tag: 'cloudron/mongodb:2.2.0@sha256:205486ff0f6bf6854610572df401cf3651bc62baf28fd26e9c5632497f10c2cb' },
|
||||
'redis': { repo: 'cloudron/redis', tag: 'cloudron/redis:2.2.0@sha256:cfdcc1a54cf29818cac99eacedc2ecf04e44995be3d06beea11dcaa09d90ed8d' },
|
||||
'redis': { repo: 'cloudron/redis', tag: 'cloudron/redis:2.3.0@sha256:0e31ec817e235b1814c04af97b1e7cf0053384aca2569570ce92bef0d95e94d2' },
|
||||
'mail': { repo: 'cloudron/mail', tag: 'cloudron/mail:2.9.4@sha256:0e169b97a0584a76197d2bbc039d8698bf93f815588b3b43c251bd83dd545465' },
|
||||
'graphite': { repo: 'cloudron/graphite', tag: 'cloudron/graphite:2.3.0@sha256:b7bc1ca4f4d0603a01369a689129aa273a938ce195fe43d00d42f4f2d5212f50' },
|
||||
'sftp': { repo: 'cloudron/sftp', tag: 'cloudron/sftp:1.1.0@sha256:0c1fe4dd6121900624dcb383251ecb0084c3810e095064933de671409d8d6d7b' }
|
||||
'sftp': { repo: 'cloudron/sftp', tag: 'cloudron/sftp:2.0.0@sha256:63cba3c70c91d440e1e436417ab33f655aa75c7bf936c0a5f4447e71e4ffbe4f' }
|
||||
}
|
||||
};
|
||||
|
||||
+1
-1
@@ -346,7 +346,7 @@ function mailboxSearch(req, res, next) {
|
||||
if (error) return callback(error);
|
||||
|
||||
aliases.forEach(function (a, idx) {
|
||||
obj.attributes['mail' + idx] = `${a}@${mailbox.domain}`;
|
||||
obj.attributes['mail' + idx] = `${a.name}@${a.domain}`;
|
||||
});
|
||||
|
||||
// ensure all filter values are also lowercase
|
||||
|
||||
+53
-37
@@ -1,52 +1,53 @@
|
||||
'use strict';
|
||||
|
||||
exports = module.exports = {
|
||||
getStatus: getStatus,
|
||||
checkConfiguration: checkConfiguration,
|
||||
getStatus,
|
||||
checkConfiguration,
|
||||
|
||||
getDomains: getDomains,
|
||||
getDomains,
|
||||
|
||||
getDomain: getDomain,
|
||||
clearDomains: clearDomains,
|
||||
getDomain,
|
||||
clearDomains,
|
||||
|
||||
onDomainAdded: onDomainAdded,
|
||||
onDomainRemoved: onDomainRemoved,
|
||||
onDomainAdded,
|
||||
onDomainRemoved,
|
||||
onMailFqdnChanged,
|
||||
|
||||
removePrivateFields: removePrivateFields,
|
||||
removePrivateFields,
|
||||
|
||||
setDnsRecords: setDnsRecords,
|
||||
onMailFqdnChanged: onMailFqdnChanged,
|
||||
setDnsRecords,
|
||||
|
||||
validateName: validateName,
|
||||
validateName,
|
||||
|
||||
setMailFromValidation: setMailFromValidation,
|
||||
setCatchAllAddress: setCatchAllAddress,
|
||||
setMailRelay: setMailRelay,
|
||||
setMailEnabled: setMailEnabled,
|
||||
setMailFromValidation,
|
||||
setCatchAllAddress,
|
||||
setMailRelay,
|
||||
setMailEnabled,
|
||||
|
||||
startMail: restartMail,
|
||||
restartMail: restartMail,
|
||||
handleCertChanged: handleCertChanged,
|
||||
getMailAuth: getMailAuth,
|
||||
restartMail,
|
||||
handleCertChanged,
|
||||
getMailAuth,
|
||||
|
||||
sendTestMail: sendTestMail,
|
||||
sendTestMail,
|
||||
|
||||
listMailboxes: listMailboxes,
|
||||
removeMailboxes: removeMailboxes,
|
||||
getMailbox: getMailbox,
|
||||
addMailbox: addMailbox,
|
||||
updateMailboxOwner: updateMailboxOwner,
|
||||
removeMailbox: removeMailbox,
|
||||
getMailboxCount,
|
||||
listMailboxes,
|
||||
removeMailboxes,
|
||||
getMailbox,
|
||||
addMailbox,
|
||||
updateMailboxOwner,
|
||||
removeMailbox,
|
||||
|
||||
getAliases: getAliases,
|
||||
setAliases: setAliases,
|
||||
getAliases,
|
||||
setAliases,
|
||||
|
||||
getLists: getLists,
|
||||
getList: getList,
|
||||
addList: addList,
|
||||
updateList: updateList,
|
||||
removeList: removeList,
|
||||
resolveList: resolveList,
|
||||
getLists,
|
||||
getList,
|
||||
addList,
|
||||
updateList,
|
||||
removeList,
|
||||
resolveList,
|
||||
|
||||
_readDkimPublicKeySync: readDkimPublicKeySync
|
||||
};
|
||||
@@ -1032,13 +1033,25 @@ function sendTestMail(domain, to, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function listMailboxes(domain, page, perPage, callback) {
|
||||
function listMailboxes(domain, search, page, perPage, callback) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert(typeof search === 'string' || search === null);
|
||||
assert.strictEqual(typeof page, 'number');
|
||||
assert.strictEqual(typeof perPage, 'number');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
mailboxdb.listMailboxes(domain, page, perPage, function (error, result) {
|
||||
mailboxdb.listMailboxes(domain, search, page, perPage, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
callback(null, result);
|
||||
});
|
||||
}
|
||||
|
||||
function getMailboxCount(domain, callback) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
mailboxdb.getMailboxCount(domain, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
callback(null, result);
|
||||
@@ -1165,11 +1178,14 @@ function setAliases(name, domain, aliases, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function getLists(domain, callback) {
|
||||
function getLists(domain, search, page, perPage, callback) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert(typeof search === 'string' || search === null);
|
||||
assert.strictEqual(typeof page, 'number');
|
||||
assert.strictEqual(typeof perPage, 'number');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
mailboxdb.getLists(domain, function (error, result) {
|
||||
mailboxdb.getLists(domain, search, page, perPage, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
callback(null, result);
|
||||
|
||||
@@ -8,7 +8,7 @@ be reset. If you did not request this reset, please ignore this message.
|
||||
To reset your password, please visit the following page:
|
||||
<%- resetLink %>
|
||||
|
||||
|
||||
Please note that the password reset link will expire in 24 hours.
|
||||
|
||||
Powered by https://cloudron.io
|
||||
|
||||
@@ -29,6 +29,10 @@ Powered by https://cloudron.io
|
||||
<a href="<%= resetLink %>">Click to reset your password</a>
|
||||
</p>
|
||||
|
||||
<br/>
|
||||
|
||||
Please note that the password reset link will expire in 24 hours.
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ Follow the link to get started.
|
||||
You are receiving this email because you were invited by <%= invitor.email %>.
|
||||
<% } %>
|
||||
|
||||
Please note that the invite link will expire in 24 hours.
|
||||
|
||||
Powered by https://cloudron.io
|
||||
|
||||
@@ -36,6 +37,9 @@ Powered by https://cloudron.io
|
||||
You are receiving this email because you were invited by <%= invitor.email %>.
|
||||
<% } %>
|
||||
|
||||
<br/>
|
||||
|
||||
Please note that the invite link will expire in 24 hours.
|
||||
<br/>
|
||||
|
||||
Powered by <a href="https://cloudron.io">Cloudron</a>
|
||||
|
||||
+58
-34
@@ -1,31 +1,32 @@
|
||||
'use strict';
|
||||
|
||||
exports = module.exports = {
|
||||
addMailbox: addMailbox,
|
||||
addList: addList,
|
||||
addMailbox,
|
||||
addList,
|
||||
|
||||
updateMailboxOwner: updateMailboxOwner,
|
||||
updateList: updateList,
|
||||
del: del,
|
||||
updateMailboxOwner,
|
||||
updateList,
|
||||
del,
|
||||
|
||||
listMailboxes: listMailboxes,
|
||||
getLists: getLists,
|
||||
getMailboxCount,
|
||||
listMailboxes,
|
||||
getLists,
|
||||
|
||||
listAllMailboxes: listAllMailboxes,
|
||||
listAllMailboxes,
|
||||
|
||||
get: get,
|
||||
getMailbox: getMailbox,
|
||||
getList: getList,
|
||||
getAlias: getAlias,
|
||||
get,
|
||||
getMailbox,
|
||||
getList,
|
||||
getAlias,
|
||||
|
||||
getAliasesForName: getAliasesForName,
|
||||
setAliasesForName: setAliasesForName,
|
||||
getAliasesForName,
|
||||
setAliasesForName,
|
||||
|
||||
getByOwnerId: getByOwnerId,
|
||||
delByOwnerId: delByOwnerId,
|
||||
delByDomain: delByDomain,
|
||||
getByOwnerId,
|
||||
delByOwnerId,
|
||||
delByDomain,
|
||||
|
||||
updateName: updateName,
|
||||
updateName,
|
||||
|
||||
_clear: clear,
|
||||
|
||||
@@ -37,6 +38,7 @@ exports = module.exports = {
|
||||
var assert = require('assert'),
|
||||
BoxError = require('./boxerror.js'),
|
||||
database = require('./database.js'),
|
||||
mysql = require('mysql'),
|
||||
safe = require('safetydance'),
|
||||
util = require('util');
|
||||
|
||||
@@ -203,20 +205,35 @@ function getMailbox(name, domain, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function listMailboxes(domain, page, perPage, callback) {
|
||||
function getMailboxCount(domain, callback) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
database.query('SELECT COUNT(*) AS total FROM mailboxes WHERE type = ? AND domain = ?', [ exports.TYPE_MAILBOX, domain ], function (error, results) {
|
||||
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
|
||||
|
||||
callback(null, results[0].total);
|
||||
});
|
||||
}
|
||||
|
||||
function listMailboxes(domain, search, page, perPage, callback) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert(typeof search === 'string' || search === null);
|
||||
assert.strictEqual(typeof page, 'number');
|
||||
assert.strictEqual(typeof perPage, 'number');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
database.query(`SELECT ${MAILBOX_FIELDS} FROM mailboxes WHERE type = ? AND domain = ? ORDER BY name LIMIT ${(page-1)*perPage},${perPage}`,
|
||||
[ exports.TYPE_MAILBOX, domain ], function (error, results) {
|
||||
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
|
||||
let query = `SELECT ${MAILBOX_FIELDS} FROM mailboxes WHERE type = ? AND domain = ?`;
|
||||
if (search) query += ' AND (name LIKE ' + mysql.escape('%' + search + '%') + ')';
|
||||
query += 'ORDER BY name LIMIT ?,?';
|
||||
|
||||
results.forEach(function (result) { postProcess(result); });
|
||||
database.query(query, [ exports.TYPE_MAILBOX, domain, (page-1)*perPage, perPage ], function (error, results) {
|
||||
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
|
||||
|
||||
callback(null, results);
|
||||
});
|
||||
results.forEach(function (result) { postProcess(result); });
|
||||
|
||||
callback(null, results);
|
||||
});
|
||||
}
|
||||
|
||||
function listAllMailboxes(page, perPage, callback) {
|
||||
@@ -224,8 +241,8 @@ function listAllMailboxes(page, perPage, callback) {
|
||||
assert.strictEqual(typeof perPage, 'number');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
database.query(`SELECT ${MAILBOX_FIELDS} FROM mailboxes WHERE type = ? ORDER BY name LIMIT ${(page-1)*perPage},${perPage}`,
|
||||
[ exports.TYPE_MAILBOX ], function (error, results) {
|
||||
database.query(`SELECT ${MAILBOX_FIELDS} FROM mailboxes WHERE type = ? ORDER BY name LIMIT ?,?`,
|
||||
[ exports.TYPE_MAILBOX, (page-1)*perPage, perPage ], function (error, results) {
|
||||
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
|
||||
|
||||
results.forEach(function (result) { postProcess(result); });
|
||||
@@ -234,18 +251,25 @@ function listAllMailboxes(page, perPage, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function getLists(domain, callback) {
|
||||
function getLists(domain, search, page, perPage, callback) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert(typeof search === 'string' || search === null);
|
||||
assert.strictEqual(typeof page, 'number');
|
||||
assert.strictEqual(typeof perPage, 'number');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
database.query('SELECT ' + MAILBOX_FIELDS + ' FROM mailboxes WHERE type = ? AND domain = ?',
|
||||
[ exports.TYPE_LIST, domain ], function (error, results) {
|
||||
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
|
||||
let query = `SELECT ${MAILBOX_FIELDS} FROM mailboxes WHERE type = ? AND domain = ?`;
|
||||
if (search) query += ' AND (name LIKE ' + mysql.escape('%' + search + '%') + ' OR membersJson LIKE ' + mysql.escape('%' + search + '%') + ')';
|
||||
|
||||
results.forEach(function (result) { postProcess(result); });
|
||||
query += 'ORDER BY name LIMIT ?,?';
|
||||
|
||||
callback(null, results);
|
||||
});
|
||||
database.query(query, [ exports.TYPE_LIST, domain, (page-1)*perPage, perPage ], function (error, results) {
|
||||
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
|
||||
|
||||
results.forEach(function (result) { postProcess(result); });
|
||||
|
||||
callback(null, results);
|
||||
});
|
||||
}
|
||||
|
||||
function getList(name, domain, callback) {
|
||||
|
||||
+12
-5
@@ -7,6 +7,7 @@ map $http_upgrade $connection_upgrade {
|
||||
# http server
|
||||
server {
|
||||
listen 80;
|
||||
server_tokens off; # hide version
|
||||
<% if (hasIPv6) { -%>
|
||||
listen [::]:80;
|
||||
<% } -%>
|
||||
@@ -42,18 +43,19 @@ server {
|
||||
server {
|
||||
<% if (vhost) { -%>
|
||||
server_name <%= vhost %>;
|
||||
listen 443 http2;
|
||||
listen 443 ssl http2;
|
||||
<% if (hasIPv6) { -%>
|
||||
listen [::]:443 http2;
|
||||
listen [::]:443 ssl http2;
|
||||
<% } -%>
|
||||
<% } else { -%>
|
||||
listen 443 http2 default_server;
|
||||
listen 443 ssl http2 default_server;
|
||||
<% if (hasIPv6) { -%>
|
||||
listen [::]:443 http2 default_server;
|
||||
listen [::]:443 ssl http2 default_server;
|
||||
<% } -%>
|
||||
<% } -%>
|
||||
|
||||
ssl on;
|
||||
server_tokens off; # hide version
|
||||
|
||||
# paths are relative to prefix and not to this file
|
||||
ssl_certificate <%= certFilePath %>;
|
||||
ssl_certificate_key <%= keyFilePath %>;
|
||||
@@ -193,6 +195,11 @@ server {
|
||||
client_max_body_size 0;
|
||||
}
|
||||
|
||||
location ~ ^/api/v1/apps/.*/files/ {
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
client_max_body_size 0;
|
||||
}
|
||||
|
||||
# graphite paths (uncomment block below and visit /graphite-web/dashboard)
|
||||
# remember to comment out the CSP policy as well to access the graphite dashboard
|
||||
# location ~ ^/graphite-web/ {
|
||||
|
||||
+1
-1
@@ -226,11 +226,11 @@ function getStatus(callback) {
|
||||
version: constants.VERSION,
|
||||
apiServerOrigin: settings.apiServerOrigin(), // used by CaaS tool
|
||||
webServerOrigin: settings.webServerOrigin(), // used by CaaS tool
|
||||
provider: settings.provider(),
|
||||
cloudronName: allSettings[settings.CLOUDRON_NAME_KEY],
|
||||
footer: allSettings[settings.FOOTER_KEY] || constants.FOOTER,
|
||||
adminFqdn: settings.adminDomain() ? settings.adminFqdn() : null,
|
||||
activated: activated,
|
||||
provider: settings.provider() // used by setup wizard of marketplace images
|
||||
}, gProvisionStatus));
|
||||
});
|
||||
});
|
||||
|
||||
+10
-23
@@ -86,7 +86,7 @@ function logout(req, res) {
|
||||
function passwordResetRequest(req, res, next) {
|
||||
if (!req.body.identifier || typeof req.body.identifier !== 'string') return next(new HttpError(401, 'A identifier must be non-empty string'));
|
||||
|
||||
users.resetPasswordByIdentifier(req.body.identifier, function (error) {
|
||||
users.sendPasswordResetByIdentifier(req.body.identifier, function (error) {
|
||||
if (error && error.reason !== BoxError.NOT_FOUND) return next(BoxError.toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(202, {}));
|
||||
@@ -102,6 +102,7 @@ function passwordReset(req, res, next) {
|
||||
users.getByResetToken(req.body.resetToken, function (error, userObject) {
|
||||
if (error) return next(new HttpError(401, 'Invalid resetToken'));
|
||||
|
||||
// if you fix the duration here, the emails and UI have to be fixed as well
|
||||
if (Date.now() - userObject.resetTokenCreationTime > 24 * 60 * 60 * 1000) return next(new HttpError(401, 'Token expired'));
|
||||
if (!userObject.username) return next(new HttpError(409, 'No username set'));
|
||||
|
||||
@@ -122,37 +123,23 @@ function passwordReset(req, res, next) {
|
||||
function setupAccount(req, res, next) {
|
||||
assert.strictEqual(typeof req.body, 'object');
|
||||
|
||||
if (!req.body.email || typeof req.body.email !== 'string') return next(new HttpError(400, 'email must be a non-empty string'));
|
||||
if (!req.body.resetToken || typeof req.body.resetToken !== 'string') return next(new HttpError(400, 'resetToken must be a non-empty string'));
|
||||
if (!req.body.password || typeof req.body.password !== 'string') return next(new HttpError(400, 'password must be a non-empty string'));
|
||||
if (!req.body.username || typeof req.body.username !== 'string') return next(new HttpError(400, 'username must be a non-empty string'));
|
||||
if (!req.body.displayName || typeof req.body.displayName !== 'string') return next(new HttpError(400, 'displayName must be a non-empty string'));
|
||||
|
||||
// only sent if profile is not locked
|
||||
if ('username' in req.body && typeof req.body.username !== 'string') return next(new HttpError(400, 'username must be a non-empty string'));
|
||||
if ('displayName' in req.body && typeof req.body.displayName !== 'string') return next(new HttpError(400, 'displayName must be a non-empty string'));
|
||||
|
||||
users.getByResetToken(req.body.resetToken, function (error, userObject) {
|
||||
if (error) return next(new HttpError(401, 'Invalid Reset Token'));
|
||||
|
||||
// if you fix the duration here, the emails and UI have to be fixed as well
|
||||
if (Date.now() - userObject.resetTokenCreationTime > 24 * 60 * 60 * 1000) return next(new HttpError(401, 'Token expired'));
|
||||
|
||||
users.update(userObject, { username: req.body.username, displayName: req.body.displayName }, auditSource.fromRequest(req), function (error) {
|
||||
if (error && error.reason === BoxError.ALREADY_EXISTS) return next(new HttpError(409, 'Username already used'));
|
||||
if (error && error.reason === BoxError.BAD_FIELD) return next(new HttpError(400, error.message));
|
||||
if (error && error.reason === BoxError.NOT_FOUND) return next(new HttpError(404, 'No such user'));
|
||||
if (error) return next(new HttpError(500, error));
|
||||
users.setupAccount(userObject, req.body, auditSource.fromRequest(req), function (error, accessToken) {
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
userObject.username = req.body.username;
|
||||
userObject.displayName = req.body.displayName;
|
||||
|
||||
// setPassword clears the resetToken
|
||||
users.setPassword(userObject, req.body.password, function (error) {
|
||||
if (error && error.reason === BoxError.BAD_FIELD) return next(new HttpError(400, error.message));
|
||||
if (error) return next(new HttpError(500, error));
|
||||
|
||||
tokens.add(tokens.ID_WEBADMIN, userObject.id, Date.now() + constants.DEFAULT_TOKEN_EXPIRATION, {}, function (error, result) {
|
||||
if (error) return next(new HttpError(500, error));
|
||||
|
||||
next(new HttpSuccess(201, { accessToken: result.accessToken }));
|
||||
});
|
||||
});
|
||||
next(new HttpSuccess(201, { accessToken }));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
'use strict';
|
||||
|
||||
exports = module.exports = {
|
||||
proxy
|
||||
};
|
||||
|
||||
var assert = require('assert'),
|
||||
BoxError = require('../boxerror.js'),
|
||||
docker = require('../docker.js'),
|
||||
middleware = require('../middleware/index.js'),
|
||||
HttpError = require('connect-lastmile').HttpError,
|
||||
safe = require('safetydance'),
|
||||
url = require('url');
|
||||
|
||||
function proxy(req, res, next) {
|
||||
assert.strictEqual(typeof req.params.id, 'string');
|
||||
|
||||
const appId = req.params.id;
|
||||
|
||||
req.clearTimeout();
|
||||
|
||||
docker.inspect('sftp', function (error, result) {
|
||||
if (error)return next(BoxError.toHttpError(error));
|
||||
|
||||
const ip = safe.query(result, 'NetworkSettings.Networks.cloudron.IPAddress', null);
|
||||
if (!ip) return next(new BoxError(BoxError.INACTIVE, 'Error getting IP of sftp service'));
|
||||
|
||||
req.url = req.originalUrl.replace(`/api/v1/apps/${appId}/files`, `/files/${appId}`);
|
||||
|
||||
const proxyOptions = url.parse(`https://${ip}:3000`);
|
||||
proxyOptions.rejectUnauthorized = false;
|
||||
const fileManagerProxy = middleware.proxy(proxyOptions);
|
||||
|
||||
fileManagerProxy(req, res, function (error) {
|
||||
if (!error) return next();
|
||||
|
||||
if (error.code === 'ECONNREFUSED') return next(new HttpError(424, 'Unable to connect to filemanager server'));
|
||||
if (error.code === 'ECONNRESET') return next(new HttpError(424, 'Unable to query filemanager server'));
|
||||
|
||||
next(new HttpError(500, error));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -10,6 +10,7 @@ exports = module.exports = {
|
||||
cloudron: require('./cloudron.js'),
|
||||
domains: require('./domains.js'),
|
||||
eventlog: require('./eventlog.js'),
|
||||
filemanager: require('./filemanager.js'),
|
||||
graphs: require('./graphs.js'),
|
||||
groups: require('./groups.js'),
|
||||
mail: require('./mail.js'),
|
||||
|
||||
+44
-22
@@ -1,33 +1,35 @@
|
||||
'use strict';
|
||||
|
||||
exports = module.exports = {
|
||||
getDomain: getDomain,
|
||||
getDomain,
|
||||
|
||||
setDnsRecords: setDnsRecords,
|
||||
setDnsRecords,
|
||||
|
||||
getStatus: getStatus,
|
||||
getStatus,
|
||||
|
||||
setMailFromValidation: setMailFromValidation,
|
||||
setCatchAllAddress: setCatchAllAddress,
|
||||
setMailRelay: setMailRelay,
|
||||
setMailEnabled: setMailEnabled,
|
||||
setMailFromValidation,
|
||||
setCatchAllAddress,
|
||||
setMailRelay,
|
||||
setMailEnabled,
|
||||
|
||||
sendTestMail: sendTestMail,
|
||||
sendTestMail,
|
||||
|
||||
listMailboxes: listMailboxes,
|
||||
getMailbox: getMailbox,
|
||||
addMailbox: addMailbox,
|
||||
updateMailbox: updateMailbox,
|
||||
removeMailbox: removeMailbox,
|
||||
listMailboxes,
|
||||
getMailbox,
|
||||
addMailbox,
|
||||
updateMailbox,
|
||||
removeMailbox,
|
||||
|
||||
getAliases: getAliases,
|
||||
setAliases: setAliases,
|
||||
getAliases,
|
||||
setAliases,
|
||||
|
||||
getLists: getLists,
|
||||
getList: getList,
|
||||
addList: addList,
|
||||
updateList: updateList,
|
||||
removeList: removeList,
|
||||
getLists,
|
||||
getList,
|
||||
addList,
|
||||
updateList,
|
||||
removeList,
|
||||
|
||||
getMailboxCount
|
||||
};
|
||||
|
||||
var assert = require('assert'),
|
||||
@@ -159,13 +161,25 @@ function listMailboxes(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 positive number'));
|
||||
|
||||
mail.listMailboxes(req.params.domain, page, perPage, function (error, result) {
|
||||
if (req.query.search && typeof req.query.search !== 'string') return next(new HttpError(400, 'search must be a string'));
|
||||
|
||||
mail.listMailboxes(req.params.domain, req.query.search || null, page, perPage, function (error, result) {
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(200, { mailboxes: result }));
|
||||
});
|
||||
}
|
||||
|
||||
function getMailboxCount(req, res, next) {
|
||||
assert.strictEqual(typeof req.params.domain, 'string');
|
||||
|
||||
mail.getMailboxCount(req.params.domain, function (error, count) {
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(200, { count }));
|
||||
});
|
||||
}
|
||||
|
||||
function getMailbox(req, res, next) {
|
||||
assert.strictEqual(typeof req.params.domain, 'string');
|
||||
assert.strictEqual(typeof req.params.name, 'string');
|
||||
@@ -248,7 +262,15 @@ function setAliases(req, res, next) {
|
||||
function getLists(req, res, next) {
|
||||
assert.strictEqual(typeof req.params.domain, 'string');
|
||||
|
||||
mail.getLists(req.params.domain, function (error, result) {
|
||||
const page = typeof req.query.page !== 'undefined' ? parseInt(req.query.page) : 1;
|
||||
if (!page || page < 0) return next(new HttpError(400, 'page query param has to be a positive number'));
|
||||
|
||||
const 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 positive number'));
|
||||
|
||||
if (req.query.search && typeof req.query.search !== 'string') return next(new HttpError(400, 'search must be a string'));
|
||||
|
||||
mail.getLists(req.params.domain, req.query.search || null, page, perPage, function (error, result) {
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(200, { lists: result }));
|
||||
|
||||
+34
-21
@@ -1,34 +1,41 @@
|
||||
'use strict';
|
||||
|
||||
exports = module.exports = {
|
||||
get: get,
|
||||
update: update,
|
||||
getAvatar: getAvatar,
|
||||
setAvatar: setAvatar,
|
||||
clearAvatar: clearAvatar,
|
||||
changePassword: changePassword,
|
||||
setTwoFactorAuthenticationSecret: setTwoFactorAuthenticationSecret,
|
||||
enableTwoFactorAuthentication: enableTwoFactorAuthentication,
|
||||
disableTwoFactorAuthentication: disableTwoFactorAuthentication
|
||||
authorize,
|
||||
get,
|
||||
update,
|
||||
getAvatar,
|
||||
setAvatar,
|
||||
clearAvatar,
|
||||
changePassword,
|
||||
setTwoFactorAuthenticationSecret,
|
||||
enableTwoFactorAuthentication,
|
||||
disableTwoFactorAuthentication,
|
||||
};
|
||||
|
||||
var assert = require('assert'),
|
||||
auditSource = require('../auditsource.js'),
|
||||
BoxError = require('../boxerror.js'),
|
||||
fs = require('fs'),
|
||||
HttpError = require('connect-lastmile').HttpError,
|
||||
HttpSuccess = require('connect-lastmile').HttpSuccess,
|
||||
path = require('path'),
|
||||
paths = require('../paths.js'),
|
||||
safe = require('safetydance'),
|
||||
users = require('../users.js'),
|
||||
settings = require('../settings.js'),
|
||||
_ = require('underscore');
|
||||
|
||||
function get(req, res, next) {
|
||||
function authorize(req, res, next) {
|
||||
assert.strictEqual(typeof req.user, 'object');
|
||||
|
||||
const emailHash = require('crypto').createHash('md5').update(req.user.email).digest('hex');
|
||||
settings.getDirectoryConfig(function (error, directoryConfig) {
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
if (directoryConfig.lockUserProfiles) return next(new HttpError(403, 'admin has disallowed users from editing profiles'));
|
||||
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
function get(req, res, next) {
|
||||
assert.strictEqual(typeof req.user, 'object');
|
||||
|
||||
next(new HttpSuccess(200, {
|
||||
id: req.user.id,
|
||||
@@ -39,7 +46,7 @@ function get(req, res, next) {
|
||||
twoFactorAuthenticationEnabled: req.user.twoFactorAuthenticationEnabled,
|
||||
role: req.user.role,
|
||||
source: req.user.source,
|
||||
avatarUrl: fs.existsSync(path.join(paths.PROFILE_ICONS_DIR, req.user.id)) ? `${settings.adminOrigin()}/api/v1/profile/avatar/${req.user.id}` : `https://www.gravatar.com/avatar/${emailHash}.jpg`
|
||||
avatarUrl: users.getAvatarUrlSync(req.user)
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -65,21 +72,27 @@ function setAvatar(req, res, next) {
|
||||
|
||||
if (!req.files.avatar) return next(new HttpError(400, 'avatar is missing'));
|
||||
|
||||
if (!safe.fs.renameSync(req.files.avatar.path, path.join(paths.PROFILE_ICONS_DIR, req.user.id))) return next(new HttpError(500, safe.error));
|
||||
users.setAvatar(req.user.id, req.files.avatar.path, function (error) {
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(202, {}));
|
||||
next(new HttpSuccess(202, {}));
|
||||
});
|
||||
}
|
||||
|
||||
function clearAvatar(req, res, next) {
|
||||
assert.strictEqual(typeof req.user, 'object');
|
||||
|
||||
safe.fs.unlinkSync(path.join(paths.PROFILE_ICONS_DIR, req.user.id));
|
||||
users.clearAvatar(req.user.id, function (error) {
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(202, {}));
|
||||
next(new HttpSuccess(202, {}));
|
||||
});
|
||||
}
|
||||
|
||||
function getAvatar(req, res) {
|
||||
res.sendFile(path.join(paths.PROFILE_ICONS_DIR, req.params.identifier));
|
||||
assert.strictEqual(typeof req.params.identifier, 'string');
|
||||
|
||||
res.sendFile(users.getAvatarFileSync(req.params.identifier));
|
||||
}
|
||||
|
||||
function changePassword(req, res, next) {
|
||||
|
||||
@@ -122,7 +122,7 @@ function getStatus(req, res, next) {
|
||||
|
||||
// check if Cloudron is not in setup state nor activated and let appstore know of the attempt
|
||||
if (!status.activated && !status.setup.active && !status.restore.active) {
|
||||
appstore.trackBeginSetup(status.provider);
|
||||
appstore.trackBeginSetup();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -234,6 +234,27 @@ function setRegistryConfig(req, res, next) {
|
||||
});
|
||||
}
|
||||
|
||||
function getDirectoryConfig(req, res, next) {
|
||||
settings.getDirectoryConfig(function (error, directoryConfig) {
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(200, directoryConfig));
|
||||
});
|
||||
}
|
||||
|
||||
function setDirectoryConfig(req, res, next) {
|
||||
assert.strictEqual(typeof req.body, 'object');
|
||||
|
||||
if (typeof req.body.lockUserProfiles !== 'boolean') return next(new HttpError(400, 'lockUserProfiles is required'));
|
||||
if (typeof req.body.mandatory2FA !== 'boolean') return next(new HttpError(400, 'mandatory2FA is required'));
|
||||
|
||||
settings.setDirectoryConfig(req.body, function (error) {
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(200, {}));
|
||||
});
|
||||
}
|
||||
|
||||
function getSysinfoConfig(req, res, next) {
|
||||
settings.getSysinfoConfig(function (error, sysinfoConfig) {
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
@@ -270,6 +291,7 @@ function get(req, res, next) {
|
||||
case settings.BOX_AUTOUPDATE_PATTERN_KEY: return getBoxAutoupdatePattern(req, res, next);
|
||||
case settings.TIME_ZONE_KEY: return getTimeZone(req, res, next);
|
||||
|
||||
case settings.DIRECTORY_CONFIG_KEY: return getDirectoryConfig(req, res, next);
|
||||
case settings.SUPPORT_CONFIG_KEY: return getSupportConfig(req, res, next);
|
||||
|
||||
default: return next(new HttpError(404, 'No such setting'));
|
||||
@@ -291,6 +313,8 @@ function set(req, res, next) {
|
||||
case settings.BOX_AUTOUPDATE_PATTERN_KEY: return setBoxAutoupdatePattern(req, res, next);
|
||||
case settings.TIME_ZONE_KEY: return setTimeZone(req, res, next);
|
||||
|
||||
case settings.DIRECTORY_CONFIG_KEY: return setDirectoryConfig(req, res, next);
|
||||
|
||||
default: return next(new HttpError(404, 'No such setting'));
|
||||
}
|
||||
}
|
||||
|
||||
+35
-11
@@ -1,18 +1,20 @@
|
||||
'use strict';
|
||||
|
||||
exports = module.exports = {
|
||||
get: get,
|
||||
update: update,
|
||||
list: list,
|
||||
create: create,
|
||||
remove: remove,
|
||||
changePassword: changePassword,
|
||||
verifyPassword: verifyPassword,
|
||||
createInvite: createInvite,
|
||||
sendInvite: sendInvite,
|
||||
setGroups: setGroups,
|
||||
get,
|
||||
update,
|
||||
list,
|
||||
create,
|
||||
remove,
|
||||
changePassword,
|
||||
verifyPassword,
|
||||
createInvite,
|
||||
sendInvite,
|
||||
setGroups,
|
||||
setAvatar,
|
||||
clearAvatar,
|
||||
|
||||
load: load
|
||||
load
|
||||
};
|
||||
|
||||
var assert = require('assert'),
|
||||
@@ -192,3 +194,25 @@ function changePassword(req, res, next) {
|
||||
next(new HttpSuccess(204));
|
||||
});
|
||||
}
|
||||
|
||||
function setAvatar(req, res, next) {
|
||||
assert.strictEqual(typeof req.resource, 'object');
|
||||
|
||||
if (!req.files.avatar) return next(new HttpError(400, 'avatar is missing'));
|
||||
|
||||
users.setAvatar(req.resource.id, req.files.avatar.path, function (error) {
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(202, {}));
|
||||
});
|
||||
}
|
||||
|
||||
function clearAvatar(req, res, next) {
|
||||
assert.strictEqual(typeof req.resource, 'object');
|
||||
|
||||
users.clearAvatar(req.resource.id, function (error) {
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(202, {}));
|
||||
});
|
||||
}
|
||||
|
||||
+164
-164
@@ -66,7 +66,6 @@ function initializeExpressSync() {
|
||||
// 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(json)
|
||||
.use(urlencoded)
|
||||
.use(middleware.cors({ origins: [ '*' ], allowCredentials: false }))
|
||||
.use(router)
|
||||
@@ -86,163 +85,164 @@ function initializeExpressSync() {
|
||||
const authorizeUserManager = routes.accesscontrol.authorize(users.ROLE_USER_MANAGER);
|
||||
|
||||
// public routes
|
||||
router.post('/api/v1/cloudron/setup', routes.provision.providerTokenAuth, routes.provision.setup); // only available until no-domain
|
||||
router.post('/api/v1/cloudron/restore', routes.provision.restore); // only available until activated
|
||||
router.post('/api/v1/cloudron/activate', routes.provision.activate);
|
||||
router.get ('/api/v1/cloudron/status', routes.provision.getStatus);
|
||||
|
||||
router.get ('/api/v1/cloudron/avatar', routes.branding.getCloudronAvatar); // this is a public alias for /api/v1/branding/cloudron_avatar
|
||||
router.post('/api/v1/cloudron/setup', json, routes.provision.providerTokenAuth, routes.provision.setup); // only available until no-domain
|
||||
router.post('/api/v1/cloudron/restore', json, routes.provision.restore); // only available until activated
|
||||
router.post('/api/v1/cloudron/activate', json, routes.provision.activate);
|
||||
router.get ('/api/v1/cloudron/status', routes.provision.getStatus);
|
||||
router.get ('/api/v1/cloudron/avatar', routes.branding.getCloudronAvatar); // this is a public alias for /api/v1/branding/cloudron_avatar
|
||||
|
||||
// login/logout routes
|
||||
router.post('/api/v1/cloudron/login', password, routes.cloudron.login);
|
||||
router.get ('/api/v1/cloudron/logout', routes.cloudron.logout); // this will invalidate the token if any and redirect to /login.html always
|
||||
router.post('/api/v1/cloudron/password_reset_request', routes.cloudron.passwordResetRequest);
|
||||
router.post('/api/v1/cloudron/password_reset', routes.cloudron.passwordReset);
|
||||
router.post('/api/v1/cloudron/setup_account', routes.cloudron.setupAccount);
|
||||
router.post('/api/v1/cloudron/login', json, password, routes.cloudron.login);
|
||||
router.get ('/api/v1/cloudron/logout', routes.cloudron.logout); // this will invalidate the token if any and redirect to /login.html always
|
||||
router.post('/api/v1/cloudron/password_reset_request', json, routes.cloudron.passwordResetRequest);
|
||||
router.post('/api/v1/cloudron/password_reset', json, routes.cloudron.passwordReset);
|
||||
router.post('/api/v1/cloudron/setup_account', json, routes.cloudron.setupAccount);
|
||||
|
||||
// developer routes
|
||||
router.post('/api/v1/developer/login', password, routes.cloudron.login); // DEPRECATED we should use the regular /api/v1/cloudron/login
|
||||
router.post('/api/v1/developer/login', json, password, routes.cloudron.login); // DEPRECATED we should use the regular /api/v1/cloudron/login
|
||||
|
||||
// cloudron routes
|
||||
router.get ('/api/v1/cloudron/update', token, authorizeAdmin, routes.cloudron.getUpdateInfo);
|
||||
router.post('/api/v1/cloudron/update', token, authorizeAdmin, routes.cloudron.update);
|
||||
router.post('/api/v1/cloudron/prepare_dashboard_domain', token, authorizeAdmin, routes.cloudron.prepareDashboardDomain);
|
||||
router.post('/api/v1/cloudron/set_dashboard_domain', token, authorizeAdmin, routes.cloudron.setDashboardAndMailDomain);
|
||||
router.post('/api/v1/cloudron/renew_certs', token, authorizeAdmin, routes.cloudron.renewCerts);
|
||||
router.post('/api/v1/cloudron/check_for_updates', token, authorizeAdmin, routes.cloudron.checkForUpdates);
|
||||
router.get ('/api/v1/cloudron/reboot', token, authorizeAdmin, routes.cloudron.isRebootRequired);
|
||||
router.post('/api/v1/cloudron/reboot', token, authorizeAdmin, routes.cloudron.reboot);
|
||||
router.get ('/api/v1/cloudron/graphs', token, authorizeAdmin, routes.graphs.getGraphs);
|
||||
router.get ('/api/v1/cloudron/disks', token, authorizeAdmin, routes.cloudron.getDisks);
|
||||
router.get ('/api/v1/cloudron/memory', token, authorizeAdmin, routes.cloudron.getMemory);
|
||||
router.get ('/api/v1/cloudron/logs/:unit', token, authorizeAdmin, routes.cloudron.getLogs);
|
||||
router.get ('/api/v1/cloudron/logstream/:unit', token, authorizeAdmin, routes.cloudron.getLogStream);
|
||||
router.get ('/api/v1/cloudron/eventlog', token, authorizeAdmin, routes.eventlog.list);
|
||||
router.get ('/api/v1/cloudron/eventlog/:eventId', token, authorizeAdmin, routes.eventlog.get);
|
||||
router.post('/api/v1/cloudron/sync_external_ldap', token, authorizeAdmin, routes.cloudron.syncExternalLdap);
|
||||
router.get ('/api/v1/cloudron/server_ip', token, authorizeAdmin, routes.cloudron.getServerIp);
|
||||
router.get ('/api/v1/cloudron/update', token, authorizeAdmin, routes.cloudron.getUpdateInfo);
|
||||
router.post('/api/v1/cloudron/update', json, token, authorizeAdmin, routes.cloudron.update);
|
||||
router.post('/api/v1/cloudron/prepare_dashboard_domain', json, token, authorizeAdmin, routes.cloudron.prepareDashboardDomain);
|
||||
router.post('/api/v1/cloudron/set_dashboard_domain', json, token, authorizeAdmin, routes.cloudron.setDashboardAndMailDomain);
|
||||
router.post('/api/v1/cloudron/renew_certs', json, token, authorizeAdmin, routes.cloudron.renewCerts);
|
||||
router.post('/api/v1/cloudron/check_for_updates', json, token, authorizeAdmin, routes.cloudron.checkForUpdates);
|
||||
router.get ('/api/v1/cloudron/reboot', token, authorizeAdmin, routes.cloudron.isRebootRequired);
|
||||
router.post('/api/v1/cloudron/reboot', json, token, authorizeAdmin, routes.cloudron.reboot);
|
||||
router.get ('/api/v1/cloudron/graphs', token, authorizeAdmin, routes.graphs.getGraphs);
|
||||
router.get ('/api/v1/cloudron/disks', token, authorizeAdmin, routes.cloudron.getDisks);
|
||||
router.get ('/api/v1/cloudron/memory', token, authorizeAdmin, routes.cloudron.getMemory);
|
||||
router.get ('/api/v1/cloudron/logs/:unit', token, authorizeAdmin, routes.cloudron.getLogs);
|
||||
router.get ('/api/v1/cloudron/logstream/:unit', token, authorizeAdmin, routes.cloudron.getLogStream);
|
||||
router.get ('/api/v1/cloudron/eventlog', token, authorizeAdmin, routes.eventlog.list);
|
||||
router.get ('/api/v1/cloudron/eventlog/:eventId', token, authorizeAdmin, routes.eventlog.get);
|
||||
router.post('/api/v1/cloudron/sync_external_ldap', json, token, authorizeAdmin, routes.cloudron.syncExternalLdap);
|
||||
router.get ('/api/v1/cloudron/server_ip', token, authorizeAdmin, routes.cloudron.getServerIp);
|
||||
|
||||
// tasks
|
||||
router.get ('/api/v1/tasks', token, authorizeAdmin, routes.tasks.list);
|
||||
router.get ('/api/v1/tasks/:taskId', token, authorizeAdmin, routes.tasks.get);
|
||||
router.get ('/api/v1/tasks/:taskId/logs', token, authorizeAdmin, routes.tasks.getLogs);
|
||||
router.get ('/api/v1/tasks/:taskId/logstream', token, authorizeAdmin, routes.tasks.getLogStream);
|
||||
router.post('/api/v1/tasks/:taskId/stop', token, authorizeAdmin, routes.tasks.stopTask);
|
||||
// task routes
|
||||
router.get ('/api/v1/tasks', token, authorizeAdmin, routes.tasks.list);
|
||||
router.get ('/api/v1/tasks/:taskId', token, authorizeAdmin, routes.tasks.get);
|
||||
router.get ('/api/v1/tasks/:taskId/logs', token, authorizeAdmin, routes.tasks.getLogs);
|
||||
router.get ('/api/v1/tasks/:taskId/logstream', token, authorizeAdmin, routes.tasks.getLogStream);
|
||||
router.post('/api/v1/tasks/:taskId/stop', json, token, authorizeAdmin, routes.tasks.stopTask);
|
||||
|
||||
// notifications
|
||||
router.get ('/api/v1/notifications', token, routes.notifications.verifyOwnership, routes.notifications.list);
|
||||
router.get ('/api/v1/notifications/:notificationId', token, routes.notifications.verifyOwnership, routes.notifications.get);
|
||||
router.post('/api/v1/notifications/:notificationId', token, routes.notifications.verifyOwnership, routes.notifications.ack);
|
||||
// notification routes
|
||||
router.get ('/api/v1/notifications', token, routes.notifications.verifyOwnership, routes.notifications.list);
|
||||
router.get ('/api/v1/notifications/:notificationId', token, routes.notifications.verifyOwnership, routes.notifications.get);
|
||||
router.post('/api/v1/notifications/:notificationId', json, token, routes.notifications.verifyOwnership, routes.notifications.ack);
|
||||
|
||||
// backups
|
||||
router.get ('/api/v1/backups', token, authorizeAdmin, routes.backups.list);
|
||||
router.post('/api/v1/backups/create', token, authorizeAdmin, routes.backups.startBackup);
|
||||
router.post('/api/v1/backups/cleanup', token, authorizeAdmin, routes.backups.cleanup);
|
||||
// backup routes
|
||||
router.get ('/api/v1/backups', token, authorizeAdmin, routes.backups.list);
|
||||
router.post('/api/v1/backups/create', token, authorizeAdmin, routes.backups.startBackup);
|
||||
router.post('/api/v1/backups/cleanup', json, token, authorizeAdmin, routes.backups.cleanup);
|
||||
|
||||
// config route (for dashboard)
|
||||
// config route (for dashboard). can return some private configuration unlike status
|
||||
router.get ('/api/v1/config', token, routes.cloudron.getConfig);
|
||||
|
||||
// working off the user behind the provided token
|
||||
router.get ('/api/v1/profile', token, routes.profile.get);
|
||||
router.post('/api/v1/profile', token, routes.profile.update);
|
||||
router.get ('/api/v1/profile/avatar/:identifier', routes.profile.getAvatar); // this is not scoped so it can used directly in img tag
|
||||
router.post('/api/v1/profile/avatar', token, multipart, routes.profile.setAvatar);
|
||||
router.del ('/api/v1/profile/avatar', token, routes.profile.clearAvatar);
|
||||
router.post('/api/v1/profile/password', token, routes.users.verifyPassword, routes.profile.changePassword);
|
||||
router.post('/api/v1/profile/twofactorauthentication', token, routes.profile.setTwoFactorAuthenticationSecret);
|
||||
router.post('/api/v1/profile/twofactorauthentication/enable', token, routes.profile.enableTwoFactorAuthentication);
|
||||
router.post('/api/v1/profile/twofactorauthentication/disable', token, routes.users.verifyPassword, routes.profile.disableTwoFactorAuthentication);
|
||||
router.get ('/api/v1/profile', token, routes.profile.get);
|
||||
router.post('/api/v1/profile', json, token, routes.profile.authorize, routes.profile.update);
|
||||
router.get ('/api/v1/profile/avatar/:identifier', routes.profile.getAvatar); // this is not scoped so it can used directly in img tag
|
||||
router.post('/api/v1/profile/avatar', json, token, multipart, routes.profile.setAvatar); // avatar is not exposed in LDAP. so it's personal and not locked
|
||||
router.del ('/api/v1/profile/avatar', token, routes.profile.clearAvatar);
|
||||
router.post('/api/v1/profile/password', json, token, routes.users.verifyPassword, routes.profile.changePassword);
|
||||
router.post('/api/v1/profile/twofactorauthentication', json, token, routes.profile.setTwoFactorAuthenticationSecret);
|
||||
router.post('/api/v1/profile/twofactorauthentication/enable', json, token, routes.profile.enableTwoFactorAuthentication);
|
||||
router.post('/api/v1/profile/twofactorauthentication/disable', json, token, routes.users.verifyPassword, routes.profile.disableTwoFactorAuthentication);
|
||||
|
||||
router.get ('/api/v1/app_passwords', token, routes.appPasswords.list);
|
||||
router.post('/api/v1/app_passwords', token, routes.appPasswords.add);
|
||||
router.get ('/api/v1/app_passwords/:id', token, routes.appPasswords.get);
|
||||
router.del ('/api/v1/app_passwords/:id', token, routes.appPasswords.del);
|
||||
// app password routes
|
||||
router.get ('/api/v1/app_passwords', token, routes.appPasswords.list);
|
||||
router.post('/api/v1/app_passwords', json, token, routes.appPasswords.add);
|
||||
router.get ('/api/v1/app_passwords/:id', token, routes.appPasswords.get);
|
||||
router.del ('/api/v1/app_passwords/:id', token, routes.appPasswords.del);
|
||||
|
||||
// access tokens
|
||||
router.get ('/api/v1/tokens', token, routes.tokens.getAll);
|
||||
router.post('/api/v1/tokens', token, routes.tokens.add);
|
||||
router.get ('/api/v1/tokens/:id', token, routes.tokens.verifyOwnership, routes.tokens.get);
|
||||
router.del ('/api/v1/tokens/:id', token, routes.tokens.verifyOwnership, routes.tokens.del);
|
||||
router.get ('/api/v1/tokens', token, routes.tokens.getAll);
|
||||
router.post('/api/v1/tokens', json, token, routes.tokens.add);
|
||||
router.get ('/api/v1/tokens/:id', token, routes.tokens.verifyOwnership, routes.tokens.get);
|
||||
router.del ('/api/v1/tokens/:id', token, routes.tokens.verifyOwnership, routes.tokens.del);
|
||||
|
||||
// user routes
|
||||
router.get ('/api/v1/users', token, authorizeUserManager, routes.users.list);
|
||||
router.post('/api/v1/users', token, authorizeUserManager, routes.users.create);
|
||||
router.get ('/api/v1/users/:userId', token, authorizeUserManager, routes.users.load, routes.users.get); // this is manage scope because it returns non-restricted fields
|
||||
router.del ('/api/v1/users/:userId', token, authorizeUserManager, routes.users.load, routes.users.remove);
|
||||
router.post('/api/v1/users/:userId', token, authorizeUserManager, routes.users.load, routes.users.update);
|
||||
router.post('/api/v1/users/:userId/password', token, authorizeUserManager, routes.users.load, routes.users.changePassword);
|
||||
router.put ('/api/v1/users/:userId/groups', token, authorizeUserManager, routes.users.load, routes.users.setGroups);
|
||||
router.post('/api/v1/users/:userId/send_invite', token, authorizeUserManager, routes.users.load, routes.users.sendInvite);
|
||||
router.post('/api/v1/users/:userId/create_invite', token, authorizeUserManager, routes.users.load, routes.users.createInvite);
|
||||
router.get ('/api/v1/users', token, authorizeUserManager, routes.users.list);
|
||||
router.post('/api/v1/users', json, token, authorizeUserManager, routes.users.create);
|
||||
router.get ('/api/v1/users/:userId', token, authorizeUserManager, routes.users.load, routes.users.get); // this is manage scope because it returns non-restricted fields
|
||||
router.del ('/api/v1/users/:userId', token, authorizeUserManager, routes.users.load, routes.users.remove);
|
||||
router.post('/api/v1/users/:userId', json, token, authorizeUserManager, routes.users.load, routes.users.update);
|
||||
router.post('/api/v1/users/:userId/password', json, token, authorizeUserManager, routes.users.load, routes.users.changePassword);
|
||||
router.put ('/api/v1/users/:userId/groups', json, token, authorizeUserManager, routes.users.load, routes.users.setGroups);
|
||||
router.post('/api/v1/users/:userId/send_invite', json, token, authorizeUserManager, routes.users.load, routes.users.sendInvite);
|
||||
router.post('/api/v1/users/:userId/create_invite', json, token, authorizeUserManager, routes.users.load, routes.users.createInvite);
|
||||
router.post('/api/v1/users/:userId/avatar', json, token, authorizeUserManager, routes.users.load, multipart, routes.users.setAvatar);
|
||||
router.del ('/api/v1/users/:userId/avatar', token, authorizeUserManager, routes.users.load, routes.users.clearAvatar);
|
||||
|
||||
// Group management
|
||||
router.get ('/api/v1/groups', token, authorizeUserManager, routes.groups.list);
|
||||
router.post('/api/v1/groups', token, authorizeUserManager, routes.groups.create);
|
||||
router.get ('/api/v1/groups/:groupId', token, authorizeUserManager, routes.groups.get);
|
||||
router.put ('/api/v1/groups/:groupId/members', token, authorizeUserManager, routes.groups.updateMembers);
|
||||
router.post('/api/v1/groups/:groupId', token, authorizeUserManager, routes.groups.update);
|
||||
router.del ('/api/v1/groups/:groupId', token, authorizeUserManager, routes.groups.remove);
|
||||
router.get ('/api/v1/groups', token, authorizeUserManager, routes.groups.list);
|
||||
router.post('/api/v1/groups', json, token, authorizeUserManager, routes.groups.create);
|
||||
router.get ('/api/v1/groups/:groupId', token, authorizeUserManager, routes.groups.get);
|
||||
router.put ('/api/v1/groups/:groupId/members', json, token, authorizeUserManager, routes.groups.updateMembers);
|
||||
router.post('/api/v1/groups/:groupId', json, token, authorizeUserManager, routes.groups.update);
|
||||
router.del ('/api/v1/groups/:groupId', token, authorizeUserManager, routes.groups.remove);
|
||||
|
||||
// appstore and subscription routes
|
||||
router.post('/api/v1/appstore/register_cloudron', token, authorizeAdmin, routes.appstore.registerCloudron);
|
||||
router.post('/api/v1/appstore/user_token', token, authorizeAdmin, routes.appstore.createUserToken);
|
||||
router.get ('/api/v1/appstore/subscription', token, authorizeAdmin, routes.appstore.getSubscription);
|
||||
router.get ('/api/v1/appstore/apps', token, authorizeAdmin, routes.appstore.getApps);
|
||||
router.get ('/api/v1/appstore/apps/:appstoreId', token, authorizeAdmin, routes.appstore.getApp);
|
||||
router.get ('/api/v1/appstore/apps/:appstoreId/versions/:versionId', token, authorizeAdmin, routes.appstore.getAppVersion);
|
||||
router.post('/api/v1/appstore/register_cloudron', json, token, authorizeAdmin, routes.appstore.registerCloudron);
|
||||
router.post('/api/v1/appstore/user_token', json, token, authorizeAdmin, routes.appstore.createUserToken);
|
||||
router.get ('/api/v1/appstore/subscription', token, authorizeAdmin, routes.appstore.getSubscription);
|
||||
router.get ('/api/v1/appstore/apps', token, authorizeAdmin, routes.appstore.getApps);
|
||||
router.get ('/api/v1/appstore/apps/:appstoreId', token, authorizeAdmin, routes.appstore.getApp);
|
||||
router.get ('/api/v1/appstore/apps/:appstoreId/versions/:versionId', token, authorizeAdmin, routes.appstore.getAppVersion);
|
||||
|
||||
// app routes
|
||||
router.get ('/api/v1/apps', token, routes.apps.getApps);
|
||||
router.get ('/api/v1/apps/:id', token, authorizeAdmin, routes.apps.load, routes.apps.getApp);
|
||||
router.get ('/api/v1/apps/:id/icon', token, routes.apps.load, routes.apps.getAppIcon);
|
||||
|
||||
router.post('/api/v1/apps/install', token, authorizeAdmin, routes.apps.install);
|
||||
router.post('/api/v1/apps/:id/uninstall', token, authorizeAdmin, routes.apps.load, routes.apps.uninstall);
|
||||
|
||||
router.post('/api/v1/apps/:id/configure/access_restriction', token, authorizeAdmin, routes.apps.load, routes.apps.setAccessRestriction);
|
||||
router.post('/api/v1/apps/:id/configure/label', token, authorizeAdmin, routes.apps.load, routes.apps.setLabel);
|
||||
router.post('/api/v1/apps/:id/configure/tags', token, authorizeAdmin, routes.apps.load, routes.apps.setTags);
|
||||
router.post('/api/v1/apps/:id/configure/icon', token, authorizeAdmin, routes.apps.load, routes.apps.setIcon);
|
||||
router.post('/api/v1/apps/:id/configure/memory_limit', token, authorizeAdmin, routes.apps.load, routes.apps.setMemoryLimit);
|
||||
router.post('/api/v1/apps/:id/configure/cpu_shares', token, authorizeAdmin, routes.apps.load, routes.apps.setCpuShares);
|
||||
router.post('/api/v1/apps/:id/configure/automatic_backup', token, authorizeAdmin, routes.apps.load, routes.apps.setAutomaticBackup);
|
||||
router.post('/api/v1/apps/:id/configure/automatic_update', token, authorizeAdmin, routes.apps.load, routes.apps.setAutomaticUpdate);
|
||||
router.post('/api/v1/apps/:id/configure/reverse_proxy', token, authorizeAdmin, routes.apps.load, routes.apps.setReverseProxyConfig);
|
||||
router.post('/api/v1/apps/:id/configure/cert', token, authorizeAdmin, routes.apps.load, routes.apps.setCertificate);
|
||||
router.post('/api/v1/apps/:id/configure/debug_mode', token, authorizeAdmin, routes.apps.load, routes.apps.setDebugMode);
|
||||
router.post('/api/v1/apps/:id/configure/mailbox', token, authorizeAdmin, routes.apps.load, routes.apps.setMailbox);
|
||||
router.post('/api/v1/apps/:id/configure/env', token, authorizeAdmin, routes.apps.load, routes.apps.setEnvironment);
|
||||
router.post('/api/v1/apps/:id/configure/data_dir', token, authorizeAdmin, routes.apps.load, routes.apps.setDataDir);
|
||||
router.post('/api/v1/apps/:id/configure/location', token, authorizeAdmin, routes.apps.load, routes.apps.setLocation);
|
||||
router.post('/api/v1/apps/:id/configure/binds', token, authorizeAdmin, routes.apps.load, routes.apps.setBinds);
|
||||
|
||||
router.post('/api/v1/apps/:id/repair', token, authorizeAdmin, routes.apps.load, routes.apps.repair);
|
||||
router.post('/api/v1/apps/:id/update', token, authorizeAdmin, routes.apps.load, routes.apps.update);
|
||||
router.post('/api/v1/apps/:id/restore', token, authorizeAdmin, routes.apps.load, routes.apps.restore);
|
||||
router.post('/api/v1/apps/:id/import', token, authorizeAdmin, routes.apps.load, routes.apps.importApp);
|
||||
router.post('/api/v1/apps/:id/backup', token, authorizeAdmin, routes.apps.load, routes.apps.backup);
|
||||
router.get ('/api/v1/apps/:id/backups', token, authorizeAdmin, routes.apps.load, routes.apps.listBackups);
|
||||
router.post('/api/v1/apps/:id/start', token, authorizeAdmin, routes.apps.load, routes.apps.start);
|
||||
router.post('/api/v1/apps/:id/stop', token, authorizeAdmin, routes.apps.load, routes.apps.stop);
|
||||
router.post('/api/v1/apps/:id/restart', token, authorizeAdmin, routes.apps.load, routes.apps.restart);
|
||||
router.get ('/api/v1/apps/:id/logstream', token, authorizeAdmin, routes.apps.load, routes.apps.getLogStream);
|
||||
router.get ('/api/v1/apps/:id/logs', token, authorizeAdmin, routes.apps.load, routes.apps.getLogs);
|
||||
router.get ('/api/v1/apps/:id/exec', token, authorizeAdmin, routes.apps.load, routes.apps.exec);
|
||||
router.post('/api/v1/apps/install', json, token, authorizeAdmin, routes.apps.install);
|
||||
router.get ('/api/v1/apps', token, routes.apps.getApps);
|
||||
router.get ('/api/v1/apps/:id', token, authorizeAdmin, routes.apps.load, routes.apps.getApp);
|
||||
router.get ('/api/v1/apps/:id/icon', token, routes.apps.load, routes.apps.getAppIcon);
|
||||
router.post('/api/v1/apps/:id/uninstall', json, token, authorizeAdmin, routes.apps.load, routes.apps.uninstall);
|
||||
router.post('/api/v1/apps/:id/configure/access_restriction', json, token, authorizeAdmin, routes.apps.load, routes.apps.setAccessRestriction);
|
||||
router.post('/api/v1/apps/:id/configure/label', json, token, authorizeAdmin, routes.apps.load, routes.apps.setLabel);
|
||||
router.post('/api/v1/apps/:id/configure/tags', json, token, authorizeAdmin, routes.apps.load, routes.apps.setTags);
|
||||
router.post('/api/v1/apps/:id/configure/icon', json, token, authorizeAdmin, routes.apps.load, routes.apps.setIcon);
|
||||
router.post('/api/v1/apps/:id/configure/memory_limit', json, token, authorizeAdmin, routes.apps.load, routes.apps.setMemoryLimit);
|
||||
router.post('/api/v1/apps/:id/configure/cpu_shares', json, token, authorizeAdmin, routes.apps.load, routes.apps.setCpuShares);
|
||||
router.post('/api/v1/apps/:id/configure/automatic_backup', json, token, authorizeAdmin, routes.apps.load, routes.apps.setAutomaticBackup);
|
||||
router.post('/api/v1/apps/:id/configure/automatic_update', json, token, authorizeAdmin, routes.apps.load, routes.apps.setAutomaticUpdate);
|
||||
router.post('/api/v1/apps/:id/configure/reverse_proxy', json, token, authorizeAdmin, routes.apps.load, routes.apps.setReverseProxyConfig);
|
||||
router.post('/api/v1/apps/:id/configure/cert', json, token, authorizeAdmin, routes.apps.load, routes.apps.setCertificate);
|
||||
router.post('/api/v1/apps/:id/configure/debug_mode', json, token, authorizeAdmin, routes.apps.load, routes.apps.setDebugMode);
|
||||
router.post('/api/v1/apps/:id/configure/mailbox', json, token, authorizeAdmin, routes.apps.load, routes.apps.setMailbox);
|
||||
router.post('/api/v1/apps/:id/configure/env', json, token, authorizeAdmin, routes.apps.load, routes.apps.setEnvironment);
|
||||
router.post('/api/v1/apps/:id/configure/data_dir', json, token, authorizeAdmin, routes.apps.load, routes.apps.setDataDir);
|
||||
router.post('/api/v1/apps/:id/configure/location', json, token, authorizeAdmin, routes.apps.load, routes.apps.setLocation);
|
||||
router.post('/api/v1/apps/:id/configure/binds', json, token, authorizeAdmin, routes.apps.load, routes.apps.setBinds);
|
||||
router.post('/api/v1/apps/:id/repair', json, token, authorizeAdmin, routes.apps.load, routes.apps.repair);
|
||||
router.post('/api/v1/apps/:id/update', json, token, authorizeAdmin, routes.apps.load, routes.apps.update);
|
||||
router.post('/api/v1/apps/:id/restore', json, token, authorizeAdmin, routes.apps.load, routes.apps.restore);
|
||||
router.post('/api/v1/apps/:id/import', json, token, authorizeAdmin, routes.apps.load, routes.apps.importApp);
|
||||
router.post('/api/v1/apps/:id/backup', json, token, authorizeAdmin, routes.apps.load, routes.apps.backup);
|
||||
router.get ('/api/v1/apps/:id/backups', token, authorizeAdmin, routes.apps.load, routes.apps.listBackups);
|
||||
router.post('/api/v1/apps/:id/start', json, token, authorizeAdmin, routes.apps.load, routes.apps.start);
|
||||
router.post('/api/v1/apps/:id/stop', json, token, authorizeAdmin, routes.apps.load, routes.apps.stop);
|
||||
router.post('/api/v1/apps/:id/restart', json, token, authorizeAdmin, routes.apps.load, routes.apps.restart);
|
||||
router.get ('/api/v1/apps/:id/logstream', token, authorizeAdmin, routes.apps.load, routes.apps.getLogStream);
|
||||
router.get ('/api/v1/apps/:id/logs', token, authorizeAdmin, routes.apps.load, routes.apps.getLogs);
|
||||
router.post('/api/v1/apps/:id/clone', json, token, authorizeAdmin, routes.apps.load, routes.apps.clone);
|
||||
router.get ('/api/v1/apps/:id/download', token, authorizeAdmin, routes.apps.load, routes.apps.downloadFile);
|
||||
router.post('/api/v1/apps/:id/upload', json, token, authorizeAdmin, multipart, routes.apps.load, routes.apps.uploadFile);
|
||||
router.use ('/api/v1/apps/:id/files/*', token, authorizeAdmin, routes.filemanager.proxy);
|
||||
router.get ('/api/v1/apps/:id/exec', token, authorizeAdmin, routes.apps.load, routes.apps.exec);
|
||||
// websocket cannot do bearer authentication
|
||||
router.get ('/api/v1/apps/:id/execws', routes.accesscontrol.websocketAuth.bind(null, users.ROLE_ADMIN), routes.apps.load, routes.apps.execWebSocket);
|
||||
router.post('/api/v1/apps/:id/clone', token, authorizeAdmin, routes.apps.load, routes.apps.clone);
|
||||
router.get ('/api/v1/apps/:id/download', token, authorizeAdmin, routes.apps.load, routes.apps.downloadFile);
|
||||
router.post('/api/v1/apps/:id/upload', token, authorizeAdmin, multipart, routes.apps.load, routes.apps.uploadFile);
|
||||
|
||||
router.get ('/api/v1/branding/:setting', token, authorizeOwner, routes.branding.get);
|
||||
router.post('/api/v1/branding/:setting', token, authorizeOwner, (req, res, next) => {
|
||||
// branding routes
|
||||
router.get ('/api/v1/branding/:setting', token, authorizeOwner, routes.branding.get);
|
||||
router.post('/api/v1/branding/:setting', json, token, authorizeOwner, (req, res, next) => {
|
||||
return req.params.setting === 'cloudron_avatar' ? multipart(req, res, next) : next();
|
||||
}, routes.branding.set);
|
||||
|
||||
// settings routes (these are for the settings tab - avatar & name have public routes for normal users. see above)
|
||||
router.get ('/api/v1/settings/:setting', token, authorizeAdmin, routes.settings.get);
|
||||
router.post('/api/v1/settings/backup_config', token, authorizeOwner, routes.settings.setBackupConfig);
|
||||
router.post('/api/v1/settings/:setting', token, authorizeAdmin, routes.settings.set);
|
||||
router.get ('/api/v1/settings/:setting', token, authorizeAdmin, routes.settings.get);
|
||||
router.post('/api/v1/settings/backup_config', json, token, authorizeOwner, routes.settings.setBackupConfig);
|
||||
router.post('/api/v1/settings/:setting', json, token, authorizeAdmin, routes.settings.set);
|
||||
|
||||
// email routes
|
||||
router.get('/api/v1/mailserver/:pathname', token, (req, res, next) => {
|
||||
@@ -253,48 +253,48 @@ function initializeExpressSync() {
|
||||
authorizeAdmin(req, res, next);
|
||||
}, routes.mailserver.proxy);
|
||||
|
||||
router.get ('/api/v1/mail/:domain', token, authorizeAdmin, routes.mail.getDomain);
|
||||
router.get ('/api/v1/mail/:domain/status', token, authorizeAdmin, routes.mail.getStatus);
|
||||
router.post('/api/v1/mail/:domain/mail_from_validation', token, authorizeAdmin, routes.mail.setMailFromValidation);
|
||||
router.post('/api/v1/mail/:domain/catch_all', token, authorizeAdmin, routes.mail.setCatchAllAddress);
|
||||
router.post('/api/v1/mail/:domain/relay', token, authorizeAdmin, routes.mail.setMailRelay);
|
||||
router.post('/api/v1/mail/:domain/enable', token, authorizeAdmin, routes.mail.setMailEnabled);
|
||||
router.post('/api/v1/mail/:domain/dns', token, authorizeAdmin, routes.mail.setDnsRecords);
|
||||
router.post('/api/v1/mail/:domain/send_test_mail', token, authorizeAdmin, routes.mail.sendTestMail);
|
||||
router.get ('/api/v1/mail/:domain/mailboxes', token, authorizeAdmin, routes.mail.listMailboxes);
|
||||
router.get ('/api/v1/mail/:domain/mailboxes/:name', token, authorizeAdmin, routes.mail.getMailbox);
|
||||
router.post('/api/v1/mail/:domain/mailboxes', token, authorizeAdmin, routes.mail.addMailbox);
|
||||
router.post('/api/v1/mail/:domain/mailboxes/:name', token, authorizeAdmin, routes.mail.updateMailbox);
|
||||
router.del ('/api/v1/mail/:domain/mailboxes/:name', token, authorizeAdmin, routes.mail.removeMailbox);
|
||||
router.get ('/api/v1/mail/:domain/mailboxes/:name/aliases', token, authorizeAdmin, routes.mail.getAliases);
|
||||
router.put ('/api/v1/mail/:domain/mailboxes/:name/aliases', token, authorizeAdmin, routes.mail.setAliases);
|
||||
router.get ('/api/v1/mail/:domain', token, authorizeAdmin, routes.mail.getDomain);
|
||||
router.get ('/api/v1/mail/:domain/status', token, authorizeAdmin, routes.mail.getStatus);
|
||||
router.post('/api/v1/mail/:domain/mail_from_validation', json, token, authorizeAdmin, routes.mail.setMailFromValidation);
|
||||
router.post('/api/v1/mail/:domain/catch_all', json, token, authorizeAdmin, routes.mail.setCatchAllAddress);
|
||||
router.post('/api/v1/mail/:domain/relay', json, token, authorizeAdmin, routes.mail.setMailRelay);
|
||||
router.post('/api/v1/mail/:domain/enable', json, token, authorizeAdmin, routes.mail.setMailEnabled);
|
||||
router.post('/api/v1/mail/:domain/dns', json, token, authorizeAdmin, routes.mail.setDnsRecords);
|
||||
router.post('/api/v1/mail/:domain/send_test_mail', json, token, authorizeAdmin, routes.mail.sendTestMail);
|
||||
router.get ('/api/v1/mail/:domain/mailbox_count', token, authorizeAdmin, routes.mail.getMailboxCount);
|
||||
router.get ('/api/v1/mail/:domain/mailboxes', token, authorizeAdmin, routes.mail.listMailboxes);
|
||||
router.get ('/api/v1/mail/:domain/mailboxes/:name', token, authorizeAdmin, routes.mail.getMailbox);
|
||||
router.post('/api/v1/mail/:domain/mailboxes', json, token, authorizeAdmin, routes.mail.addMailbox);
|
||||
router.post('/api/v1/mail/:domain/mailboxes/:name', json, token, authorizeAdmin, routes.mail.updateMailbox);
|
||||
router.del ('/api/v1/mail/:domain/mailboxes/:name', token, authorizeAdmin, routes.mail.removeMailbox);
|
||||
router.get ('/api/v1/mail/:domain/mailboxes/:name/aliases', token, authorizeAdmin, routes.mail.getAliases);
|
||||
router.put ('/api/v1/mail/:domain/mailboxes/:name/aliases', json, token, authorizeAdmin, routes.mail.setAliases);
|
||||
router.get ('/api/v1/mail/:domain/lists', token, authorizeAdmin, routes.mail.getLists);
|
||||
router.post('/api/v1/mail/:domain/lists', json, token, authorizeAdmin, routes.mail.addList);
|
||||
router.get ('/api/v1/mail/:domain/lists/:name', token, authorizeAdmin, routes.mail.getList);
|
||||
router.post('/api/v1/mail/:domain/lists/:name', json, token, authorizeAdmin, routes.mail.updateList);
|
||||
router.del ('/api/v1/mail/:domain/lists/:name', token, authorizeAdmin, routes.mail.removeList);
|
||||
|
||||
router.get ('/api/v1/mail/:domain/lists', token, authorizeAdmin, routes.mail.getLists);
|
||||
router.post('/api/v1/mail/:domain/lists', token, authorizeAdmin, routes.mail.addList);
|
||||
router.get ('/api/v1/mail/:domain/lists/:name', token, authorizeAdmin, routes.mail.getList);
|
||||
router.post('/api/v1/mail/:domain/lists/:name', token, authorizeAdmin, routes.mail.updateList);
|
||||
router.del ('/api/v1/mail/:domain/lists/:name', token, authorizeAdmin, routes.mail.removeList);
|
||||
|
||||
// support
|
||||
router.post('/api/v1/support/ticket', token, authorizeAdmin, routes.support.canCreateTicket, routes.support.createTicket);
|
||||
router.get ('/api/v1/support/remote_support', token, authorizeAdmin, routes.support.getRemoteSupport);
|
||||
router.post('/api/v1/support/remote_support', token, authorizeAdmin, routes.support.canEnableRemoteSupport, routes.support.enableRemoteSupport);
|
||||
// support routes
|
||||
router.post('/api/v1/support/ticket', json, token, authorizeAdmin, routes.support.canCreateTicket, routes.support.createTicket);
|
||||
router.get ('/api/v1/support/remote_support', token, authorizeAdmin, routes.support.getRemoteSupport);
|
||||
router.post('/api/v1/support/remote_support', json, token, authorizeAdmin, routes.support.canEnableRemoteSupport, routes.support.enableRemoteSupport);
|
||||
|
||||
// domain routes
|
||||
router.post('/api/v1/domains', token, authorizeAdmin, routes.domains.add);
|
||||
router.get ('/api/v1/domains', token, routes.domains.getAll);
|
||||
router.get ('/api/v1/domains/:domain', token, authorizeAdmin, routes.domains.get); // this is manage scope because it returns non-restricted fields
|
||||
router.put ('/api/v1/domains/:domain', token, authorizeAdmin, routes.domains.update);
|
||||
router.del ('/api/v1/domains/:domain', token, authorizeAdmin, routes.domains.del);
|
||||
router.get ('/api/v1/domains/:domain/dns_check', token, authorizeAdmin, routes.domains.checkDnsRecords);
|
||||
router.post('/api/v1/domains', json, token, authorizeAdmin, routes.domains.add);
|
||||
router.get ('/api/v1/domains', token, routes.domains.getAll);
|
||||
router.get ('/api/v1/domains/:domain', token, authorizeAdmin, routes.domains.get); // this is manage scope because it returns non-restricted fields
|
||||
router.put ('/api/v1/domains/:domain', json, token, authorizeAdmin, routes.domains.update);
|
||||
router.del ('/api/v1/domains/:domain', token, authorizeAdmin, routes.domains.del);
|
||||
router.get ('/api/v1/domains/:domain/dns_check', token, authorizeAdmin, routes.domains.checkDnsRecords);
|
||||
|
||||
// addon routes
|
||||
router.get ('/api/v1/services', token, authorizeAdmin, routes.services.getAll);
|
||||
router.get ('/api/v1/services/:service', token, authorizeAdmin, routes.services.get);
|
||||
router.post('/api/v1/services/:service', token, authorizeAdmin, routes.services.configure);
|
||||
router.get ('/api/v1/services/:service/logs', token, authorizeAdmin, routes.services.getLogs);
|
||||
router.get ('/api/v1/services/:service/logstream', token, authorizeAdmin, routes.services.getLogStream);
|
||||
router.post('/api/v1/services/:service/restart', token, authorizeAdmin, routes.services.restart);
|
||||
router.get ('/api/v1/services', token, authorizeAdmin, routes.services.getAll);
|
||||
router.get ('/api/v1/services/:service', token, authorizeAdmin, routes.services.get);
|
||||
router.post('/api/v1/services/:service', json, token, authorizeAdmin, routes.services.configure);
|
||||
router.get ('/api/v1/services/:service/logs', token, authorizeAdmin, routes.services.getLogs);
|
||||
router.get ('/api/v1/services/:service/logstream', token, authorizeAdmin, routes.services.getLogStream);
|
||||
router.post('/api/v1/services/:service/restart', json, token, authorizeAdmin, routes.services.restart);
|
||||
|
||||
// disable server socket "idle" timeout. we use the timeout middleware to handle timeouts on a route level
|
||||
// we rely on nginx for timeouts on the TCP level (see client_header_timeout)
|
||||
|
||||
+34
-1
@@ -50,6 +50,9 @@ exports = module.exports = {
|
||||
getFooter: getFooter,
|
||||
setFooter: setFooter,
|
||||
|
||||
getDirectoryConfig: getDirectoryConfig,
|
||||
setDirectoryConfig: setDirectoryConfig,
|
||||
|
||||
getAppstoreListingConfig: getAppstoreListingConfig,
|
||||
setAppstoreListingConfig: setAppstoreListingConfig,
|
||||
|
||||
@@ -86,6 +89,7 @@ exports = module.exports = {
|
||||
SYSINFO_CONFIG_KEY: 'sysinfo_config',
|
||||
APPSTORE_LISTING_CONFIG_KEY: 'appstore_listing_config',
|
||||
SUPPORT_CONFIG_KEY: 'support_config',
|
||||
DIRECTORY_CONFIG_KEY: 'directory_config',
|
||||
|
||||
// strings
|
||||
APP_AUTOUPDATE_PATTERN_KEY: 'app_autoupdate_pattern',
|
||||
@@ -157,6 +161,11 @@ let gDefaults = (function () {
|
||||
result[exports.SYSINFO_CONFIG_KEY] = {
|
||||
provider: 'generic'
|
||||
};
|
||||
result[exports.DIRECTORY_CONFIG_KEY] = {
|
||||
lockUserProfiles: false,
|
||||
mandatory2FA: false
|
||||
};
|
||||
|
||||
result[exports.ADMIN_DOMAIN_KEY] = '';
|
||||
result[exports.ADMIN_FQDN_KEY] = '';
|
||||
result[exports.API_SERVER_ORIGIN_KEY] = 'https://api.cloudron.io';
|
||||
@@ -573,6 +582,30 @@ function setSysinfoConfig(sysinfoConfig, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function getDirectoryConfig(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
settingsdb.get(exports.DIRECTORY_CONFIG_KEY, function (error, value) {
|
||||
if (error && error.reason === BoxError.NOT_FOUND) return callback(null, gDefaults[exports.DIRECTORY_CONFIG_KEY]);
|
||||
if (error) return callback(error);
|
||||
|
||||
callback(null, JSON.parse(value));
|
||||
});
|
||||
}
|
||||
|
||||
function setDirectoryConfig(directoryConfig, callback) {
|
||||
assert.strictEqual(typeof directoryConfig, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
settingsdb.set(exports.DIRECTORY_CONFIG_KEY, JSON.stringify(directoryConfig), function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
notifyChange(exports.DIRECTORY_CONFIG_KEY, directoryConfig);
|
||||
|
||||
callback(null);
|
||||
});
|
||||
}
|
||||
|
||||
function getAppstoreListingConfig(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
@@ -696,7 +729,7 @@ function getAll(callback) {
|
||||
result[exports.DEMO_KEY] = !!result[exports.DEMO_KEY];
|
||||
|
||||
// convert JSON objects
|
||||
[exports.BACKUP_CONFIG_KEY, exports.PLATFORM_CONFIG_KEY, exports.EXTERNAL_LDAP_KEY, exports.REGISTRY_CONFIG_KEY, exports.SYSINFO_CONFIG_KEY ].forEach(function (key) {
|
||||
[exports.BACKUP_CONFIG_KEY, exports.DIRECTORY_CONFIG_KEY, exports.PLATFORM_CONFIG_KEY, exports.EXTERNAL_LDAP_KEY, exports.REGISTRY_CONFIG_KEY, exports.SYSINFO_CONFIG_KEY ].forEach(function (key) {
|
||||
result[key] = typeof result[key] === 'object' ? result[key] : safe.JSON.parse(result[key]);
|
||||
});
|
||||
|
||||
|
||||
+11
-10
@@ -54,13 +54,6 @@ function checkPreconditions(apiConfig, dataLayout, callback) {
|
||||
assert(dataLayout instanceof DataLayout, 'dataLayout must be a DataLayout');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
// TODO check filesystem is mounted for sshfs and cifs so we don't write into the actual folder on disk
|
||||
if (apiConfig.provider === PROVIDER_SSHFS || apiConfig.provider === PROVIDER_CIFS || apiConfig.provider === PROVIDER_NFS) {
|
||||
const mounts = safe.fs.readFileSync('/proc/mounts', 'utf8');
|
||||
const mountInfo = mounts.split('\n').filter(function (l) { return l.indexOf(apiConfig.mountPoint) !== -1; })[0];
|
||||
if (!mountInfo) return callback(new BoxError(BoxError.FS_ERROR, `${apiConfig.mountPoint} is not mounted`));
|
||||
}
|
||||
|
||||
let used = 0;
|
||||
for (let localPath of dataLayout.localPaths()) {
|
||||
debug(`checkPreconditions: getting disk usage of ${localPath}`);
|
||||
@@ -71,9 +64,17 @@ function checkPreconditions(apiConfig, dataLayout, callback) {
|
||||
|
||||
debug(`checkPreconditions: ${used} bytes`);
|
||||
|
||||
df.file(getBackupPath(apiConfig)).then(function (diskUsage) {
|
||||
df.file(getBackupPath(apiConfig)).then(function (result) {
|
||||
|
||||
// Check filesystem is mounted so we don't write into the actual folder on disk
|
||||
if (apiConfig.provider === PROVIDER_SSHFS || apiConfig.provider === PROVIDER_CIFS || apiConfig.provider === PROVIDER_NFS) {
|
||||
if (result.mountpoint !== apiConfig.mountPoint) return callback(new BoxError(BoxError.FS_ERROR, `${apiConfig.mountPoint} is not mounted`));
|
||||
} else if (apiConfig.provider === PROVIDER_FILESYSTEM && apiConfig.externalDisk) {
|
||||
if (result.mountpoint === '/') return callback(new BoxError(BoxError.FS_ERROR, `${apiConfig.backupFolder} is not mounted`));
|
||||
}
|
||||
|
||||
const needed = used + (1024 * 1024 * 1024); // check if there is atleast 1GB left afterwards
|
||||
if (diskUsage.available <= needed) return callback(new BoxError(BoxError.FS_ERROR, `Not enough disk space for backup. Needed: ${prettyBytes(needed)} Available: ${prettyBytes(diskUsage.available)}`));
|
||||
if (result.available <= needed) return callback(new BoxError(BoxError.FS_ERROR, `Not enough disk space for backup. Needed: ${prettyBytes(needed)} Available: ${prettyBytes(result.available)}`));
|
||||
|
||||
callback(null);
|
||||
}).catch(function (error) {
|
||||
@@ -239,7 +240,7 @@ function testConfig(apiConfig, callback) {
|
||||
|
||||
const mounts = safe.fs.readFileSync('/proc/mounts', 'utf8');
|
||||
const mountInfo = mounts.split('\n').filter(function (l) { return l.indexOf(apiConfig.mountPoint) !== -1; })[0];
|
||||
if (!mountInfo) return callback(new BoxError(BoxError.BAD_FIELD, 'mountPoint is not mounted', { field: 'mountPoint' }));
|
||||
if (!mountInfo) return callback(new BoxError(BoxError.BAD_FIELD, `${apiConfig.mountPoint} is not mounted`, { field: 'mountPoint' }));
|
||||
|
||||
if (apiConfig.provider === PROVIDER_SSHFS && !mountInfo.split(' ').find(i => i === 'fuse.sshfs')) return callback(new BoxError(BoxError.BAD_FIELD, 'mountPoint must be a "fuse.sshfs" filesystem', { field: 'mountPoint' }));
|
||||
if (apiConfig.provider === PROVIDER_CIFS && !mountInfo.split(' ').find(i => i === 'cifs')) return callback(new BoxError(BoxError.BAD_FIELD, 'mountPoint must be a "cifs" filesystem', { field: 'mountPoint' }));
|
||||
|
||||
+2
-2
@@ -12,7 +12,7 @@ let assert = require('assert'),
|
||||
once = require('once'),
|
||||
path = require('path'),
|
||||
paths = require('./paths.js'),
|
||||
settings = require('./settings.js'),
|
||||
safe = require('safetydance'),
|
||||
shell = require('./shell.js');
|
||||
|
||||
// the logic here is also used in the cloudron-support tool
|
||||
@@ -24,7 +24,7 @@ function sshInfo() {
|
||||
if (constants.TEST) {
|
||||
filePath = path.join(paths.baseDir(), 'authorized_keys');
|
||||
user = process.getuid();
|
||||
} else if (settings.provider() === 'ec2' || settings.provider() === 'lightsail' || settings.provider() === 'ami') {
|
||||
} else if (safe.fs.existsSync('/home/ubuntu')) { // yellowtent user won't have access to anything deeper
|
||||
filePath = '/home/ubuntu/.ssh/authorized_keys';
|
||||
user = 'ubuntu';
|
||||
} else {
|
||||
|
||||
@@ -29,7 +29,6 @@ describe('Apps', function () {
|
||||
fallbackEmail: 'admin@me.com',
|
||||
salt: 'morton',
|
||||
createdAt: 'sometime back',
|
||||
modifiedAt: 'now',
|
||||
resetToken: hat(256),
|
||||
displayName: '',
|
||||
groupIds: [],
|
||||
@@ -45,7 +44,6 @@ describe('Apps', function () {
|
||||
fallbackEmail: 'safe@me.com',
|
||||
salt: 'morton',
|
||||
createdAt: 'sometime back',
|
||||
modifiedAt: 'now',
|
||||
resetToken: hat(256),
|
||||
displayName: '',
|
||||
groupIds: [],
|
||||
@@ -61,7 +59,6 @@ describe('Apps', function () {
|
||||
fallbackEmail: 'safe1@me.com',
|
||||
salt: 'morton',
|
||||
createdAt: 'sometime back',
|
||||
modifiedAt: 'now',
|
||||
resetToken: hat(256),
|
||||
displayName: '',
|
||||
groupIds: [ 'somegroup' ],
|
||||
|
||||
@@ -50,58 +50,10 @@ describe('Appstore', function () {
|
||||
|
||||
beforeEach(nock.cleanAll);
|
||||
|
||||
it('cannot send alive status without registering', function (done) {
|
||||
appstore.sendAliveStatus(function (error) {
|
||||
expect(error).to.be.ok();
|
||||
expect(error.reason).to.equal(BoxError.LICENSE_ERROR); // missing token
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can set cloudron token', function (done) {
|
||||
settingsdb.set(settings.CLOUDRON_TOKEN_KEY, APPSTORE_TOKEN, done);
|
||||
});
|
||||
|
||||
it('can send alive status', function (done) {
|
||||
var scope = nock(MOCK_API_SERVER_ORIGIN)
|
||||
.post(`/api/v1/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.sysinfoProvider).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 purchase an app', function (done) {
|
||||
var scope1 = nock(MOCK_API_SERVER_ORIGIN)
|
||||
.post(`/api/v1/cloudronapps?accessToken=${APPSTORE_TOKEN}`, function () { return true; })
|
||||
|
||||
@@ -73,7 +73,6 @@ var ADMIN = {
|
||||
fallbackEmail: 'admin@me.com',
|
||||
salt: 'morton',
|
||||
createdAt: 'sometime back',
|
||||
modifiedAt: 'now',
|
||||
resetToken: '',
|
||||
displayName: '',
|
||||
role: 'owner',
|
||||
|
||||
@@ -28,7 +28,7 @@ scripts=("${SOURCE_DIR}/src/scripts/clearvolume.sh" \
|
||||
for script in "${scripts[@]}"; do
|
||||
if [[ $(sudo -n "${script}" --check 2>/dev/null) != "OK" ]]; then
|
||||
echo ""
|
||||
echo "${script} does not have sudo access."
|
||||
echo "${script} does not have sudo access. Try 'sudo -n ${script} --check'"
|
||||
echo "You have to add the lines below to /etc/sudoers.d/yellowtent"
|
||||
echo ""
|
||||
echo "Defaults!${script} env_keep=\"HOME BOX_ENV\""
|
||||
|
||||
@@ -34,7 +34,6 @@ var USER_0 = {
|
||||
fallbackEmail: 'safer@me.com',
|
||||
salt: 'morton',
|
||||
createdAt: 'sometime back',
|
||||
modifiedAt: 'now',
|
||||
resetToken: hat(256),
|
||||
displayName: '',
|
||||
twoFactorAuthenticationEnabled: false,
|
||||
@@ -53,7 +52,6 @@ var USER_1 = {
|
||||
fallbackEmail: 'safer2@me.com',
|
||||
salt: 'tata',
|
||||
createdAt: 'sometime back',
|
||||
modifiedAt: 'now',
|
||||
resetToken: '',
|
||||
displayName: 'Herbert 1',
|
||||
twoFactorAuthenticationEnabled: false,
|
||||
@@ -72,7 +70,6 @@ var USER_2 = {
|
||||
fallbackEmail: 'safer3@me.com',
|
||||
salt: 'tata',
|
||||
createdAt: 'sometime back',
|
||||
modifiedAt: 'now',
|
||||
resetToken: '',
|
||||
displayName: 'Herbert 2',
|
||||
twoFactorAuthenticationEnabled: false,
|
||||
@@ -1837,7 +1834,7 @@ describe('database', function () {
|
||||
});
|
||||
|
||||
it('list mailboxes succeeds', function (done) {
|
||||
mailboxdb.listMailboxes(DOMAIN_0.domain, 1, 10, function (error, mailboxes) {
|
||||
mailboxdb.listMailboxes(DOMAIN_0.domain, null /* search */, 1, 10, function (error, mailboxes) {
|
||||
expect(error).to.be(null);
|
||||
expect(mailboxes.length).to.be(2);
|
||||
expect(mailboxes[0].name).to.be('girish');
|
||||
|
||||
@@ -21,7 +21,7 @@ describe('Dockerproxy', function () {
|
||||
dockerProxy.start(function (error) {
|
||||
expect(error).to.not.be.ok();
|
||||
|
||||
exec(`${DOCKER} run -d cloudron/base:1.0.0 "bin/bash" "-c" "while true; do echo 'perpetual walrus'; sleep 1; done"`, function (error, stdout, stderr) {
|
||||
exec(`${DOCKER} run -d cloudron/base:2.0.0 "bin/bash" "-c" "while true; do echo 'perpetual walrus'; sleep 1; done"`, function (error, stdout, stderr) {
|
||||
expect(error).to.be(null);
|
||||
expect(stderr).to.be.empty();
|
||||
|
||||
@@ -45,7 +45,7 @@ describe('Dockerproxy', function () {
|
||||
// it('wait', function (done) {} );
|
||||
|
||||
it('can get info', function (done) {
|
||||
exec(DOCKER + ' info', function (error, stdout, stderr) {
|
||||
exec(DOCKER + ' info', function (error, stdout/*, stderr*/) {
|
||||
expect(error).to.be(null);
|
||||
expect(stdout).to.contain('Containers:');
|
||||
// expect(stderr).to.be.empty(); // on some machines, i get 'No swap limit support'
|
||||
@@ -54,7 +54,7 @@ describe('Dockerproxy', function () {
|
||||
});
|
||||
|
||||
it('can create container', function (done) {
|
||||
var cmd = `${DOCKER} run cloudron/base:1.0.0 "/bin/bash" "-c" "echo 'hello'"`;
|
||||
var cmd = `${DOCKER} run cloudron/base:2.0.0 "/bin/bash" "-c" "echo 'hello'"`;
|
||||
exec(cmd, function (error, stdout, stderr) {
|
||||
expect(error).to.be(null);
|
||||
expect(stdout).to.contain('hello');
|
||||
@@ -64,7 +64,7 @@ describe('Dockerproxy', function () {
|
||||
});
|
||||
|
||||
it('proxy overwrites the container network option', function (done) {
|
||||
var cmd = `${DOCKER} run --network ifnotrewritethiswouldfail cloudron/base:1.0.0 "/bin/bash" "-c" "echo 'hello'"`;
|
||||
var cmd = `${DOCKER} run --network ifnotrewritethiswouldfail cloudron/base:2.0.0 "/bin/bash" "-c" "echo 'hello'"`;
|
||||
exec(cmd, function (error, stdout, stderr) {
|
||||
expect(error).to.be(null);
|
||||
expect(stdout).to.contain('hello');
|
||||
|
||||
@@ -36,7 +36,6 @@ var USER_0 = {
|
||||
role: 'user',
|
||||
salt: 'morton',
|
||||
createdAt: 'sometime back',
|
||||
modifiedAt: 'now',
|
||||
resetToken: hat(256),
|
||||
displayName: '',
|
||||
source: '',
|
||||
@@ -52,7 +51,6 @@ var USER_1 = { // this user has not signed up yet
|
||||
role: 'user',
|
||||
salt: 'morton',
|
||||
createdAt: 'sometime back',
|
||||
modifiedAt: 'now',
|
||||
resetToken: hat(256),
|
||||
displayName: '',
|
||||
source: '',
|
||||
|
||||
@@ -912,12 +912,12 @@ describe('User', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('resetPasswordByIdentifier', function () {
|
||||
describe('sendPasswordResetByIdentifier', function () {
|
||||
before(createOwner);
|
||||
after(cleanupUsers);
|
||||
|
||||
it('fails due to unkown email', function (done) {
|
||||
users.resetPasswordByIdentifier('unknown@mail.com', function (error) {
|
||||
users.sendPasswordResetByIdentifier('unknown@mail.com', function (error) {
|
||||
expect(error).to.be.an(BoxError);
|
||||
expect(error.reason).to.eql(BoxError.NOT_FOUND);
|
||||
done();
|
||||
@@ -925,7 +925,7 @@ describe('User', function () {
|
||||
});
|
||||
|
||||
it('fails due to unkown username', function (done) {
|
||||
users.resetPasswordByIdentifier('unknown', function (error) {
|
||||
users.sendPasswordResetByIdentifier('unknown', function (error) {
|
||||
expect(error).to.be.an(BoxError);
|
||||
expect(error.reason).to.eql(BoxError.NOT_FOUND);
|
||||
done();
|
||||
@@ -933,14 +933,14 @@ describe('User', function () {
|
||||
});
|
||||
|
||||
it('succeeds with email', function (done) {
|
||||
users.resetPasswordByIdentifier(EMAIL, function (error) {
|
||||
users.sendPasswordResetByIdentifier(EMAIL, function (error) {
|
||||
expect(error).to.not.be.ok();
|
||||
checkMails(1, done);
|
||||
});
|
||||
});
|
||||
|
||||
it('succeeds with username', function (done) {
|
||||
users.resetPasswordByIdentifier(USERNAME, function (error) {
|
||||
users.sendPasswordResetByIdentifier(USERNAME, function (error) {
|
||||
expect(error).to.not.be.ok();
|
||||
checkMails(1, done);
|
||||
});
|
||||
|
||||
+3
-4
@@ -28,7 +28,7 @@ var assert = require('assert'),
|
||||
debug = require('debug')('box:userdb'),
|
||||
mysql = require('mysql');
|
||||
|
||||
var USERS_FIELDS = [ 'id', 'username', 'email', 'fallbackEmail', 'password', 'salt', 'createdAt', 'modifiedAt', 'resetToken', 'displayName',
|
||||
var USERS_FIELDS = [ 'id', 'username', 'email', 'fallbackEmail', 'password', 'salt', 'createdAt', 'resetToken', 'displayName',
|
||||
'twoFactorAuthenticationEnabled', 'twoFactorAuthenticationSecret', 'active', 'source', 'role', 'resetTokenCreationTime' ].join(',');
|
||||
|
||||
var APP_PASSWORD_FIELDS = [ 'id', 'name', 'userId', 'identifier', 'hashedPassword', 'creationTime' ].join(',');
|
||||
@@ -166,15 +166,14 @@ function add(userId, user, callback) {
|
||||
assert.strictEqual(typeof user.fallbackEmail, 'string');
|
||||
assert.strictEqual(typeof user.salt, 'string');
|
||||
assert.strictEqual(typeof user.createdAt, 'string');
|
||||
assert.strictEqual(typeof user.modifiedAt, 'string');
|
||||
assert.strictEqual(typeof user.resetToken, 'string');
|
||||
assert.strictEqual(typeof user.displayName, 'string');
|
||||
assert.strictEqual(typeof user.source, 'string');
|
||||
assert.strictEqual(typeof user.role, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
const query = 'INSERT INTO users (id, username, password, email, fallbackEmail, salt, createdAt, modifiedAt, resetToken, displayName, source, role) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
|
||||
const args = [ userId, user.username, user.password, user.email, user.fallbackEmail, user.salt, user.createdAt, user.modifiedAt, user.resetToken, user.displayName, user.source, user.role ];
|
||||
const query = 'INSERT INTO users (id, username, password, email, fallbackEmail, salt, createdAt, resetToken, displayName, source, role) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
|
||||
const args = [ userId, user.username, user.password, user.email, user.fallbackEmail, user.salt, user.createdAt, user.resetToken, user.displayName, user.source, user.role ];
|
||||
|
||||
database.query(query, args, function (error) {
|
||||
if (error && error.code === 'ER_DUP_ENTRY' && error.sqlMessage.indexOf('users_email') !== -1) return callback(new BoxError(BoxError.ALREADY_EXISTS, 'email already exists'));
|
||||
|
||||
+96
-15
@@ -16,9 +16,8 @@ exports = module.exports = {
|
||||
getByResetToken: getByResetToken,
|
||||
getByUsername: getByUsername,
|
||||
getAdmins: getAdmins,
|
||||
resetPasswordByIdentifier: resetPasswordByIdentifier,
|
||||
setPassword: setPassword,
|
||||
update: updateUser,
|
||||
update: update,
|
||||
createOwner: createOwner,
|
||||
getOwner: getOwner,
|
||||
createInvite: createInvite,
|
||||
@@ -28,6 +27,14 @@ exports = module.exports = {
|
||||
enableTwoFactorAuthentication: enableTwoFactorAuthentication,
|
||||
disableTwoFactorAuthentication: disableTwoFactorAuthentication,
|
||||
|
||||
sendPasswordResetByIdentifier: sendPasswordResetByIdentifier,
|
||||
|
||||
setupAccount,
|
||||
getAvatarUrlSync,
|
||||
getAvatarFileSync,
|
||||
setAvatar,
|
||||
clearAvatar,
|
||||
|
||||
count: count,
|
||||
|
||||
AP_MAIL: 'mail',
|
||||
@@ -54,14 +61,17 @@ let assert = require('assert'),
|
||||
debug = require('debug')('box:user'),
|
||||
eventlog = require('./eventlog.js'),
|
||||
externalLdap = require('./externalldap.js'),
|
||||
fs = require('fs'),
|
||||
groups = require('./groups.js'),
|
||||
hat = require('./hat.js'),
|
||||
mailer = require('./mailer.js'),
|
||||
path = require('path'),
|
||||
paths = require('./paths.js'),
|
||||
qrcode = require('qrcode'),
|
||||
safe = require('safetydance'),
|
||||
settings = require('./settings.js'),
|
||||
speakeasy = require('speakeasy'),
|
||||
tokens = require('./tokens.js'),
|
||||
userdb = require('./userdb.js'),
|
||||
uuid = require('uuid'),
|
||||
validator = require('validator'),
|
||||
@@ -182,7 +192,6 @@ function create(username, password, email, displayName, options, auditSource, ca
|
||||
password: Buffer.from(derivedKey, 'binary').toString('hex'),
|
||||
salt: salt.toString('hex'),
|
||||
createdAt: now,
|
||||
modifiedAt: now,
|
||||
resetToken: '',
|
||||
displayName: displayName,
|
||||
source: source,
|
||||
@@ -407,7 +416,7 @@ function getByUsername(username, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function updateUser(user, data, auditSource, callback) {
|
||||
function update(user, data, auditSource, callback) {
|
||||
assert.strictEqual(typeof user, 'object');
|
||||
assert.strictEqual(typeof data, 'object');
|
||||
assert(auditSource && typeof auditSource === 'object');
|
||||
@@ -484,13 +493,11 @@ function getAdmins(callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function resetPasswordByIdentifier(identifier, callback) {
|
||||
function sendPasswordResetByIdentifier(identifier, callback) {
|
||||
assert.strictEqual(typeof identifier, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
var getter;
|
||||
if (identifier.indexOf('@') === -1) getter = userdb.getByUsername;
|
||||
else getter = userdb.getByEmail;
|
||||
const getter = identifier.indexOf('@') === -1 ? userdb.getByUsername : userdb.getByEmail;
|
||||
|
||||
getter(identifier.toLowerCase(), function (error, result) {
|
||||
if (error) return callback(error);
|
||||
@@ -525,7 +532,6 @@ function setPassword(user, newPassword, callback) {
|
||||
if (error) return callback(new BoxError(BoxError.CRYPTO_ERROR, error));
|
||||
|
||||
let data = {
|
||||
modifiedAt: (new Date()).toISOString(),
|
||||
password: Buffer.from(derivedKey, 'binary').toString('hex'),
|
||||
resetToken: ''
|
||||
};
|
||||
@@ -569,11 +575,12 @@ function getOwner(callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function inviteLink(user) {
|
||||
function inviteLink(user, directoryConfig) {
|
||||
let link = `${settings.adminOrigin()}/setupaccount.html?resetToken=${user.resetToken}&email=${encodeURIComponent(user.email)}`;
|
||||
|
||||
if (user.username) link += `&username=${encodeURIComponent(user.username)}`;
|
||||
if (user.displayName) link += `&displayName=${encodeURIComponent(user.displayName)}`;
|
||||
if (directoryConfig.lockUserProfiles) link += '&profileLocked=true';
|
||||
|
||||
return link;
|
||||
}
|
||||
@@ -586,11 +593,16 @@ function createInvite(user, callback) {
|
||||
|
||||
const resetToken = hat(256), resetTokenCreationTime = new Date();
|
||||
|
||||
userdb.update(user.id, { resetToken, resetTokenCreationTime }, function (error) {
|
||||
settings.getDirectoryConfig(function (error, directoryConfig) {
|
||||
if (error) return callback(error);
|
||||
|
||||
user.resetToken = resetToken;
|
||||
callback(null, { resetToken, inviteLink: inviteLink(user) });
|
||||
userdb.update(user.id, { resetToken, resetTokenCreationTime }, function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
user.resetToken = resetToken;
|
||||
|
||||
callback(null, { resetToken, inviteLink: inviteLink(user, directoryConfig) });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -602,9 +614,43 @@ function sendInvite(user, options, callback) {
|
||||
if (user.source) return callback(new BoxError(BoxError.CONFLICT, 'User is from an external directory'));
|
||||
if (!user.resetToken) return callback(new BoxError(BoxError.CONFLICT, 'Must generate resetToken to send invitation'));
|
||||
|
||||
mailer.sendInvite(user, options.invitor || null, inviteLink(user));
|
||||
settings.getDirectoryConfig(function (error, directoryConfig) {
|
||||
if (error) return callback(error);
|
||||
|
||||
callback(null);
|
||||
mailer.sendInvite(user, options.invitor || null, inviteLink(user, directoryConfig));
|
||||
|
||||
callback(null);
|
||||
});
|
||||
}
|
||||
|
||||
function setupAccount(user, data, auditSource, callback) {
|
||||
assert.strictEqual(typeof user, 'object');
|
||||
assert.strictEqual(typeof data, 'object');
|
||||
assert(auditSource && typeof auditSource === 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
settings.getDirectoryConfig(function (error, directoryConfig) {
|
||||
if (error) return callback(error);
|
||||
|
||||
const updateFunc = (done) => {
|
||||
if (directoryConfig.lockUserProfiles) return done();
|
||||
update(user, _.pick(data, 'username', 'displayName'), auditSource, done);
|
||||
};
|
||||
|
||||
updateFunc(function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
setPassword(user, data.password, function (error) { // setPassword clears the resetToken
|
||||
if (error) return callback(error);
|
||||
|
||||
tokens.add(tokens.ID_WEBADMIN, user.id, Date.now() + constants.DEFAULT_TOKEN_EXPIRATION, {}, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
callback(null, result.accessToken);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setTwoFactorAuthenticationSecret(userId, callback) {
|
||||
@@ -753,3 +799,38 @@ function delAppPassword(id, callback) {
|
||||
callback(null);
|
||||
});
|
||||
}
|
||||
|
||||
function getAvatarFileSync(id) {
|
||||
assert.strictEqual(typeof id, 'string');
|
||||
|
||||
return path.join(paths.PROFILE_ICONS_DIR, id);
|
||||
}
|
||||
|
||||
function getAvatarUrlSync(user) {
|
||||
assert.strictEqual(typeof user, 'object');
|
||||
|
||||
if (fs.existsSync(path.join(paths.PROFILE_ICONS_DIR, user.id))) return `${settings.adminOrigin()}/api/v1/profile/avatar/${user.id}`;
|
||||
|
||||
const emailHash = require('crypto').createHash('md5').update(user.email).digest('hex');
|
||||
return `https://www.gravatar.com/avatar/${emailHash}.jpg`;
|
||||
}
|
||||
|
||||
function setAvatar(id, filename, callback) {
|
||||
assert.strictEqual(typeof id, 'string');
|
||||
assert.strictEqual(typeof filename, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
fs.rename(filename, path.join(paths.PROFILE_ICONS_DIR, id), function (error) {
|
||||
if (error) return callback(new BoxError(BoxError.FS_ERROR, error.message));
|
||||
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
function clearAvatar(id, callback) {
|
||||
assert.strictEqual(typeof id, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
safe.fs.unlinkSync(path.join(paths.PROFILE_ICONS_DIR, id));
|
||||
callback();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user