Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b5303ba7f | ||
|
|
022a54278e | ||
|
|
19b50dc428 | ||
|
|
e7eac003a9 | ||
|
|
cc17c6b2cd | ||
|
|
23d16b07aa | ||
|
|
7ecb3dd771 | ||
|
|
e43f974d34 | ||
|
|
e16cd38722 | ||
|
|
9d2f81d6b9 | ||
|
|
3fe539436b | ||
|
|
76f94eb559 | ||
|
|
7630ef921d | ||
|
|
625127d298 | ||
|
|
f24c4d2805 | ||
|
|
194340afa0 | ||
|
|
fdc9639aba | ||
|
|
f95ec53a85 | ||
|
|
3d425b7030 | ||
|
|
37c6c24e0e | ||
|
|
50bdd7ec7b | ||
|
|
769cb3e251 | ||
|
|
9447c45406 | ||
|
|
66a3962cfe | ||
|
|
d145eacbaf | ||
|
|
ed03ed7bad | ||
|
|
953b463799 | ||
|
|
6d28bb0489 | ||
|
|
c2f464ea75 | ||
|
|
4c56ffc767 | ||
|
|
885aa8833c | ||
|
|
63310c44c0 | ||
|
|
05dd65718f | ||
|
|
05d3f8a667 | ||
|
|
3fa45ea728 | ||
|
|
a7d2098f09 | ||
|
|
e1ecb49d59 | ||
|
|
6facfac4c5 | ||
|
|
97d2494fe3 | ||
|
|
a54be69c96 | ||
|
|
800e25a7a7 | ||
|
|
c1ce2977fa |
12
CHANGES
12
CHANGES
@@ -1597,3 +1597,15 @@
|
||||
* Fix GCDNS crash
|
||||
* Add option to update without backing up
|
||||
|
||||
[4.0.3]
|
||||
* Fix dashboard issue for non-admins
|
||||
|
||||
[4.1.0]
|
||||
* Remove password requirement for uninstalling apps and users
|
||||
* Hosting provider edition
|
||||
* Enforce limits in mail container
|
||||
* Fix crash when using unauthenticated relay
|
||||
* Fix domain and tag filtering
|
||||
* Customizable app icons
|
||||
* Remove obsolete X-Frame-Options from nginx configs
|
||||
|
||||
|
||||
@@ -41,12 +41,7 @@ Try our demo at https://my.demo.cloudron.io (username: cloudron password: cloudr
|
||||
|
||||
## Installing
|
||||
|
||||
You can install the Cloudron platform on your own server or get a managed server
|
||||
from cloudron.io. In either case, the Cloudron platform will keep your server and
|
||||
apps up-to-date and secure.
|
||||
|
||||
* [Selfhosting](https://cloudron.io/documentation/installation/) - [Pricing](https://cloudron.io/pricing.html)
|
||||
* [Managed Hosting](https://cloudron.io/managed.html)
|
||||
[Install script](https://cloudron.io/documentation/installation/) - [Pricing](https://cloudron.io/pricing.html)
|
||||
|
||||
**Note:** This repo is a small part of what gets installed on your server - there is
|
||||
the dashboard, database addons, graph container, base image etc. Cloudron also relies
|
||||
|
||||
319
package-lock.json
generated
319
package-lock.json
generated
@@ -129,42 +129,6 @@
|
||||
"execa": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"@sinonjs/commons": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.4.0.tgz",
|
||||
"integrity": "sha512-9jHK3YF/8HtJ9wCAbG+j8cD0i0+ATS9A7gXFqS36TblLPNy6rEEc+SB0imo91eCboGaBYGV/MT1/br/J+EE7Tw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"type-detect": "4.0.8"
|
||||
}
|
||||
},
|
||||
"@sinonjs/formatio": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.1.tgz",
|
||||
"integrity": "sha512-tsHvOB24rvyvV2+zKMmPkZ7dXX6LSLKZ7aOtXY6Edklp0uRcgGpOsQTTGTcWViFyx4uhWc6GV8QdnALbIbIdeQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@sinonjs/commons": "^1",
|
||||
"@sinonjs/samsam": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"@sinonjs/samsam": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.1.tgz",
|
||||
"integrity": "sha512-wRSfmyd81swH0hA1bxJZJ57xr22kC07a1N4zuIL47yTS04bDk6AoCkczcqHEjcRPmJ+FruGJ9WBQiJwMtIElFw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@sinonjs/commons": "^1.0.2",
|
||||
"array-from": "^2.1.1",
|
||||
"lodash": "^4.17.11"
|
||||
}
|
||||
},
|
||||
"@sinonjs/text-encoding": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz",
|
||||
"integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/caseless": {
|
||||
"version": "0.12.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz",
|
||||
@@ -260,7 +224,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
|
||||
},
|
||||
@@ -319,15 +283,9 @@
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
|
||||
},
|
||||
"array-from": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz",
|
||||
"integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=",
|
||||
"dev": true
|
||||
},
|
||||
"arrify": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0="
|
||||
},
|
||||
"asn1": {
|
||||
@@ -340,7 +298,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": {
|
||||
@@ -401,7 +359,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"
|
||||
@@ -443,14 +401,6 @@
|
||||
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz",
|
||||
"integrity": "sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ=="
|
||||
},
|
||||
"bindings": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||
"requires": {
|
||||
"file-uri-to-path": "1.0.0"
|
||||
}
|
||||
},
|
||||
"bl": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz",
|
||||
@@ -547,7 +497,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": {
|
||||
@@ -562,7 +512,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",
|
||||
@@ -701,7 +651,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="
|
||||
},
|
||||
"color-convert": {
|
||||
@@ -747,7 +697,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": {
|
||||
@@ -934,7 +884,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": {
|
||||
@@ -1023,7 +973,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"
|
||||
@@ -1098,7 +1048,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": {
|
||||
@@ -1402,7 +1352,7 @@
|
||||
},
|
||||
"ent": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0="
|
||||
},
|
||||
"error-ex": {
|
||||
@@ -1499,7 +1449,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
|
||||
},
|
||||
@@ -1624,7 +1574,7 @@
|
||||
},
|
||||
"extsprintf": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
|
||||
},
|
||||
"eyes": {
|
||||
@@ -1655,11 +1605,6 @@
|
||||
"pend": "~1.2.0"
|
||||
}
|
||||
},
|
||||
"file-uri-to-path": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="
|
||||
},
|
||||
"final-fs": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/final-fs/-/final-fs-1.6.1.tgz",
|
||||
@@ -1777,7 +1722,7 @@
|
||||
"dependencies": {
|
||||
"mkdirp": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz",
|
||||
"resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz",
|
||||
"integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=",
|
||||
"dev": true
|
||||
},
|
||||
@@ -1789,7 +1734,7 @@
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "2.2.8",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz",
|
||||
"resolved": "http://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz",
|
||||
"integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=",
|
||||
"dev": true
|
||||
}
|
||||
@@ -2136,11 +2081,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"hoek": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz",
|
||||
"integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA=="
|
||||
},
|
||||
"hosted-git-info": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz",
|
||||
@@ -2253,7 +2193,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",
|
||||
@@ -2262,7 +2202,7 @@
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||
},
|
||||
"ini": {
|
||||
@@ -2282,7 +2222,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
|
||||
},
|
||||
@@ -2328,7 +2268,7 @@
|
||||
},
|
||||
"is-stream": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
|
||||
},
|
||||
"is-stream-ended": {
|
||||
@@ -2358,20 +2298,12 @@
|
||||
},
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
},
|
||||
"isemail": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/isemail/-/isemail-3.2.0.tgz",
|
||||
"integrity": "sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg==",
|
||||
"requires": {
|
||||
"punycode": "2.x.x"
|
||||
}
|
||||
},
|
||||
"isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
|
||||
},
|
||||
"isstream": {
|
||||
@@ -2389,23 +2321,6 @@
|
||||
"resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz",
|
||||
"integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc="
|
||||
},
|
||||
"joi": {
|
||||
"version": "13.7.0",
|
||||
"resolved": "https://registry.npmjs.org/joi/-/joi-13.7.0.tgz",
|
||||
"integrity": "sha512-xuY5VkHfeOYK3Hdi91ulocfuFopwgbSORmIwzcwHKESQhC7w1kD5jaVSPnqDxS2I8t3RZ9omCKAxNwXN5zG1/Q==",
|
||||
"requires": {
|
||||
"hoek": "5.x.x",
|
||||
"isemail": "3.x.x",
|
||||
"topo": "3.x.x"
|
||||
},
|
||||
"dependencies": {
|
||||
"hoek": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/hoek/-/hoek-5.0.4.tgz",
|
||||
"integrity": "sha512-Alr4ZQgoMlnere5FZJsIyfIjORBqZll5POhDsF4q64dPuJR6rNxXdDxtHSQq8OXRurhmx+PWYEE8bXRROY8h0w=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"js-base64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz",
|
||||
@@ -2465,7 +2380,7 @@
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-1.0.1.tgz",
|
||||
"resolved": "http://registry.npmjs.org/jsonfile/-/jsonfile-1.0.1.tgz",
|
||||
"integrity": "sha1-6l7+QLg2kLmGZ2FKc5L8YOhCwN0=",
|
||||
"dev": true
|
||||
},
|
||||
@@ -2485,12 +2400,6 @@
|
||||
"verror": "1.10.0"
|
||||
}
|
||||
},
|
||||
"just-extend": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz",
|
||||
"integrity": "sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==",
|
||||
"dev": true
|
||||
},
|
||||
"jwa": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
|
||||
@@ -2525,7 +2434,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"
|
||||
@@ -2533,7 +2442,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="
|
||||
}
|
||||
}
|
||||
@@ -2639,12 +2548,6 @@
|
||||
"chalk": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"lolex": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/lolex/-/lolex-4.0.1.tgz",
|
||||
"integrity": "sha512-UHuOBZ5jjsKuzbB/gRNNW8Vg8f00Emgskdq2kvZxgBJCS0aqquAuXai/SkWORlKeZEiNQWZjFZOqIUcH9LqKCw==",
|
||||
"dev": true
|
||||
},
|
||||
"loud-rejection": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz",
|
||||
@@ -2769,19 +2672,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"
|
||||
@@ -3008,7 +2911,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": {
|
||||
@@ -3019,7 +2922,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": {
|
||||
@@ -3032,13 +2935,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": {
|
||||
@@ -3058,14 +2961,6 @@
|
||||
"sqlstring": "2.3.1"
|
||||
}
|
||||
},
|
||||
"namecheap": {
|
||||
"version": "github:joshuakarjala/node-namecheap#464a9528b7ded3ee2520c2688bc98cbffb08e603",
|
||||
"from": "github:joshuakarjala/node-namecheap#464a952",
|
||||
"requires": {
|
||||
"request": "*",
|
||||
"xml2json": "*"
|
||||
}
|
||||
},
|
||||
"nan": {
|
||||
"version": "2.12.1",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.12.1.tgz",
|
||||
@@ -3086,42 +2981,6 @@
|
||||
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
|
||||
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ=="
|
||||
},
|
||||
"nise": {
|
||||
"version": "1.4.10",
|
||||
"resolved": "https://registry.npmjs.org/nise/-/nise-1.4.10.tgz",
|
||||
"integrity": "sha512-sa0RRbj53dovjc7wombHmVli9ZihXbXCQ2uH3TNm03DyvOSIQbxg+pbqDKrk2oxMK1rtLGVlKxcB9rrc6X5YjA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@sinonjs/formatio": "^3.1.0",
|
||||
"@sinonjs/text-encoding": "^0.7.1",
|
||||
"just-extend": "^4.0.2",
|
||||
"lolex": "^2.3.2",
|
||||
"path-to-regexp": "^1.7.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"isarray": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
||||
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
|
||||
"dev": true
|
||||
},
|
||||
"lolex": {
|
||||
"version": "2.7.5",
|
||||
"resolved": "https://registry.npmjs.org/lolex/-/lolex-2.7.5.tgz",
|
||||
"integrity": "sha512-l9x0+1offnKKIzYVjyXU2SiwhXDLekRzKyhnbyldPHvC7BvLPVpdNUNR2KeMAiCN2D/kLNttZgQD5WjSxuBx3Q==",
|
||||
"dev": true
|
||||
},
|
||||
"path-to-regexp": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz",
|
||||
"integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"isarray": "0.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"nock": {
|
||||
"version": "10.0.6",
|
||||
"resolved": "https://registry.npmjs.org/nock/-/nock-10.0.6.tgz",
|
||||
@@ -3171,15 +3030,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node-expat": {
|
||||
"version": "2.3.17",
|
||||
"resolved": "https://registry.npmjs.org/node-expat/-/node-expat-2.3.17.tgz",
|
||||
"integrity": "sha512-mNTxY/GMiZGayqdKZXyf6lJR7OM1JqyL0EISjE4XF7Ov7+X4zJjmlnfxCi6Gml90IEOyiYBcyJg9MHDsDp6YHw==",
|
||||
"requires": {
|
||||
"bindings": "^1.2.1",
|
||||
"nan": "^2.10.0"
|
||||
}
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.3.0.tgz",
|
||||
@@ -3353,7 +3203,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": {
|
||||
@@ -3402,7 +3252,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="
|
||||
},
|
||||
"oauth-sign": {
|
||||
@@ -3553,7 +3403,7 @@
|
||||
},
|
||||
"p-finally": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4="
|
||||
},
|
||||
"p-is-promise": {
|
||||
@@ -3592,7 +3442,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": {
|
||||
@@ -3662,7 +3512,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": {
|
||||
@@ -3757,7 +3607,7 @@
|
||||
},
|
||||
"precond": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/precond/-/precond-0.2.3.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-qpWRvKokkj8eD0hJ0kD0fvwQdaw="
|
||||
},
|
||||
"process-nextick-args": {
|
||||
@@ -3809,7 +3659,7 @@
|
||||
},
|
||||
"pseudomap": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
|
||||
},
|
||||
"psl": {
|
||||
@@ -4038,12 +3888,12 @@
|
||||
},
|
||||
"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": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE="
|
||||
},
|
||||
"resolve": {
|
||||
@@ -4327,7 +4177,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": {
|
||||
@@ -4337,7 +4187,7 @@
|
||||
},
|
||||
"shebang-command": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
|
||||
"requires": {
|
||||
"shebang-regex": "^1.0.0"
|
||||
@@ -4345,7 +4195,7 @@
|
||||
},
|
||||
"shebang-regex": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM="
|
||||
},
|
||||
"showdown": {
|
||||
@@ -4514,35 +4364,9 @@
|
||||
},
|
||||
"signal-exit": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
|
||||
},
|
||||
"sinon": {
|
||||
"version": "7.3.2",
|
||||
"resolved": "https://registry.npmjs.org/sinon/-/sinon-7.3.2.tgz",
|
||||
"integrity": "sha512-thErC1z64BeyGiPvF8aoSg0LEnptSaWE7YhdWWbWXgelOyThent7uKOnnEh9zBxDbKixtr5dEko+ws1sZMuFMA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@sinonjs/commons": "^1.4.0",
|
||||
"@sinonjs/formatio": "^3.2.1",
|
||||
"@sinonjs/samsam": "^3.3.1",
|
||||
"diff": "^3.5.0",
|
||||
"lolex": "^4.0.1",
|
||||
"nise": "^1.4.10",
|
||||
"supports-color": "^5.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"smtp-connection": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/smtp-connection/-/smtp-connection-2.12.0.tgz",
|
||||
@@ -4625,7 +4449,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": {
|
||||
@@ -4703,7 +4527,7 @@
|
||||
},
|
||||
"stream-shift": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI="
|
||||
},
|
||||
"streamsearch": {
|
||||
@@ -4752,7 +4576,7 @@
|
||||
},
|
||||
"strip-eof": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8="
|
||||
},
|
||||
"strip-indent": {
|
||||
@@ -4771,7 +4595,7 @@
|
||||
},
|
||||
"stubs": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-6NK6H6nJBXAwPAMLaQD31fiavls="
|
||||
},
|
||||
"superagent": {
|
||||
@@ -4960,21 +4784,6 @@
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
|
||||
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
|
||||
},
|
||||
"topo": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/topo/-/topo-3.0.3.tgz",
|
||||
"integrity": "sha512-IgpPtvD4kjrJ7CRA3ov2FhWQADwv+Tdqbsf1ZnPUSAtCJ9e1Z44MmoSGDXGk4IppoZA7jd/QRkNddlLJWlUZsQ==",
|
||||
"requires": {
|
||||
"hoek": "6.x.x"
|
||||
},
|
||||
"dependencies": {
|
||||
"hoek": {
|
||||
"version": "6.1.2",
|
||||
"resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.2.tgz",
|
||||
"integrity": "sha512-6qhh/wahGYZHFSFw12tBbJw5fsAhhwrrG/y3Cs0YMTv2WzMnL0oLPnQJjv1QJvEfylRSOFuP+xCu+tdx0tD16Q=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"tough-cookie": {
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
|
||||
@@ -5071,7 +4880,7 @@
|
||||
},
|
||||
"typedarray": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
|
||||
},
|
||||
"uid-safe": {
|
||||
@@ -5148,7 +4957,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": {
|
||||
@@ -5208,7 +5017,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"
|
||||
@@ -5216,12 +5025,12 @@
|
||||
"dependencies": {
|
||||
"extsprintf": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.2.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-WtlGwi9bMrp/jNdCZxHG6KP8JSk="
|
||||
},
|
||||
"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"
|
||||
@@ -5231,7 +5040,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",
|
||||
@@ -5254,7 +5063,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": {
|
||||
@@ -5309,7 +5118,7 @@
|
||||
},
|
||||
"wrap-ansi": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
|
||||
"requires": {
|
||||
"string-width": "^1.0.1",
|
||||
@@ -5351,7 +5160,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": {
|
||||
@@ -5386,16 +5195,6 @@
|
||||
"xmlbuilder": "~9.0.1"
|
||||
}
|
||||
},
|
||||
"xml2json": {
|
||||
"version": "0.11.2",
|
||||
"resolved": "https://registry.npmjs.org/xml2json/-/xml2json-0.11.2.tgz",
|
||||
"integrity": "sha512-ZJpHpPOL0T5lOvAHMnWm59iQOPqNtam5t2TMUllWZ1k5Wm8L5YyvQnkeaVnRKCvDwY5EumqXWyOjjMdQVz272A==",
|
||||
"requires": {
|
||||
"hoek": "^4.2.1",
|
||||
"joi": "^13.1.2",
|
||||
"node-expat": "^2.3.15"
|
||||
}
|
||||
},
|
||||
"xmlbuilder": {
|
||||
"version": "9.0.7",
|
||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
|
||||
@@ -5409,7 +5208,7 @@
|
||||
},
|
||||
"xtend": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
|
||||
},
|
||||
"y18n": {
|
||||
|
||||
@@ -40,13 +40,13 @@
|
||||
"js-yaml": "^3.13.1",
|
||||
"json": "^9.0.6",
|
||||
"ldapjs": "^1.0.2",
|
||||
"lodash": "^4.17.11",
|
||||
"lodash.chunk": "^4.2.0",
|
||||
"mime": "^2.4.2",
|
||||
"moment-timezone": "^0.5.25",
|
||||
"morgan": "^1.9.1",
|
||||
"multiparty": "^4.2.1",
|
||||
"mysql": "^2.17.1",
|
||||
"namecheap": "github:joshuakarjala/node-namecheap#464a952",
|
||||
"nodemailer": "^6.1.1",
|
||||
"nodemailer-smtp-transport": "^2.7.4",
|
||||
"oauth2orize": "^1.11.0",
|
||||
@@ -78,7 +78,8 @@
|
||||
"uuid": "^3.3.2",
|
||||
"valid-url": "^1.0.9",
|
||||
"validator": "^10.11.0",
|
||||
"ws": "^6.2.1"
|
||||
"ws": "^6.2.1",
|
||||
"xml2js": "^0.4.19"
|
||||
},
|
||||
"devDependencies": {
|
||||
"expect.js": "*",
|
||||
@@ -88,8 +89,7 @@
|
||||
"mock-aws-s3": "git+https://github.com/cloudron-io/mock-aws-s3.git",
|
||||
"nock": "^10.0.6",
|
||||
"node-sass": "^4.11.0",
|
||||
"recursive-readdir": "^2.2.2",
|
||||
"sinon": "^7.3.2"
|
||||
"recursive-readdir": "^2.2.2"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "./runTests",
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eu -o pipefail
|
||||
|
||||
# This script collects diagnostic information to help debug server related issues
|
||||
# It also enables SSH access for the cloudron support team
|
||||
|
||||
@@ -17,7 +19,7 @@ This script collects diagnostic information to help debug server related issues
|
||||
|
||||
# We require root
|
||||
if [[ ${EUID} -ne 0 ]]; then
|
||||
echo "This script should be run as root." > /dev/stderr
|
||||
echo "This script should be run as root. Run with sudo"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -58,21 +60,6 @@ echo -n "Generating Cloudron Support stats..."
|
||||
# clear file
|
||||
rm -rf $OUT
|
||||
|
||||
ssh_port=$(cat /etc/ssh/sshd_config | grep "Port " | sed -e "s/.*Port //")
|
||||
if [[ $SUDO_USER == "" ]]; then
|
||||
ssh_user="root"
|
||||
ssh_folder="/root/.ssh/"
|
||||
authorized_key_file="${ssh_folder}/authorized_keys"
|
||||
else
|
||||
ssh_user="$SUDO_USER"
|
||||
ssh_folder="/home/$SUDO_USER/.ssh/"
|
||||
authorized_key_file="${ssh_folder}/authorized_keys"
|
||||
fi
|
||||
|
||||
echo -e $LINE"SSH"$LINE >> $OUT
|
||||
echo "Username: ${ssh_user}" >> $OUT
|
||||
echo "Port: ${ssh_port}" >> $OUT
|
||||
|
||||
echo -e $LINE"cloudron.conf"$LINE >> $OUT
|
||||
cat /etc/cloudron/cloudron.conf &>> $OUT
|
||||
|
||||
@@ -104,20 +91,42 @@ iptables -L &>> $OUT
|
||||
|
||||
echo "Done"
|
||||
|
||||
if [[ "${enableSSH}" == "true" ]]; then
|
||||
ssh_port=$(cat /etc/ssh/sshd_config | grep "Port " | sed -e "s/.*Port //")
|
||||
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/cloudron.conf); then
|
||||
ssh_user="ubuntu"
|
||||
keys_file="/home/ubuntu/.ssh/authorized_keys"
|
||||
else
|
||||
ssh_user="root"
|
||||
keys_file="/root/.ssh/authorized_keys"
|
||||
fi
|
||||
|
||||
echo -e $LINE"SSH"$LINE >> $OUT
|
||||
echo "Username: ${ssh_user}" >> $OUT
|
||||
echo "Port: ${ssh_port}" >> $OUT
|
||||
echo "PermitRootLogin: ${permit_root_login}" >> $OUT
|
||||
echo "Key file: ${keys_file}" >> $OUT
|
||||
|
||||
echo -n "Enabling ssh access for the Cloudron support team..."
|
||||
mkdir -p $(dirname "${keys_file}") # .ssh does not exist sometimes
|
||||
touch "${keys_file}" # required for concat to work
|
||||
if ! grep -q "${CLOUDRON_SUPPORT_PUBLIC_KEY}" "${keys_file}"; then
|
||||
echo -e "\n${CLOUDRON_SUPPORT_PUBLIC_KEY}" >> "${keys_file}"
|
||||
chmod 600 "${keys_file}"
|
||||
chown "${ssh_user}" "${keys_file}"
|
||||
fi
|
||||
|
||||
echo "Done"
|
||||
fi
|
||||
|
||||
echo -n "Uploading information..."
|
||||
# for some reason not using $(cat $OUT) will not contain newlines!?
|
||||
paste_key=$(curl -X POST ${PASTEBIN}/documents --silent -d "$(cat $OUT)" | python3 -c "import sys, json; print(json.load(sys.stdin)['key'])")
|
||||
echo "Done"
|
||||
|
||||
if [[ "${enableSSH}" == "true" ]]; then
|
||||
echo -n "Enabling ssh access for the Cloudron support team..."
|
||||
mkdir -p "${ssh_folder}"
|
||||
echo -e "\n${CLOUDRON_SUPPORT_PUBLIC_KEY}" >> ${authorized_key_file}
|
||||
chown -R ${ssh_user} "${ssh_folder}"
|
||||
chmod 600 "${authorized_key_file}"
|
||||
echo "Done"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Please email the following link to support@cloudron.io"
|
||||
echo ""
|
||||
|
||||
@@ -1,16 +1,31 @@
|
||||
# add customizations here
|
||||
# after making changes run "sudo systemctl restart box"
|
||||
|
||||
# features:
|
||||
# configureBackup: true
|
||||
# backups:
|
||||
# configurable: true
|
||||
#
|
||||
# domains:
|
||||
# dynamicDns: true
|
||||
# subscription: true
|
||||
# remoteSupport: true
|
||||
#
|
||||
# changeDashboardDomain: true
|
||||
#
|
||||
# subscription:
|
||||
# configurable: true
|
||||
#
|
||||
# support:
|
||||
# email: support@cloudron.io
|
||||
#
|
||||
# remoteSupport: true
|
||||
#
|
||||
# ticketFormBody: |
|
||||
# Use this form to open support tickets. You can also write directly to [support@cloudron.io](mailto:support@cloudron.io).
|
||||
# * [Knowledge Base & App Docs](https://cloudron.io/documentation/apps/?support_view)
|
||||
# * [Custom App Packaging & API](https://cloudron.io/developer/packaging/?support_view)
|
||||
# * [Forum](https://forum.cloudron.io/)
|
||||
#
|
||||
# submitTickets: true
|
||||
#
|
||||
# alerts:
|
||||
# email: support@cloudron.io
|
||||
# notifyCloudronAdmins: false
|
||||
|
||||
#
|
||||
# footer:
|
||||
# body: '© 2019 [Cloudron](https://cloudron.io) [Forum <i class="fa fa-comments"></i>](https://forum.cloudron.io)'
|
||||
|
||||
@@ -72,10 +72,6 @@ server {
|
||||
ssl_dhparam /home/yellowtent/boxdata/dhparams.pem;
|
||||
add_header Strict-Transport-Security "max-age=15768000";
|
||||
|
||||
# https://developer.mozilla.org/en-US/docs/Web/HTTP/X-Frame-Options
|
||||
add_header X-Frame-Options "<%= xFrameOptions %>";
|
||||
proxy_hide_header X-Frame-Options;
|
||||
|
||||
# https://github.com/twitter/secureheaders
|
||||
# https://www.owasp.org/index.php/OWASP_Secure_Headers_Project#tab=Compatibility_Matrix
|
||||
# https://wiki.mozilla.org/Security/Guidelines/Web_Security
|
||||
|
||||
12
src/appdb.js
12
src/appdb.js
@@ -69,7 +69,7 @@ var APPS_FIELDS_PREFIXED = [ 'apps.id', 'apps.appStoreId', 'apps.installationSta
|
||||
'apps.health', 'apps.containerId', 'apps.manifestJson', 'apps.httpPort', 'subdomains.subdomain AS location', 'subdomains.domain',
|
||||
'apps.accessRestrictionJson', 'apps.restoreConfigJson', 'apps.oldConfigJson', 'apps.updateConfigJson', 'apps.memoryLimit',
|
||||
'apps.label', 'apps.tagsJson',
|
||||
'apps.xFrameOptions', 'apps.sso', 'apps.debugModeJson', 'apps.robotsTxt', 'apps.enableBackup',
|
||||
'apps.sso', 'apps.debugModeJson', 'apps.robotsTxt', 'apps.enableBackup',
|
||||
'apps.creationTime', 'apps.updateTime', 'apps.ownerId', 'apps.mailboxName', 'apps.enableAutomaticUpdate',
|
||||
'apps.dataDir', 'apps.ts', 'apps.healthTime' ].join(',');
|
||||
|
||||
@@ -121,9 +121,6 @@ function postProcess(result) {
|
||||
if (result.accessRestriction && !result.accessRestriction.users) result.accessRestriction.users = [];
|
||||
delete result.accessRestrictionJson;
|
||||
|
||||
// TODO remove later once all apps have this attribute
|
||||
result.xFrameOptions = result.xFrameOptions || 'SAMEORIGIN';
|
||||
|
||||
result.sso = !!result.sso; // make it bool
|
||||
result.enableBackup = !!result.enableBackup; // make it bool
|
||||
result.enableAutomaticUpdate = !!result.enableAutomaticUpdate; // make it bool
|
||||
@@ -279,7 +276,6 @@ function add(id, appStoreId, manifest, location, domain, ownerId, portBindings,
|
||||
const accessRestriction = data.accessRestriction || null;
|
||||
const accessRestrictionJson = JSON.stringify(accessRestriction);
|
||||
const memoryLimit = data.memoryLimit || 0;
|
||||
const xFrameOptions = data.xFrameOptions || '';
|
||||
const installationState = data.installationState || exports.ISTATE_PENDING_INSTALL;
|
||||
const restoreConfigJson = data.restoreConfig ? JSON.stringify(data.restoreConfig) : null; // used when cloning
|
||||
const sso = 'sso' in data ? data.sso : null;
|
||||
@@ -293,10 +289,10 @@ function add(id, appStoreId, manifest, location, domain, ownerId, portBindings,
|
||||
var queries = [];
|
||||
|
||||
queries.push({
|
||||
query: 'INSERT INTO apps (id, appStoreId, manifestJson, installationState, accessRestrictionJson, memoryLimit, xFrameOptions,'
|
||||
query: 'INSERT INTO apps (id, appStoreId, manifestJson, installationState, accessRestrictionJson, memoryLimit, '
|
||||
+ 'restoreConfigJson, sso, debugModeJson, robotsTxt, ownerId, mailboxName, label, tagsJson) '
|
||||
+ ' VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
||||
args: [ id, appStoreId, manifestJson, installationState, accessRestrictionJson, memoryLimit, xFrameOptions, restoreConfigJson,
|
||||
+ ' VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
||||
args: [ id, appStoreId, manifestJson, installationState, accessRestrictionJson, memoryLimit, restoreConfigJson,
|
||||
sso, debugModeJson, robotsTxt, ownerId, mailboxName, label, tagsJson ]
|
||||
});
|
||||
|
||||
|
||||
62
src/apps.js
62
src/apps.js
@@ -90,7 +90,6 @@ var appdb = require('./appdb.js'),
|
||||
taskmanager = require('./taskmanager.js'),
|
||||
TransformStream = require('stream').Transform,
|
||||
updateChecker = require('./updatechecker.js'),
|
||||
url = require('url'),
|
||||
util = require('util'),
|
||||
uuid = require('uuid'),
|
||||
validator = require('validator'),
|
||||
@@ -241,20 +240,6 @@ function validateMemoryLimit(manifest, memoryLimit) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc7034
|
||||
function validateXFrameOptions(xFrameOptions) {
|
||||
assert.strictEqual(typeof xFrameOptions, 'string');
|
||||
|
||||
if (xFrameOptions === 'DENY') return null;
|
||||
if (xFrameOptions === 'SAMEORIGIN') return null;
|
||||
|
||||
var parts = xFrameOptions.split(' ');
|
||||
if (parts.length !== 2 || parts[0] !== 'ALLOW-FROM') return new AppsError(AppsError.BAD_FIELD, 'xFrameOptions must be "DENY", "SAMEORIGIN" or "ALLOW-FROM uri"' );
|
||||
|
||||
var uri = url.parse(parts[1]);
|
||||
return (uri.protocol === 'http:' || uri.protocol === 'https:') ? null : new AppsError(AppsError.BAD_FIELD, 'xFrameOptions ALLOW-FROM uri must be a valid http[s] uri' );
|
||||
}
|
||||
|
||||
function validateDebugMode(debugMode) {
|
||||
assert.strictEqual(typeof debugMode, 'object');
|
||||
|
||||
@@ -372,7 +357,7 @@ function getAppConfig(app) {
|
||||
accessRestriction: app.accessRestriction,
|
||||
portBindings: app.portBindings,
|
||||
memoryLimit: app.memoryLimit,
|
||||
xFrameOptions: app.xFrameOptions || 'SAMEORIGIN',
|
||||
|
||||
robotsTxt: app.robotsTxt,
|
||||
sso: app.sso,
|
||||
alternateDomains: app.alternateDomains || [],
|
||||
@@ -389,7 +374,7 @@ function removeInternalFields(app) {
|
||||
return _.pick(app,
|
||||
'id', 'appStoreId', 'installationState', 'installationProgress', 'runState', 'health',
|
||||
'location', 'domain', 'fqdn', 'mailboxName',
|
||||
'accessRestriction', 'manifest', 'portBindings', 'iconUrl', 'memoryLimit', 'xFrameOptions',
|
||||
'accessRestriction', 'manifest', 'portBindings', 'iconUrl', 'memoryLimit',
|
||||
'sso', 'debugMode', 'robotsTxt', 'enableBackup', 'creationTime', 'updateTime', 'ts', 'tags',
|
||||
'label', 'alternateDomains', 'ownerId', 'env', 'enableAutomaticUpdate', 'dataDir');
|
||||
}
|
||||
@@ -402,8 +387,15 @@ function removeRestrictedFields(app) {
|
||||
}
|
||||
|
||||
function getIconUrlSync(app) {
|
||||
var iconPath = paths.APP_ICONS_DIR + '/' + app.id + '.png';
|
||||
return fs.existsSync(iconPath) ? '/api/v1/apps/' + app.id + '/icon' : null;
|
||||
const iconUrl = '/api/v1/apps/' + app.id + '/icon';
|
||||
|
||||
const userIconPath = `${paths.APP_ICONS_DIR}/${app.id}.user.png`;
|
||||
if (safe.fs.existsSync(userIconPath)) return iconUrl;
|
||||
|
||||
const appstoreIconPath = `${paths.APP_ICONS_DIR}/${app.id}.png`;
|
||||
if (safe.fs.existsSync(appstoreIconPath)) return iconUrl;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function postProcess(app, domainObjectMap) {
|
||||
@@ -578,7 +570,6 @@ function install(data, user, auditSource, callback) {
|
||||
cert = data.cert || null,
|
||||
key = data.key || null,
|
||||
memoryLimit = data.memoryLimit || 0,
|
||||
xFrameOptions = data.xFrameOptions || 'SAMEORIGIN',
|
||||
sso = 'sso' in data ? data.sso : null,
|
||||
debugMode = data.debugMode || null,
|
||||
robotsTxt = data.robotsTxt || null,
|
||||
@@ -613,9 +604,6 @@ function install(data, user, auditSource, callback) {
|
||||
error = validateMemoryLimit(manifest, memoryLimit);
|
||||
if (error) return callback(error);
|
||||
|
||||
error = validateXFrameOptions(xFrameOptions);
|
||||
if (error) return callback(error);
|
||||
|
||||
error = validateDebugMode(debugMode);
|
||||
if (error) return callback(error);
|
||||
|
||||
@@ -672,7 +660,6 @@ function install(data, user, auditSource, callback) {
|
||||
var data = {
|
||||
accessRestriction: accessRestriction,
|
||||
memoryLimit: memoryLimit,
|
||||
xFrameOptions: xFrameOptions,
|
||||
sso: sso,
|
||||
debugMode: debugMode,
|
||||
mailboxName: mailboxName,
|
||||
@@ -754,12 +741,6 @@ function configure(appId, data, user, auditSource, callback) {
|
||||
if (error) return callback(error);
|
||||
}
|
||||
|
||||
if ('xFrameOptions' in data) {
|
||||
values.xFrameOptions = data.xFrameOptions;
|
||||
error = validateXFrameOptions(values.xFrameOptions);
|
||||
if (error) return callback(error);
|
||||
}
|
||||
|
||||
if ('debugMode' in data) {
|
||||
values.debugMode = data.debugMode;
|
||||
error = validateDebugMode(values.debugMode);
|
||||
@@ -813,6 +794,18 @@ function configure(appId, data, user, auditSource, callback) {
|
||||
values.tags = data.tags;
|
||||
}
|
||||
|
||||
if ('icon' in data) {
|
||||
if (data.icon) {
|
||||
if (!validator.isBase64(data.icon)) return callback(new AppsError(AppsError.BAD_FIELD, 'icon is not base64'));
|
||||
|
||||
if (!safe.fs.writeFileSync(path.join(paths.APP_ICONS_DIR, appId + '.user.png'), Buffer.from(data.icon, 'base64'))) {
|
||||
return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Error saving icon:' + safe.error.message));
|
||||
}
|
||||
} else {
|
||||
safe.fs.unlinkSync(path.join(paths.APP_ICONS_DIR, appId + '.user.png'));
|
||||
}
|
||||
}
|
||||
|
||||
domains.get(domain, function (error, domainObject) {
|
||||
if (error && error.reason === DomainsError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND, 'No such domain'));
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Could not get domain info:' + error.message));
|
||||
@@ -836,7 +829,7 @@ function configure(appId, data, user, auditSource, callback) {
|
||||
|
||||
values.oldConfig = getAppConfig(app);
|
||||
|
||||
debug('Will configure app with id:%s values:%j', appId, values);
|
||||
debug(`configure: id:${appId}`);
|
||||
|
||||
appdb.setInstallationCommand(appId, appdb.ISTATE_PENDING_CONFIGURE, values, function (error) {
|
||||
if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(getDuplicateErrorDetails(error, location, domainObject, portBindings));
|
||||
@@ -864,7 +857,7 @@ function update(appId, data, auditSource, callback) {
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
debug('Will update app with id:%s', appId);
|
||||
debug(`update: id:${appId}`);
|
||||
|
||||
get(appId, function (error, app) {
|
||||
if (error) return callback(error);
|
||||
@@ -901,11 +894,11 @@ function update(appId, data, auditSource, callback) {
|
||||
if (data.icon) {
|
||||
if (!validator.isBase64(data.icon)) return callback(new AppsError(AppsError.BAD_FIELD, 'icon is not base64'));
|
||||
|
||||
if (!safe.fs.writeFileSync(path.join(paths.APP_ICONS_DIR, appId + '.png'), Buffer.from(data.icon, 'base64'))) {
|
||||
if (!safe.fs.writeFileSync(path.join(paths.APP_ICONS_DIR, appId + '.user.png'), Buffer.from(data.icon, 'base64'))) {
|
||||
return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Error saving icon:' + safe.error.message));
|
||||
}
|
||||
} else {
|
||||
safe.fs.unlinkSync(path.join(paths.APP_ICONS_DIR, appId + '.png'));
|
||||
safe.fs.unlinkSync(path.join(paths.APP_ICONS_DIR, appId + '.user.png'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1119,7 +1112,6 @@ function clone(appId, data, user, auditSource, callback) {
|
||||
installationState: appdb.ISTATE_PENDING_CLONE,
|
||||
memoryLimit: app.memoryLimit,
|
||||
accessRestriction: app.accessRestriction,
|
||||
xFrameOptions: app.xFrameOptions,
|
||||
restoreConfig: { backupId: backupId, backupFormat: backupInfo.format },
|
||||
sso: !!app.sso,
|
||||
mailboxName: mailboxName,
|
||||
|
||||
@@ -387,14 +387,15 @@ function registerCloudron(data, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function registerWithLicense(license, callback) {
|
||||
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 AppstoreError(AppstoreError.ALREADY_REGISTERED));
|
||||
|
||||
registerCloudron({ license }, callback);
|
||||
registerCloudron({ license, domain }, callback);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -446,7 +447,7 @@ function createTicket(info, callback) {
|
||||
|
||||
let url = config.apiServerOrigin() + '/api/v1/ticket';
|
||||
|
||||
info.supportEmail = custom.supportEmail(); // destination address for tickets
|
||||
info.supportEmail = custom.spec().support.email; // destination address for tickets
|
||||
|
||||
superagent.post(url).query({ accessToken: token }).send(info).timeout(10 * 1000).end(function (error, result) {
|
||||
if (error && !error.response) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, error.message));
|
||||
|
||||
@@ -420,10 +420,15 @@ function removeIcon(app, callback) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
fs.unlink(path.join(paths.APP_ICONS_DIR, app.id + '.png'), function (error) {
|
||||
if (error && error.code !== 'ENOENT') debugApp(app, 'cannot remove icon : %s', error);
|
||||
callback(null);
|
||||
});
|
||||
if (!safe.fs.unlinkSync(path.join(paths.APP_ICONS_DIR, app.id + '.png'))) {
|
||||
if (safe.error.code !== 'ENOENT') debugApp(app, 'cannot remove icon : %s', safe.error);
|
||||
}
|
||||
|
||||
if (!safe.fs.unlinkSync(path.join(paths.APP_ICONS_DIR, app.id + '.user.png'))) {
|
||||
if (safe.error.code !== 'ENOENT') debugApp(app, 'cannot remove user icon : %s', safe.error);
|
||||
}
|
||||
|
||||
callback(null);
|
||||
}
|
||||
|
||||
function cleanupLogs(app, callback) {
|
||||
@@ -697,6 +702,8 @@ function update(app, callback) {
|
||||
// FIXME: this does not handle option changes (like multipleDatabases)
|
||||
var unusedAddons = _.omit(app.manifest.addons, Object.keys(app.updateConfig.manifest.addons));
|
||||
|
||||
const FORCED_UPDATE = (app.installationState === appdb.ISTATE_PENDING_FORCE_UPDATE);
|
||||
|
||||
async.series([
|
||||
// this protects against the theoretical possibility of an app being marked for update from
|
||||
// a previous version of box code
|
||||
@@ -704,7 +711,7 @@ function update(app, callback) {
|
||||
verifyManifest.bind(null, app.updateConfig.manifest),
|
||||
|
||||
function (next) {
|
||||
if (app.installationState === appdb.ISTATE_PENDING_FORCE_UPDATE) return next(null);
|
||||
if (FORCED_UPDATE) return next(null);
|
||||
|
||||
async.series([
|
||||
updateApp.bind(null, app, { installationProgress: '15, Backing up app' }),
|
||||
@@ -791,6 +798,9 @@ function update(app, callback) {
|
||||
debugApp(app, 'Error updating app: %s', error);
|
||||
updateApp(app, { installationState: appdb.ISTATE_ERROR, installationProgress: error.message, updateTime: new Date() }, callback.bind(null, error));
|
||||
} else {
|
||||
// do not spam the notifcation view
|
||||
if (FORCED_UPDATE) return callback();
|
||||
|
||||
eventlog.add(eventlog.ACTION_APP_UPDATE_FINISH, auditsource.APP_TASK, { app: app, success: true }, callback);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -174,8 +174,7 @@ function getConfig(callback) {
|
||||
memory: os.totalmem(),
|
||||
provider: config.provider(),
|
||||
cloudronName: allSettings[settings.CLOUDRON_NAME_KEY],
|
||||
features: custom.features(),
|
||||
supportEmail: custom.supportEmail()
|
||||
uiSpec: custom.uiSpec()
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,44 +1,63 @@
|
||||
'use strict';
|
||||
|
||||
let debug = require('debug')('box:features'),
|
||||
let config = require('./config.js'),
|
||||
debug = require('debug')('box:features'),
|
||||
lodash = require('lodash'),
|
||||
paths = require('./paths.js'),
|
||||
safe = require('safetydance'),
|
||||
yaml = require('js-yaml');
|
||||
|
||||
exports = module.exports = {
|
||||
features: features,
|
||||
supportEmail: supportEmail,
|
||||
alertsEmail: alertsEmail,
|
||||
sendAlertsToCloudronAdmins: sendAlertsToCloudronAdmins
|
||||
uiSpec: uiSpec,
|
||||
spec: spec
|
||||
};
|
||||
|
||||
const gCustom = (function () {
|
||||
const DEFAULT_SPEC = {
|
||||
backups: {
|
||||
configurable: true
|
||||
},
|
||||
domains: {
|
||||
dynamicDns: true,
|
||||
changeDashboardDomain: true
|
||||
},
|
||||
subscription: {
|
||||
configurable: true
|
||||
},
|
||||
support: {
|
||||
email: 'support@cloudron.io',
|
||||
remoteSupport: true,
|
||||
ticketFormBody:
|
||||
'Use this form to open support tickets. You can also write directly to [support@cloudron.io](mailto:support@cloudron.io).\n\n'
|
||||
+ `* [Knowledge Base & App Docs](${config.webServerOrigin()}/documentation/apps/?support_view)\n`
|
||||
+ `* [Custom App Packaging & API](${config.webServerOrigin()}/developer/packaging/?support_view)\n`
|
||||
+ '* [Forum](https://forum.cloudron.io/)\n\n',
|
||||
submitTickets: true
|
||||
},
|
||||
alerts: {
|
||||
email: '',
|
||||
notifyCloudronAdmins: false
|
||||
},
|
||||
footer: {
|
||||
body: '© 2019 [Cloudron](https://cloudron.io) [Forum <i class="fa fa-comments"></i>](https://forum.cloudron.io)'
|
||||
}
|
||||
};
|
||||
|
||||
const gSpec = (function () {
|
||||
try {
|
||||
if (!safe.fs.existsSync(paths.CUSTOM_FILE)) return {};
|
||||
return yaml.safeLoad(safe.fs.readFileSync(paths.CUSTOM_FILE, 'utf8'));
|
||||
if (!safe.fs.existsSync(paths.CUSTOM_FILE)) return DEFAULT_SPEC;
|
||||
const c = yaml.safeLoad(safe.fs.readFileSync(paths.CUSTOM_FILE, 'utf8'));
|
||||
return lodash.merge({}, DEFAULT_SPEC, c);
|
||||
} catch (e) {
|
||||
debug(`Error loading features file from ${paths.CUSTOM_FILE} : ${e.message}`);
|
||||
return {};
|
||||
return DEFAULT_SPEC;
|
||||
}
|
||||
})();
|
||||
|
||||
function features() {
|
||||
return {
|
||||
dynamicDns: safe.query(gCustom, 'features.dynamicDns', true),
|
||||
remoteSupport: safe.query(gCustom, 'features.remoteSupport', true),
|
||||
subscription: safe.query(gCustom, 'features.subscription', true),
|
||||
configureBackup: safe.query(gCustom, 'features.configureBackup', true)
|
||||
};
|
||||
// flags sent to the UI. this is separate because we have values that are secret to the backend
|
||||
function uiSpec() {
|
||||
return gSpec;
|
||||
}
|
||||
|
||||
function supportEmail() {
|
||||
return safe.query(gCustom, 'support.email', 'support@cloudron.io');
|
||||
}
|
||||
|
||||
function alertsEmail() {
|
||||
return safe.query(gCustom, 'alerts.email', '');
|
||||
}
|
||||
|
||||
function sendAlertsToCloudronAdmins() {
|
||||
return safe.query(gCustom, 'alerts.notifyCloudronAdmins', true);
|
||||
}
|
||||
function spec() {
|
||||
return gSpec;
|
||||
}
|
||||
@@ -15,14 +15,14 @@ var assert = require('assert'),
|
||||
dns = require('../native-dns.js'),
|
||||
domains = require('../domains.js'),
|
||||
DomainsError = require('../domains.js').DomainsError,
|
||||
Namecheap = require('namecheap'),
|
||||
safe = require('safetydance'),
|
||||
superagent = require('superagent'),
|
||||
sysinfo = require('../sysinfo.js'),
|
||||
util = require('util'),
|
||||
waitForDns = require('./waitfordns.js');
|
||||
waitForDns = require('./waitfordns.js'),
|
||||
xml2js = require('xml2js');
|
||||
|
||||
function formatError(response) {
|
||||
return util.format('NameCheap DNS error [%s] %j', response.code, response.message);
|
||||
}
|
||||
const ENDPOINT = 'https://api.namecheap.com/xml.response';
|
||||
|
||||
function removePrivateFields(domainObject) {
|
||||
domainObject.config.token = domains.SECRET_PLACEHOLDER;
|
||||
@@ -33,37 +33,19 @@ function injectPrivateFields(newConfig, currentConfig) {
|
||||
if (newConfig.token === domains.SECRET_PLACEHOLDER) newConfig.token = currentConfig.token;
|
||||
}
|
||||
|
||||
// Only send required fields - https://www.namecheap.com/support/api/methods/domains-dns/set-hosts.aspx
|
||||
function mapHosts(hosts) {
|
||||
return hosts.map(function (host) {
|
||||
let tmp = {};
|
||||
|
||||
tmp.TTL = '300';
|
||||
tmp.RecordType = host.RecordType || host.Type;
|
||||
tmp.HostName = host.HostName || host.Name;
|
||||
tmp.Address = host.Address;
|
||||
|
||||
if (tmp.RecordType === 'MX') {
|
||||
tmp.EmailType = 'MX';
|
||||
if (host.MXPref) tmp.MXPref = host.MXPref;
|
||||
}
|
||||
|
||||
return tmp;
|
||||
});
|
||||
}
|
||||
|
||||
function getApi(dnsConfig, callback) {
|
||||
function getQuery(dnsConfig, callback) {
|
||||
assert.strictEqual(typeof dnsConfig, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
sysinfo.getPublicIp(function (error, ip) {
|
||||
if (error) return callback(new DomainsError(DomainsError.INTERNAL_ERROR, error));
|
||||
|
||||
// Note that for all NameCheap calls to go through properly, the public IP returned by the getPublicIp method below must be whitelisted on NameCheap's API dashboard
|
||||
let namecheap = new Namecheap(dnsConfig.username, dnsConfig.token, ip);
|
||||
namecheap.setUsername(dnsConfig.username);
|
||||
|
||||
callback(null, namecheap);
|
||||
callback(null, {
|
||||
ApiUser: dnsConfig.username,
|
||||
ApiKey: dnsConfig.token,
|
||||
UserName: dnsConfig.username,
|
||||
ClientIp: ip
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -74,15 +56,31 @@ function getInternal(dnsConfig, zoneName, subdomain, type, callback) {
|
||||
assert.strictEqual(typeof type, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
getApi(dnsConfig, function (error, namecheap) {
|
||||
getQuery(dnsConfig, function (error, query) {
|
||||
if (error) return callback(error);
|
||||
|
||||
namecheap.domains.dns.getHosts(zoneName, function (error, result) {
|
||||
if (error) return callback(new DomainsError(DomainsError.EXTERNAL_ERROR, formatError(error)));
|
||||
query.Command = 'namecheap.domains.dns.getHosts';
|
||||
query.SLD = zoneName.split('.')[0];
|
||||
query.TLD = zoneName.split('.')[1];
|
||||
|
||||
debug('entire getInternal response: %j', result);
|
||||
superagent.get(ENDPOINT).query(query).end(function (error, result) {
|
||||
if (error) return callback(new DomainsError(DomainsError.EXTERNAL_ERROR, error));
|
||||
|
||||
return callback(null, result['DomainDNSGetHostsResult']['host']);
|
||||
var parser = new xml2js.Parser();
|
||||
parser.parseString(result.text, function (error, result) {
|
||||
if (error) return callback(new DomainsError(DomainsError.EXTERNAL_ERROR, error));
|
||||
|
||||
var tmp = result.ApiResponse;
|
||||
if (tmp['$'].Status !== 'OK') return callback(new DomainsError(DomainsError.EXTERNAL_ERROR, safe.query(tmp, 'Errors[0].Error[0]._', 'Invalid response')));
|
||||
if (!tmp.CommandResponse[0]) return callback(new DomainsError(DomainsError.EXTERNAL_ERROR, 'Invalid response'));
|
||||
if (!tmp.CommandResponse[0].DomainDNSGetHostsResult[0]) return callback(new DomainsError(DomainsError.EXTERNAL_ERROR, 'Invalid response'));
|
||||
|
||||
var hosts = result.ApiResponse.CommandResponse[0].DomainDNSGetHostsResult[0].host.map(function (h) {
|
||||
return h['$'];
|
||||
});
|
||||
|
||||
callback(null, hosts);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -93,15 +91,42 @@ function setInternal(dnsConfig, zoneName, hosts, callback) {
|
||||
assert(Array.isArray(hosts));
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
let mappedHosts = mapHosts(hosts);
|
||||
|
||||
getApi(dnsConfig, function (error, namecheap) {
|
||||
getQuery(dnsConfig, function (error, query) {
|
||||
if (error) return callback(error);
|
||||
|
||||
namecheap.domains.dns.setHosts(zoneName, mappedHosts, function (error, result) {
|
||||
if (error) return callback(new DomainsError(DomainsError.EXTERNAL_ERROR, formatError(error)));
|
||||
query.Command = 'namecheap.domains.dns.setHosts';
|
||||
query.SLD = zoneName.split('.')[0];
|
||||
query.TLD = zoneName.split('.')[1];
|
||||
|
||||
return callback(null, result);
|
||||
// Map to query params https://www.namecheap.com/support/api/methods/domains-dns/set-hosts.aspx
|
||||
hosts.forEach(function (host, i) {
|
||||
var n = i+1; // api starts with 1 not 0
|
||||
query['TTL' + n] = '300'; // keep it low
|
||||
query['HostName' + n] = host.HostName || host.Name;
|
||||
query['RecordType' + n] = host.RecordType || host.Type;
|
||||
query['Address' + n] = host.Address;
|
||||
|
||||
if (host.Type === 'MX') {
|
||||
query['EmailType' + n] = 'MX';
|
||||
if (host.MXPref) query['MXPref' + n] = host.MXPref;
|
||||
}
|
||||
});
|
||||
|
||||
superagent.post(ENDPOINT).query(query).end(function (error, result) {
|
||||
if (error) return callback(new DomainsError(DomainsError.EXTERNAL_ERROR, error));
|
||||
|
||||
var parser = new xml2js.Parser();
|
||||
parser.parseString(result.text, function (error, result) {
|
||||
if (error) return callback(new DomainsError(DomainsError.EXTERNAL_ERROR, error));
|
||||
|
||||
var tmp = result.ApiResponse;
|
||||
if (tmp['$'].Status !== 'OK') return callback(new DomainsError(DomainsError.EXTERNAL_ERROR, safe.query(tmp, 'Errors[0].Error[0]._', 'Invalid response')));
|
||||
if (!tmp.CommandResponse[0]) return callback(new DomainsError(DomainsError.EXTERNAL_ERROR, 'Invalid response'));
|
||||
if (!tmp.CommandResponse[0].DomainDNSSetHostsResult[0]) return callback(new DomainsError(DomainsError.EXTERNAL_ERROR, 'Invalid response'));
|
||||
if (tmp.CommandResponse[0].DomainDNSSetHostsResult[0]['$'].IsSuccess !== 'true') return callback(new DomainsError(DomainsError.EXTERNAL_ERROR, 'Invalid response'));
|
||||
|
||||
callback(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ exports = module.exports = {
|
||||
'postgresql': { repo: 'cloudron/postgresql', tag: 'cloudron/postgresql:2.0.2@sha256:6dcee0731dfb9b013ed94d56205eee219040ee806c7e251db3b3886eaa4947ff' },
|
||||
'mongodb': { repo: 'cloudron/mongodb', tag: 'cloudron/mongodb:2.0.2@sha256:95e006390ddce7db637e1672eb6f3c257d3c2652747424f529b1dee3cbe6728c' },
|
||||
'redis': { repo: 'cloudron/redis', tag: 'cloudron/redis:2.0.0@sha256:8a88dd334b62b578530a014ca1a2425a54cb9df1e475f5d3a36806e5cfa22121' },
|
||||
'mail': { repo: 'cloudron/mail', tag: 'cloudron/mail:2.2.0@sha256:20e4d2508dcf712eb56481067993ae39bf541d793d44f99f6a41d630ad941d9e' },
|
||||
'mail': { repo: 'cloudron/mail', tag: 'cloudron/mail:2.3.0@sha256:5118cd2cc755a53661485a1c7507cbb546f765642fc83a82f0351d646221342f' },
|
||||
'graphite': { repo: 'cloudron/graphite', tag: 'cloudron/graphite:2.0.2@sha256:454f035d60b768153d4f31210380271b5ba1c09367c9d95c7fa37f9e39d2f59c' },
|
||||
'sftp': { repo: 'cloudron/sftp', tag: 'cloudron/sftp:0.1.0@sha256:e177c5bf5f38c84ce1dea35649c22a1b05f96eec67a54a812c5a35e585670f0f' }
|
||||
}
|
||||
|
||||
25
src/mail.js
25
src/mail.js
@@ -289,13 +289,25 @@ function checkMx(domain, mailFqdn, callback) {
|
||||
|
||||
dns.resolve(mx.domain, mx.type, DNS_OPTIONS, function (error, mxRecords) {
|
||||
if (error) return callback(error, mx);
|
||||
if (mxRecords.length === 0) return callback(null, mx);
|
||||
|
||||
if (mxRecords.length !== 0) {
|
||||
mx.status = mxRecords.length == 1 && mxRecords[0].exchange === mailFqdn;
|
||||
mx.value = mxRecords.map(function (r) { return r.priority + ' ' + r.exchange + '.'; }).join(' ');
|
||||
}
|
||||
mx.status = mxRecords.length == 1 && mxRecords[0].exchange === mailFqdn;
|
||||
mx.value = mxRecords.map(function (r) { return r.priority + ' ' + r.exchange + '.'; }).join(' ');
|
||||
|
||||
callback(null, mx);
|
||||
if (mx.status) return callback(null, mx); // MX record is "my."
|
||||
|
||||
// cloudflare might create a conflict subdomain (https://support.cloudflare.com/hc/en-us/articles/360020296512-DNS-Troubleshooting-FAQ)
|
||||
dns.resolve(mxRecords[0].exchange, 'A', DNS_OPTIONS, function (error, mxIps) {
|
||||
if (error || mxIps.length !== 1) return callback(null, mx);
|
||||
|
||||
sysinfo.getPublicIp(function (error, ip) {
|
||||
if (error) return callback(null, mx);
|
||||
|
||||
mx.status = mxIps[0] === ip;
|
||||
|
||||
callback(null, mx);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -596,13 +608,14 @@ function createMailConfig(mailFqdn, callback) {
|
||||
const enableRelay = relay.provider !== 'cloudron-smtp' && relay.provider !== 'noop',
|
||||
host = relay.host || '',
|
||||
port = relay.port || 25,
|
||||
authType = relay.username ? 'plain' : '',
|
||||
username = relay.username || '',
|
||||
password = relay.password || '';
|
||||
|
||||
if (!enableRelay) return;
|
||||
|
||||
if (!safe.fs.appendFileSync(paths.ADDON_CONFIG_DIR + '/mail/smtp_forward.ini',
|
||||
`[${domain.domain}]\nenable_outbound=true\nhost=${host}\nport=${port}\nenable_tls=true\nauth_type=plain\nauth_user=${username}\nauth_pass=${password}\n\n`, 'utf8')) {
|
||||
`[${domain.domain}]\nenable_outbound=true\nhost=${host}\nport=${port}\nenable_tls=true\nauth_type=${authType}\nauth_user=${username}\nauth_pass=${password}\n\n`, 'utf8')) {
|
||||
return callback(new Error('Could not create mail var file:' + safe.error.message));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -289,7 +289,7 @@ function appDied(mailTo, app) {
|
||||
from: mailConfig.notificationFrom,
|
||||
to: mailTo,
|
||||
subject: util.format('[%s] App %s is down', mailConfig.cloudronName, app.fqdn),
|
||||
text: render('app_down.ejs', { title: app.manifest.title, appFqdn: app.fqdn, supportEmail: custom.supportEmail(), format: 'text' })
|
||||
text: render('app_down.ejs', { title: app.manifest.title, appFqdn: app.fqdn, supportEmail: custom.spec().support.email, format: 'text' })
|
||||
};
|
||||
|
||||
sendMail(mailOptions);
|
||||
|
||||
@@ -112,7 +112,7 @@ function listByUserIdPaged(userId, page, perPage, callback) {
|
||||
var data = [ userId ];
|
||||
var query = 'SELECT ' + NOTIFICATION_FIELDS + ' FROM notifications WHERE userId=?';
|
||||
|
||||
query += ' ORDER BY creationTime, title DESC LIMIT ?,?';
|
||||
query += ' ORDER BY creationTime DESC LIMIT ?,?';
|
||||
|
||||
data.push((page-1)*perPage);
|
||||
data.push(perPage);
|
||||
|
||||
@@ -193,8 +193,8 @@ function oomEvent(eventId, app, addon, containerId, event, callback) {
|
||||
message = 'The container has been restarted automatically. Consider increasing the [memory limit](https://docs.docker.com/v17.09/edge/engine/reference/commandline/update/#update-a-containers-kernel-memory-constraints)';
|
||||
}
|
||||
|
||||
if (custom.alertsEmail()) mailer.oomEvent(custom.alertsEmail(), program, event);
|
||||
if (!custom.sendAlertsToCloudronAdmins()) return callback();
|
||||
if (custom.spec().alerts.email) mailer.oomEvent(custom.spec().alerts.email, program, event);
|
||||
if (!custom.spec().alerts.notifyCloudronAdmins) return callback();
|
||||
|
||||
actionForAllAdmins([], function (admin, done) {
|
||||
mailer.oomEvent(admin.email, program, event);
|
||||
@@ -208,8 +208,8 @@ function appUp(eventId, app, callback) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
if (custom.alertsEmail()) mailer.appUp(custom.alertsEmail(), app);
|
||||
if (!custom.sendAlertsToCloudronAdmins()) return callback();
|
||||
if (custom.spec().alerts.email) mailer.appUp(custom.spec().alerts.email, app);
|
||||
if (!custom.spec().alerts.notifyCloudronAdmins) return callback();
|
||||
|
||||
actionForAllAdmins([], function (admin, done) {
|
||||
mailer.appUp(admin.email, app);
|
||||
@@ -222,8 +222,8 @@ function appDied(eventId, app, callback) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
if (custom.alertsEmail()) mailer.appDied(custom.alertsEmail(), app);
|
||||
if (!custom.sendAlertsToCloudronAdmins()) return callback();
|
||||
if (custom.spec().alerts.email) mailer.appDied(custom.spec().alerts.email, app);
|
||||
if (!custom.spec().alerts.notifyCloudronAdmins) return callback();
|
||||
|
||||
actionForAllAdmins([], function (admin, callback) {
|
||||
mailer.appDied(admin.email, app);
|
||||
@@ -254,8 +254,8 @@ function certificateRenewalError(eventId, vhost, errorMessage, callback) {
|
||||
assert.strictEqual(typeof errorMessage, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
if (custom.alertsEmail()) mailer.certificateRenewalError(custom.alertsEmail(), vhost, errorMessage);
|
||||
if (!custom.sendAlertsToCloudronAdmins()) return callback();
|
||||
if (custom.spec().alerts.email) mailer.certificateRenewalError(custom.spec().alerts.email, vhost, errorMessage);
|
||||
if (!custom.spec().alerts.notifyCloudronAdmins) return callback();
|
||||
|
||||
actionForAllAdmins([], function (admin, callback) {
|
||||
mailer.certificateRenewalError(admin.email, vhost, errorMessage);
|
||||
@@ -269,8 +269,8 @@ function backupFailed(eventId, taskId, errorMessage, callback) {
|
||||
assert.strictEqual(typeof errorMessage, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
if (custom.alertsEmail()) mailer.backupFailed(custom.alertsEmail(), errorMessage, `${config.adminOrigin()}/logs.html?taskId=${taskId}`);
|
||||
if (!custom.sendAlertsToCloudronAdmins()) return callback();
|
||||
if (custom.spec().alerts.email) mailer.backupFailed(custom.spec().alerts.email, errorMessage, `${config.adminOrigin()}/logs.html?taskId=${taskId}`);
|
||||
if (!custom.spec().alerts.notifyCloudronAdmins) return callback();
|
||||
|
||||
actionForAllAdmins([], function (admin, callback) {
|
||||
mailer.backupFailed(admin.email, errorMessage, `${config.adminOrigin()}/logs.html?taskId=${taskId}`);
|
||||
|
||||
@@ -78,6 +78,7 @@ ProvisionError.BAD_STATE = 'Bad State';
|
||||
ProvisionError.ALREADY_SETUP = 'Already Setup';
|
||||
ProvisionError.INTERNAL_ERROR = 'Internal Error';
|
||||
ProvisionError.EXTERNAL_ERROR = 'External Error';
|
||||
ProvisionError.LICENSE_ERROR = 'License Error';
|
||||
ProvisionError.ALREADY_PROVISIONED = 'Already Provisioned';
|
||||
|
||||
function setProgress(task, message, callback) {
|
||||
@@ -108,7 +109,8 @@ function autoprovision(autoconf, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function autoRegister(callback) {
|
||||
function autoRegister(domain, callback) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
if (!fs.existsSync(paths.LICENSE_FILE)) return callback();
|
||||
@@ -118,7 +120,7 @@ function autoRegister(callback) {
|
||||
|
||||
debug('Auto-registering cloudron');
|
||||
|
||||
appstore.registerWithLicense(license.trim(), function (error) {
|
||||
appstore.registerWithLicense(license.trim(), domain, function (error) {
|
||||
if (error && error.reason !== AppstoreError.ALREADY_REGISTERED) {
|
||||
debug('Failed to auto-register cloudron', error);
|
||||
return callback(new ProvisionError(ProvisionError.LICENSE_ERROR, 'Failed to auto-register Cloudron with license. Please contact support@cloudron.io'));
|
||||
@@ -188,7 +190,7 @@ function setup(dnsConfig, autoconf, auditSource, callback) {
|
||||
callback(); // now that args are validated run the task in the background
|
||||
|
||||
async.series([
|
||||
autoRegister,
|
||||
autoRegister.bind(null, domain),
|
||||
domains.prepareDashboardDomain.bind(null, domain, auditSource, (progress) => setProgress('setup', progress.message, NOOP_CALLBACK)),
|
||||
cloudron.setDashboardDomain.bind(null, domain, auditSource), // this sets up the config.fqdn()
|
||||
mail.addDomain.bind(null, domain), // this relies on config.mailFqdn()
|
||||
|
||||
@@ -392,7 +392,6 @@ function writeAdminNginxConfig(bundle, configFileName, vhost, callback) {
|
||||
endpoint: 'admin',
|
||||
certFilePath: bundle.certFilePath,
|
||||
keyFilePath: bundle.keyFilePath,
|
||||
xFrameOptions: 'SAMEORIGIN',
|
||||
robotsTxtQuoted: JSON.stringify('User-agent: *\nDisallow: /\n')
|
||||
};
|
||||
var nginxConf = ejs.render(NGINX_APPCONFIG_EJS, data);
|
||||
@@ -457,8 +456,7 @@ function writeAppNginxConfig(app, bundle, callback) {
|
||||
endpoint: endpoint,
|
||||
certFilePath: bundle.certFilePath,
|
||||
keyFilePath: bundle.keyFilePath,
|
||||
robotsTxtQuoted: app.robotsTxt ? JSON.stringify(app.robotsTxt) : null,
|
||||
xFrameOptions: app.xFrameOptions || 'SAMEORIGIN' // once all apps have been updated/
|
||||
robotsTxtQuoted: app.robotsTxt ? JSON.stringify(app.robotsTxt) : null
|
||||
};
|
||||
var nginxConf = ejs.render(NGINX_APPCONFIG_EJS, data);
|
||||
|
||||
@@ -487,8 +485,7 @@ function writeAppRedirectNginxConfig(app, fqdn, bundle, callback) {
|
||||
endpoint: 'redirect',
|
||||
certFilePath: bundle.certFilePath,
|
||||
keyFilePath: bundle.keyFilePath,
|
||||
robotsTxtQuoted: null,
|
||||
xFrameOptions: 'SAMEORIGIN'
|
||||
robotsTxtQuoted: null
|
||||
};
|
||||
var nginxConf = ejs.render(NGINX_APPCONFIG_EJS, data);
|
||||
|
||||
|
||||
@@ -31,9 +31,7 @@ var apps = require('../apps.js'),
|
||||
AppsError = apps.AppsError,
|
||||
assert = require('assert'),
|
||||
auditSource = require('../auditsource.js'),
|
||||
config = require('../config.js'),
|
||||
debug = require('debug')('box:routes/apps'),
|
||||
fs = require('fs'),
|
||||
HttpError = require('connect-lastmile').HttpError,
|
||||
HttpSuccess = require('connect-lastmile').HttpSuccess,
|
||||
paths = require('../paths.js'),
|
||||
@@ -67,11 +65,15 @@ function getApps(req, res, next) {
|
||||
function getAppIcon(req, res, next) {
|
||||
assert.strictEqual(typeof req.params.id, 'string');
|
||||
|
||||
var iconPath = paths.APP_ICONS_DIR + '/' + req.params.id + '.png';
|
||||
fs.exists(iconPath, function (exists) {
|
||||
if (!exists) return next(new HttpError(404, 'No such icon'));
|
||||
res.sendFile(iconPath);
|
||||
});
|
||||
if (!req.query.original) {
|
||||
const userIconPath = `${paths.APP_ICONS_DIR}/${req.params.id}.user.png`;
|
||||
if (safe.fs.existsSync(userIconPath)) return res.sendFile(userIconPath);
|
||||
}
|
||||
|
||||
const appstoreIconPath = `${paths.APP_ICONS_DIR}/${req.params.id}.png`;
|
||||
if (safe.fs.existsSync(appstoreIconPath)) return res.sendFile(appstoreIconPath);
|
||||
|
||||
return next(new HttpError(404, 'No such icon'));
|
||||
}
|
||||
|
||||
function installApp(req, res, next) {
|
||||
@@ -107,8 +109,6 @@ function installApp(req, res, next) {
|
||||
|
||||
if ('memoryLimit' in data && typeof data.memoryLimit !== 'number') return next(new HttpError(400, 'memoryLimit is not a number'));
|
||||
|
||||
if (data.xFrameOptions && typeof data.xFrameOptions !== 'string') return next(new HttpError(400, 'xFrameOptions must be a string'));
|
||||
|
||||
if ('sso' in data && typeof data.sso !== 'boolean') return next(new HttpError(400, 'sso must be a boolean'));
|
||||
if ('enableBackup' in data && typeof data.enableBackup !== 'boolean') return next(new HttpError(400, 'enableBackup must be a boolean'));
|
||||
if ('enableAutomaticUpdate' in data && typeof data.enableAutomaticUpdate !== 'boolean') return next(new HttpError(400, 'enableAutomaticUpdate must be a boolean'));
|
||||
@@ -166,7 +166,6 @@ function configureApp(req, res, next) {
|
||||
if (!data.cert && data.key) return next(new HttpError(400, 'cert must be provided'));
|
||||
|
||||
if ('memoryLimit' in data && typeof data.memoryLimit !== 'number') return next(new HttpError(400, 'memoryLimit is not a number'));
|
||||
if (data.xFrameOptions && typeof data.xFrameOptions !== 'string') return next(new HttpError(400, 'xFrameOptions must be a string'));
|
||||
|
||||
if ('enableBackup' in data && typeof data.enableBackup !== 'boolean') return next(new HttpError(400, 'enableBackup must be a boolean'));
|
||||
if ('enableAutomaticUpdate' in data && typeof data.enableAutomaticUpdate !== 'boolean') return next(new HttpError(400, 'enableAutomaticUpdate must be a boolean'));
|
||||
@@ -189,6 +188,7 @@ function configureApp(req, res, next) {
|
||||
|
||||
if ('label' in data && typeof data.label !== 'string') return next(new HttpError(400, 'label must be a string'));
|
||||
if ('dataDir' in data && typeof data.dataDir !== 'string') return next(new HttpError(400, 'dataDir must be a string'));
|
||||
if ('icon' in data && typeof data.icon !== 'string') return next(new HttpError(400, 'icon is not a string'));
|
||||
|
||||
debug('Configuring app id:%s data:%j', req.params.id, data);
|
||||
|
||||
@@ -326,7 +326,6 @@ function updateApp(req, res, next) {
|
||||
if ('appStoreId' in data && typeof data.appStoreId !== 'string') return next(new HttpError(400, 'appStoreId must be a string'));
|
||||
if (!data.manifest && !data.appStoreId) return next(new HttpError(400, 'appStoreId or manifest is required'));
|
||||
|
||||
if ('icon' in data && typeof data.icon !== 'string') return next(new HttpError(400, 'icon is not a string'));
|
||||
if ('force' in data && typeof data.force !== 'boolean') return next(new HttpError(400, 'force must be a boolean'));
|
||||
|
||||
debug('Update app id:%s to manifest:%j', req.params.id, data.manifest);
|
||||
|
||||
@@ -20,6 +20,7 @@ let assert = require('assert'),
|
||||
auditSource = require('../auditsource.js'),
|
||||
cloudron = require('../cloudron.js'),
|
||||
CloudronError = cloudron.CloudronError,
|
||||
custom = require('../custom.js'),
|
||||
HttpError = require('connect-lastmile').HttpError,
|
||||
HttpSuccess = require('connect-lastmile').HttpSuccess,
|
||||
updater = require('../updater.js'),
|
||||
@@ -152,6 +153,8 @@ function getLogStream(req, res, next) {
|
||||
function setDashboardAndMailDomain(req, res, next) {
|
||||
if (!req.body.domain || typeof req.body.domain !== 'string') return next(new HttpError(400, 'domain must be a string'));
|
||||
|
||||
if (!custom.spec().domains.changeDashboardDomain) return next(new HttpError(405, 'feature disabled by admin'));
|
||||
|
||||
cloudron.setDashboardAndMailDomain(req.body.domain, auditSource.fromRequest(req), function (error) {
|
||||
if (error && error.reason === CloudronError.BAD_FIELD) return next(new HttpError(404, error.message));
|
||||
if (error) return next(new HttpError(500, error));
|
||||
@@ -163,6 +166,8 @@ function setDashboardAndMailDomain(req, res, next) {
|
||||
function prepareDashboardDomain(req, res, next) {
|
||||
if (!req.body.domain || typeof req.body.domain !== 'string') return next(new HttpError(400, 'domain must be a string'));
|
||||
|
||||
if (!custom.spec().domains.changeDashboardDomain) return next(new HttpError(405, 'feature disabled by admin'));
|
||||
|
||||
cloudron.prepareDashboardDomain(req.body.domain, auditSource.fromRequest(req), function (error, taskId) {
|
||||
if (error && error.reason === CloudronError.BAD_FIELD) return next(new HttpError(404, error.message));
|
||||
if (error && error.reason === CloudronError.BAD_STATE) return next(new HttpError(409, error.message));
|
||||
|
||||
@@ -65,7 +65,7 @@ function setup(req, res, next) {
|
||||
if (error && error.reason === ProvisionError.BAD_STATE) return next(new HttpError(409, error.message));
|
||||
if (error) return next(new HttpError(500, error));
|
||||
|
||||
next(new HttpSuccess(200));
|
||||
next(new HttpSuccess(200, {}));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ function restore(req, res, next) {
|
||||
if (error && error.reason === ProvisionError.EXTERNAL_ERROR) return next(new HttpError(424, error.message));
|
||||
if (error) return next(new HttpError(500, error));
|
||||
|
||||
next(new HttpSuccess(200));
|
||||
next(new HttpSuccess(200, {}));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -131,8 +131,8 @@ function getBackupConfig(req, res, next) {
|
||||
settings.getBackupConfig(function (error, config) {
|
||||
if (error) return next(new HttpError(500, error));
|
||||
|
||||
// used by the UI to figure if backups are disabled
|
||||
if (!custom.features().configureBackup) {
|
||||
// always send provider as it is used by the UI to figure if backups are disabled ('noop' backend)
|
||||
if (!custom.spec().backups.configurable) {
|
||||
return next(new HttpSuccess(200, { provider: config.provider }));
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ function getBackupConfig(req, res, next) {
|
||||
function setBackupConfig(req, res, next) {
|
||||
assert.strictEqual(typeof req.body, 'object');
|
||||
|
||||
if (!custom.features().configureBackup) return next(new HttpError(405, 'feature disabled by admin'));
|
||||
if (!custom.spec().backups.configurable) return next(new HttpError(405, 'feature disabled by admin'));
|
||||
|
||||
if (typeof req.body.provider !== 'string') return next(new HttpError(400, 'provider is required'));
|
||||
if (typeof req.body.retentionSecs !== 'number') return next(new HttpError(400, 'retentionSecs is required'));
|
||||
@@ -207,7 +207,7 @@ function getDynamicDnsConfig(req, res, next) {
|
||||
function setDynamicDnsConfig(req, res, next) {
|
||||
assert.strictEqual(typeof req.body, 'object');
|
||||
|
||||
if (!custom.features().dynamicDns) return next(new HttpError(405, 'feature disabled by admin'));
|
||||
if (!custom.spec().domains.dynamicDns) return next(new HttpError(405, 'feature disabled by admin'));
|
||||
|
||||
if (typeof req.body.enabled !== 'boolean') return next(new HttpError(400, 'enabled boolean is required'));
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ var appstore = require('../appstore.js'),
|
||||
function createTicket(req, res, next) {
|
||||
assert.strictEqual(typeof req.user, 'object');
|
||||
|
||||
if (!custom.spec().support.submitTickets) return next(new HttpError(405, 'feature disabled by admin'));
|
||||
|
||||
const VALID_TYPES = [ 'feedback', 'ticket', 'app_missing', 'app_error', 'upgrade_request' ];
|
||||
|
||||
if (typeof req.body.type !== 'string' || !req.body.type) return next(new HttpError(400, 'type must be string'));
|
||||
@@ -27,7 +29,7 @@ function createTicket(req, res, next) {
|
||||
if (req.body.appId && typeof req.body.appId !== 'string') return next(new HttpError(400, 'appId must be string'));
|
||||
|
||||
appstore.createTicket(_.extend({ }, req.body, { email: req.user.email, displayName: req.user.displayName }), function (error) {
|
||||
if (error) return next(new HttpError(503, `Error contacting cloudron.io: ${error.message}. Please email ${custom.supportEmail()}`));
|
||||
if (error) return next(new HttpError(503, `Error contacting cloudron.io: ${error.message}. Please email ${custom.spec().support.email}`));
|
||||
|
||||
next(new HttpSuccess(201, {}));
|
||||
});
|
||||
@@ -36,7 +38,7 @@ function createTicket(req, res, next) {
|
||||
function enableRemoteSupport(req, res, next) {
|
||||
assert.strictEqual(typeof req.body, 'object');
|
||||
|
||||
if (!custom.features().remoteSupport) return next(new HttpError(405, 'feature disabled by admin'));
|
||||
if (!custom.spec().support.remoteSupport) return next(new HttpError(405, 'feature disabled by admin'));
|
||||
|
||||
if (typeof req.body.enable !== 'boolean') return next(new HttpError(400, 'enabled is required'));
|
||||
|
||||
|
||||
@@ -56,8 +56,6 @@ const DOMAIN_0 = {
|
||||
tlsConfig: { provider: 'fallback' }
|
||||
};
|
||||
|
||||
const CLOUDRON_ID = 'somecloudronid';
|
||||
|
||||
var APP_STORE_ID = 'test', APP_ID;
|
||||
var APP_LOCATION = 'appslocation';
|
||||
var APP_LOCATION_2 = 'appslocationtwo';
|
||||
@@ -539,7 +537,6 @@ describe('App API', function () {
|
||||
|
||||
it('cannot uninstall invalid app', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/apps/whatever/uninstall')
|
||||
.send({ password: PASSWORD })
|
||||
.query({ access_token: token })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(404);
|
||||
@@ -547,28 +544,8 @@ describe('App API', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('cannot uninstall app without password', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/uninstall')
|
||||
.query({ access_token: token })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(400);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('cannot uninstall app with wrong password', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/uninstall')
|
||||
.send({ password: PASSWORD+PASSWORD })
|
||||
.query({ access_token: token })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(403);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('non admin cannot uninstall app', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/uninstall')
|
||||
.send({ password: PASSWORD })
|
||||
.query({ access_token: token_1 })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(403);
|
||||
@@ -581,7 +558,6 @@ describe('App API', function () {
|
||||
var fake2 = nock(config.apiServerOrigin()).delete(function (uri) { return uri.indexOf('/api/v1/cloudronapps/') >= 0; }).reply(204, { });
|
||||
|
||||
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/uninstall')
|
||||
.send({ password: PASSWORD })
|
||||
.query({ access_token: token })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(202);
|
||||
@@ -1148,7 +1124,6 @@ describe('App installation', function () {
|
||||
}
|
||||
|
||||
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/uninstall')
|
||||
.send({ password: PASSWORD })
|
||||
.query({ access_token: token })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(202);
|
||||
|
||||
@@ -253,7 +253,6 @@ describe('Domains API', function () {
|
||||
it('cannot delete locked domain', function (done) {
|
||||
superagent.delete(SERVER_URL + '/api/v1/domains/' + DOMAIN_0.domain)
|
||||
.query({ access_token: token })
|
||||
.send({ password: PASSWORD })
|
||||
.end(function (error, result) {
|
||||
expect(result.statusCode).to.equal(423);
|
||||
done();
|
||||
@@ -262,31 +261,9 @@ describe('Domains API', function () {
|
||||
});
|
||||
|
||||
describe('delete', function () {
|
||||
it('fails without password', function (done) {
|
||||
superagent.delete(SERVER_URL + '/api/v1/domains/' + DOMAIN_0.domain)
|
||||
.query({ access_token: token })
|
||||
.end(function (error, result) {
|
||||
expect(result.statusCode).to.equal(400);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('fails with wrong password', function (done) {
|
||||
superagent.delete(SERVER_URL + '/api/v1/domains/' + DOMAIN_0.domain)
|
||||
.query({ access_token: token })
|
||||
.send({ password: PASSWORD + PASSWORD })
|
||||
.end(function (error, result) {
|
||||
expect(result.statusCode).to.equal(403);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('fails for non-existing domain', function (done) {
|
||||
superagent.delete(SERVER_URL + '/api/v1/domains/' + DOMAIN_0.domain + DOMAIN_0.domain)
|
||||
.query({ access_token: token })
|
||||
.send({ password: PASSWORD })
|
||||
.end(function (error, result) {
|
||||
expect(result.statusCode).to.equal(404);
|
||||
|
||||
@@ -297,7 +274,6 @@ describe('Domains API', function () {
|
||||
it('succeeds', function (done) {
|
||||
superagent.delete(SERVER_URL + '/api/v1/domains/' + DOMAIN_0.domain)
|
||||
.query({ access_token: token })
|
||||
.send({ password: PASSWORD })
|
||||
.end(function (error, result) {
|
||||
expect(result.statusCode).to.equal(204);
|
||||
|
||||
|
||||
@@ -250,7 +250,6 @@ describe('Groups API', function () {
|
||||
|
||||
it('can remove empty group', function (done) {
|
||||
superagent.del(SERVER_URL + '/api/v1/groups/' + group1Object.id)
|
||||
.send({ password: PASSWORD })
|
||||
.query({ access_token: token })
|
||||
.end(function (error, result) {
|
||||
expect(result.statusCode).to.equal(204);
|
||||
@@ -260,7 +259,6 @@ describe('Groups API', function () {
|
||||
|
||||
it('can remove non-empty group', function (done) {
|
||||
superagent.del(SERVER_URL + '/api/v1/groups/' + groupObject.id)
|
||||
.send({ password: PASSWORD })
|
||||
.query({ access_token: token })
|
||||
.end(function (error, result) {
|
||||
expect(result.statusCode).to.equal(204);
|
||||
|
||||
@@ -190,29 +190,9 @@ describe('Mail API', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('cannot delete domain without password', function (done) {
|
||||
superagent.del(SERVER_URL + '/api/v1/mail/doesnotexist.com')
|
||||
.query({ access_token: token })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(400);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('cannot delete domain with wrong password', function (done) {
|
||||
superagent.del(SERVER_URL + '/api/v1/mail/doesnotexist.com')
|
||||
.send({ password: PASSWORD+PASSWORD })
|
||||
.query({ access_token: token })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(403);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('cannot delete non-existing domain', function (done) {
|
||||
superagent.del(SERVER_URL + '/api/v1/mail/doesnotexist.com')
|
||||
.query({ access_token: token })
|
||||
.send({ password: PASSWORD })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(404);
|
||||
done();
|
||||
@@ -221,7 +201,6 @@ describe('Mail API', function () {
|
||||
|
||||
it('cannot delete admin mail domain', function (done) {
|
||||
superagent.del(SERVER_URL + '/api/v1/mail/' + ADMIN_DOMAIN.domain)
|
||||
.send({ password: PASSWORD })
|
||||
.query({ access_token: token })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(409);
|
||||
@@ -231,7 +210,6 @@ describe('Mail API', function () {
|
||||
|
||||
it('can delete admin mail domain', function (done) {
|
||||
superagent.del(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain)
|
||||
.send({ password: PASSWORD })
|
||||
.query({ access_token: token })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(204);
|
||||
@@ -289,7 +267,6 @@ describe('Mail API', function () {
|
||||
dns.resolve = resolve;
|
||||
|
||||
superagent.del(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain)
|
||||
.send({ password: PASSWORD })
|
||||
.query({ access_token: token })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(204);
|
||||
@@ -539,7 +516,6 @@ describe('Mail API', function () {
|
||||
|
||||
after(function (done) {
|
||||
superagent.del(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain)
|
||||
.send({ password: PASSWORD })
|
||||
.query({ access_token: token })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(204);
|
||||
@@ -591,7 +567,6 @@ describe('Mail API', function () {
|
||||
|
||||
after(function (done) {
|
||||
superagent.del(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain)
|
||||
.send({ password: PASSWORD })
|
||||
.query({ access_token: token })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(204);
|
||||
@@ -662,7 +637,6 @@ describe('Mail API', function () {
|
||||
|
||||
after(function (done) {
|
||||
superagent.del(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain)
|
||||
.send({ password: PASSWORD })
|
||||
.query({ access_token: token })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(204);
|
||||
@@ -740,7 +714,6 @@ describe('Mail API', function () {
|
||||
|
||||
after(function (done) {
|
||||
superagent.del(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain)
|
||||
.send({ password: PASSWORD })
|
||||
.query({ access_token: token })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(204);
|
||||
@@ -846,7 +819,6 @@ describe('Mail API', function () {
|
||||
if (error) return done(error);
|
||||
|
||||
superagent.del(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain)
|
||||
.send({ password: PASSWORD })
|
||||
.query({ access_token: token })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(204);
|
||||
@@ -972,7 +944,6 @@ describe('Mail API', function () {
|
||||
if (error) return done(error);
|
||||
|
||||
superagent.del(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain)
|
||||
.send({ password: PASSWORD })
|
||||
.query({ access_token: token })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(204);
|
||||
|
||||
@@ -248,7 +248,7 @@ describe('Profile API', function () {
|
||||
.query({ access_token: token_0 })
|
||||
.send({ password: 'some wrong password', newPassword: 'MOre#$%34' })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(403);
|
||||
expect(res.statusCode).to.equal(400);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -529,7 +529,6 @@ describe('Users API', function () {
|
||||
it('remove random user fails', function (done) {
|
||||
superagent.del(SERVER_URL + '/api/v1/users/randomid')
|
||||
.query({ access_token: token })
|
||||
.send({ password: PASSWORD })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(404);
|
||||
done();
|
||||
@@ -539,46 +538,15 @@ describe('Users API', function () {
|
||||
it('user removes himself is not allowed', function (done) {
|
||||
superagent.del(SERVER_URL + '/api/v1/users/' + user_0.id)
|
||||
.query({ access_token: token })
|
||||
.send({ password: PASSWORD })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(409);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('admin cannot remove normal user without giving a password', function (done) {
|
||||
superagent.del(SERVER_URL + '/api/v1/users/' + user_1.id)
|
||||
.query({ access_token: token })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(400);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('admin cannot remove normal user with empty password', function (done) {
|
||||
superagent.del(SERVER_URL + '/api/v1/users/' + user_1.id)
|
||||
.query({ access_token: token })
|
||||
.send({ password: '' })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(403);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('admin cannot remove normal user with giving wrong password', function (done) {
|
||||
superagent.del(SERVER_URL + '/api/v1/users/' + user_1.id)
|
||||
.query({ access_token: token })
|
||||
.send({ password: PASSWORD + PASSWORD })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(403);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('admin removes normal user', function (done) {
|
||||
superagent.del(SERVER_URL + '/api/v1/users/' + user_1.id)
|
||||
.query({ access_token: token })
|
||||
.send({ password: PASSWORD })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(204);
|
||||
done();
|
||||
@@ -588,7 +556,6 @@ describe('Users API', function () {
|
||||
it('admin removes himself should not be allowed', function (done) {
|
||||
superagent.del(SERVER_URL + '/api/v1/users/' + user_0.id)
|
||||
.query({ access_token: token })
|
||||
.send({ password: PASSWORD })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(409);
|
||||
done();
|
||||
|
||||
@@ -132,7 +132,7 @@ function verifyPassword(req, res, next) {
|
||||
if (typeof req.body.password !== 'string') return next(new HttpError(400, 'API call requires user password'));
|
||||
|
||||
users.verifyWithUsername(req.user.username, req.body.password, function (error) {
|
||||
if (error && error.reason === UsersError.WRONG_PASSWORD) return next(new HttpError(403, 'Password incorrect')); // not 401 intentionally since the UI redirects for 401
|
||||
if (error && error.reason === UsersError.WRONG_PASSWORD) return next(new HttpError(400, 'Password incorrect'));
|
||||
if (error && error.reason === UsersError.NOT_FOUND) return next(new HttpError(404, 'No such user'));
|
||||
if (error) return next(new HttpError(500, error));
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ var accesscontrol = require('./accesscontrol.js'),
|
||||
middleware = require('./middleware'),
|
||||
passport = require('passport'),
|
||||
path = require('path'),
|
||||
provision = require('./provision.js'),
|
||||
routes = require('./routes/index.js'),
|
||||
ws = require('ws');
|
||||
|
||||
@@ -168,7 +167,7 @@ function initializeExpressSync() {
|
||||
router.get ('/api/v1/users', usersReadScope, routes.users.list);
|
||||
router.post('/api/v1/users', usersManageScope, routes.users.create);
|
||||
router.get ('/api/v1/users/:userId', usersManageScope, routes.users.get); // this is manage scope because it returns non-restricted fields
|
||||
router.del ('/api/v1/users/:userId', usersManageScope, routes.users.verifyPassword, routes.users.remove);
|
||||
router.del ('/api/v1/users/:userId', usersManageScope, routes.users.remove);
|
||||
router.post('/api/v1/users/:userId', usersManageScope, routes.users.update);
|
||||
router.post('/api/v1/users/:userId/password', usersManageScope, routes.users.changePassword);
|
||||
router.put ('/api/v1/users/:userId/groups', usersManageScope, routes.users.setGroups);
|
||||
@@ -182,7 +181,7 @@ function initializeExpressSync() {
|
||||
router.get ('/api/v1/groups/:groupId', usersManageScope, routes.groups.get);
|
||||
router.put ('/api/v1/groups/:groupId/members', usersManageScope, routes.groups.updateMembers);
|
||||
router.post('/api/v1/groups/:groupId', usersManageScope, routes.groups.update);
|
||||
router.del ('/api/v1/groups/:groupId', usersManageScope, routes.users.verifyPassword, routes.groups.remove);
|
||||
router.del ('/api/v1/groups/:groupId', usersManageScope, routes.groups.remove);
|
||||
|
||||
// form based login routes used by oauth2 frame
|
||||
router.get ('/api/v1/session/login', csrf, routes.oauth2.loginForm);
|
||||
@@ -222,13 +221,13 @@ function initializeExpressSync() {
|
||||
// app routes
|
||||
router.get ('/api/v1/apps', appsReadScope, routes.apps.getApps);
|
||||
router.get ('/api/v1/apps/:id', appsManageScope, routes.apps.getApp);
|
||||
router.get ('/api/v1/apps/:id/icon', routes.apps.getAppIcon);
|
||||
router.get ('/api/v1/apps/:id/icon', appsReadScope, routes.apps.getAppIcon);
|
||||
|
||||
router.post('/api/v1/apps/install', appsManageScope, routes.apps.installApp);
|
||||
router.post('/api/v1/apps/:id/uninstall', appsManageScope, routes.users.verifyPassword, routes.apps.uninstallApp);
|
||||
router.post('/api/v1/apps/:id/uninstall', appsManageScope, routes.apps.uninstallApp);
|
||||
router.post('/api/v1/apps/:id/configure', appsManageScope, routes.apps.configureApp);
|
||||
router.post('/api/v1/apps/:id/update', appsManageScope, routes.apps.updateApp);
|
||||
router.post('/api/v1/apps/:id/restore', appsManageScope, routes.users.verifyPassword, routes.apps.restoreApp);
|
||||
router.post('/api/v1/apps/:id/restore', appsManageScope, routes.apps.restoreApp);
|
||||
router.post('/api/v1/apps/:id/backup', appsManageScope, routes.apps.backupApp);
|
||||
router.get ('/api/v1/apps/:id/backups', appsManageScope, routes.apps.listBackups);
|
||||
router.post('/api/v1/apps/:id/stop', appsManageScope, routes.apps.stopApp);
|
||||
@@ -252,8 +251,8 @@ function initializeExpressSync() {
|
||||
// email routes
|
||||
router.get ('/api/v1/mail/:domain', mailScope, routes.mail.getDomain);
|
||||
router.post('/api/v1/mail', mailScope, routes.mail.addDomain);
|
||||
router.get ('/api/v1/mail/:domain/stats', mailScope, routes.users.verifyPassword, routes.mail.getDomainStats);
|
||||
router.del ('/api/v1/mail/:domain', mailScope, routes.users.verifyPassword, routes.mail.removeDomain);
|
||||
router.get ('/api/v1/mail/:domain/stats', mailScope, routes.mail.getDomainStats);
|
||||
router.del ('/api/v1/mail/:domain', mailScope, routes.mail.removeDomain);
|
||||
router.get ('/api/v1/mail/:domain/status', mailScope, routes.mail.getStatus);
|
||||
router.post('/api/v1/mail/:domain/mail_from_validation', mailScope, routes.mail.setMailFromValidation);
|
||||
router.post('/api/v1/mail/:domain/catch_all', mailScope, routes.mail.setCatchAllAddress);
|
||||
@@ -285,7 +284,7 @@ function initializeExpressSync() {
|
||||
router.get ('/api/v1/domains', domainsReadScope, routes.domains.getAll);
|
||||
router.get ('/api/v1/domains/:domain', domainsManageScope, verifyDomainLock, routes.domains.get); // this is manage scope because it returns non-restricted fields
|
||||
router.put ('/api/v1/domains/:domain', domainsManageScope, verifyDomainLock, routes.domains.update);
|
||||
router.del ('/api/v1/domains/:domain', domainsManageScope, verifyDomainLock, routes.users.verifyPassword, routes.domains.del);
|
||||
router.del ('/api/v1/domains/:domain', domainsManageScope, verifyDomainLock, routes.domains.del);
|
||||
|
||||
// addon routes
|
||||
router.get ('/api/v1/services', cloudronScope, routes.services.getAll);
|
||||
@@ -370,7 +369,6 @@ function start(callback) {
|
||||
async.series([
|
||||
routes.accesscontrol.initialize, // hooks up authentication strategies into passport
|
||||
database.initialize,
|
||||
provision.autoRegister,
|
||||
cloudron.initialize,
|
||||
gHttpServer.listen.bind(gHttpServer, config.get('port'), '127.0.0.1'),
|
||||
gSysadminHttpServer.listen.bind(gSysadminHttpServer, config.get('sysadminPort'), '127.0.0.1'),
|
||||
|
||||
@@ -14,6 +14,7 @@ let assert = require('assert'),
|
||||
path = require('path'),
|
||||
util = require('util');
|
||||
|
||||
// the logic here is also used in the cloudron-support tool
|
||||
var AUTHORIZED_KEYS_FILEPATH = config.TEST ? path.join(config.baseDir(), 'authorized_keys') : ((config.provider() === 'ec2' || config.provider() === 'lightsail' || config.provider() === 'ami') ? '/home/ubuntu/.ssh/authorized_keys' : '/root/.ssh/authorized_keys'),
|
||||
AUTHORIZED_KEYS_USER = config.TEST ? process.getuid() : ((config.provider() === 'ec2' || config.provider() === 'lightsail' || config.provider() === 'ami') ? 'ubuntu' : 'root'),
|
||||
AUTHORIZED_KEYS_CMD = path.join(__dirname, 'scripts/remotesupport.sh');
|
||||
|
||||
@@ -412,7 +412,6 @@ describe('database', function () {
|
||||
oldConfig: null,
|
||||
newConfig: null,
|
||||
memoryLimit: 4294967296,
|
||||
xFrameOptions: 'DENY',
|
||||
sso: true,
|
||||
debugMode: null,
|
||||
robotsTxt: null,
|
||||
@@ -993,7 +992,6 @@ describe('database', function () {
|
||||
oldConfig: null,
|
||||
updateConfig: null,
|
||||
memoryLimit: 4294967296,
|
||||
xFrameOptions: 'DENY',
|
||||
sso: true,
|
||||
debugMode: null,
|
||||
robotsTxt: null,
|
||||
@@ -1028,7 +1026,6 @@ describe('database', function () {
|
||||
oldConfig: null,
|
||||
updateConfig: null,
|
||||
memoryLimit: 0,
|
||||
xFrameOptions: 'SAMEORIGIN',
|
||||
sso: true,
|
||||
debugMode: null,
|
||||
robotsTxt: null,
|
||||
|
||||
@@ -2,20 +2,19 @@
|
||||
/* global it:false */
|
||||
/* global describe:false */
|
||||
/* global before:false */
|
||||
/* global beforeEach:false */
|
||||
/* global after:false */
|
||||
|
||||
'use strict';
|
||||
|
||||
var async = require('async'),
|
||||
AWS = require('aws-sdk'),
|
||||
GCDNS = require('@google-cloud/dns'),
|
||||
GCDNS = require('@google-cloud/dns').DNS,
|
||||
config = require('../config.js'),
|
||||
database = require('../database.js'),
|
||||
domains = require('../domains.js'),
|
||||
expect = require('expect.js'),
|
||||
namecheap = require('namecheap'),
|
||||
nock = require('nock'),
|
||||
sinon = require('sinon'),
|
||||
util = require('util');
|
||||
|
||||
var DOMAIN_0 = {
|
||||
@@ -596,465 +595,282 @@ describe('dns provider', function () {
|
||||
});
|
||||
});
|
||||
|
||||
xdescribe('namecheap', function () {
|
||||
let sandbox = require('sinon').createSandbox();
|
||||
describe('namecheap', function () {
|
||||
const NAMECHEAP_ENDPOINT = 'https://api.namecheap.com';
|
||||
const username = 'namecheapuser';
|
||||
const token = 'namecheaptoken';
|
||||
|
||||
let username = 'namecheapuser';
|
||||
let apiKey = 'API_KEY';
|
||||
// the success answer is always the same
|
||||
const SET_HOSTS_RETURN = `<?xml version="1.0" encoding="utf-8"?>
|
||||
<ApiResponse Status="OK" xmlns="http://api.namecheap.com/xml.response">
|
||||
<Errors />
|
||||
<Warnings />
|
||||
<RequestedCommand>namecheap.domains.dns.sethosts</RequestedCommand>
|
||||
<CommandResponse Type="namecheap.domains.dns.setHosts">
|
||||
<DomainDNSSetHostsResult Domain="cloudron.space" IsSuccess="true">
|
||||
<Warnings />
|
||||
</DomainDNSSetHostsResult>
|
||||
</CommandResponse>
|
||||
<Server>PHX01APIEXT03</Server>
|
||||
<GMTTimeDifference>--4:00</GMTTimeDifference>
|
||||
<ExecutionTime>0.408</ExecutionTime>
|
||||
</ApiResponse>`;
|
||||
|
||||
before(function (done) {
|
||||
DOMAIN_0.provider = 'namecheap';
|
||||
DOMAIN_0.config = {
|
||||
username,
|
||||
apiKey
|
||||
username: username,
|
||||
token: token
|
||||
};
|
||||
|
||||
domains.update(DOMAIN_0.domain, DOMAIN_0, AUDIT_SOURCE, done);
|
||||
});
|
||||
|
||||
after(function() {
|
||||
sandbox.restore();
|
||||
beforeEach(function () {
|
||||
nock.cleanAll();
|
||||
});
|
||||
|
||||
it('upsert non-existing record succeeds', function (done) {
|
||||
const GET_HOSTS_RETURN = `<?xml version="1.0" encoding="utf-8"?>
|
||||
<ApiResponse Status="OK" xmlns="http://api.namecheap.com/xml.response">
|
||||
<Errors />
|
||||
<Warnings />
|
||||
<RequestedCommand>namecheap.domains.dns.gethosts</RequestedCommand>
|
||||
<CommandResponse Type="namecheap.domains.dns.getHosts">
|
||||
<DomainDNSGetHostsResult Domain="cloudron.space" EmailType="MX" IsUsingOurDNS="true">
|
||||
<host HostId="160859434" Name="@" Type="MX" Address="my.nebulon.space." MXPref="10" TTL="300" AssociatedAppTitle="" FriendlyName="" IsActive="true" IsDDNSEnabled="false" />
|
||||
<host HostId="173400612" Name="@" Type="TXT" Address="v=spf1 a:my.nebulon.space ~all" MXPref="10" TTL="300" AssociatedAppTitle="" FriendlyName="" IsActive="true" IsDDNSEnabled="false" />
|
||||
</DomainDNSGetHostsResult>
|
||||
</CommandResponse>
|
||||
<Server>PHX01APIEXT04</Server>
|
||||
<GMTTimeDifference>--4:00</GMTTimeDifference>
|
||||
<ExecutionTime>0.16</ExecutionTime>
|
||||
</ApiResponse>`;
|
||||
|
||||
let getHostsReturn = {
|
||||
'Type': 'namecheap.domains.dns.getHosts',
|
||||
'DomainDNSGetHostsResult': {
|
||||
'Domain': 'example-dns-test.com',
|
||||
'EmailType': 'FWD',
|
||||
'IsUsingOurDNS': 'true',
|
||||
'host': [
|
||||
{
|
||||
'HostId': '614433',
|
||||
'Name': 'www',
|
||||
'Type': 'CNAME',
|
||||
'Address': 'parkingpage.namecheap.com.',
|
||||
'MXPref': '10',
|
||||
'TTL': '1800',
|
||||
'AssociatedAppTitle': '',
|
||||
'FriendlyName': 'CNAME Record',
|
||||
'IsActive': 'true',
|
||||
'IsDDNSEnabled': 'false'
|
||||
},
|
||||
{
|
||||
'HostId': '614432',
|
||||
'Name': '@',
|
||||
'Type': 'URL',
|
||||
'Address': 'http://www.example-dns-test.com/',
|
||||
'MXPref': '10',
|
||||
'TTL': '1800',
|
||||
'AssociatedAppTitle': 'URL Forwarding',
|
||||
'FriendlyName': 'URL Record',
|
||||
'IsActive': 'true',
|
||||
'IsDDNSEnabled': 'false'
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
var req1 = nock(NAMECHEAP_ENDPOINT).get('/xml.response')
|
||||
.query({
|
||||
ApiUser: username,
|
||||
ApiKey: token,
|
||||
UserName: username,
|
||||
ClientIp: '127.0.0.1',
|
||||
Command: 'namecheap.domains.dns.getHosts',
|
||||
SLD: DOMAIN_0.zoneName.split('.')[0],
|
||||
TLD: DOMAIN_0.zoneName.split('.')[1]
|
||||
})
|
||||
.reply(200, GET_HOSTS_RETURN);
|
||||
|
||||
let setInternalExpect = [
|
||||
{
|
||||
'HostId': '614433',
|
||||
'HostName': 'www',
|
||||
'RecordType': 'CNAME',
|
||||
'Address': 'parkingpage.namecheap.com.',
|
||||
'MXPref': '10',
|
||||
'TTL': '1800',
|
||||
'AssociatedAppTitle': '',
|
||||
'FriendlyName': 'CNAME Record',
|
||||
'IsActive': 'true',
|
||||
'IsDDNSEnabled': 'false'
|
||||
},
|
||||
{
|
||||
'HostId': '614432',
|
||||
'HostName': '@',
|
||||
'RecordType': 'URL',
|
||||
'Address': 'http://www.example-dns-test.com/',
|
||||
'MXPref': '10',
|
||||
'TTL': '1800',
|
||||
'AssociatedAppTitle': 'URL Forwarding',
|
||||
'FriendlyName': 'URL Record',
|
||||
'IsActive': 'true',
|
||||
'IsDDNSEnabled': 'false'
|
||||
},
|
||||
{
|
||||
RecordType: 'A',
|
||||
HostName: 'test',
|
||||
Address: '1.2.3.4'
|
||||
}
|
||||
];
|
||||
var req2 = nock(NAMECHEAP_ENDPOINT).post('/xml.response')
|
||||
.query({
|
||||
ApiUser: username,
|
||||
ApiKey: token,
|
||||
UserName: username,
|
||||
ClientIp: '127.0.0.1',
|
||||
Command: 'namecheap.domains.dns.setHosts',
|
||||
SLD: DOMAIN_0.zoneName.split('.')[0],
|
||||
TLD: DOMAIN_0.zoneName.split('.')[1],
|
||||
|
||||
let getHostsFake = sinon.fake.yields(null, getHostsReturn);
|
||||
let setHostsFake = sinon.fake.yields(null, true);
|
||||
let mockObj = {
|
||||
dns: {
|
||||
getHosts: getHostsFake,
|
||||
setHosts: setHostsFake
|
||||
}
|
||||
};
|
||||
TTL1: '300',
|
||||
HostName1: '@',
|
||||
RecordType1: 'MX',
|
||||
Address1: 'my.nebulon.space.',
|
||||
EmailType1: 'MX',
|
||||
MXPref1: '10',
|
||||
|
||||
sandbox.stub(namecheap.prototype, 'domains').value(mockObj);
|
||||
TTL2: '300',
|
||||
HostName2: '@',
|
||||
RecordType2: 'TXT',
|
||||
Address2: 'v=spf1 a:my.nebulon.space ~all',
|
||||
|
||||
TTL3: '300',
|
||||
HostName3: 'test',
|
||||
RecordType3: 'A',
|
||||
Address3: '1.2.3.4',
|
||||
})
|
||||
.reply(200, SET_HOSTS_RETURN);
|
||||
|
||||
domains.upsertDnsRecords('test', DOMAIN_0.domain, 'A', ['1.2.3.4'], function (error) {
|
||||
expect(error).to.eql(null);
|
||||
|
||||
expect(setHostsFake.calledOnce).to.eql(true);
|
||||
expect(setHostsFake.calledWith(DOMAIN_0.domain, setInternalExpect)).to.eql(true);
|
||||
expect(req1.isDone()).to.be.ok();
|
||||
expect(req2.isDone()).to.be.ok();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('upsert multiple non-existing records succeeds', function (done) {
|
||||
const GET_HOSTS_RETURN = `<?xml version="1.0" encoding="utf-8"?>
|
||||
<ApiResponse Status="OK" xmlns="http://api.namecheap.com/xml.response">
|
||||
<Errors />
|
||||
<Warnings />
|
||||
<RequestedCommand>namecheap.domains.dns.gethosts</RequestedCommand>
|
||||
<CommandResponse Type="namecheap.domains.dns.getHosts">
|
||||
<DomainDNSGetHostsResult Domain="cloudron.space" EmailType="MX" IsUsingOurDNS="true">
|
||||
<host HostId="160859434" Name="@" Type="MX" Address="my.nebulon.space." MXPref="10" TTL="300" AssociatedAppTitle="" FriendlyName="" IsActive="true" IsDDNSEnabled="false" />
|
||||
<host HostId="173400612" Name="@" Type="TXT" Address="v=spf1 a:my.nebulon.space ~all" MXPref="10" TTL="300" AssociatedAppTitle="" FriendlyName="" IsActive="true" IsDDNSEnabled="false" />
|
||||
</DomainDNSGetHostsResult>
|
||||
</CommandResponse>
|
||||
<Server>PHX01APIEXT04</Server>
|
||||
<GMTTimeDifference>--4:00</GMTTimeDifference>
|
||||
<ExecutionTime>0.16</ExecutionTime>
|
||||
</ApiResponse>`;
|
||||
|
||||
let getHostsReturn = {
|
||||
'Type': 'namecheap.domains.dns.getHosts',
|
||||
'DomainDNSGetHostsResult': {
|
||||
'Domain': 'example-dns-test.com',
|
||||
'EmailType': 'FWD',
|
||||
'IsUsingOurDNS': 'true',
|
||||
'host': [
|
||||
{
|
||||
'HostId': '614433',
|
||||
'Name': 'www',
|
||||
'Type': 'CNAME',
|
||||
'Address': 'parkingpage.namecheap.com.',
|
||||
'MXPref': '10',
|
||||
'TTL': '1800',
|
||||
'AssociatedAppTitle': '',
|
||||
'FriendlyName': 'CNAME Record',
|
||||
'IsActive': 'true',
|
||||
'IsDDNSEnabled': 'false'
|
||||
},
|
||||
{
|
||||
'HostId': '614432',
|
||||
'Name': '@',
|
||||
'Type': 'URL',
|
||||
'Address': 'http://www.example-dns-test.com/',
|
||||
'MXPref': '10',
|
||||
'TTL': '1800',
|
||||
'AssociatedAppTitle': 'URL Forwarding',
|
||||
'FriendlyName': 'URL Record',
|
||||
'IsActive': 'true',
|
||||
'IsDDNSEnabled': 'false'
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
var req1 = nock(NAMECHEAP_ENDPOINT).get('/xml.response')
|
||||
.query({
|
||||
ApiUser: username,
|
||||
ApiKey: token,
|
||||
UserName: username,
|
||||
ClientIp: '127.0.0.1',
|
||||
Command: 'namecheap.domains.dns.getHosts',
|
||||
SLD: DOMAIN_0.zoneName.split('.')[0],
|
||||
TLD: DOMAIN_0.zoneName.split('.')[1]
|
||||
})
|
||||
.reply(200, GET_HOSTS_RETURN);
|
||||
|
||||
let setInternalExpect = [
|
||||
{
|
||||
'HostId': '614433',
|
||||
'HostName': 'www',
|
||||
'RecordType': 'CNAME',
|
||||
'Address': 'parkingpage.namecheap.com.',
|
||||
'MXPref': '10',
|
||||
'TTL': '1800',
|
||||
'AssociatedAppTitle': '',
|
||||
'FriendlyName': 'CNAME Record',
|
||||
'IsActive': 'true',
|
||||
'IsDDNSEnabled': 'false'
|
||||
},
|
||||
{
|
||||
'HostId': '614432',
|
||||
'HostName': '@',
|
||||
'RecordType': 'URL',
|
||||
'Address': 'http://www.example-dns-test.com/',
|
||||
'MXPref': '10',
|
||||
'TTL': '1800',
|
||||
'AssociatedAppTitle': 'URL Forwarding',
|
||||
'FriendlyName': 'URL Record',
|
||||
'IsActive': 'true',
|
||||
'IsDDNSEnabled': 'false'
|
||||
},
|
||||
{
|
||||
RecordType: 'TXT',
|
||||
HostName: 'test',
|
||||
Address: '1.2.3.4'
|
||||
},
|
||||
{
|
||||
RecordType: 'TXT',
|
||||
HostName: 'test',
|
||||
Address: '2.3.4.5'
|
||||
},
|
||||
{
|
||||
RecordType: 'TXT',
|
||||
HostName: 'test',
|
||||
Address: '3.4.5.6'
|
||||
}
|
||||
];
|
||||
var req2 = nock(NAMECHEAP_ENDPOINT).post('/xml.response')
|
||||
.query({
|
||||
ApiUser: username,
|
||||
ApiKey: token,
|
||||
UserName: username,
|
||||
ClientIp: '127.0.0.1',
|
||||
Command: 'namecheap.domains.dns.setHosts',
|
||||
SLD: DOMAIN_0.zoneName.split('.')[0],
|
||||
TLD: DOMAIN_0.zoneName.split('.')[1],
|
||||
|
||||
let getHostsFake = sinon.fake.yields(null, getHostsReturn);
|
||||
let setHostsFake = sinon.fake.yields(null, true);
|
||||
let mockObj = {
|
||||
dns: {
|
||||
getHosts: getHostsFake,
|
||||
setHosts: setHostsFake
|
||||
}
|
||||
};
|
||||
TTL1: '300',
|
||||
HostName1: '@',
|
||||
RecordType1: 'MX',
|
||||
Address1: 'my.nebulon.space.',
|
||||
EmailType1: 'MX',
|
||||
MXPref1: '10',
|
||||
|
||||
sandbox.stub(namecheap.prototype, 'domains').value(mockObj);
|
||||
TTL2: '300',
|
||||
HostName2: '@',
|
||||
RecordType2: 'TXT',
|
||||
Address2: 'v=spf1 a:my.nebulon.space ~all',
|
||||
|
||||
TTL3: '300',
|
||||
HostName3: 'test',
|
||||
RecordType3: 'TXT',
|
||||
Address3: '1.2.3.4',
|
||||
|
||||
TTL4: '300',
|
||||
HostName4: 'test',
|
||||
RecordType4: 'TXT',
|
||||
Address4: '2.3.4.5',
|
||||
|
||||
TTL5: '300',
|
||||
HostName5: 'test',
|
||||
RecordType5: 'TXT',
|
||||
Address5: '3.4.5.6',
|
||||
})
|
||||
.reply(200, SET_HOSTS_RETURN);
|
||||
|
||||
domains.upsertDnsRecords('test', DOMAIN_0.domain, 'TXT', ['1.2.3.4', '2.3.4.5', '3.4.5.6'], function (error) {
|
||||
expect(error).to.eql(null);
|
||||
|
||||
expect(setHostsFake.calledOnce).to.eql(true);
|
||||
expect(setHostsFake.calledWith(DOMAIN_0.domain, setInternalExpect)).to.eql(true);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('upsert multiple non-existing MX records succeeds', function (done) {
|
||||
|
||||
let getHostsReturn = {
|
||||
'Type': 'namecheap.domains.dns.getHosts',
|
||||
'DomainDNSGetHostsResult': {
|
||||
'Domain': 'example-dns-test.com',
|
||||
'EmailType': 'FWD',
|
||||
'IsUsingOurDNS': 'true',
|
||||
'host': [
|
||||
{
|
||||
'HostId': '614433',
|
||||
'Name': 'www',
|
||||
'Type': 'CNAME',
|
||||
'Address': 'parkingpage.namecheap.com.',
|
||||
'MXPref': '10',
|
||||
'TTL': '1800',
|
||||
'AssociatedAppTitle': '',
|
||||
'FriendlyName': 'CNAME Record',
|
||||
'IsActive': 'true',
|
||||
'IsDDNSEnabled': 'false'
|
||||
},
|
||||
{
|
||||
'HostId': '614432',
|
||||
'Name': '@',
|
||||
'Type': 'URL',
|
||||
'Address': 'http://www.example-dns-test.com/',
|
||||
'MXPref': '10',
|
||||
'TTL': '1800',
|
||||
'AssociatedAppTitle': 'URL Forwarding',
|
||||
'FriendlyName': 'URL Record',
|
||||
'IsActive': 'true',
|
||||
'IsDDNSEnabled': 'false'
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
let setInternalExpect = [
|
||||
{
|
||||
'HostId': '614433',
|
||||
'HostName': 'www',
|
||||
'RecordType': 'CNAME',
|
||||
'Address': 'parkingpage.namecheap.com.',
|
||||
'MXPref': '10',
|
||||
'TTL': '1800',
|
||||
'AssociatedAppTitle': '',
|
||||
'FriendlyName': 'CNAME Record',
|
||||
'IsActive': 'true',
|
||||
'IsDDNSEnabled': 'false'
|
||||
},
|
||||
{
|
||||
'HostId': '614432',
|
||||
'HostName': '@',
|
||||
'RecordType': 'URL',
|
||||
'Address': 'http://www.example-dns-test.com/',
|
||||
'MXPref': '10',
|
||||
'TTL': '1800',
|
||||
'AssociatedAppTitle': 'URL Forwarding',
|
||||
'FriendlyName': 'URL Record',
|
||||
'IsActive': 'true',
|
||||
'IsDDNSEnabled': 'false'
|
||||
},
|
||||
{
|
||||
RecordType: 'MX',
|
||||
HostName: 'test',
|
||||
Address: '1.2.3.4',
|
||||
MXPref: '10'
|
||||
},
|
||||
{
|
||||
RecordType: 'MX',
|
||||
HostName: 'test',
|
||||
Address: '2.3.4.5',
|
||||
MXPref: '20'
|
||||
},
|
||||
{
|
||||
RecordType: 'MX',
|
||||
HostName: 'test',
|
||||
Address: '3.4.5.6',
|
||||
MXPref: '30'
|
||||
}
|
||||
];
|
||||
|
||||
let getHostsFake = sinon.fake.yields(null, getHostsReturn);
|
||||
let setHostsFake = sinon.fake.yields(null, true);
|
||||
let mockObj = {
|
||||
dns: {
|
||||
getHosts: getHostsFake,
|
||||
setHosts: setHostsFake
|
||||
}
|
||||
};
|
||||
|
||||
sandbox.stub(namecheap.prototype, 'domains').value(mockObj);
|
||||
|
||||
domains.upsertDnsRecords('test', DOMAIN_0.domain, 'MX', ['10 1.2.3.4', '20 2.3.4.5', '30 3.4.5.6'], function (error) {
|
||||
expect(error).to.eql(null);
|
||||
|
||||
expect(setHostsFake.calledOnce).to.eql(true);
|
||||
expect(setHostsFake.calledWith(DOMAIN_0.domain, setInternalExpect)).to.eql(true);
|
||||
expect(req1.isDone()).to.be.ok();
|
||||
expect(req2.isDone()).to.be.ok();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('upsert existing record succeeds', function (done) {
|
||||
const GET_HOSTS_RETURN = `<?xml version="1.0" encoding="utf-8"?>
|
||||
<ApiResponse Status="OK" xmlns="http://api.namecheap.com/xml.response">
|
||||
<Errors />
|
||||
<Warnings />
|
||||
<RequestedCommand>namecheap.domains.dns.gethosts</RequestedCommand>
|
||||
<CommandResponse Type="namecheap.domains.dns.getHosts">
|
||||
<DomainDNSGetHostsResult Domain="cloudron.space" EmailType="MX" IsUsingOurDNS="true">
|
||||
<host HostId="160859434" Name="@" Type="MX" Address="my.nebulon.space." MXPref="10" TTL="300" AssociatedAppTitle="" FriendlyName="" IsActive="true" IsDDNSEnabled="false" />
|
||||
<host HostId="173400612" Name="www" Type="CNAME" Address="1.2.3.4" MXPref="10" TTL="300" AssociatedAppTitle="" FriendlyName="" IsActive="true" IsDDNSEnabled="false" />
|
||||
</DomainDNSGetHostsResult>
|
||||
</CommandResponse>
|
||||
<Server>PHX01APIEXT04</Server>
|
||||
<GMTTimeDifference>--4:00</GMTTimeDifference>
|
||||
<ExecutionTime>0.16</ExecutionTime>
|
||||
</ApiResponse>`;
|
||||
|
||||
let getHostsReturn = {
|
||||
'Type': 'namecheap.domains.dns.getHosts',
|
||||
'DomainDNSGetHostsResult': {
|
||||
'Domain': 'example-dns-test.com',
|
||||
'EmailType': 'FWD',
|
||||
'IsUsingOurDNS': 'true',
|
||||
'host': [
|
||||
{
|
||||
'HostId': '614433',
|
||||
'Name': 'www',
|
||||
'Type': 'CNAME',
|
||||
'Address': DOMAIN_0.domain,
|
||||
'MXPref': '10',
|
||||
'TTL': '1800',
|
||||
'AssociatedAppTitle': '',
|
||||
'FriendlyName': 'CNAME Record',
|
||||
'IsActive': 'true',
|
||||
'IsDDNSEnabled': 'false'
|
||||
},
|
||||
{
|
||||
'HostId': '614432',
|
||||
'Name': '@',
|
||||
'Type': 'URL',
|
||||
'Address': 'http://www.example-dns-test.com/',
|
||||
'MXPref': '10',
|
||||
'TTL': '1800',
|
||||
'AssociatedAppTitle': 'URL Forwarding',
|
||||
'FriendlyName': 'URL Record',
|
||||
'IsActive': 'true',
|
||||
'IsDDNSEnabled': 'false'
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
var req1 = nock(NAMECHEAP_ENDPOINT).get('/xml.response')
|
||||
.query({
|
||||
ApiUser: username,
|
||||
ApiKey: token,
|
||||
UserName: username,
|
||||
ClientIp: '127.0.0.1',
|
||||
Command: 'namecheap.domains.dns.getHosts',
|
||||
SLD: DOMAIN_0.zoneName.split('.')[0],
|
||||
TLD: DOMAIN_0.zoneName.split('.')[1]
|
||||
})
|
||||
.reply(200, GET_HOSTS_RETURN);
|
||||
|
||||
let setInternalExpect = [
|
||||
{
|
||||
'HostId': '614433',
|
||||
'HostName': 'www',
|
||||
'RecordType': 'CNAME',
|
||||
'Address': '1.2.3.4',
|
||||
'MXPref': '10',
|
||||
'TTL': '1800',
|
||||
'AssociatedAppTitle': '',
|
||||
'FriendlyName': 'CNAME Record',
|
||||
'IsActive': 'true',
|
||||
'IsDDNSEnabled': 'false'
|
||||
},
|
||||
{
|
||||
'HostId': '614432',
|
||||
'HostName': '@',
|
||||
'RecordType': 'URL',
|
||||
'Address': 'http://www.example-dns-test.com/',
|
||||
'MXPref': '10',
|
||||
'TTL': '1800',
|
||||
'AssociatedAppTitle': 'URL Forwarding',
|
||||
'FriendlyName': 'URL Record',
|
||||
'IsActive': 'true',
|
||||
'IsDDNSEnabled': 'false'
|
||||
}
|
||||
];
|
||||
var req2 = nock(NAMECHEAP_ENDPOINT).post('/xml.response')
|
||||
.query({
|
||||
ApiUser: username,
|
||||
ApiKey: token,
|
||||
UserName: username,
|
||||
ClientIp: '127.0.0.1',
|
||||
Command: 'namecheap.domains.dns.setHosts',
|
||||
SLD: DOMAIN_0.zoneName.split('.')[0],
|
||||
TLD: DOMAIN_0.zoneName.split('.')[1],
|
||||
|
||||
let getHostsFake = sinon.fake.yields(null, getHostsReturn);
|
||||
let setHostsFake = sinon.fake.yields(null, true);
|
||||
let mockObj = {
|
||||
dns: {
|
||||
getHosts: getHostsFake,
|
||||
setHosts: setHostsFake
|
||||
}
|
||||
};
|
||||
TTL1: '300',
|
||||
HostName1: '@',
|
||||
RecordType1: 'MX',
|
||||
Address1: 'my.nebulon.space.',
|
||||
EmailType1: 'MX',
|
||||
MXPref1: '10',
|
||||
|
||||
sandbox.stub(namecheap.prototype, 'domains').value(mockObj);
|
||||
TTL2: '300',
|
||||
HostName2: 'www',
|
||||
RecordType2: 'CNAME',
|
||||
Address2: '1.2.3.4'
|
||||
})
|
||||
.reply(200, SET_HOSTS_RETURN);
|
||||
|
||||
domains.upsertDnsRecords('www', DOMAIN_0.domain, 'CNAME', ['1.2.3.4'], function (error) {
|
||||
expect(error).to.eql(null);
|
||||
|
||||
expect(setHostsFake.calledOnce).to.eql(true);
|
||||
expect(setHostsFake.calledWith(DOMAIN_0.domain, setInternalExpect)).to.eql(true);
|
||||
expect(req1.isDone()).to.be.ok();
|
||||
expect(req2.isDone()).to.be.ok();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('get succeeds', function(done) {
|
||||
let getHostsReturn = {
|
||||
'Type': 'namecheap.domains.dns.getHosts',
|
||||
'DomainDNSGetHostsResult': {
|
||||
'Domain': 'example-dns-test.com',
|
||||
'EmailType': 'FWD',
|
||||
'IsUsingOurDNS': 'true',
|
||||
'host': [
|
||||
{
|
||||
'HostId': '614433',
|
||||
'Name': 'www',
|
||||
'Type': 'CNAME',
|
||||
'Address': '1.2.3.4',
|
||||
'MXPref': '10',
|
||||
'TTL': '1800',
|
||||
'AssociatedAppTitle': '',
|
||||
'FriendlyName': 'CNAME Record',
|
||||
'IsActive': 'true',
|
||||
'IsDDNSEnabled': 'false'
|
||||
},
|
||||
{
|
||||
'HostId': '614432',
|
||||
'Name': 'test',
|
||||
'Type': 'A',
|
||||
'Address': '1.2.3.4',
|
||||
'MXPref': '10',
|
||||
'TTL': '1800',
|
||||
'FriendlyName': 'A Record',
|
||||
'IsActive': 'true',
|
||||
'IsDDNSEnabled': 'false'
|
||||
},
|
||||
{
|
||||
'HostId': '614431',
|
||||
'Name': 'test',
|
||||
'Type': 'A',
|
||||
'Address': '2.3.4.5',
|
||||
'MXPref': '10',
|
||||
'TTL': '1800',
|
||||
'FriendlyName': 'A Record',
|
||||
'IsActive': 'true',
|
||||
'IsDDNSEnabled': 'false'
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
const GET_HOSTS_RETURN = `<?xml version="1.0" encoding="utf-8"?>
|
||||
<ApiResponse Status="OK" xmlns="http://api.namecheap.com/xml.response">
|
||||
<Errors />
|
||||
<Warnings />
|
||||
<RequestedCommand>namecheap.domains.dns.gethosts</RequestedCommand>
|
||||
<CommandResponse Type="namecheap.domains.dns.getHosts">
|
||||
<DomainDNSGetHostsResult Domain="cloudron.space" EmailType="MX" IsUsingOurDNS="true">
|
||||
<host HostId="160859434" Name="@" Type="MX" Address="my.nebulon.space." MXPref="10" TTL="300" AssociatedAppTitle="" FriendlyName="" IsActive="true" IsDDNSEnabled="false" />
|
||||
<host HostId="173400612" Name="test" Type="A" Address="1.2.3.4" MXPref="10" TTL="300" AssociatedAppTitle="" FriendlyName="" IsActive="true" IsDDNSEnabled="false" />
|
||||
<host HostId="173400613" Name="test" Type="A" Address="2.3.4.5" MXPref="10" TTL="300" AssociatedAppTitle="" FriendlyName="" IsActive="true" IsDDNSEnabled="false" />
|
||||
</DomainDNSGetHostsResult>
|
||||
</CommandResponse>
|
||||
<Server>PHX01APIEXT04</Server>
|
||||
<GMTTimeDifference>--4:00</GMTTimeDifference>
|
||||
<ExecutionTime>0.16</ExecutionTime>
|
||||
</ApiResponse>`;
|
||||
|
||||
let getHostsFake = sinon.fake.yields(null, getHostsReturn);
|
||||
let mockObj = {
|
||||
dns: {
|
||||
getHosts: getHostsFake
|
||||
}
|
||||
};
|
||||
|
||||
sandbox.stub(namecheap.prototype, 'domains').value(mockObj);
|
||||
var req1 = nock(NAMECHEAP_ENDPOINT).get('/xml.response')
|
||||
.query({
|
||||
ApiUser: username,
|
||||
ApiKey: token,
|
||||
UserName: username,
|
||||
ClientIp: '127.0.0.1',
|
||||
Command: 'namecheap.domains.dns.getHosts',
|
||||
SLD: DOMAIN_0.zoneName.split('.')[0],
|
||||
TLD: DOMAIN_0.zoneName.split('.')[1]
|
||||
})
|
||||
.reply(200, GET_HOSTS_RETURN);
|
||||
|
||||
domains.getDnsRecords('test', DOMAIN_0.domain, 'A', function (error, result) {
|
||||
expect(error).to.eql(null);
|
||||
|
||||
expect(req1.isDone()).to.be.ok();
|
||||
expect(result).to.be.an(Array);
|
||||
expect(result.length).to.eql(2);
|
||||
expect(getHostsFake.calledOnce).to.eql(true);
|
||||
expect(result).to.eql(['1.2.3.4', '2.3.4.5']);
|
||||
|
||||
done();
|
||||
@@ -1062,130 +878,74 @@ describe('dns provider', function () {
|
||||
});
|
||||
|
||||
it('del succeeds', function (done) {
|
||||
const GET_HOSTS_RETURN = `<?xml version="1.0" encoding="utf-8"?>
|
||||
<ApiResponse Status="OK" xmlns="http://api.namecheap.com/xml.response">
|
||||
<Errors />
|
||||
<Warnings />
|
||||
<RequestedCommand>namecheap.domains.dns.gethosts</RequestedCommand>
|
||||
<CommandResponse Type="namecheap.domains.dns.getHosts">
|
||||
<DomainDNSGetHostsResult Domain="cloudron.space" EmailType="MX" IsUsingOurDNS="true">
|
||||
<host HostId="160859434" Name="@" Type="MX" Address="my.nebulon.space." MXPref="10" TTL="300" AssociatedAppTitle="" FriendlyName="" IsActive="true" IsDDNSEnabled="false" />
|
||||
<host HostId="173400612" Name="www" Type="CNAME" Address="1.2.3.4" MXPref="10" TTL="300" AssociatedAppTitle="" FriendlyName="" IsActive="true" IsDDNSEnabled="false" />
|
||||
</DomainDNSGetHostsResult>
|
||||
</CommandResponse>
|
||||
<Server>PHX01APIEXT04</Server>
|
||||
<GMTTimeDifference>--4:00</GMTTimeDifference>
|
||||
<ExecutionTime>0.16</ExecutionTime>
|
||||
</ApiResponse>`;
|
||||
|
||||
let getHostsReturn = {
|
||||
'Type': 'namecheap.domains.dns.getHosts',
|
||||
'DomainDNSGetHostsResult': {
|
||||
'Domain': 'example-dns-test.com',
|
||||
'EmailType': 'FWD',
|
||||
'IsUsingOurDNS': 'true',
|
||||
'host': [
|
||||
{
|
||||
'HostId': '614433',
|
||||
'Name': 'www',
|
||||
'Type': 'CNAME',
|
||||
'Address': '1.2.3.4',
|
||||
'MXPref': '10',
|
||||
'TTL': '1800',
|
||||
'AssociatedAppTitle': '',
|
||||
'FriendlyName': 'CNAME Record',
|
||||
'IsActive': 'true',
|
||||
'IsDDNSEnabled': 'false'
|
||||
},
|
||||
{
|
||||
'HostId': '614432',
|
||||
'Name': '@',
|
||||
'Type': 'URL',
|
||||
'Address': 'http://www.example-dns-test.com/',
|
||||
'MXPref': '10',
|
||||
'TTL': '1800',
|
||||
'AssociatedAppTitle': 'URL Forwarding',
|
||||
'FriendlyName': 'URL Record',
|
||||
'IsActive': 'true',
|
||||
'IsDDNSEnabled': 'false'
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
let setInternalExpect = [
|
||||
{
|
||||
'HostId': '614432',
|
||||
'HostName': '@',
|
||||
'RecordType': 'URL',
|
||||
'Address': 'http://www.example-dns-test.com/',
|
||||
'MXPref': '10',
|
||||
'TTL': '1800',
|
||||
'AssociatedAppTitle': 'URL Forwarding',
|
||||
'FriendlyName': 'URL Record',
|
||||
'IsActive': 'true',
|
||||
'IsDDNSEnabled': 'false'
|
||||
}
|
||||
];
|
||||
|
||||
let getHostsFake = sinon.fake.yields(null, getHostsReturn);
|
||||
let setHostsFake = sinon.fake.yields(null, true);
|
||||
let mockObj = {
|
||||
dns: {
|
||||
getHosts: getHostsFake,
|
||||
setHosts: setHostsFake
|
||||
}
|
||||
};
|
||||
|
||||
sandbox.stub(namecheap.prototype, 'domains').value(mockObj);
|
||||
var req1 = nock(NAMECHEAP_ENDPOINT).get('/xml.response')
|
||||
.query({
|
||||
ApiUser: username,
|
||||
ApiKey: token,
|
||||
UserName: username,
|
||||
ClientIp: '127.0.0.1',
|
||||
Command: 'namecheap.domains.dns.getHosts',
|
||||
SLD: DOMAIN_0.zoneName.split('.')[0],
|
||||
TLD: DOMAIN_0.zoneName.split('.')[1]
|
||||
})
|
||||
.reply(200, GET_HOSTS_RETURN);
|
||||
|
||||
domains.removeDnsRecords('www', DOMAIN_0.domain, 'CNAME', ['1.2.3.4'], function (error) {
|
||||
expect(error).to.eql(null);
|
||||
|
||||
expect(setHostsFake.calledOnce).to.eql(true);
|
||||
expect(setHostsFake.calledWith(DOMAIN_0.domain, setInternalExpect)).to.eql(true);
|
||||
expect(req1.isDone()).to.be.ok();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('del succeeds w/ non-present host', function (done) {
|
||||
it('del succeeds with non-existing domain', function (done) {
|
||||
const GET_HOSTS_RETURN = `<?xml version="1.0" encoding="utf-8"?>
|
||||
<ApiResponse Status="OK" xmlns="http://api.namecheap.com/xml.response">
|
||||
<Errors />
|
||||
<Warnings />
|
||||
<RequestedCommand>namecheap.domains.dns.gethosts</RequestedCommand>
|
||||
<CommandResponse Type="namecheap.domains.dns.getHosts">
|
||||
<DomainDNSGetHostsResult Domain="cloudron.space" EmailType="MX" IsUsingOurDNS="true">
|
||||
<host HostId="160859434" Name="@" Type="MX" Address="my.nebulon.space." MXPref="10" TTL="300" AssociatedAppTitle="" FriendlyName="" IsActive="true" IsDDNSEnabled="false" />
|
||||
<host HostId="173400612" Name="www" Type="CNAME" Address="1.2.3.4" MXPref="10" TTL="300" AssociatedAppTitle="" FriendlyName="" IsActive="true" IsDDNSEnabled="false" />
|
||||
</DomainDNSGetHostsResult>
|
||||
</CommandResponse>
|
||||
<Server>PHX01APIEXT04</Server>
|
||||
<GMTTimeDifference>--4:00</GMTTimeDifference>
|
||||
<ExecutionTime>0.16</ExecutionTime>
|
||||
</ApiResponse>`;
|
||||
|
||||
let getHostsReturn = {
|
||||
'Type': 'namecheap.domains.dns.getHosts',
|
||||
'DomainDNSGetHostsResult': {
|
||||
'Domain': 'example-dns-test.com',
|
||||
'EmailType': 'FWD',
|
||||
'IsUsingOurDNS': 'true',
|
||||
'host': [
|
||||
{
|
||||
'HostId': '614433',
|
||||
'Name': 'www',
|
||||
'Type': 'CNAME',
|
||||
'Address': 'parkingpage.namecheap.com.',
|
||||
'MXPref': '10',
|
||||
'TTL': '1800',
|
||||
'AssociatedAppTitle': '',
|
||||
'FriendlyName': 'CNAME Record',
|
||||
'IsActive': 'true',
|
||||
'IsDDNSEnabled': 'false'
|
||||
},
|
||||
{
|
||||
'HostId': '614432',
|
||||
'Name': '@',
|
||||
'Type': 'URL',
|
||||
'Address': 'http://www.example-dns-test.com/',
|
||||
'MXPref': '10',
|
||||
'TTL': '1800',
|
||||
'AssociatedAppTitle': 'URL Forwarding',
|
||||
'FriendlyName': 'URL Record',
|
||||
'IsActive': 'true',
|
||||
'IsDDNSEnabled': 'false'
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
let getHostsFake = sinon.fake.yields(null, getHostsReturn);
|
||||
let setHostsFake = sinon.fake.yields(null, true);
|
||||
let mockObj = {
|
||||
dns: {
|
||||
getHosts: getHostsFake,
|
||||
setHosts: setHostsFake
|
||||
}
|
||||
};
|
||||
|
||||
sandbox.stub(namecheap.prototype, 'domains').value(mockObj);
|
||||
var req1 = nock(NAMECHEAP_ENDPOINT).get('/xml.response')
|
||||
.query({
|
||||
ApiUser: username,
|
||||
ApiKey: token,
|
||||
UserName: username,
|
||||
ClientIp: '127.0.0.1',
|
||||
Command: 'namecheap.domains.dns.getHosts',
|
||||
SLD: DOMAIN_0.zoneName.split('.')[0],
|
||||
TLD: DOMAIN_0.zoneName.split('.')[1]
|
||||
})
|
||||
.reply(200, GET_HOSTS_RETURN);
|
||||
|
||||
domains.removeDnsRecords('test', DOMAIN_0.domain, 'A', ['1.2.3.4'], function (error) {
|
||||
expect(error).to.eql(null);
|
||||
|
||||
expect(setHostsFake.notCalled).to.eql(true);
|
||||
expect(req1.isDone()).to.be.ok();
|
||||
|
||||
done();
|
||||
});
|
||||
@@ -1375,7 +1135,7 @@ describe('dns provider', function () {
|
||||
});
|
||||
});
|
||||
|
||||
xdescribe('gcdns', function () {
|
||||
describe('gcdns', function () {
|
||||
var HOSTED_ZONES = [];
|
||||
var zoneQueue = [];
|
||||
var _OriginalGCDNS;
|
||||
@@ -1413,7 +1173,7 @@ describe('dns provider', function () {
|
||||
}
|
||||
|
||||
function fakeZone(name, ns, recordQueue) {
|
||||
var zone = GCDNS().zone(name.replace('.', '-'));
|
||||
var zone = new GCDNS().zone(name.replace('.', '-'));
|
||||
zone.metadata.dnsName = name + '.';
|
||||
zone.metadata.nameServers = ns || ['8.8.8.8', '8.8.4.4'];
|
||||
zone.getRecords = mockery(recordQueue || zoneQueue);
|
||||
@@ -1450,7 +1210,7 @@ describe('dns provider', function () {
|
||||
|
||||
it('upsert existing record succeeds', function (done) {
|
||||
zoneQueue.push([null, HOSTED_ZONES]);
|
||||
zoneQueue.push([null, [GCDNS().zone('test').record('A', { 'name': 'test', data: ['5.6.7.8'], ttl: 1 })]]);
|
||||
zoneQueue.push([null, [new GCDNS().zone('test').record('A', { 'name': 'test', data: ['5.6.7.8'], ttl: 1 })]]);
|
||||
zoneQueue.push([null, { id: '2' }]);
|
||||
|
||||
domains.upsertDnsRecords('test', DOMAIN_0.domain, 'A', ['1.2.3.4'], function (error) {
|
||||
@@ -1476,7 +1236,7 @@ describe('dns provider', function () {
|
||||
|
||||
it('get succeeds', function (done) {
|
||||
zoneQueue.push([null, HOSTED_ZONES]);
|
||||
zoneQueue.push([null, [GCDNS().zone('test').record('A', { 'name': 'test', data: ['1.2.3.4', '5.6.7.8'], ttl: 1 })]]);
|
||||
zoneQueue.push([null, [new GCDNS().zone('test').record('A', { 'name': 'test', data: ['1.2.3.4', '5.6.7.8'], ttl: 1 })]]);
|
||||
|
||||
domains.getDnsRecords('test', DOMAIN_0.domain, 'A', function (error, result) {
|
||||
expect(error).to.eql(null);
|
||||
@@ -1491,7 +1251,7 @@ describe('dns provider', function () {
|
||||
|
||||
it('del succeeds', function (done) {
|
||||
zoneQueue.push([null, HOSTED_ZONES]);
|
||||
zoneQueue.push([null, [GCDNS().zone('test').record('A', { 'name': 'test', data: ['5.6.7.8'], ttl: 1 })]]);
|
||||
zoneQueue.push([null, [new GCDNS().zone('test').record('A', { 'name': 'test', data: ['5.6.7.8'], ttl: 1 })]]);
|
||||
zoneQueue.push([null, { id: '5' }]);
|
||||
|
||||
domains.removeDnsRecords('test', DOMAIN_0.domain, 'A', ['1.2.3.4'], function (error) {
|
||||
|
||||
@@ -11,7 +11,6 @@ var async = require('async'),
|
||||
users = require('../users.js'),
|
||||
userdb = require('../userdb.js'),
|
||||
eventlogdb = require('../eventlogdb.js'),
|
||||
notificationdb = require('../notificationdb.js'),
|
||||
notifications = require('../notifications.js'),
|
||||
NotificationsError = notifications.NotificationsError,
|
||||
expect = require('expect.js');
|
||||
@@ -152,20 +151,24 @@ describe('Notifications', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('getAllPaged succeeds for second page', function (done) {
|
||||
async.timesSeries(20, function (n, callback) {
|
||||
notifications._add(USER_0.id, EVENT_0.id, 'title' + n, 'some message', callback);
|
||||
it('getAllPaged succeeds for second page (takes 5 seconds to add)', function (done) {
|
||||
async.timesSeries(5, function (n, callback) {
|
||||
// timeout is for database TIMESTAMP resolution
|
||||
setTimeout(function () {
|
||||
notifications._add(USER_0.id, EVENT_0.id, 'title' + n, 'some message', callback);
|
||||
}, 1000);
|
||||
}, function (error) {
|
||||
expect(error).to.eql(null);
|
||||
|
||||
notifications.getAllPaged(USER_0.id, null /* ack */, 2, 10, function (error, results) {
|
||||
notifications.getAllPaged(USER_0.id, null /* ack */, 2, 3, function (error, results) {
|
||||
expect(error).to.eql(null);
|
||||
expect(results).to.be.an(Array);
|
||||
expect(results.length).to.be(10);
|
||||
expect(results.length).to.be(3);
|
||||
|
||||
// we cannot compare the title because ordering is by time which is stored in mysql with seconds
|
||||
// precision. making the ordering random...
|
||||
// expect(results[0].title).to.equal('title9');
|
||||
expect(results[0].title).to.equal('title1');
|
||||
expect(results[1].title).to.equal('title0');
|
||||
// the previous tests already add one notification with 'title'
|
||||
expect(results[2].title).to.equal('title');
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user