Compare commits

...

131 Commits

Author SHA1 Message Date
Girish Ramakrishnan 5ae45381e2 fix metrics path
See 03da5cc6b382f6f7aad69395d9f8a9d29d18ec26 in installer

We now use the cgroupfs driver instead of systemd cgroup driver
2015-12-15 15:54:19 -08:00
Girish Ramakrishnan 1ae2e07883 leave note on 429 error code 2015-12-15 14:25:23 -08:00
Girish Ramakrishnan aa34850d4e fix typo 2015-12-15 12:52:41 -08:00
Girish Ramakrishnan 9f524da642 use admin@cloudron.io for email
registrations are failing because the LE server is doing a MX check.
we don't have a proper email to provide here since the box is not
activated yet. we should "update" the email at some point with
the owner information.
2015-12-15 10:39:03 -08:00
Girish Ramakrishnan 8b707e23ca update shrinkwrap 2015-12-15 10:04:45 -08:00
Girish Ramakrishnan a4ea693c3c update superagent
the latest superchanged changed the meaning of 'error'. Previously,
error implied a network error. With the latest superagent, error means
a REST api error i.e 4xx, 5xx are flagged as errors.

error && !error.response means network error
2015-12-15 09:53:37 -08:00
Girish Ramakrishnan aca443a909 update redis 2015-12-15 08:36:47 -08:00
Girish Ramakrishnan 2ae5223da9 update password-generator, validator and nock 2015-12-15 08:34:13 -08:00
Girish Ramakrishnan b5b67f2e6a define CA_ORIGIN 2015-12-15 00:49:00 -08:00
Girish Ramakrishnan fe723f5a53 remove trailing slash in url 2015-12-15 00:42:18 -08:00
Girish Ramakrishnan c55e1ff6b7 debug output the error 2015-12-15 00:23:57 -08:00
Girish Ramakrishnan 4bd88e1220 create acme data dir 2015-12-15 00:21:29 -08:00
Girish Ramakrishnan f46af93528 do not installAdminCertificate for upgrades 2015-12-14 23:37:52 -08:00
Girish Ramakrishnan 8ead0e662a detect apptask crashes 2015-12-14 19:11:26 -08:00
Girish Ramakrishnan 365ee01f96 show error dialog 2015-12-14 18:56:15 -08:00
Girish Ramakrishnan fca6de3997 add function to show the error dialog 2015-12-14 17:57:58 -08:00
Girish Ramakrishnan dceb265742 add error dialog 2015-12-14 17:52:56 -08:00
Girish Ramakrishnan 409096cbff Use production LE 2015-12-14 17:31:41 -08:00
Girish Ramakrishnan e5a40faf82 simply use fallback certs if LE fails
currently, it fails if we cannot get a cert.

This means that we need to provide some option to simply use fallback
cert. This requires UI changes that I want to avoid :-)
2015-12-14 17:13:54 -08:00
Girish Ramakrishnan 859c78c785 fix fallback cert message 2015-12-14 16:42:34 -08:00
Girish Ramakrishnan 89bff16053 fix crash 2015-12-14 14:08:45 -08:00
Girish Ramakrishnan a89476c538 fix renewal check 2015-12-14 13:52:54 -08:00
Girish Ramakrishnan f51b61e407 do not dump the csr 2015-12-14 13:41:30 -08:00
Girish Ramakrishnan 177103bccd update safetydance for readdirSync 2015-12-14 13:10:04 -08:00
Girish Ramakrishnan f31d63aabd implement cert auto-renewal 2015-12-14 12:40:39 -08:00
Girish Ramakrishnan fd20246e8b ensureCertificate: check if cert needs renewal 2015-12-14 12:38:19 -08:00
Girish Ramakrishnan 0c1ea39a02 add getApi 2015-12-14 12:28:00 -08:00
Girish Ramakrishnan a409dd026d use url file to download cert if present 2015-12-14 12:22:57 -08:00
Girish Ramakrishnan 4731f8e5a7 move key creation into the acme flow 2015-12-14 12:21:41 -08:00
Girish Ramakrishnan 7e05259b0e save url for renewal in .url files 2015-12-14 12:17:57 -08:00
Girish Ramakrishnan 14ab85dc4f do not pass outdir 2015-12-14 11:42:59 -08:00
Girish Ramakrishnan 0651bfc4b8 provide cert and key file in callback 2015-12-14 09:29:48 -08:00
Girish Ramakrishnan 21b94b2655 fix debug message 2015-12-14 08:52:43 -08:00
Girish Ramakrishnan 4e40c2341a code now uses backend 2015-12-14 08:50:57 -08:00
Girish Ramakrishnan d9a83eacd2 explicitly prune out second argument 2015-12-13 20:35:23 -08:00
Girish Ramakrishnan 7b40674c0d add a backend for caas 2015-12-13 19:09:57 -08:00
Girish Ramakrishnan 936c1989f1 refactor code a bit for renewal 2015-12-13 12:26:31 -08:00
Girish Ramakrishnan cfe336c37c fix path to acme key 2015-12-13 11:54:17 -08:00
Girish Ramakrishnan d8a1e4aab0 more debug messages 2015-12-12 20:39:24 -08:00
Girish Ramakrishnan be4d2afff3 fix path to cert 2015-12-12 20:30:50 -08:00
Girish Ramakrishnan c2a4ef5f93 maybe this gets the certificate 2015-12-12 20:30:50 -08:00
Girish Ramakrishnan 22634b4ceb tlsConfig is part of the database 2015-12-12 15:43:42 -08:00
Girish Ramakrishnan abc4975b3d add tls configuration to database 2015-12-12 15:40:33 -08:00
Girish Ramakrishnan 36d81ff8d1 do not write tls config in this version 2015-12-12 14:21:50 -08:00
Girish Ramakrishnan fe94190c2f do not save certs in database 2015-12-12 13:29:10 -08:00
Girish Ramakrishnan f32027e15b Try alternative configuration for systemd restart rate limit 2015-12-12 13:15:41 -08:00
Girish Ramakrishnan 4b6a92955b configure to get only 1 email every 10 minutes 2015-12-12 11:47:32 -08:00
Girish Ramakrishnan 35a2da744c fix typo 2015-12-11 23:29:07 -08:00
Girish Ramakrishnan 9d91340223 add settings.setTlsConfig 2015-12-11 22:39:13 -08:00
Girish Ramakrishnan e0a56f75c3 typo 2015-12-11 22:27:00 -08:00
Girish Ramakrishnan 4cfd30f9e8 use tlsConfig to determine acme or not 2015-12-11 22:25:57 -08:00
Girish Ramakrishnan 3fbcbf0e5d store tls config in database 2015-12-11 22:14:56 -08:00
Girish Ramakrishnan 8b7833e8b1 fix debug namespacing 2015-12-11 21:49:24 -08:00
Girish Ramakrishnan 66441f133d fix typo 2015-12-11 20:09:16 -08:00
Girish Ramakrishnan 8a12d6019a assert assert everywhere, hope none fires! 2015-12-11 14:50:30 -08:00
Girish Ramakrishnan 39c626dc75 more moving of nginx code 2015-12-11 14:48:39 -08:00
Girish Ramakrishnan a7480c3f29 implement installation of admin certificate via acme 2015-12-11 14:37:55 -08:00
Girish Ramakrishnan 8af682acf1 add attempt 2015-12-11 14:20:37 -08:00
Girish Ramakrishnan 95eba1db81 Add certificates.ensureCertificate which gets cert via acme 2015-12-11 14:15:44 -08:00
Girish Ramakrishnan 0b8fde7d8d rename app.setAppCertificate 2015-12-11 14:13:29 -08:00
Girish Ramakrishnan 2f7517152a rename certificates.initialize 2015-12-11 14:02:58 -08:00
Girish Ramakrishnan 3e2ea0e087 refactor certificate settings 2015-12-11 13:58:43 -08:00
Girish Ramakrishnan 723556d6a2 Add CertificatesError 2015-12-11 13:43:33 -08:00
Girish Ramakrishnan 1f53d76cef wait forever by default 2015-12-11 13:41:17 -08:00
Girish Ramakrishnan d15488431b add waitfordns.js (refactored from appstore) 2015-12-11 13:14:27 -08:00
Girish Ramakrishnan cf80fd7dc5 rename certificatemanager 2015-12-11 12:24:52 -08:00
Girish Ramakrishnan 73d891b98e move validateCertificate to certificateManager 2015-12-10 20:38:49 -08:00
Girish Ramakrishnan 875ec1028d remove backward compat code now that we have migrated 2015-12-10 16:31:22 -08:00
Girish Ramakrishnan fd985c2011 configure nginx as the last step
this allow us to wait for certificate (in the case of LE)
2015-12-10 15:26:36 -08:00
Girish Ramakrishnan 47981004c9 split port reserving to separate function
this allows us to move nginx configuration to the bottom of apptask
(required for tls cert download support)
2015-12-10 15:25:15 -08:00
Girish Ramakrishnan e3f7c8f63d use fqdn to save admin certs as well 2015-12-10 14:29:54 -08:00
Girish Ramakrishnan 853db53f82 rename admin.cert/.key to {admin_fqdn}.cert/.key 2015-12-10 14:05:44 -08:00
Girish Ramakrishnan 5992c0534a remove dead comment 2015-12-10 13:56:00 -08:00
Girish Ramakrishnan 1874c93c5c no need to template main nginx config 2015-12-10 13:54:53 -08:00
Girish Ramakrishnan 3c4adb1aed fix config path 2015-12-10 13:36:44 -08:00
Girish Ramakrishnan 66db918273 add certificate manager stub 2015-12-10 13:35:02 -08:00
Girish Ramakrishnan 69845d5ddd add config.adminFqdn() 2015-12-10 13:14:13 -08:00
Girish Ramakrishnan 42181d597b keep the requires sorted 2015-12-10 13:08:38 -08:00
Girish Ramakrishnan b56e9ca745 do not log the token 2015-12-10 12:50:54 -08:00
Girish Ramakrishnan 5fc4788269 remove test code 2015-12-10 11:09:37 -08:00
Girish Ramakrishnan d0f8293b73 treat acme as a cert backend 2015-12-10 11:08:22 -08:00
Girish Ramakrishnan 44582bcd4b download the certificate as binary 2015-12-10 11:07:10 -08:00
Girish Ramakrishnan 5c73aed953 remove unused require 2015-12-10 09:54:21 -08:00
Girish Ramakrishnan e1ec48530e acme: create cert file with the chain 2015-12-10 09:11:08 -08:00
Girish Ramakrishnan 54c4053728 add LE cross signed
https://letsencrypt.org/certs/lets-encrypt-x1-cross-signed.pem.txt
2015-12-10 09:06:36 -08:00
Girish Ramakrishnan 79ffb0df5c acme: openssl does not play well with buffers. use files instead 2015-12-10 08:57:53 -08:00
Girish Ramakrishnan c510952c88 s/privateKeyPem/accountKeyPem 2015-12-09 19:23:19 -08:00
Girish Ramakrishnan 6109da531d acme: use safe 2015-12-09 19:22:53 -08:00
Girish Ramakrishnan 56877332db pull in urlBase64Encode 2015-12-09 18:34:27 -08:00
Girish Ramakrishnan 6fc972d160 set default response type to text/plain 2015-12-09 18:34:13 -08:00
Girish Ramakrishnan 5346153d9b add ursa 2015-12-09 18:33:35 -08:00
Girish Ramakrishnan aaf266d272 convert cert to pem 2015-12-08 20:05:14 -08:00
Girish Ramakrishnan 0750db9aae rename function 2015-12-08 19:54:37 -08:00
Girish Ramakrishnan 316976d295 generate the acme account key on first run 2015-12-08 19:42:33 -08:00
Girish Ramakrishnan 593b5d945b use this fake email as the account owner for now 2015-12-08 19:15:17 -08:00
Girish Ramakrishnan 88f0240757 serve acme directory from nginx 2015-12-08 19:04:48 -08:00
Girish Ramakrishnan f5c2f8849d Add LE staging url for testing 2015-12-08 18:25:45 -08:00
Girish Ramakrishnan 5c4a8f7803 add acme support
this is not used anywhere since we want to wait for rate limits to be
fixed.

The current limits are :

    Rate limit on registrations per IP is currently 10 per 3 hours
    Rate limit on certificates per Domain is currently 5 per 7 days

The domains are counted based on https://publicsuffix.org/list/ (not TLD). Like appspot.com, herokuapp.com while not a TLD, it a public suffix. This list allows browser authors to limit how cookies can be manipulated by the subdomain of those domains. like app1.appspot.com cannot go and change things of app2.appspot.com.

This means
a) we cannot use LE for cloudron.me, cloudron.us (or we have to get on that list)

b) even for custom domains we get only 5 certs every 7 days. And one of them is taken for my.xx domain.

https://community.letsencrypt.org/t/public-beta-rate-limits/4772/38
2015-12-08 15:52:30 -08:00
Girish Ramakrishnan 5b8fdad5cb Revert "remove targetBoxVersion checks since all apps are now ported"
This reverts commit d104f2a077.

gitlab is not ported :-(
2015-12-05 02:29:06 -08:00
Girish Ramakrishnan fe819f95ec always return logs regardless of state 2015-12-04 13:13:54 -08:00
Girish Ramakrishnan be6728f8cb send support an email for app crashes 2015-12-02 16:50:00 -08:00
Girish Ramakrishnan 24d3a81bc8 remove targetBoxVersion checks since all apps are now ported 2015-12-02 15:02:16 -08:00
Girish Ramakrishnan 268c7b5bcf always create an isolated network ns 2015-12-01 13:59:45 -08:00
Girish Ramakrishnan 64716a2de5 cloudron exec: disable links for subcontainers
Dec 01 08:36:53 girish.cloudron.us node[5431]: Error: HTTP code is 409 which indicates error: undefined - Conflicting options: --net=container can't be used with links. This would result in undefined behavior
2015-12-01 00:51:41 -08:00
Girish Ramakrishnan d2c8457ab1 reset health when app is stopped 2015-11-30 15:41:56 -08:00
Johannes Zellner 667cb84af7 Protect from crash on shutdown 2015-11-27 10:05:57 +01:00
Girish Ramakrishnan df8653cdd5 Do not set Hostname for subcontainers 2015-11-26 19:26:29 -08:00
Girish Ramakrishnan 32f677ca0d make app subcontainers share network namespace with app
pid namespace sharing is coming in https://github.com/docker/docker/issues/10163
2015-11-26 19:18:31 -08:00
Johannes Zellner 6f5408f0d6 Make all json blobs in db TEXT fields 2015-11-26 12:17:02 +01:00
Johannes Zellner 23c04fb10b Use console.error() to report update errors 2015-11-26 12:04:39 +01:00
Johannes Zellner 0c5d6b1045 Set app backup progress only after we check the error 2015-11-26 12:00:44 +01:00
Johannes Zellner 33f30decd1 Support redirectURIs which already contain query params 2015-11-25 17:50:39 +01:00
Johannes Zellner 9595b63939 Correctly encode the redirectURI in oauth callback 2015-11-25 17:45:18 +01:00
Johannes Zellner b9695b09cd Fix crash due to wrong AppsError usage 2015-11-25 13:49:20 +01:00
Girish Ramakrishnan 5a0f7df377 handle scheduler error 2015-11-22 21:17:17 -08:00
Girish Ramakrishnan 2e54be3df8 Revert "fix crash in scheduler"
This reverts commit 3b5e30f922.
2015-11-22 21:13:05 -08:00
Girish Ramakrishnan 6625610aca fix crash in scheduler 2015-11-22 17:22:06 -08:00
Girish Ramakrishnan 5c9abfe97a debug output the changeIds 2015-11-19 17:49:30 -08:00
Johannes Zellner e06f3d4180 Docker bridge default ip has changed 2015-11-19 16:32:03 +01:00
Girish Ramakrishnan e3cc12da4f new addon images based on docker 1.9.0 2015-11-18 17:53:58 -08:00
Johannes Zellner 3d80821203 Give correct feedback if an app cannot be found in the appstore 2015-11-13 10:35:29 +01:00
Johannes Zellner d9bfcc7c8a Change manifestJson column from VARCHAR to TEXT 2015-11-13 10:21:03 +01:00
Johannes Zellner 8bd9a6c109 Do not serve up the status page for 500 upstream errors 2015-11-13 09:39:33 +01:00
Johannes Zellner d89db24bfc Fix indentantion 2015-11-13 09:30:33 +01:00
Johannes Zellner 352b5ca736 Update supererror 2015-11-13 09:23:32 +01:00
Girish Ramakrishnan 6bd9173a9d this docker registry keeps going down 2015-11-12 16:22:53 -08:00
Girish Ramakrishnan 0cef3e1090 do not trust the health state blindly 2015-11-12 16:16:05 -08:00
Girish Ramakrishnan 6bd68961d1 typo 2015-11-12 16:13:15 -08:00
Girish Ramakrishnan 7f8ad917d9 filter out non-healthy apps 2015-11-12 16:04:33 -08:00
Girish Ramakrishnan 7cd89accaf better pullImage debug output 2015-11-12 15:58:39 -08:00
Girish Ramakrishnan ffee084d2b new format of provisioning info 2015-11-12 14:22:43 -08:00
62 changed files with 1821 additions and 997 deletions
+2 -3
View File
@@ -14,9 +14,9 @@ var appHealthMonitor = require('./src/apphealthmonitor.js'),
async = require('async'), async = require('async'),
config = require('./src/config.js'), config = require('./src/config.js'),
ldap = require('./src/ldap.js'), ldap = require('./src/ldap.js'),
simpleauth = require('./src/simpleauth.js'),
oauthproxy = require('./src/oauthproxy.js'), oauthproxy = require('./src/oauthproxy.js'),
server = require('./src/server.js'); server = require('./src/server.js'),
simpleauth = require('./src/simpleauth.js');
console.log(); console.log();
console.log('=========================================='); console.log('==========================================');
@@ -26,7 +26,6 @@ console.log();
console.log(' Environment: ', config.CLOUDRON ? 'CLOUDRON' : 'TEST'); console.log(' Environment: ', config.CLOUDRON ? 'CLOUDRON' : 'TEST');
console.log(' Version: ', config.version()); console.log(' Version: ', config.version());
console.log(' Admin Origin: ', config.adminOrigin()); console.log(' Admin Origin: ', config.adminOrigin());
console.log(' Appstore token: ', config.token());
console.log(' Appstore API server origin: ', config.apiServerOrigin()); console.log(' Appstore API server origin: ', config.apiServerOrigin());
console.log(' Appstore Web server origin: ', config.webServerOrigin()); console.log(' Appstore Web server origin: ', config.webServerOrigin());
console.log(); console.log();
@@ -0,0 +1,16 @@
dbm = dbm || require('db-migrate');
var type = dbm.dataType;
exports.up = function(db, callback) {
db.runSql('ALTER TABLE apps MODIFY manifestJson TEXT', [], function (error) {
if (error) console.error(error);
callback(error);
});
};
exports.down = function(db, callback) {
db.runSql('ALTER TABLE apps MODIFY manifestJson VARCHAR(2048)', [], function (error) {
if (error) console.error(error);
callback(error);
});
};
@@ -0,0 +1,19 @@
dbm = dbm || require('db-migrate');
var type = dbm.dataType;
var async = require('async');
exports.up = function(db, callback) {
async.series([
db.runSql.bind(db, 'ALTER TABLE apps MODIFY accessRestrictionJson TEXT'),
db.runSql.bind(db, 'ALTER TABLE apps MODIFY lastBackupConfigJson TEXT'),
db.runSql.bind(db, 'ALTER TABLE apps MODIFY oldConfigJson TEXT')
], callback);
};
exports.down = function(db, callback) {
async.series([
db.runSql.bind(db, 'ALTER TABLE apps MODIFY accessRestrictionJson VARCHAR(2048)'),
db.runSql.bind(db, 'ALTER TABLE apps MODIFY lastBackupConfigJson VARCHAR(2048)'),
db.runSql.bind(db, 'ALTER TABLE apps MODIFY oldConfigJson VARCHAR(2048)')
], callback);
};
+4 -4
View File
@@ -45,18 +45,18 @@ CREATE TABLE IF NOT EXISTS apps(
runState VARCHAR(512), runState VARCHAR(512),
health VARCHAR(128), health VARCHAR(128),
containerId VARCHAR(128), containerId VARCHAR(128),
manifestJson VARCHAR(2048), manifestJson TEXT,
httpPort INTEGER, // this is the nginx proxy port and not manifest.httpPort httpPort INTEGER, // this is the nginx proxy port and not manifest.httpPort
location VARCHAR(128) NOT NULL UNIQUE, location VARCHAR(128) NOT NULL UNIQUE,
dnsRecordId VARCHAR(512), dnsRecordId VARCHAR(512),
accessRestrictionJson VARCHAR(2048), accessRestrictionJson TEXT,
oauthProxy BOOLEAN DEFAULT 0, oauthProxy BOOLEAN DEFAULT 0,
createdAt TIMESTAMP(2) NOT NULL DEFAULT CURRENT_TIMESTAMP, createdAt TIMESTAMP(2) NOT NULL DEFAULT CURRENT_TIMESTAMP,
lastBackupId VARCHAR(128), lastBackupId VARCHAR(128),
lastBackupConfigJson VARCHAR(2048), // used for appstore and non-appstore installs. it's here so it's easy to do REST validation lastBackupConfigJson TEXT, // used for appstore and non-appstore installs. it's here so it's easy to do REST validation
oldConfigJson VARCHAR(2048), // used to pass old config for apptask oldConfigJson TEXT, // used to pass old config for apptask
PRIMARY KEY(id)); PRIMARY KEY(id));
+168 -121
View File
@@ -7,15 +7,20 @@
"from": "async@>=1.2.1 <2.0.0", "from": "async@>=1.2.1 <2.0.0",
"resolved": "https://registry.npmjs.org/async/-/async-1.5.0.tgz" "resolved": "https://registry.npmjs.org/async/-/async-1.5.0.tgz"
}, },
"attempt": {
"version": "1.0.1",
"from": "attempt@>=1.0.1 <2.0.0",
"resolved": "http://registry.npmjs.org/attempt/-/attempt-1.0.1.tgz"
},
"aws-sdk": { "aws-sdk": {
"version": "2.2.16", "version": "2.2.22",
"from": "aws-sdk@>=2.1.46 <3.0.0", "from": "aws-sdk@>=2.1.46 <3.0.0",
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.2.16.tgz", "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.2.22.tgz",
"dependencies": { "dependencies": {
"sax": { "sax": {
"version": "0.5.3", "version": "0.5.3",
"from": "sax@0.5.3", "from": "sax@0.5.3",
"resolved": "http://registry.npmjs.org/sax/-/sax-0.5.3.tgz" "resolved": "https://registry.npmjs.org/sax/-/sax-0.5.3.tgz"
}, },
"xml2js": { "xml2js": {
"version": "0.2.8", "version": "0.2.8",
@@ -34,6 +39,11 @@
"from": "body-parser@>=1.13.1 <2.0.0", "from": "body-parser@>=1.13.1 <2.0.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.14.1.tgz", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.14.1.tgz",
"dependencies": { "dependencies": {
"bytes": {
"version": "2.1.0",
"from": "bytes@2.1.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-2.1.0.tgz"
},
"content-type": { "content-type": {
"version": "1.0.1", "version": "1.0.1",
"from": "content-type@>=1.0.1 <1.1.0", "from": "content-type@>=1.0.1 <1.1.0",
@@ -84,10 +94,20 @@
"resolved": "https://registry.npmjs.org/qs/-/qs-5.1.0.tgz" "resolved": "https://registry.npmjs.org/qs/-/qs-5.1.0.tgz"
}, },
"raw-body": { "raw-body": {
"version": "2.1.4", "version": "2.1.5",
"from": "raw-body@>=2.1.4 <2.2.0", "from": "raw-body@>=2.1.4 <2.2.0",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.4.tgz", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.5.tgz",
"dependencies": { "dependencies": {
"bytes": {
"version": "2.2.0",
"from": "bytes@2.2.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-2.2.0.tgz"
},
"iconv-lite": {
"version": "0.4.13",
"from": "iconv-lite@0.4.13",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz"
},
"unpipe": { "unpipe": {
"version": "1.0.0", "version": "1.0.0",
"from": "unpipe@1.0.0", "from": "unpipe@1.0.0",
@@ -96,9 +116,9 @@
} }
}, },
"type-is": { "type-is": {
"version": "1.6.9", "version": "1.6.10",
"from": "type-is@>=1.6.9 <1.7.0", "from": "type-is@>=1.6.9 <1.7.0",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.9.tgz", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.10.tgz",
"dependencies": { "dependencies": {
"media-typer": { "media-typer": {
"version": "0.3.0", "version": "0.3.0",
@@ -106,14 +126,14 @@
"resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz"
}, },
"mime-types": { "mime-types": {
"version": "2.1.7", "version": "2.1.8",
"from": "mime-types@>=2.1.7 <2.2.0", "from": "mime-types@>=2.1.8 <2.2.0",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.7.tgz", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.8.tgz",
"dependencies": { "dependencies": {
"mime-db": { "mime-db": {
"version": "1.19.0", "version": "1.20.0",
"from": "mime-db@>=1.19.0 <1.20.0", "from": "mime-db@>=1.20.0 <1.21.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.19.0.tgz" "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.20.0.tgz"
} }
} }
} }
@@ -122,9 +142,9 @@
} }
}, },
"bytes": { "bytes": {
"version": "2.1.0", "version": "2.2.0",
"from": "bytes@>=2.1.0 <3.0.0", "from": "bytes@>=2.1.0 <3.0.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-2.1.0.tgz" "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.2.0.tgz"
}, },
"cloudron-manifestformat": { "cloudron-manifestformat": {
"version": "2.0.2", "version": "2.0.2",
@@ -145,6 +165,11 @@
"version": "1.2.7", "version": "1.2.7",
"from": "tv4@>=1.1.9 <2.0.0", "from": "tv4@>=1.1.9 <2.0.0",
"resolved": "https://registry.npmjs.org/tv4/-/tv4-1.2.7.tgz" "resolved": "https://registry.npmjs.org/tv4/-/tv4-1.2.7.tgz"
},
"validator": {
"version": "3.43.0",
"from": "validator@>=3.34.0 <4.0.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-3.43.0.tgz"
} }
} }
}, },
@@ -260,9 +285,9 @@
} }
}, },
"cron": { "cron": {
"version": "1.0.9", "version": "1.1.0",
"from": "cron@>=1.0.9 <2.0.0", "from": "cron@>=1.0.9 <2.0.0",
"resolved": "http://registry.npmjs.org/cron/-/cron-1.0.9.tgz", "resolved": "https://registry.npmjs.org/cron/-/cron-1.1.0.tgz",
"dependencies": { "dependencies": {
"moment-timezone": { "moment-timezone": {
"version": "0.3.1", "version": "0.3.1",
@@ -423,9 +448,9 @@
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.4.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.4.tgz",
"dependencies": { "dependencies": {
"core-util-is": { "core-util-is": {
"version": "1.0.1", "version": "1.0.2",
"from": "core-util-is@>=1.0.0 <1.1.0", "from": "core-util-is@>=1.0.0 <1.1.0",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz" "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz"
}, },
"inherits": { "inherits": {
"version": "2.0.1", "version": "2.0.1",
@@ -438,9 +463,9 @@
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
}, },
"process-nextick-args": { "process-nextick-args": {
"version": "1.0.3", "version": "1.0.6",
"from": "process-nextick-args@>=1.0.0 <1.1.0", "from": "process-nextick-args@>=1.0.0 <1.1.0",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.3.tgz" "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.6.tgz"
}, },
"string_decoder": { "string_decoder": {
"version": "0.10.31", "version": "0.10.31",
@@ -472,9 +497,9 @@
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.13.tgz", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.13.tgz",
"dependencies": { "dependencies": {
"core-util-is": { "core-util-is": {
"version": "1.0.1", "version": "1.0.2",
"from": "core-util-is@>=1.0.0 <1.1.0", "from": "core-util-is@>=1.0.0 <1.1.0",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz" "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz"
}, },
"isarray": { "isarray": {
"version": "0.0.1", "version": "0.0.1",
@@ -1325,25 +1350,15 @@
} }
}, },
"dockerode": { "dockerode": {
"version": "2.2.3", "version": "2.2.7",
"from": "dockerode@>=2.2.2 <3.0.0", "from": "dockerode@>=2.2.2 <3.0.0",
"resolved": "https://registry.npmjs.org/dockerode/-/dockerode-2.2.3.tgz", "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-2.2.7.tgz",
"dependencies": { "dependencies": {
"docker-modem": { "docker-modem": {
"version": "0.2.6", "version": "0.2.8",
"from": "docker-modem@>=0.2.0 <0.3.0", "from": "docker-modem@>=0.2.0 <0.3.0",
"resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-0.2.6.tgz", "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-0.2.8.tgz",
"dependencies": { "dependencies": {
"debug": {
"version": "0.7.4",
"from": "debug@>=0.7.4 <0.8.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz"
},
"follow-redirects": {
"version": "0.0.3",
"from": "follow-redirects@0.0.3",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-0.0.3.tgz"
},
"JSONStream": { "JSONStream": {
"version": "0.10.0", "version": "0.10.0",
"from": "JSONStream@0.10.0", "from": "JSONStream@0.10.0",
@@ -1361,6 +1376,16 @@
} }
} }
}, },
"debug": {
"version": "0.7.4",
"from": "debug@>=0.7.4 <0.8.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz"
},
"follow-redirects": {
"version": "0.0.3",
"from": "follow-redirects@0.0.3",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-0.0.3.tgz"
},
"querystring": { "querystring": {
"version": "0.2.0", "version": "0.2.0",
"from": "querystring@0.2.0", "from": "querystring@0.2.0",
@@ -1372,9 +1397,9 @@
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz",
"dependencies": { "dependencies": {
"core-util-is": { "core-util-is": {
"version": "1.0.1", "version": "1.0.2",
"from": "core-util-is@>=1.0.0 <1.1.0", "from": "core-util-is@>=1.0.0 <1.1.0",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz" "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz"
}, },
"isarray": { "isarray": {
"version": "0.0.1", "version": "0.0.1",
@@ -1384,7 +1409,7 @@
"string_decoder": { "string_decoder": {
"version": "0.10.31", "version": "0.10.31",
"from": "string_decoder@>=0.10.0 <0.11.0", "from": "string_decoder@>=0.10.0 <0.11.0",
"resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
}, },
"inherits": { "inherits": {
"version": "2.0.1", "version": "2.0.1",
@@ -1392,6 +1417,11 @@
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
} }
} }
},
"split-ca": {
"version": "1.0.0",
"from": "split-ca@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.0.tgz"
} }
} }
} }
@@ -1450,9 +1480,9 @@
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz",
"dependencies": { "dependencies": {
"lru-cache": { "lru-cache": {
"version": "2.7.0", "version": "2.7.3",
"from": "lru-cache@>=2.0.0 <3.0.0", "from": "lru-cache@>=2.0.0 <3.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.0.tgz" "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz"
}, },
"sigmund": { "sigmund": {
"version": "1.0.1", "version": "1.0.1",
@@ -1476,14 +1506,14 @@
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.2.13.tgz", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.2.13.tgz",
"dependencies": { "dependencies": {
"mime-types": { "mime-types": {
"version": "2.1.7", "version": "2.1.8",
"from": "mime-types@>=2.1.6 <2.2.0", "from": "mime-types@>=2.1.6 <2.2.0",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.7.tgz", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.8.tgz",
"dependencies": { "dependencies": {
"mime-db": { "mime-db": {
"version": "1.19.0", "version": "1.20.0",
"from": "mime-db@>=1.19.0 <1.20.0", "from": "mime-db@>=1.20.0 <1.21.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.19.0.tgz" "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.20.0.tgz"
} }
} }
}, },
@@ -1584,9 +1614,9 @@
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz" "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz"
}, },
"proxy-addr": { "proxy-addr": {
"version": "1.0.8", "version": "1.0.10",
"from": "proxy-addr@>=1.0.8 <1.1.0", "from": "proxy-addr@>=1.0.8 <1.1.0",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.0.8.tgz", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.0.10.tgz",
"dependencies": { "dependencies": {
"forwarded": { "forwarded": {
"version": "0.1.0", "version": "0.1.0",
@@ -1594,9 +1624,9 @@
"resolved": "http://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz" "resolved": "http://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz"
}, },
"ipaddr.js": { "ipaddr.js": {
"version": "1.0.1", "version": "1.0.5",
"from": "ipaddr.js@1.0.1", "from": "ipaddr.js@1.0.5",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.0.1.tgz" "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.0.5.tgz"
} }
} }
}, },
@@ -1650,9 +1680,9 @@
"resolved": "http://registry.npmjs.org/serve-static/-/serve-static-1.10.0.tgz" "resolved": "http://registry.npmjs.org/serve-static/-/serve-static-1.10.0.tgz"
}, },
"type-is": { "type-is": {
"version": "1.6.9", "version": "1.6.10",
"from": "type-is@>=1.6.6 <1.7.0", "from": "type-is@>=1.6.6 <1.7.0",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.9.tgz", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.10.tgz",
"dependencies": { "dependencies": {
"media-typer": { "media-typer": {
"version": "0.3.0", "version": "0.3.0",
@@ -1660,14 +1690,14 @@
"resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz"
}, },
"mime-types": { "mime-types": {
"version": "2.1.7", "version": "2.1.8",
"from": "mime-types@>=2.1.6 <2.2.0", "from": "mime-types@>=2.1.6 <2.2.0",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.7.tgz", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.8.tgz",
"dependencies": { "dependencies": {
"mime-db": { "mime-db": {
"version": "1.19.0", "version": "1.20.0",
"from": "mime-db@>=1.19.0 <1.20.0", "from": "mime-db@>=1.20.0 <1.21.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.19.0.tgz" "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.20.0.tgz"
} }
} }
} }
@@ -1919,7 +1949,7 @@
"bignumber.js": { "bignumber.js": {
"version": "2.0.7", "version": "2.0.7",
"from": "bignumber.js@2.0.7", "from": "bignumber.js@2.0.7",
"resolved": "http://registry.npmjs.org/bignumber.js/-/bignumber.js-2.0.7.tgz" "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.0.7.tgz"
}, },
"readable-stream": { "readable-stream": {
"version": "1.1.13", "version": "1.1.13",
@@ -1927,9 +1957,9 @@
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.13.tgz", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.13.tgz",
"dependencies": { "dependencies": {
"core-util-is": { "core-util-is": {
"version": "1.0.1", "version": "1.0.2",
"from": "core-util-is@>=1.0.0 <1.1.0", "from": "core-util-is@>=1.0.0 <1.1.0",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz" "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz"
}, },
"isarray": { "isarray": {
"version": "0.0.1", "version": "0.0.1",
@@ -1939,7 +1969,7 @@
"string_decoder": { "string_decoder": {
"version": "0.10.31", "version": "0.10.31",
"from": "string_decoder@>=0.10.0 <0.11.0", "from": "string_decoder@>=0.10.0 <0.11.0",
"resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
}, },
"inherits": { "inherits": {
"version": "2.0.1", "version": "2.0.1",
@@ -2001,14 +2031,14 @@
} }
}, },
"node-uuid": { "node-uuid": {
"version": "1.4.3", "version": "1.4.7",
"from": "node-uuid@>=1.4.3 <2.0.0", "from": "node-uuid@>=1.4.3 <2.0.0",
"resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.3.tgz" "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.7.tgz"
}, },
"nodemailer": { "nodemailer": {
"version": "1.9.0", "version": "1.10.0",
"from": "nodemailer@>=1.3.0 <2.0.0", "from": "nodemailer@>=1.3.0 <2.0.0",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-1.9.0.tgz", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-1.10.0.tgz",
"dependencies": { "dependencies": {
"libmime": { "libmime": {
"version": "1.2.0", "version": "1.2.0",
@@ -2139,9 +2169,9 @@
} }
}, },
"once": { "once": {
"version": "1.3.2", "version": "1.3.3",
"from": "once@>=1.3.2 <2.0.0", "from": "once@>=1.3.2 <2.0.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.3.2.tgz", "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz",
"dependencies": { "dependencies": {
"wrappy": { "wrappy": {
"version": "1.0.1", "version": "1.0.1",
@@ -2228,9 +2258,9 @@
} }
}, },
"password-generator": { "password-generator": {
"version": "1.0.1", "version": "2.0.2",
"from": "password-generator@>=1.0.0 <2.0.0", "from": "password-generator@>=2.0.2 <3.0.0",
"resolved": "https://registry.npmjs.org/password-generator/-/password-generator-1.0.1.tgz", "resolved": "https://registry.npmjs.org/password-generator/-/password-generator-2.0.2.tgz",
"dependencies": { "dependencies": {
"optimist": { "optimist": {
"version": "0.6.1", "version": "0.6.1",
@@ -2257,9 +2287,9 @@
"resolved": "https://registry.npmjs.org/proxy-middleware/-/proxy-middleware-0.13.1.tgz" "resolved": "https://registry.npmjs.org/proxy-middleware/-/proxy-middleware-0.13.1.tgz"
}, },
"safetydance": { "safetydance": {
"version": "0.0.19", "version": "0.1.0",
"from": "safetydance@0.0.19", "from": "safetydance@>=0.1.0 <0.2.0",
"resolved": "https://registry.npmjs.org/safetydance/-/safetydance-0.0.19.tgz" "resolved": "https://registry.npmjs.org/safetydance/-/safetydance-0.1.0.tgz"
}, },
"semver": { "semver": {
"version": "4.3.6", "version": "4.3.6",
@@ -2306,25 +2336,20 @@
} }
}, },
"superagent": { "superagent": {
"version": "0.21.0", "version": "1.5.0",
"from": "superagent@>=0.21.0 <0.22.0", "from": "superagent@>=1.5.0 <2.0.0",
"resolved": "https://registry.npmjs.org/superagent/-/superagent-0.21.0.tgz", "resolved": "https://registry.npmjs.org/superagent/-/superagent-1.5.0.tgz",
"dependencies": { "dependencies": {
"qs": { "qs": {
"version": "1.2.0", "version": "2.3.3",
"from": "qs@1.2.0", "from": "qs@2.3.3",
"resolved": "https://registry.npmjs.org/qs/-/qs-1.2.0.tgz" "resolved": "https://registry.npmjs.org/qs/-/qs-2.3.3.tgz"
}, },
"formidable": { "formidable": {
"version": "1.0.14", "version": "1.0.14",
"from": "formidable@1.0.14", "from": "formidable@1.0.14",
"resolved": "https://registry.npmjs.org/formidable/-/formidable-1.0.14.tgz" "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.0.14.tgz"
}, },
"mime": {
"version": "1.2.11",
"from": "mime@1.2.11",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz"
},
"component-emitter": { "component-emitter": {
"version": "1.1.2", "version": "1.1.2",
"from": "component-emitter@1.1.2", "from": "component-emitter@1.1.2",
@@ -2347,14 +2372,19 @@
}, },
"extend": { "extend": {
"version": "1.2.1", "version": "1.2.1",
"from": "extend@>=1.2.1 <1.3.0", "from": "extend@1.2.1",
"resolved": "https://registry.npmjs.org/extend/-/extend-1.2.1.tgz" "resolved": "https://registry.npmjs.org/extend/-/extend-1.2.1.tgz"
}, },
"form-data": { "form-data": {
"version": "0.1.3", "version": "0.2.0",
"from": "form-data@0.1.3", "from": "form-data@0.2.0",
"resolved": "http://registry.npmjs.org/form-data/-/form-data-0.1.3.tgz", "resolved": "https://registry.npmjs.org/form-data/-/form-data-0.2.0.tgz",
"dependencies": { "dependencies": {
"async": {
"version": "0.9.2",
"from": "async@>=0.9.0 <0.10.0",
"resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz"
},
"combined-stream": { "combined-stream": {
"version": "0.0.7", "version": "0.0.7",
"from": "combined-stream@>=0.0.4 <0.1.0", "from": "combined-stream@>=0.0.4 <0.1.0",
@@ -2367,10 +2397,17 @@
} }
} }
}, },
"async": { "mime-types": {
"version": "0.9.2", "version": "2.0.14",
"from": "async@>=0.9.0 <0.10.0", "from": "mime-types@>=2.0.3 <2.1.0",
"resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz" "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.0.14.tgz",
"dependencies": {
"mime-db": {
"version": "1.12.0",
"from": "mime-db@>=1.12.0 <1.13.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.12.0.tgz"
}
}
} }
} }
}, },
@@ -2380,9 +2417,9 @@
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.27-1.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.27-1.tgz",
"dependencies": { "dependencies": {
"core-util-is": { "core-util-is": {
"version": "1.0.1", "version": "1.0.2",
"from": "core-util-is@>=1.0.0 <1.1.0", "from": "core-util-is@>=1.0.0 <1.1.0",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz" "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz"
}, },
"isarray": { "isarray": {
"version": "0.0.1", "version": "0.0.1",
@@ -2392,7 +2429,7 @@
"string_decoder": { "string_decoder": {
"version": "0.10.31", "version": "0.10.31",
"from": "string_decoder@>=0.10.0 <0.11.0", "from": "string_decoder@>=0.10.0 <0.11.0",
"resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
}, },
"inherits": { "inherits": {
"version": "2.0.1", "version": "2.0.1",
@@ -2404,31 +2441,24 @@
} }
}, },
"supererror": { "supererror": {
"version": "0.7.0", "version": "0.7.1",
"from": "supererror@>=0.7.0 <0.8.0", "from": "supererror@>=0.7.1 <0.8.0",
"resolved": "https://registry.npmjs.org/supererror/-/supererror-0.7.0.tgz", "resolved": "https://registry.npmjs.org/supererror/-/supererror-0.7.1.tgz",
"dependencies": { "dependencies": {
"colors": { "colors": {
"version": "1.0.3", "version": "1.1.2",
"from": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", "from": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz",
"resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz" "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz"
}, },
"prettyjson": { "prettyjson": {
"version": "1.1.0", "version": "1.1.3",
"from": "https://registry.npmjs.org/prettyjson/-/prettyjson-1.1.0.tgz", "from": "https://registry.npmjs.org/prettyjson/-/prettyjson-1.1.3.tgz",
"resolved": "https://registry.npmjs.org/prettyjson/-/prettyjson-1.1.0.tgz", "resolved": "https://registry.npmjs.org/prettyjson/-/prettyjson-1.1.3.tgz"
"dependencies": { },
"colors": { "minimist": {
"version": "0.6.2", "version": "1.2.0",
"from": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", "from": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz" "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz"
},
"minimist": {
"version": "0.0.8",
"from": "minimist@0.0.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz"
}
}
} }
} }
}, },
@@ -2442,15 +2472,32 @@
"from": "underscore@>=1.7.0 <2.0.0", "from": "underscore@>=1.7.0 <2.0.0",
"resolved": "http://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz" "resolved": "http://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz"
}, },
"ursa": {
"version": "0.9.1",
"from": "ursa@>=0.9.1 <0.10.0",
"resolved": "https://registry.npmjs.org/ursa/-/ursa-0.9.1.tgz",
"dependencies": {
"bindings": {
"version": "1.2.1",
"from": "bindings@>=1.2.1 <2.0.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz"
},
"nan": {
"version": "2.1.0",
"from": "nan@>=2.0.9 <3.0.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.1.0.tgz"
}
}
},
"valid-url": { "valid-url": {
"version": "1.0.9", "version": "1.0.9",
"from": "valid-url@>=1.0.9 <2.0.0", "from": "valid-url@>=1.0.9 <2.0.0",
"resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz" "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz"
}, },
"validator": { "validator": {
"version": "3.43.0", "version": "4.4.0",
"from": "validator@>=3.30.0 <4.0.0", "from": "validator@>=4.4.0 <5.0.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-3.43.0.tgz" "resolved": "https://registry.npmjs.org/validator/-/validator-4.4.0.tgz"
}, },
"x509": { "x509": {
"version": "0.2.3", "version": "0.2.3",
+9 -7
View File
@@ -14,6 +14,7 @@
], ],
"dependencies": { "dependencies": {
"async": "^1.2.1", "async": "^1.2.1",
"attempt": "^1.0.1",
"aws-sdk": "^2.1.46", "aws-sdk": "^2.1.46",
"body-parser": "^1.13.1", "body-parser": "^1.13.1",
"bytes": "^2.1.0", "bytes": "^2.1.0",
@@ -51,18 +52,19 @@
"passport-http-bearer": "^1.0.1", "passport-http-bearer": "^1.0.1",
"passport-local": "^1.0.0", "passport-local": "^1.0.0",
"passport-oauth2-client-password": "^0.1.2", "passport-oauth2-client-password": "^0.1.2",
"password-generator": "^1.0.0", "password-generator": "^2.0.2",
"proxy-middleware": "^0.13.0", "proxy-middleware": "^0.13.0",
"safetydance": "0.0.19", "safetydance": "^0.1.0",
"semver": "^4.3.6", "semver": "^4.3.6",
"serve-favicon": "^2.2.0", "serve-favicon": "^2.2.0",
"split": "^1.0.0", "split": "^1.0.0",
"superagent": "~0.21.0", "superagent": "^1.5.0",
"supererror": "^0.7.0", "supererror": "^0.7.1",
"tail-stream": "https://registry.npmjs.org/tail-stream/-/tail-stream-0.2.1.tgz", "tail-stream": "https://registry.npmjs.org/tail-stream/-/tail-stream-0.2.1.tgz",
"underscore": "^1.7.0", "underscore": "^1.7.0",
"ursa": "^0.9.1",
"valid-url": "^1.0.9", "valid-url": "^1.0.9",
"validator": "^3.30.0", "validator": "^4.4.0",
"x509": "^0.2.2" "x509": "^0.2.2"
}, },
"devDependencies": { "devDependencies": {
@@ -83,9 +85,9 @@
"istanbul": "*", "istanbul": "*",
"js2xmlparser": "^1.0.0", "js2xmlparser": "^1.0.0",
"mocha": "*", "mocha": "*",
"nock": "^2.6.0", "nock": "^3.4.0",
"node-sass": "^3.0.0-alpha.0", "node-sass": "^3.0.0-alpha.0",
"redis": "^0.12.1", "redis": "^2.4.2",
"request": "^2.65.0", "request": "^2.65.0",
"sinon": "^1.12.2", "sinon": "^1.12.2",
"yargs": "^3.15.0" "yargs": "^3.15.0"
+8 -8
View File
@@ -3,17 +3,17 @@
# If you change the infra version, be sure to put a warning # If you change the infra version, be sure to put a warning
# in the change log # in the change log
INFRA_VERSION=20 INFRA_VERSION=21
# WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING # WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
# These constants are used in the installer script as well # These constants are used in the installer script as well
BASE_IMAGE=cloudron/base:0.7.0 BASE_IMAGE=cloudron/base:0.8.0
MYSQL_IMAGE=cloudron/mysql:0.7.0 MYSQL_IMAGE=cloudron/mysql:0.8.0
POSTGRESQL_IMAGE=cloudron/postgresql:0.7.0 POSTGRESQL_IMAGE=cloudron/postgresql:0.8.0
MONGODB_IMAGE=cloudron/mongodb:0.7.0 MONGODB_IMAGE=cloudron/mongodb:0.8.0
REDIS_IMAGE=cloudron/redis:0.7.0 # if you change this, fix src/addons.js as well REDIS_IMAGE=cloudron/redis:0.8.0 # if you change this, fix src/addons.js as well
MAIL_IMAGE=cloudron/mail:0.8.0 MAIL_IMAGE=cloudron/mail:0.9.0
GRAPHITE_IMAGE=cloudron/graphite:0.7.0 GRAPHITE_IMAGE=cloudron/graphite:0.8.0
MYSQL_REPO=cloudron/mysql MYSQL_REPO=cloudron/mysql
POSTGRESQL_REPO=cloudron/postgresql POSTGRESQL_REPO=cloudron/postgresql
+5
View File
@@ -11,6 +11,7 @@ arg_is_custom_domain="false"
arg_restore_key="" arg_restore_key=""
arg_restore_url="" arg_restore_url=""
arg_retire="false" arg_retire="false"
arg_tls_config=""
arg_tls_cert="" arg_tls_cert=""
arg_tls_key="" arg_tls_key=""
arg_token="" arg_token=""
@@ -37,6 +38,9 @@ EOF
arg_tls_cert=$(echo "$2" | $json tlsCert) arg_tls_cert=$(echo "$2" | $json tlsCert)
arg_tls_key=$(echo "$2" | $json tlsKey) arg_tls_key=$(echo "$2" | $json tlsKey)
arg_tls_config=$(echo "$2" | $json tlsConfig)
[[ "${arg_tls_config}" == "null" ]] && arg_tls_config=""
arg_restore_url=$(echo "$2" | $json restore.url) arg_restore_url=$(echo "$2" | $json restore.url)
[[ "${arg_restore_url}" == "null" ]] && arg_restore_url="" [[ "${arg_restore_url}" == "null" ]] && arg_restore_url=""
@@ -66,5 +70,6 @@ echo "restore url: ${arg_restore_url}"
echo "tls cert: ${arg_tls_cert}" echo "tls cert: ${arg_tls_cert}"
echo "tls key: ${arg_tls_key}" echo "tls key: ${arg_tls_key}"
echo "token: ${arg_token}" echo "token: ${arg_token}"
echo "tlsConfig: ${arg_tls_config}"
echo "version: ${arg_version}" echo "version: ${arg_version}"
echo "web server: ${arg_web_server_origin}" echo "web server: ${arg_web_server_origin}"
+2
View File
@@ -14,4 +14,6 @@ User=yellowtent
Group=yellowtent Group=yellowtent
MemoryLimit=200M MemoryLimit=200M
TimeoutStopSec=5s TimeoutStopSec=5s
StartLimitInterval=1
StartLimitBurst=60
+17 -11
View File
@@ -40,6 +40,7 @@ set_progress "10" "Ensuring directories"
mkdir -p "${DATA_DIR}/box/appicons" mkdir -p "${DATA_DIR}/box/appicons"
mkdir -p "${DATA_DIR}/box/certs" mkdir -p "${DATA_DIR}/box/certs"
mkdir -p "${DATA_DIR}/box/mail" mkdir -p "${DATA_DIR}/box/mail"
mkdir -p "${DATA_DIR}/box/acme" # acme keys
mkdir -p "${DATA_DIR}/graphite" mkdir -p "${DATA_DIR}/graphite"
mkdir -p "${DATA_DIR}/mysql" mkdir -p "${DATA_DIR}/mysql"
@@ -48,6 +49,7 @@ mkdir -p "${DATA_DIR}/mongodb"
mkdir -p "${DATA_DIR}/snapshots" mkdir -p "${DATA_DIR}/snapshots"
mkdir -p "${DATA_DIR}/addons" mkdir -p "${DATA_DIR}/addons"
mkdir -p "${DATA_DIR}/collectd/collectd.conf.d" mkdir -p "${DATA_DIR}/collectd/collectd.conf.d"
mkdir -p "${DATA_DIR}/acme" # acme challenges
# bookkeep the version as part of data # bookkeep the version as part of data
echo "{ \"version\": \"${arg_version}\", \"boxVersionsUrl\": \"${arg_box_versions_url}\" }" > "${DATA_DIR}/box/version" echo "{ \"version\": \"${arg_version}\", \"boxVersionsUrl\": \"${arg_box_versions_url}\" }" > "${DATA_DIR}/box/version"
@@ -97,20 +99,16 @@ ln -sfF "df-disk_by-uuid_${vda1_id}" "${DATA_DIR}/graphite/whisper/collectd/loca
service collectd restart service collectd restart
set_progress "30" "Setup nginx" set_progress "30" "Setup nginx"
# setup naked domain to use admin by default. app restoration will overwrite this config
mkdir -p "${DATA_DIR}/nginx/applications" mkdir -p "${DATA_DIR}/nginx/applications"
cp "${script_dir}/start/nginx/nginx.conf" "${DATA_DIR}/nginx/nginx.conf"
cp "${script_dir}/start/nginx/mime.types" "${DATA_DIR}/nginx/mime.types" cp "${script_dir}/start/nginx/mime.types" "${DATA_DIR}/nginx/mime.types"
# generate the main nginx config file
${BOX_SRC_DIR}/node_modules/.bin/ejs-cli -f "${script_dir}/start/nginx/nginx.ejs" \
-O "{ \"sourceDir\": \"${BOX_SRC_DIR}\" }" > "${DATA_DIR}/nginx/nginx.conf"
# generate these for update code paths as well to overwrite splash # generate these for update code paths as well to overwrite splash
admin_cert_file="cert/host.cert" admin_cert_file="${DATA_DIR}/nginx/cert/host.cert"
admin_key_file="cert/host.key" admin_key_file="${DATA_DIR}/nginx/cert/host.key"
if [[ -f "${DATA_DIR}/box/certs/admin.cert" && -f "${DATA_DIR}/box/certs/admin.key" ]]; then if [[ -f "${DATA_DIR}/box/certs/${admin_fqdn}.cert" && -f "${DATA_DIR}/box/certs/${admin_fqdn}.key" ]]; then
admin_cert_file="${DATA_DIR}/box/certs/admin.cert" admin_cert_file="${DATA_DIR}/box/certs/${admin_fqdn}.cert"
admin_key_file="${DATA_DIR}/box/certs/admin.key" admin_key_file="${DATA_DIR}/box/certs/${admin_fqdn}.key"
fi fi
${BOX_SRC_DIR}/node_modules/.bin/ejs-cli -f "${script_dir}/start/nginx/appconfig.ejs" \ ${BOX_SRC_DIR}/node_modules/.bin/ejs-cli -f "${script_dir}/start/nginx/appconfig.ejs" \
-O "{ \"vhost\": \"${admin_fqdn}\", \"adminOrigin\": \"${admin_origin}\", \"endpoint\": \"admin\", \"sourceDir\": \"${BOX_SRC_DIR}\", \"certFilePath\": \"${admin_cert_file}\", \"keyFilePath\": \"${admin_key_file}\" }" > "${DATA_DIR}/nginx/applications/admin.conf" -O "{ \"vhost\": \"${admin_fqdn}\", \"adminOrigin\": \"${admin_origin}\", \"endpoint\": \"admin\", \"sourceDir\": \"${BOX_SRC_DIR}\", \"certFilePath\": \"${admin_cert_file}\", \"keyFilePath\": \"${admin_key_file}\" }" > "${DATA_DIR}/nginx/applications/admin.conf"
@@ -125,7 +123,7 @@ else
fi fi
set_progress "33" "Changing ownership" set_progress "33" "Changing ownership"
chown "${USER}:${USER}" -R "${DATA_DIR}/box" "${DATA_DIR}/nginx" "${DATA_DIR}/collectd" "${DATA_DIR}/addons" chown "${USER}:${USER}" -R "${DATA_DIR}/box" "${DATA_DIR}/nginx" "${DATA_DIR}/collectd" "${DATA_DIR}/addons" "${DATA_DIR}/acme"
chown "${USER}:${USER}" "${DATA_DIR}" chown "${USER}:${USER}" "${DATA_DIR}"
set_progress "40" "Setting up infra" set_progress "40" "Setting up infra"
@@ -179,6 +177,14 @@ if [[ ! -z "${arg_dns_config}" ]]; then
-e "REPLACE INTO settings (name, value) VALUES (\"dns_config\", '$arg_dns_config')" box -e "REPLACE INTO settings (name, value) VALUES (\"dns_config\", '$arg_dns_config')" box
fi fi
# Add TLS Configuration
if [[ ! -z "${arg_tls_config}" ]]; then
echo "Add TLS Config"
mysql -u root -p${mysql_root_password} \
-e "REPLACE INTO settings (name, value) VALUES (\"tls_config\", '$arg_tls_config')" box
fi
# Add webadmin oauth client # Add webadmin oauth client
# The domain might have changed, therefor we have to update the record # The domain might have changed, therefor we have to update the record
# !!! This needs to be in sync with the webadmin, specifically login_callback.js # !!! This needs to be in sync with the webadmin, specifically login_callback.js
+2 -1
View File
@@ -37,7 +37,8 @@ server {
proxy_set_header Upgrade $http_upgrade; proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade; proxy_set_header Connection $connection_upgrade;
error_page 500 502 503 504 @appstatus; # only serve up the status page if we get proxy gateway errors
error_page 502 503 504 @appstatus;
location @appstatus { location @appstatus {
return 307 <%= adminOrigin %>/appstatus.html?referrer=https://$host$request_uri; return 307 <%= adminOrigin %>/appstatus.html?referrer=https://$host$request_uri;
} }
@@ -38,6 +38,12 @@ http {
deny all; deny all;
} }
# acme challenges
location /.well-known/acme-challenge/ {
default_type text/plain;
alias /home/yellowtent/data/acme/;
}
location / { location / {
# redirect everything to HTTPS # redirect everything to HTTPS
return 301 https://$host$request_uri; return 301 https://$host$request_uri;
@@ -55,7 +61,7 @@ http {
error_page 404 = @fallback; error_page 404 = @fallback;
location @fallback { location @fallback {
internal; internal;
root <%= sourceDir %>/webadmin/dist; root /home/yellowtent/box/webadmin/dist;
rewrite ^/$ /nakeddomain.html break; rewrite ^/$ /nakeddomain.html break;
} }
+6 -6
View File
@@ -325,10 +325,10 @@ function setupSimpleAuth(app, options, callback) {
if (error) return callback(error); if (error) return callback(error);
var env = [ var env = [
'SIMPLE_AUTH_SERVER=172.17.42.1', 'SIMPLE_AUTH_SERVER=172.17.0.1',
'SIMPLE_AUTH_PORT=' + config.get('simpleAuthPort'), 'SIMPLE_AUTH_PORT=' + config.get('simpleAuthPort'),
'SIMPLE_AUTH_URL=http://172.17.42.1:' + config.get('simpleAuthPort'), // obsolete, remove 'SIMPLE_AUTH_URL=http://172.17.0.1:' + config.get('simpleAuthPort'), // obsolete, remove
'SIMPLE_AUTH_ORIGIN=http://172.17.42.1:' + config.get('simpleAuthPort'), 'SIMPLE_AUTH_ORIGIN=http://172.17.0.1:' + config.get('simpleAuthPort'),
'SIMPLE_AUTH_CLIENT_ID=' + id 'SIMPLE_AUTH_CLIENT_ID=' + id
]; ];
@@ -359,9 +359,9 @@ function setupLdap(app, options, callback) {
assert.strictEqual(typeof callback, 'function'); assert.strictEqual(typeof callback, 'function');
var env = [ var env = [
'LDAP_SERVER=172.17.42.1', 'LDAP_SERVER=172.17.0.1',
'LDAP_PORT=' + config.get('ldapPort'), 'LDAP_PORT=' + config.get('ldapPort'),
'LDAP_URL=ldap://172.17.42.1:' + config.get('ldapPort'), 'LDAP_URL=ldap://172.17.0.1:' + config.get('ldapPort'),
'LDAP_USERS_BASE_DN=ou=users,dc=cloudron', 'LDAP_USERS_BASE_DN=ou=users,dc=cloudron',
'LDAP_GROUPS_BASE_DN=ou=groups,dc=cloudron', 'LDAP_GROUPS_BASE_DN=ou=groups,dc=cloudron',
'LDAP_BIND_DN=cn='+ app.id + ',ou=apps,dc=cloudron', 'LDAP_BIND_DN=cn='+ app.id + ',ou=apps,dc=cloudron',
@@ -778,7 +778,7 @@ function setupRedis(app, options, callback) {
name: 'redis-' + app.id, name: 'redis-' + app.id,
Hostname: 'redis-' + app.location, Hostname: 'redis-' + app.location,
Tty: true, Tty: true,
Image: 'cloudron/redis:0.7.0', // if you change this, fix setup/INFRA_VERSION as well Image: 'cloudron/redis:0.8.0', // if you change this, fix setup/INFRA_VERSION as well
Cmd: null, Cmd: null,
Volumes: { Volumes: {
'/tmp': {}, '/tmp': {},
+9 -3
View File
@@ -92,8 +92,10 @@ function checkAppHealth(app, callback) {
.redirects(0) .redirects(0)
.timeout(HEALTHCHECK_INTERVAL) .timeout(HEALTHCHECK_INTERVAL)
.end(function (error, res) { .end(function (error, res) {
if (error && !error.response) {
if (error || res.status >= 400) { // 2xx and 3xx are ok debugApp(app, 'not alive (network error): %s', error.message);
setHealth(app, appdb.HEALTH_UNHEALTHY, callback);
} else if (res.statusCode >= 400) { // 2xx and 3xx are ok
debugApp(app, 'not alive : %s', error || res.status); debugApp(app, 'not alive : %s', error || res.status);
setHealth(app, appdb.HEALTH_UNHEALTHY, callback); setHealth(app, appdb.HEALTH_UNHEALTHY, callback);
} else { } else {
@@ -110,7 +112,11 @@ function processApps(callback) {
async.each(apps, checkAppHealth, function (error) { async.each(apps, checkAppHealth, function (error) {
if (error) console.error(error); if (error) console.error(error);
debug('apps alive: [%s]', apps.map(function (a) { return a.location; }).join(', ')); var alive = apps
.filter(function (a) { return a.installationState === appdb.ISTATE_INSTALLED && a.runState === appdb.RSTATE_RUNNING && a.health === appdb.HEALTH_HEALTHY; })
.map(function (a) { return a.location; }).join(', ');
debug('apps alive: [%s]', alive);
callback(null); callback(null);
}); });
+8 -9
View File
@@ -48,6 +48,7 @@ var addons = require('./addons.js'),
async = require('async'), async = require('async'),
backups = require('./backups.js'), backups = require('./backups.js'),
BackupsError = require('./backups.js').BackupsError, BackupsError = require('./backups.js').BackupsError,
certificates = require('./certificates.js'),
config = require('./config.js'), config = require('./config.js'),
constants = require('./constants.js'), constants = require('./constants.js'),
DatabaseError = require('./databaseerror.js'), DatabaseError = require('./databaseerror.js'),
@@ -59,7 +60,6 @@ var addons = require('./addons.js'),
path = require('path'), path = require('path'),
paths = require('./paths.js'), paths = require('./paths.js'),
safe = require('safetydance'), safe = require('safetydance'),
settings = require('./settings.js'),
semver = require('semver'), semver = require('semver'),
shell = require('./shell.js'), shell = require('./shell.js'),
spawn = require('child_process').spawn, spawn = require('child_process').spawn,
@@ -176,7 +176,7 @@ function validatePortBindings(portBindings, tcpPorts) {
if (!Number.isInteger(portBindings[env])) return new Error(portBindings[env] + ' is not an integer'); if (!Number.isInteger(portBindings[env])) return new Error(portBindings[env] + ' is not an integer');
if (portBindings[env] <= 0 || portBindings[env] > 65535) return new Error(portBindings[env] + ' is out of range'); if (portBindings[env] <= 0 || portBindings[env] > 65535) return new Error(portBindings[env] + ' is out of range');
if (RESERVED_PORTS.indexOf(portBindings[env]) !== -1) return new AppsError(AppsError.PORT_RESERVED, + portBindings[env]); if (RESERVED_PORTS.indexOf(portBindings[env]) !== -1) return new AppsError(AppsError.PORT_RESERVED, String(portBindings[env]));
} }
// it is OK if there is no 1-1 mapping between values in manifest.tcpPorts and portBindings. missing values implies // it is OK if there is no 1-1 mapping between values in manifest.tcpPorts and portBindings. missing values implies
@@ -291,9 +291,10 @@ function purchase(appStoreId, callback) {
var url = config.apiServerOrigin() + '/api/v1/apps/' + appStoreId + '/purchase'; var url = config.apiServerOrigin() + '/api/v1/apps/' + appStoreId + '/purchase';
superagent.post(url).query({ token: config.token() }).end(function (error, res) { superagent.post(url).query({ token: config.token() }).end(function (error, res) {
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error)); if (error && !error.response) return callback(new AppsError(AppsError.EXTERNAL_ERROR, error));
if (res.status === 402) return callback(new AppsError(AppsError.BILLING_REQUIRED)); if (res.statusCode === 402) return callback(new AppsError(AppsError.BILLING_REQUIRED));
if (res.status !== 201 && res.status !== 200) return callback(new Error(util.format('App purchase failed. %s %j', res.status, res.body))); if (res.statusCode === 404) return callback(new AppsError(AppsError.NOT_FOUND));
if (res.statusCode !== 201 && res.statusCode !== 200) return callback(new Error(util.format('App purchase failed. %s %j', res.status, res.body)));
callback(null); callback(null);
}); });
@@ -339,7 +340,7 @@ function install(appId, appStoreId, manifest, location, portBindings, accessRest
} }
} }
error = settings.validateCertificate(cert, key, config.appFqdn(location)); error = certificates.validateCertificate(cert, key, config.appFqdn(location));
if (error) return callback(new AppsError(AppsError.BAD_CERTIFICATE, error.message)); if (error) return callback(new AppsError(AppsError.BAD_CERTIFICATE, error.message));
debug('Will install app with id : ' + appId); debug('Will install app with id : ' + appId);
@@ -380,7 +381,7 @@ function configure(appId, location, portBindings, accessRestriction, oauthProxy,
error = validateAccessRestriction(accessRestriction); error = validateAccessRestriction(accessRestriction);
if (error) return callback(new AppsError(AppsError.BAD_FIELD, error.message)); if (error) return callback(new AppsError(AppsError.BAD_FIELD, error.message));
error = settings.validateCertificate(cert, key, config.appFqdn(location)); error = certificates.validateCertificate(cert, key, config.appFqdn(location));
if (error) return callback(new AppsError(AppsError.BAD_CERTIFICATE, error.message)); if (error) return callback(new AppsError(AppsError.BAD_CERTIFICATE, error.message));
appdb.get(appId, function (error, app) { appdb.get(appId, function (error, app) {
@@ -496,8 +497,6 @@ function getLogs(appId, lines, follow, callback) {
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND)); if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND));
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error)); if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
if (app.installationState !== appdb.ISTATE_INSTALLED) return callback(new AppsError(AppsError.BAD_STATE, util.format('App is in %s state.', app.installationState)));
var args = [ '--output=json', '--no-pager', '--lines=' + lines ]; var args = [ '--output=json', '--no-pager', '--lines=' + lines ];
if (follow) args.push('--follow'); if (follow) args.push('--follow');
args = args.concat(appLogFilter(app)); args = args.concat(appLogFilter(app));
+36 -64
View File
@@ -9,7 +9,7 @@ exports = module.exports = {
startTask: startTask, startTask: startTask,
// exported for testing // exported for testing
_getFreePort: getFreePort, _reserveHttpPort: reserveHttpPort,
_configureNginx: configureNginx, _configureNginx: configureNginx,
_unconfigureNginx: unconfigureNginx, _unconfigureNginx: unconfigureNginx,
_createVolume: createVolume, _createVolume: createVolume,
@@ -19,7 +19,6 @@ exports = module.exports = {
_verifyManifest: verifyManifest, _verifyManifest: verifyManifest,
_registerSubdomain: registerSubdomain, _registerSubdomain: registerSubdomain,
_unregisterSubdomain: unregisterSubdomain, _unregisterSubdomain: unregisterSubdomain,
_reloadNginx: reloadNginx,
_waitForDnsPropagation: waitForDnsPropagation _waitForDnsPropagation: waitForDnsPropagation
}; };
@@ -36,6 +35,7 @@ var addons = require('./addons.js'),
apps = require('./apps.js'), apps = require('./apps.js'),
assert = require('assert'), assert = require('assert'),
async = require('async'), async = require('async'),
certificates = require('./certificates.js'),
clientdb = require('./clientdb.js'), clientdb = require('./clientdb.js'),
config = require('./config.js'), config = require('./config.js'),
database = require('./database.js'), database = require('./database.js'),
@@ -47,6 +47,7 @@ var addons = require('./addons.js'),
hat = require('hat'), hat = require('hat'),
manifestFormat = require('cloudron-manifestformat'), manifestFormat = require('cloudron-manifestformat'),
net = require('net'), net = require('net'),
nginx = require('./nginx.js'),
path = require('path'), path = require('path'),
paths = require('./paths.js'), paths = require('./paths.js'),
safe = require('safetydance'), safe = require('safetydance'),
@@ -59,9 +60,7 @@ var addons = require('./addons.js'),
uuid = require('node-uuid'), uuid = require('node-uuid'),
_ = require('underscore'); _ = require('underscore');
var NGINX_APPCONFIG_EJS = fs.readFileSync(__dirname + '/../setup/start/nginx/appconfig.ejs', { encoding: 'utf8' }), var COLLECTD_CONFIG_EJS = fs.readFileSync(__dirname + '/collectd.config.ejs', { encoding: 'utf8' }),
COLLECTD_CONFIG_EJS = fs.readFileSync(__dirname + '/collectd.config.ejs', { encoding: 'utf8' }),
RELOAD_NGINX_CMD = path.join(__dirname, 'scripts/reloadnginx.sh'),
RELOAD_COLLECTD_CMD = path.join(__dirname, 'scripts/reloadcollectd.sh'), RELOAD_COLLECTD_CMD = path.join(__dirname, 'scripts/reloadcollectd.sh'),
RMAPPDIR_CMD = path.join(__dirname, 'scripts/rmappdir.sh'), RMAPPDIR_CMD = path.join(__dirname, 'scripts/rmappdir.sh'),
CREATEAPPDIR_CMD = path.join(__dirname, 'scripts/createappdir.sh'); CREATEAPPDIR_CMD = path.join(__dirname, 'scripts/createappdir.sh');
@@ -77,66 +76,34 @@ function debugApp(app, args) {
debug(prefix + ' ' + util.format.apply(util, Array.prototype.slice.call(arguments, 1))); debug(prefix + ' ' + util.format.apply(util, Array.prototype.slice.call(arguments, 1)));
} }
// We expect conflicts to not happen despite closing the port (parallel app installs, app update does not reconfigure nginx etc) function reserveHttpPort(app, callback) {
// https://tools.ietf.org/html/rfc6056#section-3.5 says linux uses random ephemeral port allocation
function getFreePort(callback) {
var server = net.createServer(); var server = net.createServer();
server.listen(0, function () { server.listen(0, function () {
var port = server.address().port; var port = server.address().port;
server.close(function () { updateApp(app, { httpPort: port }, function (error) {
return callback(null, port); if (error) {
server.close();
return callback(error);
}
server.close(callback);
}); });
}); });
} }
function reloadNginx(callback) { function configureNginx(app, callback) {
shell.sudo('reloadNginx', [ RELOAD_NGINX_CMD ], callback); var vhost = config.appFqdn(app.location);
}
function configureNginx(app, callback) { certificates.ensureCertificate(vhost, function (error, certFilePath, keyFilePath) {
getFreePort(function (error, freePort) {
if (error) return callback(error); if (error) return callback(error);
var sourceDir = path.resolve(__dirname, '..'); nginx.configureApp(app, certFilePath, keyFilePath, callback);
var endpoint = app.oauthProxy ? 'oauthproxy' : 'app';
var vhost = config.appFqdn(app.location);
var certFilePath = safe.fs.statSync(path.join(paths.APP_CERTS_DIR, vhost + '.cert')) ? path.join(paths.APP_CERTS_DIR, vhost + '.cert') : 'cert/host.cert';
var keyFilePath = safe.fs.statSync(path.join(paths.APP_CERTS_DIR, vhost + '.key')) ? path.join(paths.APP_CERTS_DIR, vhost + '.key') : 'cert/host.key';
var data = {
sourceDir: sourceDir,
adminOrigin: config.adminOrigin(),
vhost: vhost,
port: freePort,
endpoint: endpoint,
certFilePath: certFilePath,
keyFilePath: keyFilePath
};
var nginxConf = ejs.render(NGINX_APPCONFIG_EJS, data);
var nginxConfigFilename = path.join(paths.NGINX_APPCONFIG_DIR, app.id + '.conf');
debugApp(app, 'writing config to %s', nginxConfigFilename);
if (!safe.fs.writeFileSync(nginxConfigFilename, nginxConf)) {
debugApp(app, 'Error creating nginx config : %s', safe.error.message);
return callback(safe.error);
}
async.series([
exports._reloadNginx,
updateApp.bind(null, app, { httpPort: freePort })
], callback);
}); });
} }
function unconfigureNginx(app, callback) { function unconfigureNginx(app, callback) {
var nginxConfigFilename = path.join(paths.NGINX_APPCONFIG_DIR, app.id + '.conf'); // TODO: maybe revoke the cert
if (!safe.fs.unlinkSync(nginxConfigFilename)) { nginx.unconfigureApp(app, callback);
debugApp(app, 'Error removing nginx configuration : %s', safe.error.message);
return callback(null);
}
exports._reloadNginx(callback);
} }
function createContainer(app, callback) { function createContainer(app, callback) {
@@ -234,8 +201,8 @@ function downloadIcon(app, callback) {
.get(iconUrl) .get(iconUrl)
.buffer(true) .buffer(true)
.end(function (error, res) { .end(function (error, res) {
if (error) return callback(new Error('Error downloading icon:' + error.message)); if (error && !error.response) return callback(new Error('Network error downloading icon:' + error.message));
if (res.status !== 200) return callback(null); // ignore error. this can also happen for apps installed with cloudron-cli if (res.statusCode !== 200) return callback(null); // ignore error. this can also happen for apps installed with cloudron-cli
if (!safe.fs.writeFileSync(path.join(paths.APPICONS_DIR, app.id + '.png'), res.body)) return callback(new Error('Error saving icon:' + safe.error.message)); if (!safe.fs.writeFileSync(path.join(paths.APPICONS_DIR, app.id + '.png'), res.body)) return callback(new Error('Error saving icon:' + safe.error.message));
@@ -343,6 +310,7 @@ function install(app, callback) {
// teardown for re-installs // teardown for re-installs
updateApp.bind(null, app, { installationProgress: '10, Cleaning up old install' }), updateApp.bind(null, app, { installationProgress: '10, Cleaning up old install' }),
unconfigureNginx.bind(null, app),
removeCollectdProfile.bind(null, app), removeCollectdProfile.bind(null, app),
stopApp.bind(null, app), stopApp.bind(null, app),
deleteContainers.bind(null, app), deleteContainers.bind(null, app),
@@ -351,10 +319,8 @@ function install(app, callback) {
unregisterSubdomain.bind(null, app, app.location), unregisterSubdomain.bind(null, app, app.location),
removeOAuthProxyCredentials.bind(null, app), removeOAuthProxyCredentials.bind(null, app),
// removeIcon.bind(null, app), // do not remove icon for non-appstore installs // removeIcon.bind(null, app), // do not remove icon for non-appstore installs
unconfigureNginx.bind(null, app),
updateApp.bind(null, app, { installationProgress: '15, Configure nginx' }), reserveHttpPort.bind(null, app),
configureNginx.bind(null, app),
updateApp.bind(null, app, { installationProgress: '20, Downloading icon' }), updateApp.bind(null, app, { installationProgress: '20, Downloading icon' }),
downloadIcon.bind(null, app), downloadIcon.bind(null, app),
@@ -385,6 +351,9 @@ function install(app, callback) {
updateApp.bind(null, app, { installationProgress: '90, Waiting for DNS propagation' }), updateApp.bind(null, app, { installationProgress: '90, Waiting for DNS propagation' }),
exports._waitForDnsPropagation.bind(null, app), exports._waitForDnsPropagation.bind(null, app),
updateApp.bind(null, app, { installationProgress: '95, Configure nginx' }),
configureNginx.bind(null, app),
// done! // done!
function (callback) { function (callback) {
debugApp(app, 'installed'); debugApp(app, 'installed');
@@ -429,6 +398,7 @@ function restore(app, callback) {
async.series([ async.series([
updateApp.bind(null, app, { installationProgress: '10, Cleaning up old install' }), updateApp.bind(null, app, { installationProgress: '10, Cleaning up old install' }),
unconfigureNginx.bind(null, app),
removeCollectdProfile.bind(null, app), removeCollectdProfile.bind(null, app),
stopApp.bind(null, app), stopApp.bind(null, app),
deleteContainers.bind(null, app), deleteContainers.bind(null, app),
@@ -442,10 +412,8 @@ function restore(app, callback) {
}, },
removeOAuthProxyCredentials.bind(null, app), removeOAuthProxyCredentials.bind(null, app),
removeIcon.bind(null, app), removeIcon.bind(null, app),
unconfigureNginx.bind(null, app),
updateApp.bind(null, app, { installationProgress: '30, Configuring Nginx' }), reserveHttpPort.bind(null, app),
configureNginx.bind(null, app),
updateApp.bind(null, app, { installationProgress: '40, Downloading icon' }), updateApp.bind(null, app, { installationProgress: '40, Downloading icon' }),
downloadIcon.bind(null, app), downloadIcon.bind(null, app),
@@ -476,6 +444,9 @@ function restore(app, callback) {
updateApp.bind(null, app, { installationProgress: '90, Waiting for DNS propagation' }), updateApp.bind(null, app, { installationProgress: '90, Waiting for DNS propagation' }),
exports._waitForDnsPropagation.bind(null, app), exports._waitForDnsPropagation.bind(null, app),
updateApp.bind(null, app, { installationProgress: '95, Configuring Nginx' }),
configureNginx.bind(null, app),
// done! // done!
function (callback) { function (callback) {
debugApp(app, 'restored'); debugApp(app, 'restored');
@@ -495,6 +466,7 @@ function restore(app, callback) {
function configure(app, callback) { function configure(app, callback) {
async.series([ async.series([
updateApp.bind(null, app, { installationProgress: '10, Cleaning up old install' }), updateApp.bind(null, app, { installationProgress: '10, Cleaning up old install' }),
unconfigureNginx.bind(null, app),
removeCollectdProfile.bind(null, app), removeCollectdProfile.bind(null, app),
stopApp.bind(null, app), stopApp.bind(null, app),
deleteContainers.bind(null, app), deleteContainers.bind(null, app),
@@ -504,10 +476,8 @@ function configure(app, callback) {
unregisterSubdomain(app, app.oldConfig.location, next); unregisterSubdomain(app, app.oldConfig.location, next);
}, },
removeOAuthProxyCredentials.bind(null, app), removeOAuthProxyCredentials.bind(null, app),
unconfigureNginx.bind(null, app),
updateApp.bind(null, app, { installationProgress: '25, Configuring Nginx' }), reserveHttpPort.bind(null, app),
configureNginx.bind(null, app),
updateApp.bind(null, app, { installationProgress: '30, Create OAuth proxy credentials' }), updateApp.bind(null, app, { installationProgress: '30, Create OAuth proxy credentials' }),
allocateOAuthProxyCredentials.bind(null, app), allocateOAuthProxyCredentials.bind(null, app),
@@ -530,6 +500,9 @@ function configure(app, callback) {
updateApp.bind(null, app, { installationProgress: '80, Waiting for DNS propagation' }), updateApp.bind(null, app, { installationProgress: '80, Waiting for DNS propagation' }),
exports._waitForDnsPropagation.bind(null, app), exports._waitForDnsPropagation.bind(null, app),
updateApp.bind(null, app, { installationProgress: '90, Configuring Nginx' }),
configureNginx.bind(null, app),
// done! // done!
function (callback) { function (callback) {
debugApp(app, 'configured'); debugApp(app, 'configured');
@@ -660,7 +633,7 @@ function stopApp(app, callback) {
docker.stopContainers(app.id, function (error) { docker.stopContainers(app.id, function (error) {
if (error) return callback(error); if (error) return callback(error);
updateApp(app, { runState: appdb.RSTATE_STOPPED }, callback); updateApp(app, { runState: appdb.RSTATE_STOPPED, health: null }, callback);
}); });
} }
@@ -723,4 +696,3 @@ if (require.main === module) {
}); });
}); });
} }
+414
View File
@@ -0,0 +1,414 @@
/* jslint node:true */
'use strict';
var assert = require('assert'),
async = require('async'),
config = require('../config.js'),
crypto = require('crypto'),
debug = require('debug')('box:cert/acme'),
fs = require('fs'),
path = require('path'),
paths = require('../paths.js'),
safe = require('safetydance'),
superagent = require('superagent'),
ursa = require('ursa'),
util = require('util'),
_ = require('underscore');
var CA_PROD = 'https://acme-v01.api.letsencrypt.org',
CA_STAGING = 'https://acme-staging.api.letsencrypt.org',
CA_ORIGIN = CA_PROD,
LE_AGREEMENT = 'https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf';
exports = module.exports = {
getCertificate: getCertificate
};
function AcmeError(reason, errorOrMessage) {
assert.strictEqual(typeof reason, 'string');
assert(errorOrMessage instanceof Error || typeof errorOrMessage === 'string' || typeof errorOrMessage === 'undefined');
Error.call(this);
Error.captureStackTrace(this, this.constructor);
this.name = this.constructor.name;
this.reason = reason;
if (typeof errorOrMessage === 'undefined') {
this.message = reason;
} else if (typeof errorOrMessage === 'string') {
this.message = errorOrMessage;
} else {
this.message = 'Internal error';
this.nestedError = errorOrMessage;
}
}
util.inherits(AcmeError, Error);
AcmeError.INTERNAL_ERROR = 'Internal Error';
AcmeError.EXTERNAL_ERROR = 'External Error';
AcmeError.ALREADY_EXISTS = 'Already Exists';
AcmeError.NOT_COMPLETED = 'Not Completed';
AcmeError.FORBIDDEN = 'Forbidden';
// http://jose.readthedocs.org/en/latest/
// https://www.ietf.org/proceedings/92/slides/slides-92-acme-1.pdf
// https://community.letsencrypt.org/t/list-of-client-implementations/2103
function getNonce(callback) {
superagent.get(CA_ORIGIN + '/directory', function (error, response) {
if (error) return callback(error);
if (response.statusCode !== 200) return callback(new Error('Invalid response code when fetching nonce : ' + response.statusCode));
return callback(null, response.headers['Replay-Nonce'.toLowerCase()]);
});
}
// urlsafe base64 encoding (jose)
function urlBase64Encode(string) {
return string.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}
function b64(str) {
var buf = util.isBuffer(str) ? str : new Buffer(str);
return urlBase64Encode(buf.toString('base64'));
}
function sendSignedRequest(url, accountKeyPem, payload, callback) {
assert.strictEqual(typeof url, 'string');
assert(util.isBuffer(accountKeyPem));
assert.strictEqual(typeof payload, 'string');
assert.strictEqual(typeof callback, 'function');
var privateKey = ursa.createPrivateKey(accountKeyPem);
var header = {
alg: 'RS256',
jwk: {
e: b64(privateKey.getExponent()),
kty: 'RSA',
n: b64(privateKey.getModulus())
}
};
var payload64 = b64(payload);
getNonce(function (error, nonce) {
if (error) return callback(error);
debug('sendSignedRequest: using nonce %s for url %s', nonce, url);
var protected64 = b64(JSON.stringify(_.extend({ }, header, { nonce: nonce })));
var signer = ursa.createSigner('sha256');
signer.update(protected64 + '.' + payload64, 'utf8');
var signature64 = urlBase64Encode(signer.sign(privateKey, 'base64'));
var data = {
header: header,
protected: protected64,
payload: payload64,
signature: signature64
};
superagent.post(url).set('Content-Type', 'application/x-www-form-urlencoded').send(JSON.stringify(data)).end(function (error, res) {
if (error && !error.response) return callback(error); // network errors
callback(null, res);
});
});
}
function registerUser(accountKeyPem, email, callback) {
assert(util.isBuffer(accountKeyPem));
assert.strictEqual(typeof email, 'string');
assert.strictEqual(typeof callback, 'function');
var payload = {
resource: 'new-reg',
contact: [ 'mailto:' + email ],
agreement: LE_AGREEMENT
};
debug('registerUser: %s', email);
sendSignedRequest(CA_ORIGIN + '/acme/new-reg', accountKeyPem, JSON.stringify(payload), function (error, result) {
if (error) return callback(new AcmeError(AcmeError.EXTERNAL_ERROR, 'Network error when registering user: ' + error.message));
if (result.statusCode === 409) return callback(new AcmeError(AcmeError.ALREADY_EXISTS, result.body.detail));
if (result.statusCode !== 201) return callback(new AcmeError(AcmeError.EXTERNAL_ERROR, util.format('Failed to register user. Expecting 201, got %s %s', result.statusCode, result.text)));
debug('registerUser: registered user %s', email);
callback();
});
}
function registerDomain(accountKeyPem, domain, callback) {
assert(util.isBuffer(accountKeyPem));
assert.strictEqual(typeof domain, 'string');
assert.strictEqual(typeof callback, 'function');
var payload = {
resource: 'new-authz',
identifier: {
type: 'dns',
value: domain
}
};
debug('registerDomain: %s', domain);
sendSignedRequest(CA_ORIGIN + '/acme/new-authz', accountKeyPem, JSON.stringify(payload), function (error, result) {
if (error) return callback(new AcmeError(AcmeError.EXTERNAL_ERROR, 'Network error when registering domain: ' + error.message));
if (result.statusCode === 403) return callback(new AcmeError(AcmeError.FORBIDDEN, result.body.detail));
if (result.statusCode !== 201) return callback(new AcmeError(AcmeError.EXTERNAL_ERROR, util.format('Failed to register user. Expecting 201, got %s %s', result.statusCode, result.text)));
debug('registerDomain: registered %s', domain);
callback(null, result.body);
});
}
function prepareHttpChallenge(accountKeyPem, challenge, callback) {
assert(util.isBuffer(accountKeyPem));
assert.strictEqual(typeof challenge, 'object');
assert.strictEqual(typeof callback, 'function');
debug('prepareHttpChallenge: preparing for challenge %j', challenge);
var token = challenge.token;
var privateKey = ursa.createPrivateKey(accountKeyPem);
var jwk = {
e: b64(privateKey.getExponent()),
kty: 'RSA',
n: b64(privateKey.getModulus())
};
var shasum = crypto.createHash('sha256');
shasum.update(JSON.stringify(jwk));
var thumbprint = urlBase64Encode(shasum.digest('base64'));
var keyAuthorization = token + '.' + thumbprint;
debug('prepareHttpChallenge: writing %s to %s', keyAuthorization, path.join(paths.ACME_CHALLENGES_DIR, token));
fs.writeFile(path.join(paths.ACME_CHALLENGES_DIR, token), token + '.' + thumbprint, function (error) {
if (error) return callback(new AcmeError(AcmeError.INTERNAL_ERROR, error));
callback();
});
}
function notifyChallengeReady(accountKeyPem, challenge, callback) {
assert(util.isBuffer(accountKeyPem));
assert.strictEqual(typeof challenge, 'object');
assert.strictEqual(typeof callback, 'function');
debug('notifyChallengeReady: %s was met', challenge.uri);
var keyAuthorization = fs.readFileSync(path.join(paths.ACME_CHALLENGES_DIR, challenge.token), 'utf8');
var payload = {
resource: 'challenge',
keyAuthorization: keyAuthorization
};
sendSignedRequest(challenge.uri, accountKeyPem, JSON.stringify(payload), function (error, result) {
if (error) return callback(new AcmeError(AcmeError.EXTERNAL_ERROR, 'Network error when notifying challenge: ' + error.message));
if (result.statusCode !== 202) return callback(new AcmeError(AcmeError.EXTERNAL_ERROR, util.format('Failed to notify challenge. Expecting 202, got %s %s', result.statusCode, result.text)));
callback();
});
}
function waitForChallenge(challenge, callback) {
assert.strictEqual(typeof challenge, 'object');
assert.strictEqual(typeof callback, 'function');
debug('waitingForChallenge: %j', challenge);
async.retry({ times: 10, interval: 5000 }, function (retryCallback) {
debug('waitingForChallenge: getting status');
superagent.get(challenge.uri).end(function (error, result) {
if (error && !error.response) {
debug('waitForChallenge: network error getting uri %s', challenge.uri);
return retryCallback(new AcmeError(AcmeError.EXTERNAL_ERROR, error.message)); // network error
}
if (result.statusCode !== 202) {
debug('waitForChallenge: invalid response code getting uri %s', result.statusCode);
return retryCallback(new AcmeError(AcmeError.EXTERNAL_ERROR, 'Bad response code:' + result.statusCode));
}
debug('waitForChallenge: status is "%s"', result.body.status);
if (result.body.status === 'pending') return retryCallback(new AcmeError(AcmeError.NOT_COMPLETED));
else if (result.body.status === 'valid') return retryCallback();
else return retryCallback(new AcmeError(AcmeError.EXTERNAL_ERROR, 'Unexpected status: ' + result.body.status));
});
}, function retryFinished(error) {
// async.retry will pass 'undefined' as second arg making it unusable with async.waterfall()
callback(error);
});
}
// https://community.letsencrypt.org/t/public-beta-rate-limits/4772 for rate limits
function signCertificate(accountKeyPem, domain, csrDer, callback) {
assert(util.isBuffer(accountKeyPem));
assert.strictEqual(typeof domain, 'string');
assert(util.isBuffer(csrDer));
assert.strictEqual(typeof callback, 'function');
var outdir = paths.APP_CERTS_DIR;
var payload = {
resource: 'new-cert',
csr: b64(csrDer)
};
debug('signCertificate: sending new-cert request');
sendSignedRequest(CA_ORIGIN + '/acme/new-cert', accountKeyPem, JSON.stringify(payload), function (error, result) {
if (error) return callback(new AcmeError(AcmeError.EXTERNAL_ERROR, 'Network error when signing certificate: ' + error.message));
// 429 means we reached the cert limit for this domain
if (result.statusCode !== 201) return callback(new AcmeError(AcmeError.EXTERNAL_ERROR, util.format('Failed to sign certificate. Expecting 201, got %s %s', result.statusCode, result.text)));
var certUrl = result.headers.location;
if (!certUrl) return callback(new AcmeError(AcmeError.EXTERNAL_ERROR, 'Missing location in downloadCertificate'));
safe.fs.writeFileSync(path.join(outdir, domain + '.url'), certUrl, 'utf8'); // for renewal
return callback(null, result.headers.location);
});
}
function createKeyAndCsr(domain, callback) {
assert.strictEqual(typeof domain, 'string');
assert.strictEqual(typeof callback, 'function');
var outdir = paths.APP_CERTS_DIR;
var execSync = safe.child_process.execSync;
var privateKeyFile = path.join(outdir, domain + '.key');
var key = execSync('openssl genrsa 4096');
if (!key) return callback(new AcmeError(AcmeError.INTERNAL_ERROR, safe.error));
if (!safe.fs.writeFileSync(privateKeyFile, key)) return callback(new AcmeError(AcmeError.INTERNAL_ERROR, safe.error));
debug('createKeyAndCsr: key file saved at %s', privateKeyFile);
var csrDer = execSync(util.format('openssl req -new -key %s -outform DER -subj /CN=%s', privateKeyFile, domain));
if (!csrDer) return callback(new AcmeError(AcmeError.INTERNAL_ERROR, safe.error));
var csrFile = path.join(outdir, domain + '.csr');
if (!safe.fs.writeFileSync(csrFile, csrFile)) return callback(new AcmeError(AcmeError.INTERNAL_ERROR, safe.error));
debug('createKeyAndCsr: csr file (DER) saved at %s', csrFile);
callback(null, csrDer);
}
function downloadCertificate(domain, certUrl, callback) {
assert.strictEqual(typeof domain, 'string');
assert.strictEqual(typeof certUrl, 'string');
assert.strictEqual(typeof callback, 'function');
var outdir = paths.APP_CERTS_DIR;
superagent.get(certUrl).buffer().parse(function (res, done) {
var data = [ ];
res.on('data', function(chunk) { data.push(chunk); });
res.on('end', function () { res.text = Buffer.concat(data); done(); });
}).end(function (error, result) {
if (error && !error.response) return callback(new AcmeError(AcmeError.EXTERNAL_ERROR, 'Network error when downloading certificate'));
if (result.statusCode === 202) return callback(new AcmeError(AcmeError.INTERNAL_ERROR, 'Retry not implemented yet'));
if (result.statusCode !== 200) return callback(new AcmeError(AcmeError.EXTERNAL_ERROR, util.format('Failed to get cert. Expecting 200, got %s %s', result.statusCode, result.text)));
var certificateDer = result.text;
var execSync = safe.child_process.execSync;
safe.fs.writeFileSync(path.join(outdir, domain + '.der'), certificateDer);
debug('downloadCertificate: cert der file saved');
var certificatePem = execSync('openssl x509 -inform DER -outform PEM', { input: certificateDer }); // this is really just base64 encoding with header
if (!certificatePem) return callback(new AcmeError(AcmeError.INTERNAL_ERROR, safe.error));
var chainPem = safe.fs.readFileSync(__dirname + '/lets-encrypt-x1-cross-signed.pem.txt');
if (!chainPem) return callback(new AcmeError(AcmeError.INTERNAL_ERROR, safe.error));
var certificateFile = path.join(outdir, domain + '.cert');
var fullChainPem = Buffer.concat([certificatePem, chainPem]);
if (!safe.fs.writeFileSync(certificateFile, fullChainPem)) return callback(new AcmeError(AcmeError.INTERNAL_ERROR, safe.error));
debug('downloadCertificate: cert file saved at %s', certificateFile);
callback();
});
}
function acmeFlow(domain, callback) {
assert.strictEqual(typeof domain, 'string');
assert.strictEqual(typeof callback, 'function');
// registering user with an email requires A or MX record (https://github.com/letsencrypt/boulder/issues/1197)
// we cannot use admin@fqdn because the user might not have set it up.
// we cannot use owner email because we don't have it yet (the admin cert is fetched before activation)
// one option is to update the owner email when a second cert is requested (https://github.com/ietf-wg-acme/acme/issues/30)
var email = 'admin@cloudron.io';
var accountKeyPem;
if (!fs.existsSync(paths.ACME_ACCOUNT_KEY_FILE)) {
debug('getCertificate: generating acme account key on first run');
accountKeyPem = safe.child_process.execSync('openssl genrsa 4096');
if (!accountKeyPem) return callback(new AcmeError(AcmeError.INTERNAL_ERROR, safe.error));
safe.fs.writeFileSync(paths.ACME_ACCOUNT_KEY_FILE, accountKeyPem);
} else {
debug('getCertificate: using existing acme account key');
accountKeyPem = fs.readFileSync(paths.ACME_ACCOUNT_KEY_FILE);
}
registerUser(accountKeyPem, email, function (error) {
if (error && error.reason !== AcmeError.ALREADY_EXISTS) return callback(error);
registerDomain(accountKeyPem, domain, function (error, result) {
if (error) return callback(error);
debug('acmeFlow: challenges: %j', result);
var httpChallenges = result.challenges.filter(function(x) { return x.type === 'http-01'; });
if (httpChallenges.length === 0) return callback(new AcmeError(AcmeError.EXTERNAL_ERROR, 'no http challenges'));
var challenge = httpChallenges[0];
async.waterfall([
prepareHttpChallenge.bind(null, accountKeyPem, challenge),
notifyChallengeReady.bind(null, accountKeyPem, challenge),
waitForChallenge.bind(null, challenge),
createKeyAndCsr.bind(null, domain),
signCertificate.bind(null, accountKeyPem, domain),
downloadCertificate.bind(null, domain)
], callback);
});
});
}
function getCertificate(domain, callback) {
assert.strictEqual(typeof domain, 'string');
assert.strictEqual(typeof callback, 'function');
var outdir = paths.APP_CERTS_DIR;
var certUrl = safe.fs.readFileSync(path.join(outdir, domain + '.url'), 'utf8');
var certificateGetter;
if (certUrl) {
debug('getCertificate: renewing existing cert for %s from %s', domain, certUrl);
certificateGetter = downloadCertificate.bind(null, domain, certUrl);
} else {
debug('getCertificate: start acme flow for %s', domain);
certificateGetter = acmeFlow.bind(null, domain);
}
certificateGetter(function (error) {
if (error) return callback(error);
callback(null, path.join(outdir, domain + '.cert'), path.join(outdir, domain + '.key'));
});
}
+17
View File
@@ -0,0 +1,17 @@
'use strict';
exports = module.exports = {
getCertificate: getCertificate
};
var assert = require('assert'),
debug = require('debug')('box:cert/caas.js');
function getCertificate(domain, callback) {
assert.strictEqual(typeof domain, 'string');
assert.strictEqual(typeof callback, 'function');
debug('getCertificate: using fallback certificate', domain);
return callback(null, 'cert/host.cert', 'cert/host.key');
}
@@ -0,0 +1,27 @@
-----BEGIN CERTIFICATE-----
MIIEqDCCA5CgAwIBAgIRAJgT9HUT5XULQ+dDHpceRL0wDQYJKoZIhvcNAQELBQAw
PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD
Ew5EU1QgUm9vdCBDQSBYMzAeFw0xNTEwMTkyMjMzMzZaFw0yMDEwMTkyMjMzMzZa
MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD
ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAJzTDPBa5S5Ht3JdN4OzaGMw6tc1Jhkl4b2+NfFwki+3uEtB
BaupnjUIWOyxKsRohwuj43Xk5vOnYnG6eYFgH9eRmp/z0HhncchpDpWRz/7mmelg
PEjMfspNdxIknUcbWuu57B43ABycrHunBerOSuu9QeU2mLnL/W08lmjfIypCkAyG
dGfIf6WauFJhFBM/ZemCh8vb+g5W9oaJ84U/l4avsNwa72sNlRZ9xCugZbKZBDZ1
gGusSvMbkEl4L6KWTyogJSkExnTA0DHNjzE4lRa6qDO4Q/GxH8Mwf6J5MRM9LTb4
4/zyM2q5OTHFr8SNDR1kFjOq+oQpttQLwNh9w5MCAwEAAaOCAZIwggGOMBIGA1Ud
EwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgGGMH8GCCsGAQUFBwEBBHMwcTAy
BggrBgEFBQcwAYYmaHR0cDovL2lzcmcudHJ1c3RpZC5vY3NwLmlkZW50cnVzdC5j
b20wOwYIKwYBBQUHMAKGL2h0dHA6Ly9hcHBzLmlkZW50cnVzdC5jb20vcm9vdHMv
ZHN0cm9vdGNheDMucDdjMB8GA1UdIwQYMBaAFMSnsaR7LHH62+FLkHX/xBVghYkQ
MFQGA1UdIARNMEswCAYGZ4EMAQIBMD8GCysGAQQBgt8TAQEBMDAwLgYIKwYBBQUH
AgEWImh0dHA6Ly9jcHMucm9vdC14MS5sZXRzZW5jcnlwdC5vcmcwPAYDVR0fBDUw
MzAxoC+gLYYraHR0cDovL2NybC5pZGVudHJ1c3QuY29tL0RTVFJPT1RDQVgzQ1JM
LmNybDATBgNVHR4EDDAKoQgwBoIELm1pbDAdBgNVHQ4EFgQUqEpqYwR93brm0Tm3
pkVl7/Oo7KEwDQYJKoZIhvcNAQELBQADggEBANHIIkus7+MJiZZQsY14cCoBG1hd
v0J20/FyWo5ppnfjL78S2k4s2GLRJ7iD9ZDKErndvbNFGcsW+9kKK/TnY21hp4Dd
ITv8S9ZYQ7oaoqs7HwhEMY9sibED4aXw09xrJZTC9zK1uIfW6t5dHQjuOWv+HHoW
ZnupyxpsEUlEaFb+/SCI4KCSBdAsYxAcsHYI5xxEI4LutHp6s3OT2FuO90WfdsIk
6q78OMSdn875bNjdBYAqxUp2/LEIHfDBkLoQz0hFJmwAbYahqKaLn73PAAm1X2kj
f1w8DdnkabOLGeOVcj9LQ+s67vBykx4anTjURkbqZslUEUsn2k5xeua2zUk=
-----END CERTIFICATE-----
+241
View File
@@ -0,0 +1,241 @@
/* jslint node:true */
'use strict';
var acme = require('./cert/acme.js'),
assert = require('assert'),
async = require('async'),
caas = require('./cert/caas.js'),
cloudron = require('./cloudron.js'),
config = require('./config.js'),
constants = require('./constants.js'),
debug = require('debug')('box:src/certificates'),
fs = require('fs'),
nginx = require('./nginx.js'),
path = require('path'),
paths = require('./paths.js'),
safe = require('safetydance'),
settings = require('./settings.js'),
sysinfo = require('./sysinfo.js'),
util = require('util'),
waitForDns = require('./waitfordns.js'),
x509 = require('x509');
exports = module.exports = {
installAdminCertificate: installAdminCertificate,
autoRenew: autoRenew,
setFallbackCertificate: setFallbackCertificate,
setAdminCertificate: setAdminCertificate,
CertificatesError: CertificatesError,
validateCertificate: validateCertificate,
ensureCertificate: ensureCertificate
};
var NOOP_CALLBACK = function (error) { if (error) debug(error); };
function CertificatesError(reason, errorOrMessage) {
assert.strictEqual(typeof reason, 'string');
assert(errorOrMessage instanceof Error || typeof errorOrMessage === 'string' || typeof errorOrMessage === 'undefined');
Error.call(this);
Error.captureStackTrace(this, this.constructor);
this.name = this.constructor.name;
this.reason = reason;
if (typeof errorOrMessage === 'undefined') {
this.message = reason;
} else if (typeof errorOrMessage === 'string') {
this.message = errorOrMessage;
} else {
this.message = 'Internal error';
this.nestedError = errorOrMessage;
}
}
util.inherits(CertificatesError, Error);
CertificatesError.INTERNAL_ERROR = 'Internal Error';
CertificatesError.INVALID_CERT = 'Invalid certificate';
function getApi(callback) {
settings.getTlsConfig(function (error, tlsConfig) {
if (error) return callback(error);
var api = tlsConfig.provider === 'caas' ? caas : acme;
callback(null, api);
});
}
function installAdminCertificate(callback) {
if (cloudron.isConfiguredSync()) return callback();
settings.getTlsConfig(function (error, tlsConfig) {
if (error) return callback(error);
if (tlsConfig.provider === 'caas') return callback();
waitForDns(config.adminFqdn(), sysinfo.getIp(), config.fqdn(), function (error) {
if (error) return callback(error); // this cannot happen because we retry forever
ensureCertificate(config.adminFqdn(), function (error, certFilePath, keyFilePath) {
if (error) { // currently, this can never happen
debug('Error obtaining certificate. Proceed anyway', error);
return callback();
}
nginx.configureAdmin(certFilePath, keyFilePath, callback);
});
});
});
}
function needsRenewalSync(certFilePath) {
var result = safe.child_process.execSync('openssl x509 -checkend %s -in %s', 60 * 60 * 24 * 5, certFilePath);
return result === null; // command errored
}
function autoRenew(callback) {
debug('autoRenew: Checking certificates for renewal');
callback = callback || NOOP_CALLBACK;
var filenames = safe.fs.readdirSync(paths.APP_CERTS_DIR);
if (!filenames) {
debug('autoRenew: Error getting filenames: %s', safe.error.message);
return;
}
var certs = filenames.filter(function (f) {
return f.match(/\.cert$/) !== null && needsRenewalSync(path.join(paths.APP_CERTS_DIR, f));
});
debug('autoRenew: %j needs to be renewed', certs);
getApi(function (error, api) {
if (error) return callback(error);
async.eachSeries(certs, function iterator(cert, iteratorCallback) {
var domain = cert.match(/^(.*)\.cert$/)[1];
if (domain === 'host') return iteratorCallback(); // cannot renew fallback cert
api.getCertificate(domain, function (error) {
if (error) debug('autoRenew: could not renew cert for %s', domain, error);
iteratorCallback(); // move on to next cert
});
});
});
}
// note: https://tools.ietf.org/html/rfc4346#section-7.4.2 (certificate_list) requires that the
// servers certificate appears first (and not the intermediate cert)
function validateCertificate(cert, key, fqdn) {
assert(cert === null || typeof cert === 'string');
assert(key === null || typeof key === 'string');
assert.strictEqual(typeof fqdn, 'string');
if (cert === null && key === null) return null;
if (!cert && key) return new Error('missing cert');
if (cert && !key) return new Error('missing key');
var content;
try {
content = x509.parseCert(cert);
} catch (e) {
return new Error('invalid cert: ' + e.message);
}
// check expiration
if (content.notAfter < new Date()) return new Error('cert expired');
function matchesDomain(domain) {
if (domain === fqdn) return true;
if (domain.indexOf('*') === 0 && domain.slice(2) === fqdn.slice(fqdn.indexOf('.') + 1)) return true;
return false;
}
// check domain
var domains = content.altNames.concat(content.subject.commonName);
if (!domains.some(matchesDomain)) return new Error(util.format('cert is not valid for this domain. Expecting %s in %j', fqdn, domains));
// http://httpd.apache.org/docs/2.0/ssl/ssl_faq.html#verify
var certModulus = safe.child_process.execSync('openssl x509 -noout -modulus', { encoding: 'utf8', input: cert });
var keyModulus = safe.child_process.execSync('openssl rsa -noout -modulus', { encoding: 'utf8', input: key });
if (certModulus !== keyModulus) return new Error('key does not match the cert');
return null;
}
function setFallbackCertificate(cert, key, callback) {
assert.strictEqual(typeof cert, 'string');
assert.strictEqual(typeof key, 'string');
assert.strictEqual(typeof callback, 'function');
var error = validateCertificate(cert, key, '*.' + config.fqdn());
if (error) return callback(new CertificatesError(CertificatesError.INVALID_CERT, error.message));
// backup the cert
if (!safe.fs.writeFileSync(path.join(paths.APP_CERTS_DIR, 'host.cert'), cert)) return callback(new CertificatesError(CertificatesError.INTERNAL_ERROR, safe.error.message));
if (!safe.fs.writeFileSync(path.join(paths.APP_CERTS_DIR, 'host.key'), key)) return callback(new CertificatesError(CertificatesError.INTERNAL_ERROR, safe.error.message));
// copy over fallback cert
if (!safe.fs.writeFileSync(path.join(paths.NGINX_CERT_DIR, 'host.cert'), cert)) return callback(new CertificatesError(CertificatesError.INTERNAL_ERROR, safe.error.message));
if (!safe.fs.writeFileSync(path.join(paths.NGINX_CERT_DIR, 'host.key'), key)) return callback(new CertificatesError(CertificatesError.INTERNAL_ERROR, safe.error.message));
nginx.reload(function (error) {
if (error) return callback(new CertificatesError(CertificatesError.INTERNAL_ERROR, error));
return callback(null);
});
}
function setAdminCertificate(cert, key, callback) {
assert.strictEqual(typeof cert, 'string');
assert.strictEqual(typeof key, 'string');
assert.strictEqual(typeof callback, 'function');
var vhost = config.appFqdn(constants.ADMIN_LOCATION);
var certFilePath = path.join(paths.APP_CERTS_DIR, vhost + '.cert');
var keyFilePath = path.join(paths.APP_CERTS_DIR, vhost + '.key');
var error = validateCertificate(cert, key, vhost);
if (error) return callback(new CertificatesError(CertificatesError.INVALID_CERT, error.message));
// backup the cert
if (!safe.fs.writeFileSync(certFilePath, cert)) return callback(new CertificatesError(CertificatesError.INTERNAL_ERROR, safe.error.message));
if (!safe.fs.writeFileSync(keyFilePath, key)) return callback(new CertificatesError(CertificatesError.INTERNAL_ERROR, safe.error.message));
nginx.configureAdmin(certFilePath, keyFilePath, callback);
}
function ensureCertificate(domain, callback) {
assert.strictEqual(typeof domain, 'string');
assert.strictEqual(typeof callback, 'function');
// check if user uploaded a specific cert. ideally, we should not mix user certs and automatic certs as we do here...
var userCertFilePath = path.join(paths.APP_CERTS_DIR, domain + '.cert');
var userKeyFilePath = path.join(paths.APP_CERTS_DIR, domain + '.key');
if (fs.existsSync(userCertFilePath) && fs.existsSync(userKeyFilePath)) {
debug('ensureCertificate: %s. certificate already exists at %s', domain, userKeyFilePath);
if (!needsRenewalSync(userCertFilePath)) return callback(null, userCertFilePath, userKeyFilePath);
debug('ensureCertificate: %s cert require renewal', domain);
}
getApi(function (error, api) {
if (error) return callback(error);
debug('ensureCertificate: getting certificate for %s', domain);
api.getCertificate(domain, function (error, certFilePath, keyFilePath) {
if (error) {
debug('ensureCertificate: could not get certificate. using fallback certs', error);
return callback(null, 'cert/host.cert', 'cert/host.key'); // use fallback certs
}
callback(null, certFilePath, keyFilePath);
});
});
}
+31 -20
View File
@@ -178,7 +178,7 @@ function setTimeZone(ip, callback) {
debug('setTimeZone ip:%s', ip); debug('setTimeZone ip:%s', ip);
superagent.get('http://www.telize.com/geoip/' + ip).end(function (error, result) { superagent.get('http://www.telize.com/geoip/' + ip).end(function (error, result) {
if (error || result.statusCode !== 200) { if ((error && !error.response) || result.statusCode !== 200) {
debug('Failed to get geo location', error); debug('Failed to get geo location', error);
return callback(null); return callback(null);
} }
@@ -260,8 +260,8 @@ function getCloudronDetails(callback) {
.get(config.apiServerOrigin() + '/api/v1/boxes/' + config.fqdn()) .get(config.apiServerOrigin() + '/api/v1/boxes/' + config.fqdn())
.query({ token: config.token() }) .query({ token: config.token() })
.end(function (error, result) { .end(function (error, result) {
if (error) return callback(error); if (error && !error.response) return callback(error);
if (result.status !== 200) return callback(new CloudronError(CloudronError.EXTERNAL_ERROR, util.format('%s %j', result.status, result.body))); if (result.statusCode !== 200) return callback(new CloudronError(CloudronError.EXTERNAL_ERROR, util.format('%s %j', result.status, result.body)));
gCloudronDetails = result.body.box; gCloudronDetails = result.body.box;
@@ -318,7 +318,7 @@ function sendHeartbeat() {
var url = config.apiServerOrigin() + '/api/v1/boxes/' + config.fqdn() + '/heartbeat'; var url = config.apiServerOrigin() + '/api/v1/boxes/' + config.fqdn() + '/heartbeat';
superagent.post(url).query({ token: config.token(), version: config.version() }).timeout(10000).end(function (error, result) { superagent.post(url).query({ token: config.token(), version: config.version() }).timeout(10000).end(function (error, result) {
if (error) debug('Error sending heartbeat.', error); if (error && !error.response) debug('Network error sending heartbeat.', error);
else if (result.statusCode !== 200) debug('Server responded to heartbeat with %s %s', result.statusCode, result.text); else if (result.statusCode !== 200) debug('Server responded to heartbeat with %s %s', result.statusCode, result.text);
else debug('Heartbeat sent to %s', url); else debug('Heartbeat sent to %s', url);
}); });
@@ -413,10 +413,11 @@ function addDnsRecords() {
debug('addDnsRecords: will update %j', records); debug('addDnsRecords: will update %j', records);
async.eachSeries(records, function (record, iteratorCallback) { async.mapSeries(records, function (record, iteratorCallback) {
subdomains.update(record.subdomain, record.type, record.values, iteratorCallback); subdomains.update(record.subdomain, record.type, record.values, iteratorCallback);
}, function (error) { }, function (error, changeIds) {
if (error) debug('addDnsRecords: failed to update : %s. will retry', error); if (error) debug('addDnsRecords: failed to update : %s. will retry', error);
else debug('addDnsRecords: records %j added with changeIds %j', records, changeIds);
retryCallback(error); retryCallback(error);
}); });
@@ -465,10 +466,10 @@ function migrate(size, region, callback) {
.query({ token: config.token() }) .query({ token: config.token() })
.send({ size: size, region: region, restoreKey: restoreKey }) .send({ size: size, region: region, restoreKey: restoreKey })
.end(function (error, result) { .end(function (error, result) {
if (error) return unlock(error); if (error && !error.response) return unlock(error);
if (result.status === 409) return unlock(new CloudronError(CloudronError.BAD_STATE)); if (result.statusCode === 409) return unlock(new CloudronError(CloudronError.BAD_STATE));
if (result.status === 404) return unlock(new CloudronError(CloudronError.NOT_FOUND)); if (result.statusCode === 404) return unlock(new CloudronError(CloudronError.NOT_FOUND));
if (result.status !== 202) return unlock(new CloudronError(CloudronError.EXTERNAL_ERROR, util.format('%s %j', result.status, result.body))); if (result.statusCode !== 202) return unlock(new CloudronError(CloudronError.EXTERNAL_ERROR, util.format('%s %j', result.status, result.body)));
return unlock(null); return unlock(null);
}); });
@@ -491,7 +492,7 @@ function update(boxUpdateInfo, callback) {
debug('Starting upgrade'); debug('Starting upgrade');
doUpgrade(boxUpdateInfo, function (error) { doUpgrade(boxUpdateInfo, function (error) {
if (error) { if (error) {
debug('Upgrade failed with error: %s', error); console.error('Upgrade failed with error:', error);
locker.unlock(locker.OP_BOX_UPDATE); locker.unlock(locker.OP_BOX_UPDATE);
} }
}); });
@@ -499,7 +500,7 @@ function update(boxUpdateInfo, callback) {
debug('Starting update'); debug('Starting update');
doUpdate(boxUpdateInfo, function (error) { doUpdate(boxUpdateInfo, function (error) {
if (error) { if (error) {
debug('Update failed with error: %s', error); console.error('Update failed with error:', error);
locker.unlock(locker.OP_BOX_UPDATE); locker.unlock(locker.OP_BOX_UPDATE);
} }
}); });
@@ -525,8 +526,8 @@ function doUpgrade(boxUpdateInfo, callback) {
.query({ token: config.token() }) .query({ token: config.token() })
.send({ version: boxUpdateInfo.version }) .send({ version: boxUpdateInfo.version })
.end(function (error, result) { .end(function (error, result) {
if (error) return upgradeError(new Error('Error making upgrade request: ' + error)); if (error && !error.response) return upgradeError(new Error('Network error making upgrade request: ' + error));
if (result.status !== 202) return upgradeError(new Error(util.format('Server not ready to upgrade. statusCode: %s body: %j', result.status, result.body))); if (result.statusCode !== 202) return upgradeError(new Error(util.format('Server not ready to upgrade. statusCode: %s body: %j', result.status, result.body)));
progress.set(progress.UPDATE, 10, 'Updating base system'); progress.set(progress.UPDATE, 10, 'Updating base system');
@@ -554,8 +555,8 @@ function doUpdate(boxUpdateInfo, callback) {
superagent.get(config.apiServerOrigin() + '/api/v1/boxes/' + config.fqdn() + '/sourcetarballurl') superagent.get(config.apiServerOrigin() + '/api/v1/boxes/' + config.fqdn() + '/sourcetarballurl')
.query({ token: config.token(), boxVersion: boxUpdateInfo.version }) .query({ token: config.token(), boxVersion: boxUpdateInfo.version })
.end(function (error, result) { .end(function (error, result) {
if (error) return updateError(new Error('Error fetching sourceTarballUrl: ' + error)); if (error && !error.response) return updateError(new Error('Network error fetching sourceTarballUrl: ' + error));
if (result.status !== 200) return updateError(new Error('Error fetching sourceTarballUrl status: ' + result.status)); if (result.statusCode !== 200) return updateError(new Error('Error fetching sourceTarballUrl status: ' + result.statusCode));
if (!safe.query(result, 'body.url')) return updateError(new Error('Error fetching sourceTarballUrl response: ' + JSON.stringify(result.body))); if (!safe.query(result, 'body.url')) return updateError(new Error('Error fetching sourceTarballUrl response: ' + JSON.stringify(result.body)));
// NOTE: the args here are tied to the installer revision, box code and appstore provisioning logic // NOTE: the args here are tied to the installer revision, box code and appstore provisioning logic
@@ -572,6 +573,16 @@ function doUpdate(boxUpdateInfo, callback) {
tlsKey: fs.readFileSync(path.join(paths.NGINX_CERT_DIR, 'host.key'), 'utf8'), tlsKey: fs.readFileSync(path.join(paths.NGINX_CERT_DIR, 'host.key'), 'utf8'),
isCustomDomain: config.isCustomDomain(), isCustomDomain: config.isCustomDomain(),
appstore: {
token: config.token(),
apiServerOrigin: config.apiServerOrigin()
},
caas: {
token: config.token(),
apiServerOrigin: config.apiServerOrigin(),
webServerOrigin: config.webServerOrigin()
},
version: boxUpdateInfo.version, version: boxUpdateInfo.version,
boxVersionsUrl: config.get('boxVersionsUrl') boxVersionsUrl: config.get('boxVersionsUrl')
} }
@@ -580,8 +591,8 @@ function doUpdate(boxUpdateInfo, callback) {
debug('updating box %j', args); debug('updating box %j', args);
superagent.post(INSTALLER_UPDATE_URL).send(args).end(function (error, result) { superagent.post(INSTALLER_UPDATE_URL).send(args).end(function (error, result) {
if (error) return updateError(error); if (error && !error.response) return updateError(error);
if (result.status !== 202) return updateError(new Error('Error initiating update: ' + JSON.stringify(result.body))); if (result.statusCode !== 202) return updateError(new Error('Error initiating update: ' + JSON.stringify(result.body)));
progress.set(progress.UPDATE, 10, 'Updating cloudron software'); progress.set(progress.UPDATE, 10, 'Updating cloudron software');
@@ -684,13 +695,13 @@ function backupBoxAndApps(callback) {
++processed; ++processed;
apps.backupApp(app, app.manifest.addons, function (error, backupId) { apps.backupApp(app, app.manifest.addons, function (error, backupId) {
progress.set(progress.BACKUP, step * processed, 'Backed up app at ' + app.location);
if (error && error.reason !== AppsError.BAD_STATE) { if (error && error.reason !== AppsError.BAD_STATE) {
debugApp(app, 'Unable to backup', error); debugApp(app, 'Unable to backup', error);
return iteratorCallback(error); return iteratorCallback(error);
} }
progress.set(progress.BACKUP, step * processed, 'Backed up app at ' + app.location);
iteratorCallback(null, backupId || null); // clear backupId if is in BAD_STATE and never backed up iteratorCallback(null, backupId || null); // clear backupId if is in BAD_STATE and never backed up
}); });
}, function appsBackedUp(error, backupIds) { }, function appsBackedUp(error, backupIds) {
+3 -3
View File
@@ -1,6 +1,6 @@
LoadPlugin "table" LoadPlugin "table"
<Plugin table> <Plugin table>
<Table "/sys/fs/cgroup/memory/system.slice/docker-<%= containerId %>.scope/memory.stat"> <Table "/sys/fs/cgroup/memory/system.slice/docker.service/docker/<%= containerId %>/memory.stat">
Instance "<%= appId %>-memory" Instance "<%= appId %>-memory"
Separator " \\n" Separator " \\n"
<Result> <Result>
@@ -10,7 +10,7 @@ LoadPlugin "table"
</Result> </Result>
</Table> </Table>
<Table "/sys/fs/cgroup/memory/system.slice/docker-<%= containerId %>.scope/memory.max_usage_in_bytes"> <Table "/sys/fs/cgroup/memory/system.slice/docker.service/docker/<%= containerId %>/memory.max_usage_in_bytes">
Instance "<%= appId %>-memory" Instance "<%= appId %>-memory"
Separator "\\n" Separator "\\n"
<Result> <Result>
@@ -20,7 +20,7 @@ LoadPlugin "table"
</Result> </Result>
</Table> </Table>
<Table "/sys/fs/cgroup/cpuacct/system.slice/docker-<%= containerId %>.scope/cpuacct.stat"> <Table "/sys/fs/cgroup/cpuacct/docker/<%= containerId %>/cpuacct.stat">
Instance "<%= appId %>-cpu" Instance "<%= appId %>-cpu"
Separator " \\n" Separator " \\n"
<Result> <Result>
+5
View File
@@ -28,6 +28,7 @@ exports = module.exports = {
// these values are derived // these values are derived
adminOrigin: adminOrigin, adminOrigin: adminOrigin,
internalAdminOrigin: internalAdminOrigin, internalAdminOrigin: internalAdminOrigin,
adminFqdn: adminFqdn,
appFqdn: appFqdn, appFqdn: appFqdn,
zoneName: zoneName, zoneName: zoneName,
@@ -155,6 +156,10 @@ function appFqdn(location) {
return isCustomDomain() ? location + '.' + fqdn() : location + '-' + fqdn(); return isCustomDomain() ? location + '.' + fqdn() : location + '-' + fqdn();
} }
function adminFqdn() {
return appFqdn(constants.ADMIN_LOCATION);
}
function adminOrigin() { function adminOrigin() {
return 'https://' + appFqdn(constants.ADMIN_LOCATION); return 'https://' + appFqdn(constants.ADMIN_LOCATION);
} }
+14 -1
View File
@@ -7,6 +7,7 @@ exports = module.exports = {
var apps = require('./apps.js'), var apps = require('./apps.js'),
assert = require('assert'), assert = require('assert'),
certificates = require('./certificates.js'),
cloudron = require('./cloudron.js'), cloudron = require('./cloudron.js'),
config = require('./config.js'), config = require('./config.js'),
CronJob = require('cron').CronJob, CronJob = require('cron').CronJob,
@@ -23,7 +24,8 @@ var gAutoupdaterJob = null,
gBackupJob = null, gBackupJob = null,
gCleanupTokensJob = null, gCleanupTokensJob = null,
gDockerVolumeCleanerJob = null, gDockerVolumeCleanerJob = null,
gSchedulerSyncJob = null; gSchedulerSyncJob = null,
gCertificateRenewJob = null;
var NOOP_CALLBACK = function (error) { if (error) console.error(error); }; var NOOP_CALLBACK = function (error) { if (error) console.error(error); };
@@ -107,6 +109,14 @@ function recreateJobs(unusedTimeZone, callback) {
timeZone: allSettings[settings.TIME_ZONE_KEY] timeZone: allSettings[settings.TIME_ZONE_KEY]
}); });
if (gCertificateRenewJob) gCertificateRenewJob.stop();
gCertificateRenewJob = new CronJob({
cronTime: '00 00 */12 * * *', // every 12 hours
onTick: certificates.autoRenew,
start: true,
timeZone: allSettings[settings.TIME_ZONE_KEY]
});
settings.events.removeListener(settings.AUTOUPDATE_PATTERN_KEY, autoupdatePatternChanged); settings.events.removeListener(settings.AUTOUPDATE_PATTERN_KEY, autoupdatePatternChanged);
settings.events.on(settings.AUTOUPDATE_PATTERN_KEY, autoupdatePatternChanged); settings.events.on(settings.AUTOUPDATE_PATTERN_KEY, autoupdatePatternChanged);
autoupdatePatternChanged(allSettings[settings.AUTOUPDATE_PATTERN_KEY]); autoupdatePatternChanged(allSettings[settings.AUTOUPDATE_PATTERN_KEY]);
@@ -179,5 +189,8 @@ function uninitialize(callback) {
if (gSchedulerSyncJob) gSchedulerSyncJob.stop(); if (gSchedulerSyncJob) gSchedulerSyncJob.stop();
gSchedulerSyncJob = null; gSchedulerSyncJob = null;
if (gCertificateRenewJob) gCertificateRenewJob.stop();
gCertificateRenewJob = null;
callback(); callback();
} }
+2
View File
@@ -126,6 +126,8 @@ function clear(callback) {
function beginTransaction(callback) { function beginTransaction(callback) {
assert.strictEqual(typeof callback, 'function'); assert.strictEqual(typeof callback, 'function');
if (gConnectionPool === null) return callback(new Error('No database connection pool.'));
gConnectionPool.getConnection(function (error, connection) { gConnectionPool.getConnection(function (error, connection) {
if (error) return callback(error); if (error) return callback(error);
+3 -2
View File
@@ -38,6 +38,7 @@ function DeveloperError(reason, errorOrMessage) {
} }
util.inherits(DeveloperError, Error); util.inherits(DeveloperError, Error);
DeveloperError.INTERNAL_ERROR = 'Internal Error'; DeveloperError.INTERNAL_ERROR = 'Internal Error';
DeveloperError.EXTERNAL_ERROR = 'External Error';
function enabled(callback) { function enabled(callback) {
assert.strictEqual(typeof callback, 'function'); assert.strictEqual(typeof callback, 'function');
@@ -77,8 +78,8 @@ function getNonApprovedApps(callback) {
var url = config.apiServerOrigin() + '/api/v1/boxes/' + config.fqdn() + '/apps'; var url = config.apiServerOrigin() + '/api/v1/boxes/' + config.fqdn() + '/apps';
superagent.get(url).query({ token: config.token(), boxVersion: config.version() }).end(function (error, result) { superagent.get(url).query({ token: config.token(), boxVersion: config.version() }).end(function (error, result) {
if (error) return callback(new DeveloperError(DeveloperError.INTERNAL_ERROR, error)); if (error && !error.response) return callback(new DeveloperError(DeveloperError.EXTERNAL_ERROR, error));
if (result.status !== 200) return callback(new DeveloperError(DeveloperError.INTERNAL_ERROR, util.format('App listing failed. %s %j', result.status, result.body))); if (result.statusCode !== 200) return callback(new DeveloperError(DeveloperError.EXTERNAL_ERROR, util.format('App listing failed. %s %j', result.status, result.body)));
callback(null, result.body.apps || []); callback(null, result.body.apps || []);
}); });
+11 -11
View File
@@ -40,9 +40,9 @@ function add(dnsConfig, zoneName, subdomain, type, values, callback) {
.query({ token: dnsConfig.token }) .query({ token: dnsConfig.token })
.send(data) .send(data)
.end(function (error, result) { .end(function (error, result) {
if (error) return callback(error); if (error && !error.response) return callback(error);
if (result.status === 420) return callback(new SubdomainError(SubdomainError.STILL_BUSY)); if (result.statusCode === 420) return callback(new SubdomainError(SubdomainError.STILL_BUSY));
if (result.status !== 201) return callback(new SubdomainError(SubdomainError.EXTERNAL_ERROR, util.format('%s %j', result.status, result.body))); if (result.statusCode !== 201) return callback(new SubdomainError(SubdomainError.EXTERNAL_ERROR, util.format('%s %j', result.statusCode, result.body)));
return callback(null, result.body.changeId); return callback(null, result.body.changeId);
}); });
@@ -63,8 +63,8 @@ function get(dnsConfig, zoneName, subdomain, type, callback) {
.get(config.apiServerOrigin() + '/api/v1/domains/' + fqdn) .get(config.apiServerOrigin() + '/api/v1/domains/' + fqdn)
.query({ token: dnsConfig.token, type: type }) .query({ token: dnsConfig.token, type: type })
.end(function (error, result) { .end(function (error, result) {
if (error) return callback(error); if (error && !error.response) return callback(error);
if (result.status !== 200) return callback(new SubdomainError(SubdomainError.EXTERNAL_ERROR, util.format('%s %j', result.status, result.body))); if (result.statusCode !== 200) return callback(new SubdomainError(SubdomainError.EXTERNAL_ERROR, util.format('%s %j', result.statusCode, result.body)));
return callback(null, result.body.values); return callback(null, result.body.values);
}); });
@@ -107,10 +107,10 @@ function del(dnsConfig, zoneName, subdomain, type, values, callback) {
.query({ token: dnsConfig.token }) .query({ token: dnsConfig.token })
.send(data) .send(data)
.end(function (error, result) { .end(function (error, result) {
if (error) return callback(error); if (error && !error.response) return callback(error);
if (result.status === 420) return callback(new SubdomainError(SubdomainError.STILL_BUSY)); if (result.statusCode === 420) return callback(new SubdomainError(SubdomainError.STILL_BUSY));
if (result.status === 404) return callback(new SubdomainError(SubdomainError.NOT_FOUND)); if (result.statusCode === 404) return callback(new SubdomainError(SubdomainError.NOT_FOUND));
if (result.status !== 204) return callback(new SubdomainError(SubdomainError.EXTERNAL_ERROR, util.format('%s %j', result.status, result.body))); if (result.statusCode !== 204) return callback(new SubdomainError(SubdomainError.EXTERNAL_ERROR, util.format('%s %j', result.statusCode, result.body)));
return callback(null); return callback(null);
}); });
@@ -127,8 +127,8 @@ function getChangeStatus(dnsConfig, changeId, callback) {
.get(config.apiServerOrigin() + '/api/v1/domains/' + config.fqdn() + '/status/' + changeId) .get(config.apiServerOrigin() + '/api/v1/domains/' + config.fqdn() + '/status/' + changeId)
.query({ token: dnsConfig.token }) .query({ token: dnsConfig.token })
.end(function (error, result) { .end(function (error, result) {
if (error) return callback(error); if (error && !error.response) return callback(error);
if (result.status !== 200) return callback(new SubdomainError(SubdomainError.EXTERNAL_ERROR, util.format('%s %j', result.status, result.body))); if (result.statusCode !== 200) return callback(new SubdomainError(SubdomainError.EXTERNAL_ERROR, util.format('%s %j', result.statusCode, result.body)));
return callback(null, result.body.status); return callback(null, result.body.status);
}); });
+15 -11
View File
@@ -65,18 +65,17 @@ function pullImage(manifest, callback) {
// is emitted as a chunk // is emitted as a chunk
stream.on('data', function (chunk) { stream.on('data', function (chunk) {
var data = safe.JSON.parse(chunk) || { }; var data = safe.JSON.parse(chunk) || { };
debug('pullImage data: %j', data); debug('pullImage %s: %j', manifest.id, data);
// The information here is useless because this is per layer as opposed to per image // The information here is useless because this is per layer as opposed to per image
if (data.status) { if (data.status) {
// debugApp(app, 'progress: %s', data.status); // progressDetail { current, total }
} else if (data.error) { } else if (data.error) {
debug('pullImage error detail: %s', data.errorDetail.message); debug('pullImage error %s: %s', manifest.id, data.errorDetail.message);
} }
}); });
stream.on('end', function () { stream.on('end', function () {
debug('downloaded image %s successfully', manifest.dockerImage); debug('downloaded image %s of %s successfully', manifest.dockerImage, manifest.id);
var image = docker.getImage(manifest.dockerImage); var image = docker.getImage(manifest.dockerImage);
@@ -85,14 +84,14 @@ function pullImage(manifest, callback) {
if (!data || !data.Config) return callback(new Error('Missing Config in image:' + JSON.stringify(data, null, 4))); if (!data || !data.Config) return callback(new Error('Missing Config in image:' + JSON.stringify(data, null, 4)));
if (!data.Config.Entrypoint && !data.Config.Cmd) return callback(new Error('Only images with entry point are allowed')); if (!data.Config.Entrypoint && !data.Config.Cmd) return callback(new Error('Only images with entry point are allowed'));
debug('This image exposes ports: %j', data.Config.ExposedPorts); debug('This image of %s exposes ports: %j', manifest.id, data.Config.ExposedPorts);
callback(null); callback(null);
}); });
}); });
stream.on('error', function (error) { stream.on('error', function (error) {
debug('error pulling image %s : %j', manifest.dockerImage, error); debug('error pulling image %s of %s: %j', manifest.dockerImage, manifest.id, error);
callback(error); callback(error);
}); });
@@ -103,12 +102,12 @@ function downloadImage(manifest, callback) {
assert.strictEqual(typeof manifest, 'object'); assert.strictEqual(typeof manifest, 'object');
assert.strictEqual(typeof callback, 'function'); assert.strictEqual(typeof callback, 'function');
debug('downloadImage %s', manifest.dockerImage); debug('downloadImage %s %s', manifest.id, manifest.dockerImage);
var attempt = 1; var attempt = 1;
async.retry({ times: 5, interval: 15000 }, function (retryCallback) { async.retry({ times: 10, interval: 15000 }, function (retryCallback) {
debug('Downloading image. attempt: %s', attempt++); debug('Downloading image %s %s. attempt: %s', manifest.id, manifest.dockerImage, attempt++);
pullImage(manifest, function (error) { pullImage(manifest, function (error) {
if (error) console.error(error); if (error) console.error(error);
@@ -155,6 +154,9 @@ function createSubcontainer(app, name, cmd, options, callback) {
} }
var memoryLimit = manifest.memoryLimit || 1024 * 1024 * 200; // 200mb by default var memoryLimit = manifest.memoryLimit || 1024 * 1024 * 200; // 200mb by default
// for subcontainers, this should ideally be false. but docker does not allow network sharing if the app container is not running
// this means cloudron exec does not work
var isolatedNetworkNs = true;
addons.getEnvironment(app, function (error, addonEnv) { addons.getEnvironment(app, function (error, addonEnv) {
if (error) return callback(new Error('Error getting addon environment : ' + error)); if (error) return callback(new Error('Error getting addon environment : ' + error));
@@ -162,7 +164,8 @@ function createSubcontainer(app, name, cmd, options, callback) {
var containerOptions = { var containerOptions = {
name: name, // used for filtering logs name: name, // used for filtering logs
// do _not_ set hostname to app fqdn. doing so sets up the dns name to look up the internal docker ip. this makes curl from within container fail // do _not_ set hostname to app fqdn. doing so sets up the dns name to look up the internal docker ip. this makes curl from within container fail
Hostname: semver.gte(targetBoxVersion(app.manifest), '0.0.77') ? app.location : config.appFqdn(app.location), // for subcontainers, this should not be set because we already share the network namespace with app container
Hostname: isolatedNetworkNs ? (semver.gte(targetBoxVersion(app.manifest), '0.0.77') ? app.location : config.appFqdn(app.location)) : null,
Tty: isAppContainer, Tty: isAppContainer,
Image: app.manifest.dockerImage, Image: app.manifest.dockerImage,
Cmd: cmd, Cmd: cmd,
@@ -184,13 +187,14 @@ function createSubcontainer(app, name, cmd, options, callback) {
PortBindings: isAppContainer ? dockerPortBindings : { }, PortBindings: isAppContainer ? dockerPortBindings : { },
PublishAllPorts: false, PublishAllPorts: false,
ReadonlyRootfs: semver.gte(targetBoxVersion(app.manifest), '0.0.66'), // see also Volumes in startContainer ReadonlyRootfs: semver.gte(targetBoxVersion(app.manifest), '0.0.66'), // see also Volumes in startContainer
Links: addons.getLinksSync(app, app.manifest.addons),
RestartPolicy: { RestartPolicy: {
"Name": isAppContainer ? "always" : "no", "Name": isAppContainer ? "always" : "no",
"MaximumRetryCount": 0 "MaximumRetryCount": 0
}, },
CpuShares: 512, // relative to 1024 for system processes CpuShares: 512, // relative to 1024 for system processes
VolumesFrom: isAppContainer ? null : [ app.containerId + ":rw" ], VolumesFrom: isAppContainer ? null : [ app.containerId + ":rw" ],
NetworkMode: isolatedNetworkNs ? 'default' : ('container:' + app.containerId), // share network namespace with parent
Links: isolatedNetworkNs ? addons.getLinksSync(app, app.manifest.addons) : null, // links is redundant with --net=container
SecurityOpt: config.CLOUDRON ? [ "apparmor:docker-cloudron-app" ] : null // profile available only on cloudron SecurityOpt: config.CLOUDRON ? [ "apparmor:docker-cloudron-app" ] : null // profile available only on cloudron
} }
}; };
+1 -2
View File
@@ -5,8 +5,7 @@ Dear Admin,
The application titled '<%= title %>' that you installed at <%= appFqdn %> The application titled '<%= title %>' that you installed at <%= appFqdn %>
is not responding. is not responding.
This is most likely a problem in the application. Please report this issue to This is most likely a problem in the application.
support@cloudron.io (by forwarding this email).
You are receiving this email because you are an Admin of the Cloudron at <%= fqdn %>. You are receiving this email because you are an Admin of the Cloudron at <%= fqdn %>.
+1 -1
View File
@@ -284,7 +284,7 @@ function appDied(app) {
var mailOptions = { var mailOptions = {
from: config.get('adminEmail'), from: config.get('adminEmail'),
to: adminEmails.join(', '), to: adminEmails.concat('support@cloudron.io').join(', '),
subject: util.format('App %s is down', app.location), subject: util.format('App %s is down', app.location),
text: render('app_down.ejs', { fqdn: config.fqdn(), title: app.manifest.title, appFqdn: config.appFqdn(app.location), format: 'text' }) text: render('app_down.ejs', { fqdn: config.fqdn(), title: app.manifest.title, appFqdn: config.appFqdn(app.location), format: 'text' })
}; };
+93
View File
@@ -0,0 +1,93 @@
/* jslint node:true */
'use strict';
var assert = require('assert'),
config = require('./config.js'),
debug = require('debug')('box:src/nginx'),
ejs = require('ejs'),
fs = require('fs'),
path = require('path'),
paths = require('./paths.js'),
safe = require('safetydance'),
shell = require('./shell.js');
exports = module.exports = {
configureAdmin: configureAdmin,
configureApp: configureApp,
unconfigureApp: unconfigureApp,
reload: reload
};
var NGINX_APPCONFIG_EJS = fs.readFileSync(__dirname + '/../setup/start/nginx/appconfig.ejs', { encoding: 'utf8' }),
RELOAD_NGINX_CMD = path.join(__dirname, 'scripts/reloadnginx.sh');
function configureAdmin(certFilePath, keyFilePath, callback) {
assert.strictEqual(typeof certFilePath, 'string');
assert.strictEqual(typeof keyFilePath, 'string');
assert.strictEqual(typeof callback, 'function');
var data = {
sourceDir: path.resolve(__dirname, '..'),
adminOrigin: config.adminOrigin(),
vhost: config.adminFqdn(),
endpoint: 'admin',
certFilePath: certFilePath,
keyFilePath: keyFilePath
};
var nginxConf = ejs.render(NGINX_APPCONFIG_EJS, data);
var nginxConfigFilename = path.join(paths.NGINX_APPCONFIG_DIR, 'admin.conf');
if (!safe.fs.writeFileSync(nginxConfigFilename, nginxConf)) return callback(safe.error);
reload(callback);
}
function configureApp(app, certFilePath, keyFilePath, callback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof certFilePath, 'string');
assert.strictEqual(typeof keyFilePath, 'string');
assert.strictEqual(typeof callback, 'function');
var sourceDir = path.resolve(__dirname, '..');
var endpoint = app.oauthProxy ? 'oauthproxy' : 'app';
var vhost = config.appFqdn(app.location);
var data = {
sourceDir: sourceDir,
adminOrigin: config.adminOrigin(),
vhost: vhost,
port: app.httpPort,
endpoint: endpoint,
certFilePath: certFilePath,
keyFilePath: keyFilePath
};
var nginxConf = ejs.render(NGINX_APPCONFIG_EJS, data);
var nginxConfigFilename = path.join(paths.NGINX_APPCONFIG_DIR, app.id + '.conf');
debug('writing config for "%s" to %s', app.location, nginxConfigFilename);
if (!safe.fs.writeFileSync(nginxConfigFilename, nginxConf)) {
debug('Error creating nginx config for "%s" : %s', app.location, safe.error.message);
return callback(safe.error);
}
reload(callback);
}
function unconfigureApp(app, callback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof callback, 'function');
var nginxConfigFilename = path.join(paths.NGINX_APPCONFIG_DIR, app.id + '.conf');
if (!safe.fs.unlinkSync(nginxConfigFilename)) {
debug('Error removing nginx configuration of "%s": %s', app.location, safe.error.message);
return callback(null);
}
reload(callback);
}
function reload(callback) {
shell.sudo('reload', [ RELOAD_NGINX_CMD ], callback);
}
+2 -2
View File
@@ -35,9 +35,9 @@ args.forEach(function (arg) {
}); });
if (code && redirectURI) { if (code && redirectURI) {
window.location.href = redirectURI + '?code=' + code + (state ? '&state=' + state : ''); window.location.href = redirectURI + (redirectURI.indexOf('?') !== -1 ? '&' : '?') + 'code=' + code + (state ? '&state=' + state : '');
} else if (redirectURI && accessToken) { } else if (redirectURI && accessToken) {
window.location.href = redirectURI + '?token=' + accessToken + (state ? '&state=' + state : ''); window.location.href = redirectURI + (redirectURI.indexOf('?') !== -1 ? '&' : '?') + 'token=' + accessToken + (state ? '&state=' + state : '');
} else { } else {
window.location.href = '/api/v1/session/login'; window.location.href = '/api/v1/session/login';
} }
+1 -1
View File
@@ -92,7 +92,7 @@ function authenticate(req, res, next) {
.post(config.internalAdminOrigin() + '/api/v1/oauth/token') .post(config.internalAdminOrigin() + '/api/v1/oauth/token')
.query(query).send(data) .query(query).send(data)
.end(function (error, result) { .end(function (error, result) {
if (error) { if (error && !error.response) {
console.error(error); console.error(error);
return res.send(500, 'Unable to contact the oauth server.'); return res.send(500, 'Unable to contact the oauth server.');
} }
+4 -1
View File
@@ -28,5 +28,8 @@ exports = module.exports = {
CLOUDRON_AVATAR_FILE: path.join(config.baseDir(), 'data/box/avatar.png'), CLOUDRON_AVATAR_FILE: path.join(config.baseDir(), 'data/box/avatar.png'),
CLOUDRON_DEFAULT_AVATAR_FILE: path.join(__dirname + '/../assets/avatar.png'), CLOUDRON_DEFAULT_AVATAR_FILE: path.join(__dirname + '/../assets/avatar.png'),
UPDATE_CHECKER_FILE: path.join(config.baseDir(), 'data/box/updatechecker.json') UPDATE_CHECKER_FILE: path.join(config.baseDir(), 'data/box/updatechecker.json'),
ACME_CHALLENGES_DIR: path.join(config.baseDir(), 'data/acme'),
ACME_ACCOUNT_KEY_FILE: path.join(config.baseDir(), 'data/box/acme/acme.key')
}; };
+1
View File
@@ -133,6 +133,7 @@ function installApp(req, res, next) {
if (error && error.reason === AppsError.PORT_CONFLICT) return next(new HttpError(409, 'Port ' + error.message + ' is already in use.')); if (error && error.reason === AppsError.PORT_CONFLICT) return next(new HttpError(409, 'Port ' + error.message + ' is already in use.'));
if (error && error.reason === AppsError.BAD_FIELD) return next(new HttpError(400, error.message)); if (error && error.reason === AppsError.BAD_FIELD) return next(new HttpError(400, error.message));
if (error && error.reason === AppsError.BILLING_REQUIRED) return next(new HttpError(402, 'Billing required')); if (error && error.reason === AppsError.BILLING_REQUIRED) return next(new HttpError(402, 'Billing required'));
if (error && error.reason === AppsError.NOT_FOUND) return next(new HttpError(404, 'No such app'));
if (error && error.reason === AppsError.BAD_CERTIFICATE) return next(new HttpError(400, error.message)); if (error && error.reason === AppsError.BAD_CERTIFICATE) return next(new HttpError(400, error.message));
if (error && error.reason === AppsError.USER_REQUIRED) return next(new HttpError(400, 'accessRestriction must specify one user')); if (error && error.reason === AppsError.USER_REQUIRED) return next(new HttpError(400, 'accessRestriction must specify one user'));
if (error) return next(new HttpError(500, error)); if (error) return next(new HttpError(500, error));
+2 -2
View File
@@ -59,7 +59,7 @@ function activate(req, res, next) {
// Now let the api server know we got activated // Now let the api server know we got activated
superagent.post(config.apiServerOrigin() + '/api/v1/boxes/' + config.fqdn() + '/setup/done').query({ setupToken:req.query.setupToken }).end(function (error, result) { superagent.post(config.apiServerOrigin() + '/api/v1/boxes/' + config.fqdn() + '/setup/done').query({ setupToken:req.query.setupToken }).end(function (error, result) {
if (error) return next(new HttpError(500, error)); if (error && !error.response) return next(new HttpError(500, error));
if (result.statusCode === 403) return next(new HttpError(403, 'Invalid token')); if (result.statusCode === 403) return next(new HttpError(403, 'Invalid token'));
if (result.statusCode === 409) return next(new HttpError(409, 'Already setup')); if (result.statusCode === 409) return next(new HttpError(409, 'Already setup'));
if (result.statusCode !== 201) return next(new HttpError(500, result.text ? result.text.message : 'Internal error')); if (result.statusCode !== 201) return next(new HttpError(500, result.text ? result.text.message : 'Internal error'));
@@ -75,7 +75,7 @@ function setupTokenAuth(req, res, next) {
if (typeof req.query.setupToken !== 'string') return next(new HttpError(400, 'no setupToken provided')); if (typeof req.query.setupToken !== 'string') return next(new HttpError(400, 'no setupToken provided'));
superagent.get(config.apiServerOrigin() + '/api/v1/boxes/' + config.fqdn() + '/setup/verify').query({ setupToken:req.query.setupToken }).end(function (error, result) { superagent.get(config.apiServerOrigin() + '/api/v1/boxes/' + config.fqdn() + '/setup/verify').query({ setupToken:req.query.setupToken }).end(function (error, result) {
if (error) return next(new HttpError(500, error)); if (error && !error.response) return next(new HttpError(500, error));
if (result.statusCode === 403) return next(new HttpError(403, 'Invalid token')); if (result.statusCode === 403) return next(new HttpError(403, 'Invalid token'));
if (result.statusCode === 409) return next(new HttpError(409, 'Already setup')); if (result.statusCode === 409) return next(new HttpError(409, 'Already setup'));
if (result.statusCode !== 200) return next(new HttpError(500, result.text ? result.text.message : 'Internal error')); if (result.statusCode !== 200) return next(new HttpError(500, result.text ? result.text.message : 'Internal error'));
+1 -1
View File
@@ -359,7 +359,7 @@ var authorization = [
var redirectPath = url.parse(redirectURI).path; var redirectPath = url.parse(redirectURI).path;
var redirectOrigin = client.redirectURI; var redirectOrigin = client.redirectURI;
callback(null, client, '/api/v1/session/callback?redirectURI=' + url.resolve(redirectOrigin, redirectPath)); callback(null, client, '/api/v1/session/callback?redirectURI=' + encodeURIComponent(url.resolve(redirectOrigin, redirectPath)));
}); });
}), }),
function (req, res, next) { function (req, res, next) {
+6 -4
View File
@@ -23,6 +23,8 @@ exports = module.exports = {
}; };
var assert = require('assert'), var assert = require('assert'),
certificates = require('../certificates.js'),
CertificatesError = require('../certificates.js').CertificatesError,
HttpError = require('connect-lastmile').HttpError, HttpError = require('connect-lastmile').HttpError,
HttpSuccess = require('connect-lastmile').HttpSuccess, HttpSuccess = require('connect-lastmile').HttpSuccess,
safe = require('safetydance'), safe = require('safetydance'),
@@ -142,8 +144,8 @@ function setCertificate(req, res, next) {
if (!req.body.cert || typeof req.body.cert !== 'string') return next(new HttpError(400, 'cert must be a string')); if (!req.body.cert || typeof req.body.cert !== 'string') return next(new HttpError(400, 'cert must be a string'));
if (!req.body.key || typeof req.body.key !== 'string') return next(new HttpError(400, 'key must be a string')); if (!req.body.key || typeof req.body.key !== 'string') return next(new HttpError(400, 'key must be a string'));
settings.setCertificate(req.body.cert, req.body.key, function (error) { certificates.setFallbackCertificate(req.body.cert, req.body.key, function (error) {
if (error && error.reason === SettingsError.INVALID_CERT) return next(new HttpError(400, error.message)); if (error && error.reason === CertificatesError.INVALID_CERT) return next(new HttpError(400, error.message));
if (error) return next(new HttpError(500, error)); if (error) return next(new HttpError(500, error));
next(new HttpSuccess(202, {})); next(new HttpSuccess(202, {}));
@@ -157,8 +159,8 @@ function setAdminCertificate(req, res, next) {
if (!req.body.cert || typeof req.body.cert !== 'string') return next(new HttpError(400, 'cert must be a string')); if (!req.body.cert || typeof req.body.cert !== 'string') return next(new HttpError(400, 'cert must be a string'));
if (!req.body.key || typeof req.body.key !== 'string') return next(new HttpError(400, 'key must be a string')); if (!req.body.key || typeof req.body.key !== 'string') return next(new HttpError(400, 'key must be a string'));
settings.setAdminCertificate(req.body.cert, req.body.key, function (error) { certificates.setAdminCertificate(req.body.cert, req.body.key, function (error) {
if (error && error.reason === SettingsError.INVALID_CERT) return next(new HttpError(400, error.message)); if (error && error.reason === CertificatesError.INVALID_CERT) return next(new HttpError(400, error.message));
if (error) return next(new HttpError(500, error)); if (error) return next(new HttpError(500, error));
next(new HttpSuccess(202, {})); next(new HttpSuccess(202, {}));
+98 -97
View File
@@ -27,7 +27,7 @@ var appdb = require('../../appdb.js'),
nock = require('nock'), nock = require('nock'),
paths = require('../../paths.js'), paths = require('../../paths.js'),
redis = require('redis'), redis = require('redis'),
request = require('superagent'), superagent = require('superagent'),
safe = require('safetydance'), safe = require('safetydance'),
server = require('../../server.js'), server = require('../../server.js'),
settings = require('../../settings.js'), settings = require('../../settings.js'),
@@ -114,11 +114,10 @@ function setup(done) {
var scope1 = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/setup/verify?setupToken=somesetuptoken').reply(200, {}); var scope1 = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/setup/verify?setupToken=somesetuptoken').reply(200, {});
var scope2 = nock(config.apiServerOrigin()).post('/api/v1/boxes/' + config.fqdn() + '/setup/done?setupToken=somesetuptoken').reply(201, {}); var scope2 = nock(config.apiServerOrigin()).post('/api/v1/boxes/' + config.fqdn() + '/setup/done?setupToken=somesetuptoken').reply(201, {});
request.post(SERVER_URL + '/api/v1/cloudron/activate') superagent.post(SERVER_URL + '/api/v1/cloudron/activate')
.query({ setupToken: 'somesetuptoken' }) .query({ setupToken: 'somesetuptoken' })
.send({ username: USERNAME, password: PASSWORD, email: EMAIL }) .send({ username: USERNAME, password: PASSWORD, email: EMAIL })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result).to.be.ok(); expect(result).to.be.ok();
expect(result.statusCode).to.eql(201); expect(result.statusCode).to.eql(201);
expect(scope1.isDone()).to.be.ok(); expect(scope1.isDone()).to.be.ok();
@@ -137,11 +136,10 @@ function setup(done) {
}, },
function (callback) { function (callback) {
request.post(SERVER_URL + '/api/v1/users') superagent.post(SERVER_URL + '/api/v1/users')
.query({ access_token: token }) .query({ access_token: token })
.send({ username: USERNAME_1, email: EMAIL_1 }) .send({ username: USERNAME_1, email: EMAIL_1 })
.end(function (err, res) { .end(function (err, res) {
expect(err).to.not.be.ok();
expect(res.statusCode).to.equal(201); expect(res.statusCode).to.equal(201);
callback(null); callback(null);
@@ -156,6 +154,7 @@ function setup(done) {
}, },
settings.setDnsConfig.bind(null, { provider: 'route53', accessKeyId: 'accessKeyId', secretAccessKey: 'secretAccessKey', endpoint: 'http://localhost:5353' }), settings.setDnsConfig.bind(null, { provider: 'route53', accessKeyId: 'accessKeyId', secretAccessKey: 'secretAccessKey', endpoint: 'http://localhost:5353' }),
settings.setTlsConfig.bind(null, { provider: 'caas' }),
settings.setBackupConfig.bind(null, { provider: 'caas', token: 'BACKUP_TOKEN', bucket: 'Bucket', prefix: 'Prefix' }) settings.setBackupConfig.bind(null, { provider: 'caas', token: 'BACKUP_TOKEN', bucket: 'Bucket', prefix: 'Prefix' })
], done); ], done);
} }
@@ -197,174 +196,174 @@ describe('App API', function () {
}); });
it('app install fails - missing manifest', function (done) { it('app install fails - missing manifest', function (done) {
request.post(SERVER_URL + '/api/v1/apps/install') superagent.post(SERVER_URL + '/api/v1/apps/install')
.query({ access_token: token }) .query({ access_token: token })
.send({ appStoreId: APP_STORE_ID, password: PASSWORD }) .send({ appStoreId: APP_STORE_ID, password: PASSWORD })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(400); expect(res.statusCode).to.equal(400);
expect(res.body.message).to.eql('manifest is required'); expect(res.body.message).to.eql('manifest is required');
done(err); done();
}); });
}); });
it('app install fails - missing appId', function (done) { it('app install fails - missing appId', function (done) {
request.post(SERVER_URL + '/api/v1/apps/install') superagent.post(SERVER_URL + '/api/v1/apps/install')
.query({ access_token: token }) .query({ access_token: token })
.send({ manifest: APP_MANIFEST, password: PASSWORD }) .send({ manifest: APP_MANIFEST, password: PASSWORD })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(400); expect(res.statusCode).to.equal(400);
expect(res.body.message).to.eql('appStoreId is required'); expect(res.body.message).to.eql('appStoreId is required');
done(err); done();
}); });
}); });
it('app install fails - invalid json', function (done) { it('app install fails - invalid json', function (done) {
request.post(SERVER_URL + '/api/v1/apps/install') superagent.post(SERVER_URL + '/api/v1/apps/install')
.query({ access_token: token }) .query({ access_token: token })
.send('garbage') .send('garbage')
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(400); expect(res.statusCode).to.equal(400);
done(err); done();
}); });
}); });
it('app install fails - invalid location', function (done) { it('app install fails - invalid location', function (done) {
request.post(SERVER_URL + '/api/v1/apps/install') superagent.post(SERVER_URL + '/api/v1/apps/install')
.query({ access_token: token }) .query({ access_token: token })
.send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: '!awesome', accessRestriction: null, oauthProxy: false }) .send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: '!awesome', accessRestriction: null, oauthProxy: false })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(400); expect(res.statusCode).to.equal(400);
expect(res.body.message).to.eql('Hostname can only contain alphanumerics and hyphen'); expect(res.body.message).to.eql('Hostname can only contain alphanumerics and hyphen');
done(err); done();
}); });
}); });
it('app install fails - invalid location type', function (done) { it('app install fails - invalid location type', function (done) {
request.post(SERVER_URL + '/api/v1/apps/install') superagent.post(SERVER_URL + '/api/v1/apps/install')
.query({ access_token: token }) .query({ access_token: token })
.send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: 42, accessRestriction: null, oauthProxy: false }) .send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: 42, accessRestriction: null, oauthProxy: false })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(400); expect(res.statusCode).to.equal(400);
expect(res.body.message).to.eql('location is required'); expect(res.body.message).to.eql('location is required');
done(err); done();
}); });
}); });
it('app install fails - reserved admin location', function (done) { it('app install fails - reserved admin location', function (done) {
request.post(SERVER_URL + '/api/v1/apps/install') superagent.post(SERVER_URL + '/api/v1/apps/install')
.query({ access_token: token }) .query({ access_token: token })
.send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: constants.ADMIN_LOCATION, accessRestriction: null, oauthProxy: false }) .send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: constants.ADMIN_LOCATION, accessRestriction: null, oauthProxy: false })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(400); expect(res.statusCode).to.equal(400);
expect(res.body.message).to.eql(constants.ADMIN_LOCATION + ' is reserved'); expect(res.body.message).to.eql(constants.ADMIN_LOCATION + ' is reserved');
done(err); done();
}); });
}); });
it('app install fails - reserved api location', function (done) { it('app install fails - reserved api location', function (done) {
request.post(SERVER_URL + '/api/v1/apps/install') superagent.post(SERVER_URL + '/api/v1/apps/install')
.query({ access_token: token }) .query({ access_token: token })
.send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: constants.API_LOCATION, accessRestriction: null, oauthProxy: true }) .send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: constants.API_LOCATION, accessRestriction: null, oauthProxy: true })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(400); expect(res.statusCode).to.equal(400);
expect(res.body.message).to.eql(constants.API_LOCATION + ' is reserved'); expect(res.body.message).to.eql(constants.API_LOCATION + ' is reserved');
done(err); done();
}); });
}); });
it('app install fails - portBindings must be object', function (done) { it('app install fails - portBindings must be object', function (done) {
request.post(SERVER_URL + '/api/v1/apps/install') superagent.post(SERVER_URL + '/api/v1/apps/install')
.query({ access_token: token }) .query({ access_token: token })
.send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: APP_LOCATION, portBindings: 23, accessRestriction: null, oauthProxy: false }) .send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: APP_LOCATION, portBindings: 23, accessRestriction: null, oauthProxy: false })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(400); expect(res.statusCode).to.equal(400);
expect(res.body.message).to.eql('portBindings must be an object'); expect(res.body.message).to.eql('portBindings must be an object');
done(err); done();
}); });
}); });
it('app install fails - accessRestriction is required', function (done) { it('app install fails - accessRestriction is required', function (done) {
request.post(SERVER_URL + '/api/v1/apps/install') superagent.post(SERVER_URL + '/api/v1/apps/install')
.query({ access_token: token }) .query({ access_token: token })
.send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: APP_LOCATION, portBindings: {}, oauthProxy: false }) .send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: APP_LOCATION, portBindings: {}, oauthProxy: false })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(400); expect(res.statusCode).to.equal(400);
expect(res.body.message).to.eql('accessRestriction is required'); expect(res.body.message).to.eql('accessRestriction is required');
done(err); done();
}); });
}); });
it('app install fails - accessRestriction type is wrong', function (done) { it('app install fails - accessRestriction type is wrong', function (done) {
request.post(SERVER_URL + '/api/v1/apps/install') superagent.post(SERVER_URL + '/api/v1/apps/install')
.query({ access_token: token }) .query({ access_token: token })
.send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: APP_LOCATION, portBindings: {}, accessRestriction: '', oauthProxy: false }) .send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: APP_LOCATION, portBindings: {}, accessRestriction: '', oauthProxy: false })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(400); expect(res.statusCode).to.equal(400);
expect(res.body.message).to.eql('accessRestriction is required'); expect(res.body.message).to.eql('accessRestriction is required');
done(err); done();
}); });
}); });
it('app install fails - accessRestriction no users not allowed', function (done) { it('app install fails - accessRestriction no users not allowed', function (done) {
request.post(SERVER_URL + '/api/v1/apps/install') superagent.post(SERVER_URL + '/api/v1/apps/install')
.query({ access_token: token }) .query({ access_token: token })
.send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST_1, password: PASSWORD, location: APP_LOCATION, portBindings: {}, accessRestriction: null, oauthProxy: false }) .send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST_1, password: PASSWORD, location: APP_LOCATION, portBindings: {}, accessRestriction: null, oauthProxy: false })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(400); expect(res.statusCode).to.equal(400);
expect(res.body.message).to.eql('accessRestriction must specify one user'); expect(res.body.message).to.eql('accessRestriction must specify one user');
done(err); done();
}); });
}); });
it('app install fails - accessRestriction too many users not allowed', function (done) { it('app install fails - accessRestriction too many users not allowed', function (done) {
request.post(SERVER_URL + '/api/v1/apps/install') superagent.post(SERVER_URL + '/api/v1/apps/install')
.query({ access_token: token }) .query({ access_token: token })
.send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST_1, password: PASSWORD, location: APP_LOCATION, portBindings: {}, accessRestriction: { users: [ 'one', 'two' ] }, oauthProxy: false }) .send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST_1, password: PASSWORD, location: APP_LOCATION, portBindings: {}, accessRestriction: { users: [ 'one', 'two' ] }, oauthProxy: false })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(400); expect(res.statusCode).to.equal(400);
expect(res.body.message).to.eql('accessRestriction must specify one user'); expect(res.body.message).to.eql('accessRestriction must specify one user');
done(err); done();
}); });
}); });
it('app install fails - oauthProxy is required', function (done) { it('app install fails - oauthProxy is required', function (done) {
request.post(SERVER_URL + '/api/v1/apps/install') superagent.post(SERVER_URL + '/api/v1/apps/install')
.query({ access_token: token }) .query({ access_token: token })
.send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: APP_LOCATION, portBindings: {}, accessRestriction: null }) .send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: APP_LOCATION, portBindings: {}, accessRestriction: null })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(400); expect(res.statusCode).to.equal(400);
expect(res.body.message).to.eql('oauthProxy must be a boolean'); expect(res.body.message).to.eql('oauthProxy must be a boolean');
done(err); done();
}); });
}); });
it('app install fails for non admin', function (done) { it('app install fails for non admin', function (done) {
request.post(SERVER_URL + '/api/v1/apps/install') superagent.post(SERVER_URL + '/api/v1/apps/install')
.query({ access_token: token_1 }) .query({ access_token: token_1 })
.send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: APP_LOCATION, portBindings: null, accessRestriction: null, oauthProxy: false }) .send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: APP_LOCATION, portBindings: null, accessRestriction: null, oauthProxy: false })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(403); expect(res.statusCode).to.equal(403);
done(err); done();
}); });
}); });
it('app install fails due to purchase failure', function (done) { it('app install fails due to purchase failure', function (done) {
var fake = nock(config.apiServerOrigin()).post('/api/v1/apps/test/purchase?token=APPSTORE_TOKEN').reply(402, {}); var fake = nock(config.apiServerOrigin()).post('/api/v1/apps/test/purchase?token=APPSTORE_TOKEN').reply(402, {});
request.post(SERVER_URL + '/api/v1/apps/install') superagent.post(SERVER_URL + '/api/v1/apps/install')
.query({ access_token: token }) .query({ access_token: token })
.send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: APP_LOCATION, portBindings: null, accessRestriction: null, oauthProxy: false }) .send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: APP_LOCATION, portBindings: null, accessRestriction: null, oauthProxy: false })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(402); expect(res.statusCode).to.equal(402);
expect(fake.isDone()).to.be.ok(); expect(fake.isDone()).to.be.ok();
done(err); done();
}); });
}); });
it('app install succeeds with purchase', function (done) { it('app install succeeds with purchase', function (done) {
var fake = nock(config.apiServerOrigin()).post('/api/v1/apps/test/purchase?token=APPSTORE_TOKEN').reply(201, {}); var fake = nock(config.apiServerOrigin()).post('/api/v1/apps/test/purchase?token=APPSTORE_TOKEN').reply(201, {});
request.post(SERVER_URL + '/api/v1/apps/install') superagent.post(SERVER_URL + '/api/v1/apps/install')
.query({ access_token: token }) .query({ access_token: token })
.send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: APP_LOCATION, portBindings: null, accessRestriction: null, oauthProxy: false }) .send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: APP_LOCATION, portBindings: null, accessRestriction: null, oauthProxy: false })
.end(function (err, res) { .end(function (err, res) {
@@ -372,14 +371,14 @@ describe('App API', function () {
expect(res.body.id).to.be.a('string'); expect(res.body.id).to.be.a('string');
APP_ID = res.body.id; APP_ID = res.body.id;
expect(fake.isDone()).to.be.ok(); expect(fake.isDone()).to.be.ok();
done(err); done();
}); });
}); });
it('app install fails because of conflicting location', function (done) { it('app install fails because of conflicting location', function (done) {
var fake = nock(config.apiServerOrigin()).post('/api/v1/apps/test/purchase?token=APPSTORE_TOKEN').reply(201, {}); var fake = nock(config.apiServerOrigin()).post('/api/v1/apps/test/purchase?token=APPSTORE_TOKEN').reply(201, {});
request.post(SERVER_URL + '/api/v1/apps/install') superagent.post(SERVER_URL + '/api/v1/apps/install')
.query({ access_token: token }) .query({ access_token: token })
.send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: APP_LOCATION, portBindings: null, accessRestriction: null, oauthProxy: false }) .send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: APP_LOCATION, portBindings: null, accessRestriction: null, oauthProxy: false })
.end(function (err, res) { .end(function (err, res) {
@@ -390,120 +389,120 @@ describe('App API', function () {
}); });
it('can get app status', function (done) { it('can get app status', function (done) {
request.get(SERVER_URL + '/api/v1/apps/' + APP_ID) superagent.get(SERVER_URL + '/api/v1/apps/' + APP_ID)
.query({ access_token: token }) .query({ access_token: token })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(200); expect(res.statusCode).to.equal(200);
expect(res.body.id).to.eql(APP_ID); expect(res.body.id).to.eql(APP_ID);
expect(res.body.installationState).to.be.ok(); expect(res.body.installationState).to.be.ok();
done(err); done();
}); });
}); });
it('cannot get invalid app status', function (done) { it('cannot get invalid app status', function (done) {
request.get(SERVER_URL + '/api/v1/apps/kubachi') superagent.get(SERVER_URL + '/api/v1/apps/kubachi')
.query({ access_token: token }) .query({ access_token: token })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(404); expect(res.statusCode).to.equal(404);
done(err); done();
}); });
}); });
it('can get all apps', function (done) { it('can get all apps', function (done) {
request.get(SERVER_URL + '/api/v1/apps') superagent.get(SERVER_URL + '/api/v1/apps')
.query({ access_token: token }) .query({ access_token: token })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(200); expect(res.statusCode).to.equal(200);
expect(res.body.apps).to.be.an('array'); expect(res.body.apps).to.be.an('array');
expect(res.body.apps[0].id).to.eql(APP_ID); expect(res.body.apps[0].id).to.eql(APP_ID);
expect(res.body.apps[0].installationState).to.be.ok(); expect(res.body.apps[0].installationState).to.be.ok();
done(err); done();
}); });
}); });
it('non admin can get all apps', function (done) { it('non admin can get all apps', function (done) {
request.get(SERVER_URL + '/api/v1/apps') superagent.get(SERVER_URL + '/api/v1/apps')
.query({ access_token: token_1 }) .query({ access_token: token_1 })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(200); expect(res.statusCode).to.equal(200);
expect(res.body.apps).to.be.an('array'); expect(res.body.apps).to.be.an('array');
expect(res.body.apps[0].id).to.eql(APP_ID); expect(res.body.apps[0].id).to.eql(APP_ID);
expect(res.body.apps[0].installationState).to.be.ok(); expect(res.body.apps[0].installationState).to.be.ok();
done(err); done();
}); });
}); });
it('can get appBySubdomain', function (done) { it('can get appBySubdomain', function (done) {
request.get(SERVER_URL + '/api/v1/subdomains/' + APP_LOCATION) superagent.get(SERVER_URL + '/api/v1/subdomains/' + APP_LOCATION)
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(200); expect(res.statusCode).to.equal(200);
expect(res.body.id).to.eql(APP_ID); expect(res.body.id).to.eql(APP_ID);
expect(res.body.installationState).to.be.ok(); expect(res.body.installationState).to.be.ok();
done(err); done();
}); });
}); });
it('cannot get invalid app by Subdomain', function (done) { it('cannot get invalid app by Subdomain', function (done) {
request.get(SERVER_URL + '/api/v1/subdomains/tikaloma') superagent.get(SERVER_URL + '/api/v1/subdomains/tikaloma')
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(404); expect(res.statusCode).to.equal(404);
done(err); done();
}); });
}); });
it('cannot uninstall invalid app', function (done) { it('cannot uninstall invalid app', function (done) {
request.post(SERVER_URL + '/api/v1/apps/whatever/uninstall') superagent.post(SERVER_URL + '/api/v1/apps/whatever/uninstall')
.send({ password: PASSWORD }) .send({ password: PASSWORD })
.query({ access_token: token }) .query({ access_token: token })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(404); expect(res.statusCode).to.equal(404);
done(err); done();
}); });
}); });
it('cannot uninstall app without password', function (done) { it('cannot uninstall app without password', function (done) {
request.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/uninstall') superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/uninstall')
.query({ access_token: token }) .query({ access_token: token })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(400); expect(res.statusCode).to.equal(400);
done(err); done();
}); });
}); });
it('cannot uninstall app with wrong password', function (done) { it('cannot uninstall app with wrong password', function (done) {
request.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/uninstall') superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/uninstall')
.send({ password: PASSWORD+PASSWORD }) .send({ password: PASSWORD+PASSWORD })
.query({ access_token: token }) .query({ access_token: token })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(403); expect(res.statusCode).to.equal(403);
done(err); done();
}); });
}); });
it('non admin cannot uninstall app', function (done) { it('non admin cannot uninstall app', function (done) {
request.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/uninstall') superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/uninstall')
.send({ password: PASSWORD }) .send({ password: PASSWORD })
.query({ access_token: token_1 }) .query({ access_token: token_1 })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(403); expect(res.statusCode).to.equal(403);
done(err); done();
}); });
}); });
it('can uninstall app', function (done) { it('can uninstall app', function (done) {
request.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/uninstall') superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/uninstall')
.send({ password: PASSWORD }) .send({ password: PASSWORD })
.query({ access_token: token }) .query({ access_token: token })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(202); expect(res.statusCode).to.equal(202);
done(err); done();
}); });
}); });
it('app install succeeds already purchased', function (done) { it('app install succeeds already purchased', function (done) {
var fake = nock(config.apiServerOrigin()).post('/api/v1/apps/test/purchase?token=APPSTORE_TOKEN').reply(200, {}); var fake = nock(config.apiServerOrigin()).post('/api/v1/apps/test/purchase?token=APPSTORE_TOKEN').reply(200, {});
request.post(SERVER_URL + '/api/v1/apps/install') superagent.post(SERVER_URL + '/api/v1/apps/install')
.query({ access_token: token }) .query({ access_token: token })
.send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: APP_LOCATION_2, portBindings: null, accessRestriction: null, oauthProxy: false }) .send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: APP_LOCATION_2, portBindings: null, accessRestriction: null, oauthProxy: false })
.end(function (err, res) { .end(function (err, res) {
@@ -511,7 +510,7 @@ describe('App API', function () {
expect(res.body.id).to.be.a('string'); expect(res.body.id).to.be.a('string');
APP_ID = res.body.id; APP_ID = res.body.id;
expect(fake.isDone()).to.be.ok(); expect(fake.isDone()).to.be.ok();
done(err); done();
}); });
}); });
@@ -521,7 +520,7 @@ describe('App API', function () {
settings.setDeveloperMode(true, function (error) { settings.setDeveloperMode(true, function (error) {
expect(error).to.be(null); expect(error).to.be(null);
request.post(SERVER_URL + '/api/v1/developer/login') superagent.post(SERVER_URL + '/api/v1/developer/login')
.send({ username: USERNAME, password: PASSWORD }) .send({ username: USERNAME, password: PASSWORD })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok(); expect(error).to.not.be.ok();
@@ -532,7 +531,7 @@ describe('App API', function () {
// overwrite non dev token // overwrite non dev token
token = result.body.token; token = result.body.token;
request.post(SERVER_URL + '/api/v1/apps/install') superagent.post(SERVER_URL + '/api/v1/apps/install')
.query({ access_token: token }) .query({ access_token: token })
.send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, location: APP_LOCATION+APP_LOCATION, portBindings: null, accessRestriction: null, oauthProxy: false }) .send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, location: APP_LOCATION+APP_LOCATION, portBindings: null, accessRestriction: null, oauthProxy: false })
.end(function (err, res) { .end(function (err, res) {
@@ -540,18 +539,18 @@ describe('App API', function () {
expect(res.body.id).to.be.a('string'); expect(res.body.id).to.be.a('string');
expect(fake.isDone()).to.be.ok(); expect(fake.isDone()).to.be.ok();
APP_ID = res.body.id; APP_ID = res.body.id;
done(err); done();
}); });
}); });
}); });
}); });
it('can uninstall app without password but developer token', function (done) { it('can uninstall app without password but developer token', function (done) {
request.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/uninstall') superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/uninstall')
.query({ access_token: token }) .query({ access_token: token })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(202); expect(res.statusCode).to.equal(202);
done(err); done();
}); });
}); });
}); });
@@ -628,7 +627,7 @@ describe('App installation', function () {
var count = 0; var count = 0;
function checkInstallStatus() { function checkInstallStatus() {
request.get(SERVER_URL + '/api/v1/apps/' + APP_ID) superagent.get(SERVER_URL + '/api/v1/apps/' + APP_ID)
.query({ access_token: token }) .query({ access_token: token })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(200); expect(res.statusCode).to.equal(200);
@@ -639,7 +638,7 @@ describe('App installation', function () {
}); });
} }
request.post(SERVER_URL + '/api/v1/apps/install') superagent.post(SERVER_URL + '/api/v1/apps/install')
.query({ access_token: token }) .query({ access_token: token })
.send({ appId: APP_ID, appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: APP_LOCATION, portBindings: null, accessRestriction: null, oauthProxy: false }) .send({ appId: APP_ID, appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: APP_LOCATION, portBindings: null, accessRestriction: null, oauthProxy: false })
.end(function (err, res) { .end(function (err, res) {
@@ -705,7 +704,7 @@ describe('App installation', function () {
it('installation - is up and running', function (done) { it('installation - is up and running', function (done) {
expect(appResult.httpPort).to.be(undefined); expect(appResult.httpPort).to.be(undefined);
setTimeout(function () { setTimeout(function () {
request.get('http://localhost:' + appEntry.httpPort + appResult.manifest.healthCheckPath) superagent.get('http://localhost:' + appEntry.httpPort + appResult.manifest.healthCheckPath)
.end(function (err, res) { .end(function (err, res) {
expect(!err).to.be.ok(); expect(!err).to.be.ok();
expect(res.statusCode).to.equal(200); expect(res.statusCode).to.equal(200);
@@ -842,7 +841,7 @@ describe('App installation', function () {
}); });
xit('logs - stdout and stderr', function (done) { xit('logs - stdout and stderr', function (done) {
request.get(SERVER_URL + '/api/v1/apps/' + APP_ID + '/logs') superagent.get(SERVER_URL + '/api/v1/apps/' + APP_ID + '/logs')
.query({ access_token: token }) .query({ access_token: token })
.end(function (err, res) { .end(function (err, res) {
var data = ''; var data = '';
@@ -856,7 +855,7 @@ describe('App installation', function () {
}); });
xit('logStream - requires event-stream accept header', function (done) { xit('logStream - requires event-stream accept header', function (done) {
request.get(SERVER_URL + '/api/v1/apps/' + APP_ID + '/logstream') superagent.get(SERVER_URL + '/api/v1/apps/' + APP_ID + '/logstream')
.query({ access_token: token, fromLine: 0 }) .query({ access_token: token, fromLine: 0 })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.be(400); expect(res.statusCode).to.be(400);
@@ -895,7 +894,7 @@ describe('App installation', function () {
}); });
it('non admin cannot stop app', function (done) { it('non admin cannot stop app', function (done) {
request.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/stop') superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/stop')
.query({ access_token: token_1 }) .query({ access_token: token_1 })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(403); expect(res.statusCode).to.equal(403);
@@ -904,7 +903,7 @@ describe('App installation', function () {
}); });
it('can stop app', function (done) { it('can stop app', function (done) {
request.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/stop') superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/stop')
.query({ access_token: token }) .query({ access_token: token })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(202); expect(res.statusCode).to.equal(202);
@@ -915,7 +914,7 @@ describe('App installation', function () {
it('did stop the app', function (done) { it('did stop the app', function (done) {
// give the app a couple of seconds to die // give the app a couple of seconds to die
setTimeout(function () { setTimeout(function () {
request.get('http://localhost:' + appEntry.httpPort + appResult.manifest.healthCheckPath) superagent.get('http://localhost:' + appEntry.httpPort + appResult.manifest.healthCheckPath)
.end(function (err, res) { .end(function (err, res) {
expect(err).to.be.ok(); expect(err).to.be.ok();
done(); done();
@@ -924,7 +923,7 @@ describe('App installation', function () {
}); });
it('nonadmin cannot start app', function (done) { it('nonadmin cannot start app', function (done) {
request.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/start') superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/start')
.query({ access_token: token_1 }) .query({ access_token: token_1 })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(403); expect(res.statusCode).to.equal(403);
@@ -933,7 +932,7 @@ describe('App installation', function () {
}); });
it('can start app', function (done) { it('can start app', function (done) {
request.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/start') superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/start')
.query({ access_token: token }) .query({ access_token: token })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(202); expect(res.statusCode).to.equal(202);
@@ -943,7 +942,7 @@ describe('App installation', function () {
it('did start the app', function (done) { it('did start the app', function (done) {
setTimeout(function () { setTimeout(function () {
request.get('http://localhost:' + appEntry.httpPort + appResult.manifest.healthCheckPath) superagent.get('http://localhost:' + appEntry.httpPort + appResult.manifest.healthCheckPath)
.end(function (err, res) { .end(function (err, res) {
expect(!err).to.be.ok(); expect(!err).to.be.ok();
expect(res.statusCode).to.equal(200); expect(res.statusCode).to.equal(200);
@@ -955,7 +954,7 @@ describe('App installation', function () {
it('can uninstall app', function (done) { it('can uninstall app', function (done) {
var count = 0; var count = 0;
function checkUninstallStatus() { function checkUninstallStatus() {
request.get(SERVER_URL + '/api/v1/apps/' + APP_ID) superagent.get(SERVER_URL + '/api/v1/apps/' + APP_ID)
.query({ access_token: token }) .query({ access_token: token })
.end(function (err, res) { .end(function (err, res) {
if (res.statusCode === 404) return done(null); if (res.statusCode === 404) return done(null);
@@ -964,7 +963,7 @@ describe('App installation', function () {
}); });
} }
request.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/uninstall') superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/uninstall')
.send({ password: PASSWORD }) .send({ password: PASSWORD })
.query({ access_token: token }) .query({ access_token: token })
.end(function (err, res) { .end(function (err, res) {
@@ -1064,6 +1063,8 @@ describe('App installation - port bindings', function () {
settings.setDnsConfig.bind(null, { provider: 'route53', accessKeyId: 'accessKeyId', secretAccessKey: 'secretAccessKey', endpoint: 'http://localhost:5353' }), settings.setDnsConfig.bind(null, { provider: 'route53', accessKeyId: 'accessKeyId', secretAccessKey: 'secretAccessKey', endpoint: 'http://localhost:5353' }),
settings.setTlsConfig.bind(null, { provider: 'caas' }),
function (callback) { function (callback) {
awsHockInstance awsHockInstance
.get('/2013-04-01/hostedzone') .get('/2013-04-01/hostedzone')
@@ -1096,7 +1097,7 @@ describe('App installation - port bindings', function () {
var count = 0; var count = 0;
function checkInstallStatus() { function checkInstallStatus() {
request.get(SERVER_URL + '/api/v1/apps/' + APP_ID) superagent.get(SERVER_URL + '/api/v1/apps/' + APP_ID)
.query({ access_token: token }) .query({ access_token: token })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(200); expect(res.statusCode).to.equal(200);
@@ -1107,7 +1108,7 @@ describe('App installation - port bindings', function () {
}); });
} }
request.post(SERVER_URL + '/api/v1/apps/install') superagent.post(SERVER_URL + '/api/v1/apps/install')
.query({ access_token: token }) .query({ access_token: token })
.send({ appId: APP_ID, appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: APP_LOCATION, portBindings: { ECHO_SERVER_PORT: 7171 }, accessRestriction: null, oauthProxy: false }) .send({ appId: APP_ID, appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: APP_LOCATION, portBindings: { ECHO_SERVER_PORT: 7171 }, accessRestriction: null, oauthProxy: false })
.end(function (err, res) { .end(function (err, res) {
@@ -1163,7 +1164,7 @@ describe('App installation - port bindings', function () {
var tryCount = 20; var tryCount = 20;
expect(appResult.httpPort).to.be(undefined); expect(appResult.httpPort).to.be(undefined);
(function healthCheck() { (function healthCheck() {
request.get('http://localhost:' + appEntry.httpPort + appResult.manifest.healthCheckPath) superagent.get('http://localhost:' + appEntry.httpPort + appResult.manifest.healthCheckPath)
.end(function (err, res) { .end(function (err, res) {
if (err || res.statusCode !== 200) { if (err || res.statusCode !== 200) {
if (--tryCount === 0) return done(new Error('Timedout')); if (--tryCount === 0) return done(new Error('Timedout'));
@@ -1253,7 +1254,7 @@ describe('App installation - port bindings', function () {
assert.strictEqual(typeof count, 'number'); assert.strictEqual(typeof count, 'number');
assert.strictEqual(typeof done, 'function'); assert.strictEqual(typeof done, 'function');
request.get(SERVER_URL + '/api/v1/apps/' + APP_ID) superagent.get(SERVER_URL + '/api/v1/apps/' + APP_ID)
.query({ access_token: token }) .query({ access_token: token })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(200); expect(res.statusCode).to.equal(200);
@@ -1265,7 +1266,7 @@ describe('App installation - port bindings', function () {
} }
it('cannot reconfigure app with missing location', function (done) { it('cannot reconfigure app with missing location', function (done) {
request.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure') superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure')
.query({ access_token: token }) .query({ access_token: token })
.send({ appId: APP_ID, password: PASSWORD, portBindings: { ECHO_SERVER_PORT: 7172 }, accessRestriction: null, oauthProxy: true }) .send({ appId: APP_ID, password: PASSWORD, portBindings: { ECHO_SERVER_PORT: 7172 }, accessRestriction: null, oauthProxy: true })
.end(function (err, res) { .end(function (err, res) {
@@ -1275,7 +1276,7 @@ describe('App installation - port bindings', function () {
}); });
it('cannot reconfigure app with missing accessRestriction', function (done) { it('cannot reconfigure app with missing accessRestriction', function (done) {
request.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure') superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure')
.query({ access_token: token }) .query({ access_token: token })
.send({ appId: APP_ID, password: PASSWORD, location: APP_LOCATION_NEW, portBindings: { ECHO_SERVER_PORT: 7172 }, oauthProxy: false }) .send({ appId: APP_ID, password: PASSWORD, location: APP_LOCATION_NEW, portBindings: { ECHO_SERVER_PORT: 7172 }, oauthProxy: false })
.end(function (err, res) { .end(function (err, res) {
@@ -1285,7 +1286,7 @@ describe('App installation - port bindings', function () {
}); });
it('cannot reconfigure app with missing oauthProxy', function (done) { it('cannot reconfigure app with missing oauthProxy', function (done) {
request.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure') superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure')
.query({ access_token: token }) .query({ access_token: token })
.send({ appId: APP_ID, password: PASSWORD, location: APP_LOCATION_NEW, portBindings: { ECHO_SERVER_PORT: 7172 }, accessRestriction: null }) .send({ appId: APP_ID, password: PASSWORD, location: APP_LOCATION_NEW, portBindings: { ECHO_SERVER_PORT: 7172 }, accessRestriction: null })
.end(function (err, res) { .end(function (err, res) {
@@ -1295,7 +1296,7 @@ describe('App installation - port bindings', function () {
}); });
it('cannot reconfigure app with only the cert, no key', function (done) { it('cannot reconfigure app with only the cert, no key', function (done) {
request.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure') superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure')
.query({ access_token: token }) .query({ access_token: token })
.send({ appId: APP_ID, password: PASSWORD, location: APP_LOCATION_NEW, portBindings: { ECHO_SERVER_PORT: 7172 }, accessRestriction: null, oauthProxy: true, cert: validCert1 }) .send({ appId: APP_ID, password: PASSWORD, location: APP_LOCATION_NEW, portBindings: { ECHO_SERVER_PORT: 7172 }, accessRestriction: null, oauthProxy: true, cert: validCert1 })
.end(function (err, res) { .end(function (err, res) {
@@ -1305,7 +1306,7 @@ describe('App installation - port bindings', function () {
}); });
it('cannot reconfigure app with only the key, no cert', function (done) { it('cannot reconfigure app with only the key, no cert', function (done) {
request.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure') superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure')
.query({ access_token: token }) .query({ access_token: token })
.send({ appId: APP_ID, password: PASSWORD, location: APP_LOCATION_NEW, portBindings: { ECHO_SERVER_PORT: 7172 }, accessRestriction: null, oauthProxy: true, key: validKey1 }) .send({ appId: APP_ID, password: PASSWORD, location: APP_LOCATION_NEW, portBindings: { ECHO_SERVER_PORT: 7172 }, accessRestriction: null, oauthProxy: true, key: validKey1 })
.end(function (err, res) { .end(function (err, res) {
@@ -1315,7 +1316,7 @@ describe('App installation - port bindings', function () {
}); });
it('cannot reconfigure app with cert not bein a string', function (done) { it('cannot reconfigure app with cert not bein a string', function (done) {
request.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure') superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure')
.query({ access_token: token }) .query({ access_token: token })
.send({ appId: APP_ID, password: PASSWORD, location: APP_LOCATION_NEW, portBindings: { ECHO_SERVER_PORT: 7172 }, accessRestriction: null, oauthProxy: true, cert: 1234, key: validKey1 }) .send({ appId: APP_ID, password: PASSWORD, location: APP_LOCATION_NEW, portBindings: { ECHO_SERVER_PORT: 7172 }, accessRestriction: null, oauthProxy: true, cert: 1234, key: validKey1 })
.end(function (err, res) { .end(function (err, res) {
@@ -1325,7 +1326,7 @@ describe('App installation - port bindings', function () {
}); });
it('cannot reconfigure app with key not bein a string', function (done) { it('cannot reconfigure app with key not bein a string', function (done) {
request.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure') superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure')
.query({ access_token: token }) .query({ access_token: token })
.send({ appId: APP_ID, password: PASSWORD, location: APP_LOCATION_NEW, portBindings: { ECHO_SERVER_PORT: 7172 }, accessRestriction: null, oauthProxy: true, cert: validCert1, key: 1234 }) .send({ appId: APP_ID, password: PASSWORD, location: APP_LOCATION_NEW, portBindings: { ECHO_SERVER_PORT: 7172 }, accessRestriction: null, oauthProxy: true, cert: validCert1, key: 1234 })
.end(function (err, res) { .end(function (err, res) {
@@ -1335,7 +1336,7 @@ describe('App installation - port bindings', function () {
}); });
it('non admin cannot reconfigure app', function (done) { it('non admin cannot reconfigure app', function (done) {
request.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure') superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure')
.query({ access_token: token_1 }) .query({ access_token: token_1 })
.send({ appId: APP_ID, password: PASSWORD, location: APP_LOCATION_NEW, portBindings: { ECHO_SERVER_PORT: 7172 }, accessRestriction: null, oauthProxy: true }) .send({ appId: APP_ID, password: PASSWORD, location: APP_LOCATION_NEW, portBindings: { ECHO_SERVER_PORT: 7172 }, accessRestriction: null, oauthProxy: true })
.end(function (err, res) { .end(function (err, res) {
@@ -1345,7 +1346,7 @@ describe('App installation - port bindings', function () {
}); });
it('can reconfigure app', function (done) { it('can reconfigure app', function (done) {
request.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure') superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure')
.query({ access_token: token }) .query({ access_token: token })
.send({ appId: APP_ID, password: PASSWORD, location: APP_LOCATION_NEW, portBindings: { ECHO_SERVER_PORT: 7172 }, accessRestriction: null, oauthProxy: true }) .send({ appId: APP_ID, password: PASSWORD, location: APP_LOCATION_NEW, portBindings: { ECHO_SERVER_PORT: 7172 }, accessRestriction: null, oauthProxy: true })
.end(function (err, res) { .end(function (err, res) {
@@ -1429,7 +1430,7 @@ describe('App installation - port bindings', function () {
}); });
it('can reconfigure app with custom certificate', function (done) { it('can reconfigure app with custom certificate', function (done) {
request.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure') superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure')
.query({ access_token: token }) .query({ access_token: token })
.send({ appId: APP_ID, password: PASSWORD, location: APP_LOCATION_NEW, portBindings: { ECHO_SERVER_PORT: 7172 }, accessRestriction: null, oauthProxy: true, cert: validCert1, key: validKey1 }) .send({ appId: APP_ID, password: PASSWORD, location: APP_LOCATION_NEW, portBindings: { ECHO_SERVER_PORT: 7172 }, accessRestriction: null, oauthProxy: true, cert: validCert1, key: validKey1 })
.end(function (err, res) { .end(function (err, res) {
@@ -1439,7 +1440,7 @@ describe('App installation - port bindings', function () {
}); });
it('can stop app', function (done) { it('can stop app', function (done) {
request.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/stop') superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/stop')
.query({ access_token: token }) .query({ access_token: token })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(202); expect(res.statusCode).to.equal(202);
@@ -1464,7 +1465,7 @@ describe('App installation - port bindings', function () {
it('can uninstall app', function (done) { it('can uninstall app', function (done) {
var count = 0; var count = 0;
function checkUninstallStatus() { function checkUninstallStatus() {
request.get(SERVER_URL + '/api/v1/apps/' + APP_ID) superagent.get(SERVER_URL + '/api/v1/apps/' + APP_ID)
.query({ access_token: token }) .query({ access_token: token })
.end(function (err, res) { .end(function (err, res) {
if (res.statusCode === 404) return done(null); if (res.statusCode === 404) return done(null);
@@ -1473,7 +1474,7 @@ describe('App installation - port bindings', function () {
}); });
} }
request.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/uninstall') superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/uninstall')
.send({ password: PASSWORD }) .send({ password: PASSWORD })
.query({ access_token: token }) .query({ access_token: token })
.end(function (err, res) { .end(function (err, res) {
+10 -14
View File
@@ -11,7 +11,7 @@ var appdb = require('../../appdb.js'),
config = require('../../config.js'), config = require('../../config.js'),
database = require('../../database.js'), database = require('../../database.js'),
expect = require('expect.js'), expect = require('expect.js'),
request = require('superagent'), superagent = require('superagent'),
server = require('../../server.js'), server = require('../../server.js'),
settings = require('../../settings.js'), settings = require('../../settings.js'),
nock = require('nock'), nock = require('nock'),
@@ -33,11 +33,10 @@ function setup(done) {
var scope1 = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/setup/verify?setupToken=somesetuptoken').reply(200, {}); var scope1 = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/setup/verify?setupToken=somesetuptoken').reply(200, {});
var scope2 = nock(config.apiServerOrigin()).post('/api/v1/boxes/' + config.fqdn() + '/setup/done?setupToken=somesetuptoken').reply(201, {}); var scope2 = nock(config.apiServerOrigin()).post('/api/v1/boxes/' + config.fqdn() + '/setup/done?setupToken=somesetuptoken').reply(201, {});
request.post(SERVER_URL + '/api/v1/cloudron/activate') superagent.post(SERVER_URL + '/api/v1/cloudron/activate')
.query({ setupToken: 'somesetuptoken' }) .query({ setupToken: 'somesetuptoken' })
.send({ username: USERNAME, password: PASSWORD, email: EMAIL }) .send({ username: USERNAME, password: PASSWORD, email: EMAIL })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result).to.be.ok(); expect(result).to.be.ok();
expect(result.statusCode).to.eql(201); expect(result.statusCode).to.eql(201);
expect(scope1.isDone()).to.be.ok(); expect(scope1.isDone()).to.be.ok();
@@ -74,22 +73,22 @@ describe('Backups API', function () {
after(cleanup); after(cleanup);
describe('get', function () { describe('get', function () {
it('cannot get backups with appstore request failing', function (done) { it('cannot get backups with appstore superagent failing', function (done) {
var req = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/backups?token=BACKUP_TOKEN').reply(401, {}); var req = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/backups?token=BACKUP_TOKEN').reply(401, {});
request.get(SERVER_URL + '/api/v1/backups') superagent.get(SERVER_URL + '/api/v1/backups')
.query({ access_token: token }) .query({ access_token: token })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(503); expect(res.statusCode).to.equal(503);
expect(req.isDone()).to.be.ok(); expect(req.isDone()).to.be.ok();
done(err); done();
}); });
}); });
it('can get backups', function (done) { it('can get backups', function (done) {
var req = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/backups?token=BACKUP_TOKEN').reply(200, { backups: ['foo', 'bar']}); var req = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/backups?token=BACKUP_TOKEN').reply(200, { backups: ['foo', 'bar']});
request.get(SERVER_URL + '/api/v1/backups') superagent.get(SERVER_URL + '/api/v1/backups')
.query({ access_token: token }) .query({ access_token: token })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(200); expect(res.statusCode).to.equal(200);
@@ -97,26 +96,24 @@ describe('Backups API', function () {
expect(res.body.backups).to.be.an(Array); expect(res.body.backups).to.be.an(Array);
expect(res.body.backups[0]).to.eql('foo'); expect(res.body.backups[0]).to.eql('foo');
expect(res.body.backups[1]).to.eql('bar'); expect(res.body.backups[1]).to.eql('bar');
done(err); done();
}); });
}); });
}); });
describe('create', function () { describe('create', function () {
it('fails due to mising token', function (done) { it('fails due to mising token', function (done) {
request.post(SERVER_URL + '/api/v1/backups') superagent.post(SERVER_URL + '/api/v1/backups')
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
}); });
it('fails due to wrong token', function (done) { it('fails due to wrong token', function (done) {
request.post(SERVER_URL + '/api/v1/backups') superagent.post(SERVER_URL + '/api/v1/backups')
.query({ access_token: token.toUpperCase() }) .query({ access_token: token.toUpperCase() })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
@@ -127,10 +124,9 @@ describe('Backups API', function () {
.post('/api/v1/boxes/' + config.fqdn() + '/awscredentials?token=BACKUP_TOKEN') .post('/api/v1/boxes/' + config.fqdn() + '/awscredentials?token=BACKUP_TOKEN')
.reply(201, { credentials: { AccessKeyId: 'accessKeyId', SecretAccessKey: 'secretAccessKey', SessionToken: 'sessionToken' } }); .reply(201, { credentials: { AccessKeyId: 'accessKeyId', SecretAccessKey: 'secretAccessKey', SessionToken: 'sessionToken' } });
request.post(SERVER_URL + '/api/v1/backups') superagent.post(SERVER_URL + '/api/v1/backups')
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(202); expect(result.statusCode).to.equal(202);
function checkAppstoreServerCalled() { function checkAppstoreServerCalled() {
-41
View File
@@ -46,7 +46,6 @@ describe('OAuth Clients API', function () {
.query({ setupToken: 'somesetuptoken' }) .query({ setupToken: 'somesetuptoken' })
.send({ username: USERNAME, password: PASSWORD, email: EMAIL }) .send({ username: USERNAME, password: PASSWORD, email: EMAIL })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result).to.be.ok(); expect(result).to.be.ok();
expect(result.statusCode).to.equal(201); expect(result.statusCode).to.equal(201);
expect(scope1.isDone()).to.be.ok(); expect(scope1.isDone()).to.be.ok();
@@ -73,7 +72,6 @@ describe('OAuth Clients API', function () {
.query({ access_token: token }) .query({ access_token: token })
.send({ appId: 'someApp', redirectURI: 'http://foobar.com', scope: 'profile' }) .send({ appId: 'someApp', redirectURI: 'http://foobar.com', scope: 'profile' })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(412); expect(result.statusCode).to.equal(412);
done(); done();
}); });
@@ -89,7 +87,6 @@ describe('OAuth Clients API', function () {
superagent.post(SERVER_URL + '/api/v1/oauth/clients') superagent.post(SERVER_URL + '/api/v1/oauth/clients')
.send({ appId: 'someApp', redirectURI: 'http://foobar.com', scope: 'profile' }) .send({ appId: 'someApp', redirectURI: 'http://foobar.com', scope: 'profile' })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
@@ -100,7 +97,6 @@ describe('OAuth Clients API', function () {
.query({ access_token: token }) .query({ access_token: token })
.send({ redirectURI: 'http://foobar.com', scope: 'profile' }) .send({ redirectURI: 'http://foobar.com', scope: 'profile' })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(); done();
}); });
@@ -111,7 +107,6 @@ describe('OAuth Clients API', function () {
.query({ access_token: token }) .query({ access_token: token })
.send({ appId: '', redirectURI: 'http://foobar.com', scope: 'profile' }) .send({ appId: '', redirectURI: 'http://foobar.com', scope: 'profile' })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(); done();
}); });
@@ -122,7 +117,6 @@ describe('OAuth Clients API', function () {
.query({ access_token: token }) .query({ access_token: token })
.send({ appId: 'someApp', redirectURI: 'http://foobar.com' }) .send({ appId: 'someApp', redirectURI: 'http://foobar.com' })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(); done();
}); });
@@ -133,7 +127,6 @@ describe('OAuth Clients API', function () {
.query({ access_token: token }) .query({ access_token: token })
.send({ appId: 'someApp', redirectURI: 'http://foobar.com', scope: '' }) .send({ appId: 'someApp', redirectURI: 'http://foobar.com', scope: '' })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(); done();
}); });
@@ -144,7 +137,6 @@ describe('OAuth Clients API', function () {
.query({ access_token: token }) .query({ access_token: token })
.send({ appId: 'someApp', scope: 'profile' }) .send({ appId: 'someApp', scope: 'profile' })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(); done();
}); });
@@ -155,7 +147,6 @@ describe('OAuth Clients API', function () {
.query({ access_token: token }) .query({ access_token: token })
.send({ appId: 'someApp', redirectURI: '', scope: 'profile' }) .send({ appId: 'someApp', redirectURI: '', scope: 'profile' })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(); done();
}); });
@@ -166,7 +157,6 @@ describe('OAuth Clients API', function () {
.query({ access_token: token }) .query({ access_token: token })
.send({ appId: 'someApp', redirectURI: 'foobar', scope: 'profile' }) .send({ appId: 'someApp', redirectURI: 'foobar', scope: 'profile' })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(); done();
}); });
@@ -177,7 +167,6 @@ describe('OAuth Clients API', function () {
.query({ access_token: token }) .query({ access_token: token })
.send({ appId: 'someApp', redirectURI: 'http://foobar.com', scope: 'profile' }) .send({ appId: 'someApp', redirectURI: 'http://foobar.com', scope: 'profile' })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(201); expect(result.statusCode).to.equal(201);
expect(result.body.id).to.be.a('string'); expect(result.body.id).to.be.a('string');
expect(result.body.appId).to.be.a('string'); expect(result.body.appId).to.be.a('string');
@@ -211,7 +200,6 @@ describe('OAuth Clients API', function () {
.query({ setupToken: 'somesetuptoken' }) .query({ setupToken: 'somesetuptoken' })
.send({ username: USERNAME, password: PASSWORD, email: EMAIL }) .send({ username: USERNAME, password: PASSWORD, email: EMAIL })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result).to.be.ok(); expect(result).to.be.ok();
expect(scope1.isDone()).to.be.ok(); expect(scope1.isDone()).to.be.ok();
expect(scope2.isDone()).to.be.ok(); expect(scope2.isDone()).to.be.ok();
@@ -230,7 +218,6 @@ describe('OAuth Clients API', function () {
.query({ access_token: token }) .query({ access_token: token })
.send({ appId: CLIENT_0.appId, redirectURI: CLIENT_0.redirectURI, scope: CLIENT_0.scope }) .send({ appId: CLIENT_0.appId, redirectURI: CLIENT_0.redirectURI, scope: CLIENT_0.scope })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(201); expect(result.statusCode).to.equal(201);
CLIENT_0 = result.body; CLIENT_0 = result.body;
@@ -252,7 +239,6 @@ describe('OAuth Clients API', function () {
superagent.get(SERVER_URL + '/api/v1/oauth/clients/' + CLIENT_0.id) superagent.get(SERVER_URL + '/api/v1/oauth/clients/' + CLIENT_0.id)
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(412); expect(result.statusCode).to.equal(412);
done(); done();
}); });
@@ -267,7 +253,6 @@ describe('OAuth Clients API', function () {
it('fails without token', function (done) { it('fails without token', function (done) {
superagent.get(SERVER_URL + '/api/v1/oauth/clients/' + CLIENT_0.id) superagent.get(SERVER_URL + '/api/v1/oauth/clients/' + CLIENT_0.id)
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
@@ -278,7 +263,6 @@ describe('OAuth Clients API', function () {
superagent.get(SERVER_URL + '/api/v1/oauth/clients/' + CLIENT_0.id.toUpperCase()) superagent.get(SERVER_URL + '/api/v1/oauth/clients/' + CLIENT_0.id.toUpperCase())
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(404); expect(result.statusCode).to.equal(404);
done(); done();
}); });
@@ -288,7 +272,6 @@ describe('OAuth Clients API', function () {
superagent.get(SERVER_URL + '/api/v1/oauth/clients/' + CLIENT_0.id) superagent.get(SERVER_URL + '/api/v1/oauth/clients/' + CLIENT_0.id)
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(200); expect(result.statusCode).to.equal(200);
expect(result.body).to.eql(CLIENT_0); expect(result.body).to.eql(CLIENT_0);
done(); done();
@@ -318,7 +301,6 @@ describe('OAuth Clients API', function () {
.query({ setupToken: 'somesetuptoken' }) .query({ setupToken: 'somesetuptoken' })
.send({ username: USERNAME, password: PASSWORD, email: EMAIL }) .send({ username: USERNAME, password: PASSWORD, email: EMAIL })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result).to.be.ok(); expect(result).to.be.ok();
expect(scope1.isDone()).to.be.ok(); expect(scope1.isDone()).to.be.ok();
expect(scope2.isDone()).to.be.ok(); expect(scope2.isDone()).to.be.ok();
@@ -337,7 +319,6 @@ describe('OAuth Clients API', function () {
.query({ access_token: token }) .query({ access_token: token })
.send({ appId: CLIENT_0.appId, redirectURI: CLIENT_0.redirectURI, scope: CLIENT_0.scope }) .send({ appId: CLIENT_0.appId, redirectURI: CLIENT_0.redirectURI, scope: CLIENT_0.scope })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(201); expect(result.statusCode).to.equal(201);
CLIENT_0 = result.body; CLIENT_0 = result.body;
@@ -359,7 +340,6 @@ describe('OAuth Clients API', function () {
superagent.del(SERVER_URL + '/api/v1/oauth/clients/' + CLIENT_0.id) superagent.del(SERVER_URL + '/api/v1/oauth/clients/' + CLIENT_0.id)
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(412); expect(result.statusCode).to.equal(412);
done(); done();
}); });
@@ -374,7 +354,6 @@ describe('OAuth Clients API', function () {
it('fails without token', function (done) { it('fails without token', function (done) {
superagent.del(SERVER_URL + '/api/v1/oauth/clients/' + CLIENT_0.id) superagent.del(SERVER_URL + '/api/v1/oauth/clients/' + CLIENT_0.id)
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
@@ -385,7 +364,6 @@ describe('OAuth Clients API', function () {
superagent.del(SERVER_URL + '/api/v1/oauth/clients/' + CLIENT_0.id.toUpperCase()) superagent.del(SERVER_URL + '/api/v1/oauth/clients/' + CLIENT_0.id.toUpperCase())
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(404); expect(result.statusCode).to.equal(404);
done(); done();
}); });
@@ -395,13 +373,11 @@ describe('OAuth Clients API', function () {
superagent.del(SERVER_URL + '/api/v1/oauth/clients/' + CLIENT_0.id) superagent.del(SERVER_URL + '/api/v1/oauth/clients/' + CLIENT_0.id)
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(204); expect(result.statusCode).to.equal(204);
superagent.get(SERVER_URL + '/api/v1/oauth/clients/' + CLIENT_0.id) superagent.get(SERVER_URL + '/api/v1/oauth/clients/' + CLIENT_0.id)
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.be(null);
expect(result.statusCode).to.equal(404); expect(result.statusCode).to.equal(404);
done(); done();
@@ -443,7 +419,6 @@ describe('Clients', function () {
.query({ setupToken: 'somesetuptoken' }) .query({ setupToken: 'somesetuptoken' })
.send({ username: USER_0.username, password: USER_0.password, email: USER_0.email }) .send({ username: USER_0.username, password: USER_0.password, email: USER_0.email })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result).to.be.ok(); expect(result).to.be.ok();
expect(result.statusCode).to.eql(201); expect(result.statusCode).to.eql(201);
expect(scope1.isDone()).to.be.ok(); expect(scope1.isDone()).to.be.ok();
@@ -473,7 +448,6 @@ describe('Clients', function () {
it('fails due to missing token', function (done) { it('fails due to missing token', function (done) {
superagent.get(SERVER_URL + '/api/v1/oauth/clients') superagent.get(SERVER_URL + '/api/v1/oauth/clients')
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
@@ -483,7 +457,6 @@ describe('Clients', function () {
superagent.get(SERVER_URL + '/api/v1/oauth/clients') superagent.get(SERVER_URL + '/api/v1/oauth/clients')
.query({ access_token: '' }) .query({ access_token: '' })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
@@ -493,7 +466,6 @@ describe('Clients', function () {
superagent.get(SERVER_URL + '/api/v1/oauth/clients') superagent.get(SERVER_URL + '/api/v1/oauth/clients')
.query({ access_token: token.toUpperCase() }) .query({ access_token: token.toUpperCase() })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
@@ -503,7 +475,6 @@ describe('Clients', function () {
superagent.get(SERVER_URL + '/api/v1/oauth/clients') superagent.get(SERVER_URL + '/api/v1/oauth/clients')
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(200); expect(result.statusCode).to.equal(200);
expect(result.body.clients.length).to.eql(1); expect(result.body.clients.length).to.eql(1);
@@ -521,7 +492,6 @@ describe('Clients', function () {
it('fails due to missing token', function (done) { it('fails due to missing token', function (done) {
superagent.get(SERVER_URL + '/api/v1/oauth/clients/cid-webadmin/tokens') superagent.get(SERVER_URL + '/api/v1/oauth/clients/cid-webadmin/tokens')
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
@@ -531,7 +501,6 @@ describe('Clients', function () {
superagent.get(SERVER_URL + '/api/v1/oauth/clients/cid-webadmin/tokens') superagent.get(SERVER_URL + '/api/v1/oauth/clients/cid-webadmin/tokens')
.query({ access_token: '' }) .query({ access_token: '' })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
@@ -541,7 +510,6 @@ describe('Clients', function () {
superagent.get(SERVER_URL + '/api/v1/oauth/clients/cid-webadmin/tokens') superagent.get(SERVER_URL + '/api/v1/oauth/clients/cid-webadmin/tokens')
.query({ access_token: token.toUpperCase() }) .query({ access_token: token.toUpperCase() })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
@@ -551,7 +519,6 @@ describe('Clients', function () {
superagent.get(SERVER_URL + '/api/v1/oauth/clients/CID-WEBADMIN/tokens') superagent.get(SERVER_URL + '/api/v1/oauth/clients/CID-WEBADMIN/tokens')
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(404); expect(result.statusCode).to.equal(404);
done(); done();
}); });
@@ -561,7 +528,6 @@ describe('Clients', function () {
superagent.get(SERVER_URL + '/api/v1/oauth/clients/cid-webadmin/tokens') superagent.get(SERVER_URL + '/api/v1/oauth/clients/cid-webadmin/tokens')
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(200); expect(result.statusCode).to.equal(200);
expect(result.body.tokens.length).to.eql(1); expect(result.body.tokens.length).to.eql(1);
@@ -579,7 +545,6 @@ describe('Clients', function () {
it('fails due to missing token', function (done) { it('fails due to missing token', function (done) {
superagent.del(SERVER_URL + '/api/v1/oauth/clients/cid-webadmin/tokens') superagent.del(SERVER_URL + '/api/v1/oauth/clients/cid-webadmin/tokens')
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
@@ -589,7 +554,6 @@ describe('Clients', function () {
superagent.del(SERVER_URL + '/api/v1/oauth/clients/cid-webadmin/tokens') superagent.del(SERVER_URL + '/api/v1/oauth/clients/cid-webadmin/tokens')
.query({ access_token: '' }) .query({ access_token: '' })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
@@ -599,7 +563,6 @@ describe('Clients', function () {
superagent.del(SERVER_URL + '/api/v1/oauth/clients/cid-webadmin/tokens') superagent.del(SERVER_URL + '/api/v1/oauth/clients/cid-webadmin/tokens')
.query({ access_token: token.toUpperCase() }) .query({ access_token: token.toUpperCase() })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
@@ -609,7 +572,6 @@ describe('Clients', function () {
superagent.del(SERVER_URL + '/api/v1/oauth/clients/CID-WEBADMIN/tokens') superagent.del(SERVER_URL + '/api/v1/oauth/clients/CID-WEBADMIN/tokens')
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(404); expect(result.statusCode).to.equal(404);
done(); done();
}); });
@@ -619,7 +581,6 @@ describe('Clients', function () {
superagent.get(SERVER_URL + '/api/v1/oauth/clients/cid-webadmin/tokens') superagent.get(SERVER_URL + '/api/v1/oauth/clients/cid-webadmin/tokens')
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(200); expect(result.statusCode).to.equal(200);
expect(result.body.tokens.length).to.eql(1); expect(result.body.tokens.length).to.eql(1);
@@ -628,14 +589,12 @@ describe('Clients', function () {
superagent.del(SERVER_URL + '/api/v1/oauth/clients/cid-webadmin/tokens') superagent.del(SERVER_URL + '/api/v1/oauth/clients/cid-webadmin/tokens')
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(204); expect(result.statusCode).to.equal(204);
// further calls with this token should not work // further calls with this token should not work
superagent.get(SERVER_URL + '/api/v1/profile') superagent.get(SERVER_URL + '/api/v1/profile')
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
+35 -71
View File
@@ -11,7 +11,7 @@ var async = require('async'),
database = require('../../database.js'), database = require('../../database.js'),
expect = require('expect.js'), expect = require('expect.js'),
nock = require('nock'), nock = require('nock'),
request = require('superagent'), superagent = require('superagent'),
server = require('../../server.js'), server = require('../../server.js'),
shell = require('../../shell.js'); shell = require('../../shell.js');
@@ -54,10 +54,9 @@ describe('Cloudron', function () {
after(cleanup); after(cleanup);
it('fails due to missing setupToken', function (done) { it('fails due to missing setupToken', function (done) {
request.post(SERVER_URL + '/api/v1/cloudron/activate') superagent.post(SERVER_URL + '/api/v1/cloudron/activate')
.send({ username: '', password: 'somepassword', email: 'admin@foo.bar' }) .send({ username: '', password: 'somepassword', email: 'admin@foo.bar' })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(); done();
}); });
@@ -66,11 +65,10 @@ describe('Cloudron', function () {
it('fails due to empty username', function (done) { it('fails due to empty username', function (done) {
var scope = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/setup/verify?setupToken=somesetuptoken').reply(200, {}); var scope = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/setup/verify?setupToken=somesetuptoken').reply(200, {});
request.post(SERVER_URL + '/api/v1/cloudron/activate') superagent.post(SERVER_URL + '/api/v1/cloudron/activate')
.query({ setupToken: 'somesetuptoken' }) .query({ setupToken: 'somesetuptoken' })
.send({ username: '', password: 'somepassword', email: 'admin@foo.bar' }) .send({ username: '', password: 'somepassword', email: 'admin@foo.bar' })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
expect(scope.isDone()).to.be.ok(); expect(scope.isDone()).to.be.ok();
done(); done();
@@ -80,11 +78,10 @@ describe('Cloudron', function () {
it('fails due to empty password', function (done) { it('fails due to empty password', function (done) {
var scope = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/setup/verify?setupToken=somesetuptoken').reply(200, {}); var scope = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/setup/verify?setupToken=somesetuptoken').reply(200, {});
request.post(SERVER_URL + '/api/v1/cloudron/activate') superagent.post(SERVER_URL + '/api/v1/cloudron/activate')
.query({ setupToken: 'somesetuptoken' }) .query({ setupToken: 'somesetuptoken' })
.send({ username: 'someuser', password: '', email: 'admin@foo.bar' }) .send({ username: 'someuser', password: '', email: 'admin@foo.bar' })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
expect(scope.isDone()).to.be.ok(); expect(scope.isDone()).to.be.ok();
done(); done();
@@ -94,11 +91,10 @@ describe('Cloudron', function () {
it('fails due to empty email', function (done) { it('fails due to empty email', function (done) {
var scope = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/setup/verify?setupToken=somesetuptoken').reply(200, {}); var scope = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/setup/verify?setupToken=somesetuptoken').reply(200, {});
request.post(SERVER_URL + '/api/v1/cloudron/activate') superagent.post(SERVER_URL + '/api/v1/cloudron/activate')
.query({ setupToken: 'somesetuptoken' }) .query({ setupToken: 'somesetuptoken' })
.send({ username: 'someuser', password: 'somepassword', email: '' }) .send({ username: 'someuser', password: 'somepassword', email: '' })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
expect(scope.isDone()).to.be.ok(); expect(scope.isDone()).to.be.ok();
done(); done();
@@ -108,11 +104,10 @@ describe('Cloudron', function () {
it('fails due to empty name', function (done) { it('fails due to empty name', function (done) {
var scope = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/setup/verify?setupToken=somesetuptoken').reply(200, {}); var scope = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/setup/verify?setupToken=somesetuptoken').reply(200, {});
request.post(SERVER_URL + '/api/v1/cloudron/activate') superagent.post(SERVER_URL + '/api/v1/cloudron/activate')
.query({ setupToken: 'somesetuptoken' }) .query({ setupToken: 'somesetuptoken' })
.send({ username: 'someuser', password: '', email: 'admin@foo.bar', name: '' }) .send({ username: 'someuser', password: '', email: 'admin@foo.bar', name: '' })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
expect(scope.isDone()).to.be.ok(); expect(scope.isDone()).to.be.ok();
done(); done();
@@ -122,11 +117,10 @@ describe('Cloudron', function () {
it('fails due to invalid email', function (done) { it('fails due to invalid email', function (done) {
var scope = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/setup/verify?setupToken=somesetuptoken').reply(200, {}); var scope = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/setup/verify?setupToken=somesetuptoken').reply(200, {});
request.post(SERVER_URL + '/api/v1/cloudron/activate') superagent.post(SERVER_URL + '/api/v1/cloudron/activate')
.query({ setupToken: 'somesetuptoken' }) .query({ setupToken: 'somesetuptoken' })
.send({ username: 'someuser', password: 'somepassword', email: 'invalidemail' }) .send({ username: 'someuser', password: 'somepassword', email: 'invalidemail' })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
expect(scope.isDone()).to.be.ok(); expect(scope.isDone()).to.be.ok();
done(); done();
@@ -137,11 +131,10 @@ describe('Cloudron', function () {
var scope1 = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/setup/verify?setupToken=somesetuptoken').reply(200, {}); var scope1 = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/setup/verify?setupToken=somesetuptoken').reply(200, {});
var scope2 = nock(config.apiServerOrigin()).post('/api/v1/boxes/' + config.fqdn() + '/setup/done?setupToken=somesetuptoken').reply(201, {}); var scope2 = nock(config.apiServerOrigin()).post('/api/v1/boxes/' + config.fqdn() + '/setup/done?setupToken=somesetuptoken').reply(201, {});
request.post(SERVER_URL + '/api/v1/cloudron/activate') superagent.post(SERVER_URL + '/api/v1/cloudron/activate')
.query({ setupToken: 'somesetuptoken' }) .query({ setupToken: 'somesetuptoken' })
.send({ username: 'someuser', password: 'somepassword', email: 'admin@foo.bar', name: 'tester' }) .send({ username: 'someuser', password: 'somepassword', email: 'admin@foo.bar', name: 'tester' })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(201); expect(result.statusCode).to.equal(201);
expect(scope1.isDone()).to.be.ok(); expect(scope1.isDone()).to.be.ok();
expect(scope2.isDone()).to.be.ok(); expect(scope2.isDone()).to.be.ok();
@@ -152,11 +145,10 @@ describe('Cloudron', function () {
it('fails the second time', function (done) { it('fails the second time', function (done) {
var scope = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/setup/verify?setupToken=somesetuptoken').reply(200, {}); var scope = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/setup/verify?setupToken=somesetuptoken').reply(200, {});
request.post(SERVER_URL + '/api/v1/cloudron/activate') superagent.post(SERVER_URL + '/api/v1/cloudron/activate')
.query({ setupToken: 'somesetuptoken' }) .query({ setupToken: 'somesetuptoken' })
.send({ username: 'someuser', password: 'somepassword', email: 'admin@foo.bar' }) .send({ username: 'someuser', password: 'somepassword', email: 'admin@foo.bar' })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(409); expect(result.statusCode).to.equal(409);
expect(scope.isDone()).to.be.ok(); expect(scope.isDone()).to.be.ok();
done(); done();
@@ -175,11 +167,10 @@ describe('Cloudron', function () {
config._reset(); config._reset();
request.post(SERVER_URL + '/api/v1/cloudron/activate') superagent.post(SERVER_URL + '/api/v1/cloudron/activate')
.query({ setupToken: 'somesetuptoken' }) .query({ setupToken: 'somesetuptoken' })
.send({ username: USERNAME, password: PASSWORD, email: EMAIL }) .send({ username: USERNAME, password: PASSWORD, email: EMAIL })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result).to.be.ok(); expect(result).to.be.ok();
expect(scope1.isDone()).to.be.ok(); expect(scope1.isDone()).to.be.ok();
expect(scope2.isDone()).to.be.ok(); expect(scope2.isDone()).to.be.ok();
@@ -196,19 +187,17 @@ describe('Cloudron', function () {
after(cleanup); after(cleanup);
it('cannot get without token', function (done) { it('cannot get without token', function (done) {
request.get(SERVER_URL + '/api/v1/cloudron/config') superagent.get(SERVER_URL + '/api/v1/cloudron/config')
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
}); });
it('succeeds without appstore', function (done) { it('succeeds without appstore', function (done) {
request.get(SERVER_URL + '/api/v1/cloudron/config') superagent.get(SERVER_URL + '/api/v1/cloudron/config')
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(200); expect(result.statusCode).to.equal(200);
expect(result.body.apiServerOrigin).to.eql('http://localhost:6060'); expect(result.body.apiServerOrigin).to.eql('http://localhost:6060');
expect(result.body.webServerOrigin).to.eql(null); expect(result.body.webServerOrigin).to.eql(null);
@@ -230,10 +219,9 @@ describe('Cloudron', function () {
it('succeeds', function (done) { it('succeeds', function (done) {
var scope = nock(config.apiServerOrigin()).get('/api/v1/boxes/localhost?token=' + config.token()).reply(200, { box: { region: 'sfo', size: '1gb' }}); var scope = nock(config.apiServerOrigin()).get('/api/v1/boxes/localhost?token=' + config.token()).reply(200, { box: { region: 'sfo', size: '1gb' }});
request.get(SERVER_URL + '/api/v1/cloudron/config') superagent.get(SERVER_URL + '/api/v1/cloudron/config')
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(200); expect(result.statusCode).to.equal(200);
expect(result.body.apiServerOrigin).to.eql('http://localhost:6060'); expect(result.body.apiServerOrigin).to.eql('http://localhost:6060');
expect(result.body.webServerOrigin).to.eql(null); expect(result.body.webServerOrigin).to.eql(null);
@@ -267,11 +255,10 @@ describe('Cloudron', function () {
config._reset(); config._reset();
request.post(SERVER_URL + '/api/v1/cloudron/activate') superagent.post(SERVER_URL + '/api/v1/cloudron/activate')
.query({ setupToken: 'somesetuptoken' }) .query({ setupToken: 'somesetuptoken' })
.send({ username: USERNAME, password: PASSWORD, email: EMAIL }) .send({ username: USERNAME, password: PASSWORD, email: EMAIL })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result).to.be.ok(); expect(result).to.be.ok();
expect(scope1.isDone()).to.be.ok(); expect(scope1.isDone()).to.be.ok();
expect(scope2.isDone()).to.be.ok(); expect(scope2.isDone()).to.be.ok();
@@ -284,11 +271,10 @@ describe('Cloudron', function () {
}, },
function setupBackupConfig(callback) { function setupBackupConfig(callback) {
request.post(SERVER_URL + '/api/v1/settings/backup_config') superagent.post(SERVER_URL + '/api/v1/settings/backup_config')
.send({ provider: 'caas', token: 'BACKUP_TOKEN', bucket: 'Bucket', prefix: 'Prefix' }) .send({ provider: 'caas', token: 'BACKUP_TOKEN', bucket: 'Bucket', prefix: 'Prefix' })
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(200); expect(result.statusCode).to.equal(200);
callback(); callback();
@@ -301,65 +287,59 @@ describe('Cloudron', function () {
after(cleanup); after(cleanup);
it('fails without token', function (done) { it('fails without token', function (done) {
request.post(SERVER_URL + '/api/v1/cloudron/migrate') superagent.post(SERVER_URL + '/api/v1/cloudron/migrate')
.send({ size: 'small', region: 'sfo'}) .send({ size: 'small', region: 'sfo'})
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
}); });
it('fails without password', function (done) { it('fails without password', function (done) {
request.post(SERVER_URL + '/api/v1/cloudron/migrate') superagent.post(SERVER_URL + '/api/v1/cloudron/migrate')
.send({ size: 'small', region: 'sfo'}) .send({ size: 'small', region: 'sfo'})
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(); done();
}); });
}); });
it('fails with missing size', function (done) { it('fails with missing size', function (done) {
request.post(SERVER_URL + '/api/v1/cloudron/migrate') superagent.post(SERVER_URL + '/api/v1/cloudron/migrate')
.send({ region: 'sfo', password: PASSWORD }) .send({ region: 'sfo', password: PASSWORD })
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(); done();
}); });
}); });
it('fails with wrong size type', function (done) { it('fails with wrong size type', function (done) {
request.post(SERVER_URL + '/api/v1/cloudron/migrate') superagent.post(SERVER_URL + '/api/v1/cloudron/migrate')
.send({ size: 4, region: 'sfo', password: PASSWORD }) .send({ size: 4, region: 'sfo', password: PASSWORD })
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(); done();
}); });
}); });
it('fails with missing region', function (done) { it('fails with missing region', function (done) {
request.post(SERVER_URL + '/api/v1/cloudron/migrate') superagent.post(SERVER_URL + '/api/v1/cloudron/migrate')
.send({ size: 'small', password: PASSWORD }) .send({ size: 'small', password: PASSWORD })
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(); done();
}); });
}); });
it('fails with wrong region type', function (done) { it('fails with wrong region type', function (done) {
request.post(SERVER_URL + '/api/v1/cloudron/migrate') superagent.post(SERVER_URL + '/api/v1/cloudron/migrate')
.send({ size: 'small', region: 4, password: PASSWORD }) .send({ size: 'small', region: 4, password: PASSWORD })
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(); done();
}); });
@@ -383,11 +363,10 @@ describe('Cloudron', function () {
injectShellMock(); injectShellMock();
request.post(SERVER_URL + '/api/v1/cloudron/migrate') superagent.post(SERVER_URL + '/api/v1/cloudron/migrate')
.send({ size: 'small', region: 'sfo', password: PASSWORD }) .send({ size: 'small', region: 'sfo', password: PASSWORD })
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(202); expect(result.statusCode).to.equal(202);
function checkAppstoreServerCalled() { function checkAppstoreServerCalled() {
@@ -420,11 +399,10 @@ describe('Cloudron', function () {
injectShellMock(); injectShellMock();
request.post(SERVER_URL + '/api/v1/cloudron/migrate') superagent.post(SERVER_URL + '/api/v1/cloudron/migrate')
.send({ size: 'small', region: 'sfo', password: PASSWORD }) .send({ size: 'small', region: 'sfo', password: PASSWORD })
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(202); expect(result.statusCode).to.equal(202);
function checkAppstoreServerCalled() { function checkAppstoreServerCalled() {
@@ -452,11 +430,10 @@ describe('Cloudron', function () {
config._reset(); config._reset();
request.post(SERVER_URL + '/api/v1/cloudron/activate') superagent.post(SERVER_URL + '/api/v1/cloudron/activate')
.query({ setupToken: 'somesetuptoken' }) .query({ setupToken: 'somesetuptoken' })
.send({ username: USERNAME, password: PASSWORD, email: EMAIL }) .send({ username: USERNAME, password: PASSWORD, email: EMAIL })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result).to.be.ok(); expect(result).to.be.ok();
expect(scope1.isDone()).to.be.ok(); expect(scope1.isDone()).to.be.ok();
expect(scope2.isDone()).to.be.ok(); expect(scope2.isDone()).to.be.ok();
@@ -473,125 +450,112 @@ describe('Cloudron', function () {
after(cleanup); after(cleanup);
it('fails without token', function (done) { it('fails without token', function (done) {
request.post(SERVER_URL + '/api/v1/cloudron/feedback') superagent.post(SERVER_URL + '/api/v1/cloudron/feedback')
.send({ type: 'ticket', subject: 'some subject', description: 'some description' }) .send({ type: 'ticket', subject: 'some subject', description: 'some description' })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
}); });
it('fails without type', function (done) { it('fails without type', function (done) {
request.post(SERVER_URL + '/api/v1/cloudron/feedback') superagent.post(SERVER_URL + '/api/v1/cloudron/feedback')
.send({ subject: 'some subject', description: 'some description' }) .send({ subject: 'some subject', description: 'some description' })
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(); done();
}); });
}); });
it('fails with empty type', function (done) { it('fails with empty type', function (done) {
request.post(SERVER_URL + '/api/v1/cloudron/feedback') superagent.post(SERVER_URL + '/api/v1/cloudron/feedback')
.send({ type: '', subject: 'some subject', description: 'some description' }) .send({ type: '', subject: 'some subject', description: 'some description' })
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(); done();
}); });
}); });
it('fails with unknown type', function (done) { it('fails with unknown type', function (done) {
request.post(SERVER_URL + '/api/v1/cloudron/feedback') superagent.post(SERVER_URL + '/api/v1/cloudron/feedback')
.send({ type: 'foobar', subject: 'some subject', description: 'some description' }) .send({ type: 'foobar', subject: 'some subject', description: 'some description' })
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(); done();
}); });
}); });
it('succeeds with ticket type', function (done) { it('succeeds with ticket type', function (done) {
request.post(SERVER_URL + '/api/v1/cloudron/feedback') superagent.post(SERVER_URL + '/api/v1/cloudron/feedback')
.send({ type: 'ticket', subject: 'some subject', description: 'some description' }) .send({ type: 'ticket', subject: 'some subject', description: 'some description' })
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(201); expect(result.statusCode).to.equal(201);
done(); done();
}); });
}); });
it('succeeds with app type', function (done) { it('succeeds with app type', function (done) {
request.post(SERVER_URL + '/api/v1/cloudron/feedback') superagent.post(SERVER_URL + '/api/v1/cloudron/feedback')
.send({ type: 'app', subject: 'some subject', description: 'some description' }) .send({ type: 'app', subject: 'some subject', description: 'some description' })
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(201); expect(result.statusCode).to.equal(201);
done(); done();
}); });
}); });
it('fails without description', function (done) { it('fails without description', function (done) {
request.post(SERVER_URL + '/api/v1/cloudron/feedback') superagent.post(SERVER_URL + '/api/v1/cloudron/feedback')
.send({ type: 'ticket', subject: 'some subject' }) .send({ type: 'ticket', subject: 'some subject' })
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(); done();
}); });
}); });
it('fails with empty subject', function (done) { it('fails with empty subject', function (done) {
request.post(SERVER_URL + '/api/v1/cloudron/feedback') superagent.post(SERVER_URL + '/api/v1/cloudron/feedback')
.send({ type: 'ticket', subject: '', description: 'some description' }) .send({ type: 'ticket', subject: '', description: 'some description' })
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(); done();
}); });
}); });
it('fails with empty description', function (done) { it('fails with empty description', function (done) {
request.post(SERVER_URL + '/api/v1/cloudron/feedback') superagent.post(SERVER_URL + '/api/v1/cloudron/feedback')
.send({ type: 'ticket', subject: 'some subject', description: '' }) .send({ type: 'ticket', subject: 'some subject', description: '' })
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(); done();
}); });
}); });
it('succeeds with feedback type', function (done) { it('succeeds with feedback type', function (done) {
request.post(SERVER_URL + '/api/v1/cloudron/feedback') superagent.post(SERVER_URL + '/api/v1/cloudron/feedback')
.send({ type: 'feedback', subject: 'some subject', description: 'some description' }) .send({ type: 'feedback', subject: 'some subject', description: 'some description' })
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(201); expect(result.statusCode).to.equal(201);
done(); done();
}); });
}); });
it('fails without subject', function (done) { it('fails without subject', function (done) {
request.post(SERVER_URL + '/api/v1/cloudron/feedback') superagent.post(SERVER_URL + '/api/v1/cloudron/feedback')
.send({ type: 'ticket', description: 'some description' }) .send({ type: 'ticket', description: 'some description' })
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(); done();
}); });
}); });
}); });
}); });
+26 -51
View File
@@ -11,7 +11,7 @@ var async = require('async'),
database = require('../../database.js'), database = require('../../database.js'),
expect = require('expect.js'), expect = require('expect.js'),
nock = require('nock'), nock = require('nock'),
request = require('superagent'), superagent = require('superagent'),
server = require('../../server.js'), server = require('../../server.js'),
settings = require('../../settings.js'); settings = require('../../settings.js');
@@ -43,11 +43,10 @@ describe('Developer API', function () {
var scope1 = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/setup/verify?setupToken=somesetuptoken').reply(200, {}); var scope1 = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/setup/verify?setupToken=somesetuptoken').reply(200, {});
var scope2 = nock(config.apiServerOrigin()).post('/api/v1/boxes/' + config.fqdn() + '/setup/done?setupToken=somesetuptoken').reply(201, {}); var scope2 = nock(config.apiServerOrigin()).post('/api/v1/boxes/' + config.fqdn() + '/setup/done?setupToken=somesetuptoken').reply(201, {});
request.post(SERVER_URL + '/api/v1/cloudron/activate') superagent.post(SERVER_URL + '/api/v1/cloudron/activate')
.query({ setupToken: 'somesetuptoken' }) .query({ setupToken: 'somesetuptoken' })
.send({ username: USERNAME, password: PASSWORD, email: EMAIL }) .send({ username: USERNAME, password: PASSWORD, email: EMAIL })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result).to.be.ok(); expect(result).to.be.ok();
expect(scope1.isDone()).to.be.ok(); expect(scope1.isDone()).to.be.ok();
expect(scope2.isDone()).to.be.ok(); expect(scope2.isDone()).to.be.ok();
@@ -67,9 +66,8 @@ describe('Developer API', function () {
settings.setDeveloperMode(true, function (error) { settings.setDeveloperMode(true, function (error) {
expect(error).to.be(null); expect(error).to.be(null);
request.get(SERVER_URL + '/api/v1/developer') superagent.get(SERVER_URL + '/api/v1/developer')
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
@@ -80,10 +78,9 @@ describe('Developer API', function () {
settings.setDeveloperMode(true, function (error) { settings.setDeveloperMode(true, function (error) {
expect(error).to.be(null); expect(error).to.be(null);
request.get(SERVER_URL + '/api/v1/developer') superagent.get(SERVER_URL + '/api/v1/developer')
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(200); expect(result.statusCode).to.equal(200);
done(); done();
}); });
@@ -94,10 +91,9 @@ describe('Developer API', function () {
settings.setDeveloperMode(false, function (error) { settings.setDeveloperMode(false, function (error) {
expect(error).to.be(null); expect(error).to.be(null);
request.get(SERVER_URL + '/api/v1/developer') superagent.get(SERVER_URL + '/api/v1/developer')
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(412); expect(result.statusCode).to.equal(412);
done(); done();
}); });
@@ -114,11 +110,10 @@ describe('Developer API', function () {
var scope1 = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/setup/verify?setupToken=somesetuptoken').reply(200, {}); var scope1 = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/setup/verify?setupToken=somesetuptoken').reply(200, {});
var scope2 = nock(config.apiServerOrigin()).post('/api/v1/boxes/' + config.fqdn() + '/setup/done?setupToken=somesetuptoken').reply(201, {}); var scope2 = nock(config.apiServerOrigin()).post('/api/v1/boxes/' + config.fqdn() + '/setup/done?setupToken=somesetuptoken').reply(201, {});
request.post(SERVER_URL + '/api/v1/cloudron/activate') superagent.post(SERVER_URL + '/api/v1/cloudron/activate')
.query({ setupToken: 'somesetuptoken' }) .query({ setupToken: 'somesetuptoken' })
.send({ username: USERNAME, password: PASSWORD, email: EMAIL }) .send({ username: USERNAME, password: PASSWORD, email: EMAIL })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result).to.be.ok(); expect(result).to.be.ok();
expect(scope1.isDone()).to.be.ok(); expect(scope1.isDone()).to.be.ok();
expect(scope2.isDone()).to.be.ok(); expect(scope2.isDone()).to.be.ok();
@@ -135,82 +130,74 @@ describe('Developer API', function () {
after(cleanup); after(cleanup);
it('fails without token', function (done) { it('fails without token', function (done) {
request.post(SERVER_URL + '/api/v1/developer') superagent.post(SERVER_URL + '/api/v1/developer')
.send({ enabled: true }) .send({ enabled: true })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
}); });
it('fails due to missing password', function (done) { it('fails due to missing password', function (done) {
request.post(SERVER_URL + '/api/v1/developer') superagent.post(SERVER_URL + '/api/v1/developer')
.query({ access_token: token }) .query({ access_token: token })
.send({ enabled: true }) .send({ enabled: true })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(); done();
}); });
}); });
it('fails due to empty password', function (done) { it('fails due to empty password', function (done) {
request.post(SERVER_URL + '/api/v1/developer') superagent.post(SERVER_URL + '/api/v1/developer')
.query({ access_token: token }) .query({ access_token: token })
.send({ password: '', enabled: true }) .send({ password: '', enabled: true })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(403); expect(result.statusCode).to.equal(403);
done(); done();
}); });
}); });
it('fails due to wrong password', function (done) { it('fails due to wrong password', function (done) {
request.post(SERVER_URL + '/api/v1/developer') superagent.post(SERVER_URL + '/api/v1/developer')
.query({ access_token: token }) .query({ access_token: token })
.send({ password: PASSWORD.toUpperCase(), enabled: true }) .send({ password: PASSWORD.toUpperCase(), enabled: true })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(403); expect(result.statusCode).to.equal(403);
done(); done();
}); });
}); });
it('fails due to missing enabled property', function (done) { it('fails due to missing enabled property', function (done) {
request.post(SERVER_URL + '/api/v1/developer') superagent.post(SERVER_URL + '/api/v1/developer')
.query({ access_token: token }) .query({ access_token: token })
.send({ password: PASSWORD }) .send({ password: PASSWORD })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(); done();
}); });
}); });
it('fails due to wrong enabled property type', function (done) { it('fails due to wrong enabled property type', function (done) {
request.post(SERVER_URL + '/api/v1/developer') superagent.post(SERVER_URL + '/api/v1/developer')
.query({ access_token: token }) .query({ access_token: token })
.send({ password: PASSWORD, enabled: 'true' }) .send({ password: PASSWORD, enabled: 'true' })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(); done();
}); });
}); });
it('succeeds enabling', function (done) { it('succeeds enabling', function (done) {
request.post(SERVER_URL + '/api/v1/developer') superagent.post(SERVER_URL + '/api/v1/developer')
.query({ access_token: token }) .query({ access_token: token })
.send({ password: PASSWORD, enabled: true }) .send({ password: PASSWORD, enabled: true })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(200); expect(result.statusCode).to.equal(200);
request.get(SERVER_URL + '/api/v1/developer') superagent.get(SERVER_URL + '/api/v1/developer')
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(200); expect(result.statusCode).to.equal(200);
done(); done();
}); });
@@ -218,17 +205,15 @@ describe('Developer API', function () {
}); });
it('succeeds disabling', function (done) { it('succeeds disabling', function (done) {
request.post(SERVER_URL + '/api/v1/developer') superagent.post(SERVER_URL + '/api/v1/developer')
.query({ access_token: token }) .query({ access_token: token })
.send({ password: PASSWORD, enabled: false }) .send({ password: PASSWORD, enabled: false })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(200); expect(result.statusCode).to.equal(200);
request.get(SERVER_URL + '/api/v1/developer') superagent.get(SERVER_URL + '/api/v1/developer')
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(412); expect(result.statusCode).to.equal(412);
done(); done();
}); });
@@ -247,11 +232,10 @@ describe('Developer API', function () {
var scope1 = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/setup/verify?setupToken=somesetuptoken').reply(200, {}); var scope1 = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/setup/verify?setupToken=somesetuptoken').reply(200, {});
var scope2 = nock(config.apiServerOrigin()).post('/api/v1/boxes/' + config.fqdn() + '/setup/done?setupToken=somesetuptoken').reply(201, {}); var scope2 = nock(config.apiServerOrigin()).post('/api/v1/boxes/' + config.fqdn() + '/setup/done?setupToken=somesetuptoken').reply(201, {});
request.post(SERVER_URL + '/api/v1/cloudron/activate') superagent.post(SERVER_URL + '/api/v1/cloudron/activate')
.query({ setupToken: 'somesetuptoken' }) .query({ setupToken: 'somesetuptoken' })
.send({ username: USERNAME, password: PASSWORD, email: EMAIL }) .send({ username: USERNAME, password: PASSWORD, email: EMAIL })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result).to.be.ok(); expect(result).to.be.ok();
expect(scope1.isDone()).to.be.ok(); expect(scope1.isDone()).to.be.ok();
expect(scope2.isDone()).to.be.ok(); expect(scope2.isDone()).to.be.ok();
@@ -268,79 +252,71 @@ describe('Developer API', function () {
after(cleanup); after(cleanup);
it('fails without body', function (done) { it('fails without body', function (done) {
request.post(SERVER_URL + '/api/v1/developer/login') superagent.post(SERVER_URL + '/api/v1/developer/login')
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
}); });
it('fails without username', function (done) { it('fails without username', function (done) {
request.post(SERVER_URL + '/api/v1/developer/login') superagent.post(SERVER_URL + '/api/v1/developer/login')
.send({ password: PASSWORD }) .send({ password: PASSWORD })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
}); });
it('fails without password', function (done) { it('fails without password', function (done) {
request.post(SERVER_URL + '/api/v1/developer/login') superagent.post(SERVER_URL + '/api/v1/developer/login')
.send({ username: USERNAME }) .send({ username: USERNAME })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
}); });
it('fails with empty username', function (done) { it('fails with empty username', function (done) {
request.post(SERVER_URL + '/api/v1/developer/login') superagent.post(SERVER_URL + '/api/v1/developer/login')
.send({ username: '', password: PASSWORD }) .send({ username: '', password: PASSWORD })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
}); });
it('fails with empty password', function (done) { it('fails with empty password', function (done) {
request.post(SERVER_URL + '/api/v1/developer/login') superagent.post(SERVER_URL + '/api/v1/developer/login')
.send({ username: USERNAME, password: '' }) .send({ username: USERNAME, password: '' })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
}); });
it('fails with unknown username', function (done) { it('fails with unknown username', function (done) {
request.post(SERVER_URL + '/api/v1/developer/login') superagent.post(SERVER_URL + '/api/v1/developer/login')
.send({ username: USERNAME.toUpperCase(), password: PASSWORD }) .send({ username: USERNAME.toUpperCase(), password: PASSWORD })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
}); });
it('fails with wrong password', function (done) { it('fails with wrong password', function (done) {
request.post(SERVER_URL + '/api/v1/developer/login') superagent.post(SERVER_URL + '/api/v1/developer/login')
.send({ username: USERNAME, password: PASSWORD.toUpperCase() }) .send({ username: USERNAME, password: PASSWORD.toUpperCase() })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
}); });
it('with username succeeds', function (done) { it('with username succeeds', function (done) {
request.post(SERVER_URL + '/api/v1/developer/login') superagent.post(SERVER_URL + '/api/v1/developer/login')
.send({ username: USERNAME, password: PASSWORD }) .send({ username: USERNAME, password: PASSWORD })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(200); expect(result.statusCode).to.equal(200);
expect(result.body.expiresAt).to.be.a('number'); expect(result.body.expiresAt).to.be.a('number');
expect(result.body.token).to.be.a('string'); expect(result.body.token).to.be.a('string');
@@ -349,10 +325,9 @@ describe('Developer API', function () {
}); });
it('with email succeeds', function (done) { it('with email succeeds', function (done) {
request.post(SERVER_URL + '/api/v1/developer/login') superagent.post(SERVER_URL + '/api/v1/developer/login')
.send({ username: EMAIL, password: PASSWORD }) .send({ username: EMAIL, password: PASSWORD })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(200); expect(result.statusCode).to.equal(200);
expect(result.body.expiresAt).to.be.a('number'); expect(result.body.expiresAt).to.be.a('number');
expect(result.body.token).to.be.a('string'); expect(result.body.token).to.be.a('string');
-23
View File
@@ -319,7 +319,6 @@ describe('OAuth2', function () {
it('fails due to missing redirect_uri param', function (done) { it('fails due to missing redirect_uri param', function (done) {
superagent.get(SERVER_URL + '/api/v1/oauth/dialog/authorize') superagent.get(SERVER_URL + '/api/v1/oauth/dialog/authorize')
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.text.indexOf('<!-- error tester -->')).to.not.equal(-1); expect(result.text.indexOf('<!-- error tester -->')).to.not.equal(-1);
expect(result.text.indexOf('Invalid request. redirect_uri query param is not set.')).to.not.equal(-1); expect(result.text.indexOf('Invalid request. redirect_uri query param is not set.')).to.not.equal(-1);
expect(result.statusCode).to.equal(200); expect(result.statusCode).to.equal(200);
@@ -330,7 +329,6 @@ describe('OAuth2', function () {
it('fails due to missing client_id param', function (done) { it('fails due to missing client_id param', function (done) {
superagent.get(SERVER_URL + '/api/v1/oauth/dialog/authorize?redirect_uri=http://someredirect') superagent.get(SERVER_URL + '/api/v1/oauth/dialog/authorize?redirect_uri=http://someredirect')
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.text.indexOf('<!-- error tester -->')).to.not.equal(-1); expect(result.text.indexOf('<!-- error tester -->')).to.not.equal(-1);
expect(result.text.indexOf('Invalid request. client_id query param is not set.')).to.not.equal(-1); expect(result.text.indexOf('Invalid request. client_id query param is not set.')).to.not.equal(-1);
expect(result.statusCode).to.equal(200); expect(result.statusCode).to.equal(200);
@@ -341,7 +339,6 @@ describe('OAuth2', function () {
it('fails due to missing response_type param', function (done) { it('fails due to missing response_type param', function (done) {
superagent.get(SERVER_URL + '/api/v1/oauth/dialog/authorize?redirect_uri=http://someredirect&client_id=someclientid') superagent.get(SERVER_URL + '/api/v1/oauth/dialog/authorize?redirect_uri=http://someredirect&client_id=someclientid')
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.text.indexOf('<!-- error tester -->')).to.not.equal(-1); expect(result.text.indexOf('<!-- error tester -->')).to.not.equal(-1);
expect(result.text.indexOf('Invalid request. response_type query param is not set.')).to.not.equal(-1); expect(result.text.indexOf('Invalid request. response_type query param is not set.')).to.not.equal(-1);
expect(result.statusCode).to.equal(200); expect(result.statusCode).to.equal(200);
@@ -352,7 +349,6 @@ describe('OAuth2', function () {
it('fails for unkown grant type', function (done) { it('fails for unkown grant type', function (done) {
superagent.get(SERVER_URL + '/api/v1/oauth/dialog/authorize?redirect_uri=http://someredirect&client_id=someclientid&response_type=foobar') superagent.get(SERVER_URL + '/api/v1/oauth/dialog/authorize?redirect_uri=http://someredirect&client_id=someclientid&response_type=foobar')
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.text.indexOf('<!-- error tester -->')).to.not.equal(-1); expect(result.text.indexOf('<!-- error tester -->')).to.not.equal(-1);
expect(result.text.indexOf('Invalid request. Only token and code response types are supported.')).to.not.equal(-1); expect(result.text.indexOf('Invalid request. Only token and code response types are supported.')).to.not.equal(-1);
expect(result.statusCode).to.equal(200); expect(result.statusCode).to.equal(200);
@@ -363,7 +359,6 @@ describe('OAuth2', function () {
it('succeeds for grant type code', function (done) { it('succeeds for grant type code', function (done) {
superagent.get(SERVER_URL + '/api/v1/oauth/dialog/authorize?redirect_uri=http://someredirect&client_id=someclientid&response_type=code') superagent.get(SERVER_URL + '/api/v1/oauth/dialog/authorize?redirect_uri=http://someredirect&client_id=someclientid&response_type=code')
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.text).to.eql('<script>window.location.href = "/api/v1/session/login?returnTo=http://someredirect";</script>'); expect(result.text).to.eql('<script>window.location.href = "/api/v1/session/login?returnTo=http://someredirect";</script>');
expect(result.statusCode).to.equal(200); expect(result.statusCode).to.equal(200);
done(); done();
@@ -373,7 +368,6 @@ describe('OAuth2', function () {
it('succeeds for grant type token', function (done) { it('succeeds for grant type token', function (done) {
superagent.get(SERVER_URL + '/api/v1/oauth/dialog/authorize?redirect_uri=http://someredirect&client_id=someclientid&response_type=token') superagent.get(SERVER_URL + '/api/v1/oauth/dialog/authorize?redirect_uri=http://someredirect&client_id=someclientid&response_type=token')
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.text).to.eql('<script>window.location.href = "/api/v1/session/login?returnTo=http://someredirect";</script>'); expect(result.text).to.eql('<script>window.location.href = "/api/v1/session/login?returnTo=http://someredirect";</script>');
expect(result.statusCode).to.equal(200); expect(result.statusCode).to.equal(200);
done(); done();
@@ -388,7 +382,6 @@ describe('OAuth2', function () {
it('fails without prior authentication call and not returnTo query', function (done) { it('fails without prior authentication call and not returnTo query', function (done) {
superagent.get(SERVER_URL + '/api/v1/session/login') superagent.get(SERVER_URL + '/api/v1/session/login')
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.text.indexOf('<!-- error tester -->')).to.not.equal(-1); expect(result.text.indexOf('<!-- error tester -->')).to.not.equal(-1);
expect(result.text.indexOf('Invalid login request. No returnTo provided.')).to.not.equal(-1); expect(result.text.indexOf('Invalid login request. No returnTo provided.')).to.not.equal(-1);
expect(result.statusCode).to.equal(200); expect(result.statusCode).to.equal(200);
@@ -401,7 +394,6 @@ describe('OAuth2', function () {
superagent.get(SERVER_URL + '/api/v1/session/login?returnTo=http://someredirect') superagent.get(SERVER_URL + '/api/v1/session/login?returnTo=http://someredirect')
.redirects(0) .redirects(0)
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(302); expect(result.statusCode).to.equal(302);
expect(result.headers.location).to.eql('http://someredirect'); expect(result.headers.location).to.eql('http://someredirect');
@@ -413,7 +405,6 @@ describe('OAuth2', function () {
superagent.get(SERVER_URL + '/api/v1/oauth/dialog/authorize?redirect_uri=http://someredirect&response_type=code') superagent.get(SERVER_URL + '/api/v1/oauth/dialog/authorize?redirect_uri=http://someredirect&response_type=code')
.redirects(0) .redirects(0)
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.text.indexOf('<!-- error tester -->')).to.not.equal(-1); expect(result.text.indexOf('<!-- error tester -->')).to.not.equal(-1);
expect(result.text.indexOf('Invalid request. client_id query param is not set.')).to.not.equal(-1); expect(result.text.indexOf('Invalid request. client_id query param is not set.')).to.not.equal(-1);
expect(result.statusCode).to.equal(200); expect(result.statusCode).to.equal(200);
@@ -1289,7 +1280,6 @@ describe('Password', function () {
it('reset request succeeds', function (done) { it('reset request succeeds', function (done) {
superagent.get(SERVER_URL + '/api/v1/session/password/resetRequest.html') superagent.get(SERVER_URL + '/api/v1/session/password/resetRequest.html')
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.text.indexOf('<!-- tester -->')).to.not.equal(-1); expect(result.text.indexOf('<!-- tester -->')).to.not.equal(-1);
expect(result.statusCode).to.equal(200); expect(result.statusCode).to.equal(200);
done(); done();
@@ -1299,7 +1289,6 @@ describe('Password', function () {
it('setup fails due to missing reset_token', function (done) { it('setup fails due to missing reset_token', function (done) {
superagent.get(SERVER_URL + '/api/v1/session/password/setup.html') superagent.get(SERVER_URL + '/api/v1/session/password/setup.html')
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(); done();
}); });
@@ -1309,7 +1298,6 @@ describe('Password', function () {
superagent.get(SERVER_URL + '/api/v1/session/password/setup.html') superagent.get(SERVER_URL + '/api/v1/session/password/setup.html')
.query({ reset_token: hat(256) }) .query({ reset_token: hat(256) })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
@@ -1319,7 +1307,6 @@ describe('Password', function () {
superagent.get(SERVER_URL + '/api/v1/session/password/setup.html') superagent.get(SERVER_URL + '/api/v1/session/password/setup.html')
.query({ reset_token: USER_0.resetToken }) .query({ reset_token: USER_0.resetToken })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(200); expect(result.statusCode).to.equal(200);
expect(result.text.indexOf('<!-- tester -->')).to.not.equal(-1); expect(result.text.indexOf('<!-- tester -->')).to.not.equal(-1);
done(); done();
@@ -1329,7 +1316,6 @@ describe('Password', function () {
it('reset fails due to missing reset_token', function (done) { it('reset fails due to missing reset_token', function (done) {
superagent.get(SERVER_URL + '/api/v1/session/password/reset.html') superagent.get(SERVER_URL + '/api/v1/session/password/reset.html')
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(); done();
}); });
@@ -1339,7 +1325,6 @@ describe('Password', function () {
superagent.get(SERVER_URL + '/api/v1/session/password/reset.html') superagent.get(SERVER_URL + '/api/v1/session/password/reset.html')
.query({ reset_token: hat(256) }) .query({ reset_token: hat(256) })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
@@ -1349,7 +1334,6 @@ describe('Password', function () {
superagent.get(SERVER_URL + '/api/v1/session/password/reset.html') superagent.get(SERVER_URL + '/api/v1/session/password/reset.html')
.query({ reset_token: USER_0.resetToken }) .query({ reset_token: USER_0.resetToken })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.text.indexOf('<!-- tester -->')).to.not.equal(-1); expect(result.text.indexOf('<!-- tester -->')).to.not.equal(-1);
expect(result.statusCode).to.equal(200); expect(result.statusCode).to.equal(200);
done(); done();
@@ -1359,7 +1343,6 @@ describe('Password', function () {
it('sent succeeds', function (done) { it('sent succeeds', function (done) {
superagent.get(SERVER_URL + '/api/v1/session/password/sent.html') superagent.get(SERVER_URL + '/api/v1/session/password/sent.html')
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.text.indexOf('<!-- tester -->')).to.not.equal(-1); expect(result.text.indexOf('<!-- tester -->')).to.not.equal(-1);
expect(result.statusCode).to.equal(200); expect(result.statusCode).to.equal(200);
done(); done();
@@ -1375,7 +1358,6 @@ describe('Password', function () {
superagent.post(SERVER_URL + '/api/v1/session/password/resetRequest') superagent.post(SERVER_URL + '/api/v1/session/password/resetRequest')
.send({ identifier: USER_0.email }) .send({ identifier: USER_0.email })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.text.indexOf('<!-- tester -->')).to.not.equal(-1); expect(result.text.indexOf('<!-- tester -->')).to.not.equal(-1);
expect(result.statusCode).to.equal(200); expect(result.statusCode).to.equal(200);
done(); done();
@@ -1391,7 +1373,6 @@ describe('Password', function () {
superagent.post(SERVER_URL + '/api/v1/session/password/reset') superagent.post(SERVER_URL + '/api/v1/session/password/reset')
.send({ password: 'somepassword' }) .send({ password: 'somepassword' })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(); done();
}); });
@@ -1401,7 +1382,6 @@ describe('Password', function () {
superagent.post(SERVER_URL + '/api/v1/session/password/reset') superagent.post(SERVER_URL + '/api/v1/session/password/reset')
.send({ resetToken: hat(256) }) .send({ resetToken: hat(256) })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(); done();
}); });
@@ -1411,7 +1391,6 @@ describe('Password', function () {
superagent.post(SERVER_URL + '/api/v1/session/password/reset') superagent.post(SERVER_URL + '/api/v1/session/password/reset')
.send({ password: '', resetToken: hat(256) }) .send({ password: '', resetToken: hat(256) })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
@@ -1421,7 +1400,6 @@ describe('Password', function () {
superagent.post(SERVER_URL + '/api/v1/session/password/reset') superagent.post(SERVER_URL + '/api/v1/session/password/reset')
.send({ password: '', resetToken: '' }) .send({ password: '', resetToken: '' })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
@@ -1439,7 +1417,6 @@ describe('Password', function () {
superagent.post(SERVER_URL + '/api/v1/session/password/reset') superagent.post(SERVER_URL + '/api/v1/session/password/reset')
.send({ password: 'somepassword', resetToken: USER_0.resetToken }) .send({ password: 'somepassword', resetToken: USER_0.resetToken })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(scope.isDone()).to.be.ok(); expect(scope.isDone()).to.be.ok();
expect(result.statusCode).to.equal(200); expect(result.statusCode).to.equal(200);
done(); done();
+33 -41
View File
@@ -13,7 +13,7 @@ var appdb = require('../../appdb.js'),
expect = require('expect.js'), expect = require('expect.js'),
path = require('path'), path = require('path'),
paths = require('../../paths.js'), paths = require('../../paths.js'),
request = require('superagent'), superagent = require('superagent'),
server = require('../../server.js'), server = require('../../server.js'),
settings = require('../../settings.js'), settings = require('../../settings.js'),
fs = require('fs'), fs = require('fs'),
@@ -38,11 +38,10 @@ function setup(done) {
var scope1 = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/setup/verify?setupToken=somesetuptoken').reply(200, {}); var scope1 = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/setup/verify?setupToken=somesetuptoken').reply(200, {});
var scope2 = nock(config.apiServerOrigin()).post('/api/v1/boxes/' + config.fqdn() + '/setup/done?setupToken=somesetuptoken').reply(201, {}); var scope2 = nock(config.apiServerOrigin()).post('/api/v1/boxes/' + config.fqdn() + '/setup/done?setupToken=somesetuptoken').reply(201, {});
request.post(SERVER_URL + '/api/v1/cloudron/activate') superagent.post(SERVER_URL + '/api/v1/cloudron/activate')
.query({ setupToken: 'somesetuptoken' }) .query({ setupToken: 'somesetuptoken' })
.send({ username: USERNAME, password: PASSWORD, email: EMAIL }) .send({ username: USERNAME, password: PASSWORD, email: EMAIL })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result).to.be.ok(); expect(result).to.be.ok();
expect(result.statusCode).to.eql(201); expect(result.statusCode).to.eql(201);
expect(scope1.isDone()).to.be.ok(); expect(scope1.isDone()).to.be.ok();
@@ -78,17 +77,17 @@ describe('Settings API', function () {
describe('autoupdate_pattern', function () { describe('autoupdate_pattern', function () {
it('can get auto update pattern (default)', function (done) { it('can get auto update pattern (default)', function (done) {
request.get(SERVER_URL + '/api/v1/settings/autoupdate_pattern') superagent.get(SERVER_URL + '/api/v1/settings/autoupdate_pattern')
.query({ access_token: token }) .query({ access_token: token })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(200); expect(res.statusCode).to.equal(200);
expect(res.body.pattern).to.be.ok(); expect(res.body.pattern).to.be.ok();
done(err); done();
}); });
}); });
it('cannot set autoupdate_pattern without pattern', function (done) { it('cannot set autoupdate_pattern without pattern', function (done) {
request.post(SERVER_URL + '/api/v1/settings/autoupdate_pattern') superagent.post(SERVER_URL + '/api/v1/settings/autoupdate_pattern')
.query({ access_token: token }) .query({ access_token: token })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(400); expect(res.statusCode).to.equal(400);
@@ -102,7 +101,7 @@ describe('Settings API', function () {
eventPattern = pattern; eventPattern = pattern;
}); });
request.post(SERVER_URL + '/api/v1/settings/autoupdate_pattern') superagent.post(SERVER_URL + '/api/v1/settings/autoupdate_pattern')
.query({ access_token: token }) .query({ access_token: token })
.send({ pattern: '00 30 11 * * 1-5' }) .send({ pattern: '00 30 11 * * 1-5' })
.end(function (err, res) { .end(function (err, res) {
@@ -118,7 +117,7 @@ describe('Settings API', function () {
eventPattern = pattern; eventPattern = pattern;
}); });
request.post(SERVER_URL + '/api/v1/settings/autoupdate_pattern') superagent.post(SERVER_URL + '/api/v1/settings/autoupdate_pattern')
.query({ access_token: token }) .query({ access_token: token })
.send({ pattern: 'never' }) .send({ pattern: 'never' })
.end(function (err, res) { .end(function (err, res) {
@@ -129,7 +128,7 @@ describe('Settings API', function () {
}); });
it('cannot set invalid autoupdate_pattern', function (done) { it('cannot set invalid autoupdate_pattern', function (done) {
request.post(SERVER_URL + '/api/v1/settings/autoupdate_pattern') superagent.post(SERVER_URL + '/api/v1/settings/autoupdate_pattern')
.query({ access_token: token }) .query({ access_token: token })
.send({ pattern: '1 3 x 5 6' }) .send({ pattern: '1 3 x 5 6' })
.end(function (err, res) { .end(function (err, res) {
@@ -143,17 +142,17 @@ describe('Settings API', function () {
var name = 'foobar'; var name = 'foobar';
it('get default succeeds', function (done) { it('get default succeeds', function (done) {
request.get(SERVER_URL + '/api/v1/settings/cloudron_name') superagent.get(SERVER_URL + '/api/v1/settings/cloudron_name')
.query({ access_token: token }) .query({ access_token: token })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(200); expect(res.statusCode).to.equal(200);
expect(res.body.name).to.be.ok(); expect(res.body.name).to.be.ok();
done(err); done();
}); });
}); });
it('cannot set without name', function (done) { it('cannot set without name', function (done) {
request.post(SERVER_URL + '/api/v1/settings/cloudron_name') superagent.post(SERVER_URL + '/api/v1/settings/cloudron_name')
.query({ access_token: token }) .query({ access_token: token })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(400); expect(res.statusCode).to.equal(400);
@@ -162,7 +161,7 @@ describe('Settings API', function () {
}); });
it('cannot set empty name', function (done) { it('cannot set empty name', function (done) {
request.post(SERVER_URL + '/api/v1/settings/cloudron_name') superagent.post(SERVER_URL + '/api/v1/settings/cloudron_name')
.query({ access_token: token }) .query({ access_token: token })
.send({ name: '' }) .send({ name: '' })
.end(function (err, res) { .end(function (err, res) {
@@ -172,7 +171,7 @@ describe('Settings API', function () {
}); });
it('set succeeds', function (done) { it('set succeeds', function (done) {
request.post(SERVER_URL + '/api/v1/settings/cloudron_name') superagent.post(SERVER_URL + '/api/v1/settings/cloudron_name')
.query({ access_token: token }) .query({ access_token: token })
.send({ name: name }) .send({ name: name })
.end(function (err, res) { .end(function (err, res) {
@@ -182,29 +181,29 @@ describe('Settings API', function () {
}); });
it('get succeeds', function (done) { it('get succeeds', function (done) {
request.get(SERVER_URL + '/api/v1/settings/cloudron_name') superagent.get(SERVER_URL + '/api/v1/settings/cloudron_name')
.query({ access_token: token }) .query({ access_token: token })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(200); expect(res.statusCode).to.equal(200);
expect(res.body.name).to.eql(name); expect(res.body.name).to.eql(name);
done(err); done();
}); });
}); });
}); });
describe('cloudron_avatar', function () { describe('cloudron_avatar', function () {
it('get default succeeds', function (done) { it('get default succeeds', function (done) {
request.get(SERVER_URL + '/api/v1/settings/cloudron_avatar') superagent.get(SERVER_URL + '/api/v1/settings/cloudron_avatar')
.query({ access_token: token }) .query({ access_token: token })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(200); expect(res.statusCode).to.equal(200);
expect(res.body).to.be.a(Buffer); expect(res.body).to.be.a(Buffer);
done(err); done();
}); });
}); });
it('cannot set without data', function (done) { it('cannot set without data', function (done) {
request.post(SERVER_URL + '/api/v1/settings/cloudron_avatar') superagent.post(SERVER_URL + '/api/v1/settings/cloudron_avatar')
.query({ access_token: token }) .query({ access_token: token })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(400); expect(res.statusCode).to.equal(400);
@@ -213,7 +212,7 @@ describe('Settings API', function () {
}); });
it('set succeeds', function (done) { it('set succeeds', function (done) {
request.post(SERVER_URL + '/api/v1/settings/cloudron_avatar') superagent.post(SERVER_URL + '/api/v1/settings/cloudron_avatar')
.query({ access_token: token }) .query({ access_token: token })
.attach('avatar', paths.CLOUDRON_DEFAULT_AVATAR_FILE) .attach('avatar', paths.CLOUDRON_DEFAULT_AVATAR_FILE)
.end(function (err, res) { .end(function (err, res) {
@@ -223,7 +222,7 @@ describe('Settings API', function () {
}); });
it('get succeeds', function (done) { it('get succeeds', function (done) {
request.get(SERVER_URL + '/api/v1/settings/cloudron_avatar') superagent.get(SERVER_URL + '/api/v1/settings/cloudron_avatar')
.query({ access_token: token }) .query({ access_token: token })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(200); expect(res.statusCode).to.equal(200);
@@ -235,17 +234,17 @@ describe('Settings API', function () {
describe('dns_config', function () { describe('dns_config', function () {
it('get dns_config fails', function (done) { it('get dns_config fails', function (done) {
request.get(SERVER_URL + '/api/v1/settings/dns_config') superagent.get(SERVER_URL + '/api/v1/settings/dns_config')
.query({ access_token: token }) .query({ access_token: token })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(200); expect(res.statusCode).to.equal(200);
expect(res.body).to.eql({}); expect(res.body).to.eql({});
done(err); done();
}); });
}); });
it('cannot set without data', function (done) { it('cannot set without data', function (done) {
request.post(SERVER_URL + '/api/v1/settings/dns_config') superagent.post(SERVER_URL + '/api/v1/settings/dns_config')
.query({ access_token: token }) .query({ access_token: token })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(400); expect(res.statusCode).to.equal(400);
@@ -254,7 +253,7 @@ describe('Settings API', function () {
}); });
it('set succeeds', function (done) { it('set succeeds', function (done) {
request.post(SERVER_URL + '/api/v1/settings/dns_config') superagent.post(SERVER_URL + '/api/v1/settings/dns_config')
.query({ access_token: token }) .query({ access_token: token })
.send({ provider: 'route53', accessKeyId: 'accessKey', secretAccessKey: 'secretAccessKey' }) .send({ provider: 'route53', accessKeyId: 'accessKey', secretAccessKey: 'secretAccessKey' })
.end(function (err, res) { .end(function (err, res) {
@@ -264,12 +263,12 @@ describe('Settings API', function () {
}); });
it('get succeeds', function (done) { it('get succeeds', function (done) {
request.get(SERVER_URL + '/api/v1/settings/dns_config') superagent.get(SERVER_URL + '/api/v1/settings/dns_config')
.query({ access_token: token }) .query({ access_token: token })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(200); expect(res.statusCode).to.equal(200);
expect(res.body).to.eql({ provider: 'route53', accessKeyId: 'accessKey', secretAccessKey: 'secretAccessKey', region: 'us-east-1', endpoint: null }); expect(res.body).to.eql({ provider: 'route53', accessKeyId: 'accessKey', secretAccessKey: 'secretAccessKey', region: 'us-east-1', endpoint: null });
done(err); done();
}); });
}); });
}); });
@@ -284,75 +283,68 @@ describe('Settings API', function () {
var validKey1 = '-----BEGIN RSA PRIVATE KEY-----\nMIIBOQIBAAJBALQUp/TtlYxwAEWVnD4bNcr0SJmuUnWWme7rhGE333PsxdGvxwWd\nlWBjeOBq27JHmzdZ3NS/J7Z4nSs2JyXYRkkCAwEAAQJALV2eykcoC48TonQEPmkg\nbhaIS57syw67jMLsQImQ02UABKzqHPEKLXPOZhZPS9hsC/hGIehwiYCXMUlrl+WF\nAQIhAOntBI6qaecNjAAVG7UbZclMuHROUONmZUF1KNq6VyV5AiEAxRLkfHWy52CM\njOQrX347edZ30f4QczvugXwsyuU9A1ECIGlGZ8Sk4OBA8n6fAUcyO06qnmCJVlHg\npTUeOvKk5c9RAiBs28+8dCNbrbhVhx/yQr9FwNM0+ttJW/yWJ+pyNQhr0QIgJTT6\nxwCWYOtbioyt7B9l+ENy3AMSO3Uq+xmIKkvItK4=\n-----END RSA PRIVATE KEY-----'; var validKey1 = '-----BEGIN RSA PRIVATE KEY-----\nMIIBOQIBAAJBALQUp/TtlYxwAEWVnD4bNcr0SJmuUnWWme7rhGE333PsxdGvxwWd\nlWBjeOBq27JHmzdZ3NS/J7Z4nSs2JyXYRkkCAwEAAQJALV2eykcoC48TonQEPmkg\nbhaIS57syw67jMLsQImQ02UABKzqHPEKLXPOZhZPS9hsC/hGIehwiYCXMUlrl+WF\nAQIhAOntBI6qaecNjAAVG7UbZclMuHROUONmZUF1KNq6VyV5AiEAxRLkfHWy52CM\njOQrX347edZ30f4QczvugXwsyuU9A1ECIGlGZ8Sk4OBA8n6fAUcyO06qnmCJVlHg\npTUeOvKk5c9RAiBs28+8dCNbrbhVhx/yQr9FwNM0+ttJW/yWJ+pyNQhr0QIgJTT6\nxwCWYOtbioyt7B9l+ENy3AMSO3Uq+xmIKkvItK4=\n-----END RSA PRIVATE KEY-----';
it('cannot set certificate without token', function (done) { it('cannot set certificate without token', function (done) {
request.post(SERVER_URL + '/api/v1/settings/certificate') superagent.post(SERVER_URL + '/api/v1/settings/certificate')
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
}); });
it('cannot set certificate without certificate', function (done) { it('cannot set certificate without certificate', function (done) {
request.post(SERVER_URL + '/api/v1/settings/certificate') superagent.post(SERVER_URL + '/api/v1/settings/certificate')
.query({ access_token: token }) .query({ access_token: token })
.send({ key: validKey1 }) .send({ key: validKey1 })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(); done();
}); });
}); });
it('cannot set certificate without key', function (done) { it('cannot set certificate without key', function (done) {
request.post(SERVER_URL + '/api/v1/settings/certificate') superagent.post(SERVER_URL + '/api/v1/settings/certificate')
.query({ access_token: token }) .query({ access_token: token })
.send({ cert: validCert1 }) .send({ cert: validCert1 })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(); done();
}); });
}); });
it('cannot set certificate with cert not being a string', function (done) { it('cannot set certificate with cert not being a string', function (done) {
request.post(SERVER_URL + '/api/v1/settings/certificate') superagent.post(SERVER_URL + '/api/v1/settings/certificate')
.query({ access_token: token }) .query({ access_token: token })
.send({ cert: 1234, key: validKey1 }) .send({ cert: 1234, key: validKey1 })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(); done();
}); });
}); });
it('cannot set certificate with key not being a string', function (done) { it('cannot set certificate with key not being a string', function (done) {
request.post(SERVER_URL + '/api/v1/settings/certificate') superagent.post(SERVER_URL + '/api/v1/settings/certificate')
.query({ access_token: token }) .query({ access_token: token })
.send({ cert: validCert1, key: true }) .send({ cert: validCert1, key: true })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(); done();
}); });
}); });
it('cannot set non wildcard certificate', function (done) { it('cannot set non wildcard certificate', function (done) {
request.post(SERVER_URL + '/api/v1/settings/certificate') superagent.post(SERVER_URL + '/api/v1/settings/certificate')
.query({ access_token: token }) .query({ access_token: token })
.send({ cert: validCert0, key: validKey0 }) .send({ cert: validCert0, key: validKey0 })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(); done();
}); });
}); });
it('can set certificate', function (done) { it('can set certificate', function (done) {
request.post(SERVER_URL + '/api/v1/settings/certificate') superagent.post(SERVER_URL + '/api/v1/settings/certificate')
.query({ access_token: token }) .query({ access_token: token })
.send({ cert: validCert1, key: validKey1 }) .send({ cert: validCert1, key: validKey1 })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(202); expect(result.statusCode).to.equal(202);
done(); done();
}); });
+21 -34
View File
@@ -12,7 +12,7 @@ var clientdb = require('../../clientdb.js'),
config = require('../../config.js'), config = require('../../config.js'),
database = require('../../database.js'), database = require('../../database.js'),
expect = require('expect.js'), expect = require('expect.js'),
request = require('superagent'), superagent = require('superagent'),
server = require('../../server.js'), server = require('../../server.js'),
simpleauth = require('../../simpleauth.js'), simpleauth = require('../../simpleauth.js'),
nock = require('nock'); nock = require('nock');
@@ -109,7 +109,7 @@ describe('SimpleAuth API', function () {
var scope1 = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/setup/verify?setupToken=somesetuptoken').reply(200, {}); var scope1 = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/setup/verify?setupToken=somesetuptoken').reply(200, {});
var scope2 = nock(config.apiServerOrigin()).post('/api/v1/boxes/' + config.fqdn() + '/setup/done?setupToken=somesetuptoken').reply(201, {}); var scope2 = nock(config.apiServerOrigin()).post('/api/v1/boxes/' + config.fqdn() + '/setup/done?setupToken=somesetuptoken').reply(201, {});
request.post(SERVER_URL + '/api/v1/cloudron/activate') superagent.post(SERVER_URL + '/api/v1/cloudron/activate')
.query({ setupToken: 'somesetuptoken' }) .query({ setupToken: 'somesetuptoken' })
.send({ username: USERNAME, password: PASSWORD, email: EMAIL }) .send({ username: USERNAME, password: PASSWORD, email: EMAIL })
.end(function (error, result) { .end(function (error, result) {
@@ -146,10 +146,9 @@ describe('SimpleAuth API', function () {
it('cannot login without clientId', function (done) { it('cannot login without clientId', function (done) {
var body = {}; var body = {};
request.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login') superagent.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login')
.send(body) .send(body)
.end(function (error, result) { .end(function (error, result) {
expect(error).to.be(null);
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(); done();
}); });
@@ -160,10 +159,9 @@ describe('SimpleAuth API', function () {
clientId: 'someclientid' clientId: 'someclientid'
}; };
request.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login') superagent.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login')
.send(body) .send(body)
.end(function (error, result) { .end(function (error, result) {
expect(error).to.be(null);
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(); done();
}); });
@@ -175,10 +173,9 @@ describe('SimpleAuth API', function () {
username: USERNAME username: USERNAME
}; };
request.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login') superagent.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login')
.send(body) .send(body)
.end(function (error, result) { .end(function (error, result) {
expect(error).to.be(null);
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(); done();
}); });
@@ -191,10 +188,9 @@ describe('SimpleAuth API', function () {
password: PASSWORD password: PASSWORD
}; };
request.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login') superagent.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login')
.send(body) .send(body)
.end(function (error, result) { .end(function (error, result) {
expect(error).to.be(null);
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
@@ -207,10 +203,9 @@ describe('SimpleAuth API', function () {
password: PASSWORD password: PASSWORD
}; };
request.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login') superagent.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login')
.send(body) .send(body)
.end(function (error, result) { .end(function (error, result) {
expect(error).to.be(null);
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
@@ -223,10 +218,9 @@ describe('SimpleAuth API', function () {
password: '' password: ''
}; };
request.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login') superagent.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login')
.send(body) .send(body)
.end(function (error, result) { .end(function (error, result) {
expect(error).to.be(null);
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
@@ -239,10 +233,9 @@ describe('SimpleAuth API', function () {
password: PASSWORD+PASSWORD password: PASSWORD+PASSWORD
}; };
request.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login') superagent.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login')
.send(body) .send(body)
.end(function (error, result) { .end(function (error, result) {
expect(error).to.be(null);
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
@@ -255,10 +248,9 @@ describe('SimpleAuth API', function () {
password: PASSWORD password: PASSWORD
}; };
request.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login') superagent.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login')
.send(body) .send(body)
.end(function (error, result) { .end(function (error, result) {
expect(error).to.be(null);
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
@@ -271,10 +263,9 @@ describe('SimpleAuth API', function () {
password: PASSWORD password: PASSWORD
}; };
request.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login') superagent.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login')
.send(body) .send(body)
.end(function (error, result) { .end(function (error, result) {
expect(error).to.be(null);
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
@@ -287,7 +278,7 @@ describe('SimpleAuth API', function () {
password: PASSWORD password: PASSWORD
}; };
request.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login') superagent.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login')
.send(body) .send(body)
.end(function (error, result) { .end(function (error, result) {
expect(error).to.be(null); expect(error).to.be(null);
@@ -299,7 +290,7 @@ describe('SimpleAuth API', function () {
expect(result.body.user.email).to.be.a('string'); expect(result.body.user.email).to.be.a('string');
expect(result.body.user.admin).to.be.a('boolean'); expect(result.body.user.admin).to.be.a('boolean');
request.get(SERVER_URL + '/api/v1/profile') superagent.get(SERVER_URL + '/api/v1/profile')
.query({ access_token: result.body.accessToken }) .query({ access_token: result.body.accessToken })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.be(null); expect(error).to.be(null);
@@ -318,7 +309,7 @@ describe('SimpleAuth API', function () {
password: PASSWORD password: PASSWORD
}; };
request.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login') superagent.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login')
.send(body) .send(body)
.end(function (error, result) { .end(function (error, result) {
expect(error).to.be(null); expect(error).to.be(null);
@@ -330,7 +321,7 @@ describe('SimpleAuth API', function () {
expect(result.body.user.email).to.be.a('string'); expect(result.body.user.email).to.be.a('string');
expect(result.body.user.admin).to.be.a('boolean'); expect(result.body.user.admin).to.be.a('boolean');
request.get(SERVER_URL + '/api/v1/profile') superagent.get(SERVER_URL + '/api/v1/profile')
.query({ access_token: result.body.accessToken }) .query({ access_token: result.body.accessToken })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.be(null); expect(error).to.be(null);
@@ -349,10 +340,9 @@ describe('SimpleAuth API', function () {
password: PASSWORD password: PASSWORD
}; };
request.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login') superagent.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login')
.send(body) .send(body)
.end(function (error, result) { .end(function (error, result) {
expect(error).to.be(null);
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
@@ -369,7 +359,7 @@ describe('SimpleAuth API', function () {
password: PASSWORD password: PASSWORD
}; };
request.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login') superagent.post(SIMPLE_AUTH_ORIGIN + '/api/v1/login')
.send(body) .send(body)
.end(function (error, result) { .end(function (error, result) {
expect(error).to.be(null); expect(error).to.be(null);
@@ -382,35 +372,32 @@ describe('SimpleAuth API', function () {
}); });
it('fails without access_token', function (done) { it('fails without access_token', function (done) {
request.get(SIMPLE_AUTH_ORIGIN + '/api/v1/logout') superagent.get(SIMPLE_AUTH_ORIGIN + '/api/v1/logout')
.end(function (error, result) { .end(function (error, result) {
expect(error).to.be(null);
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(); done();
}); });
}); });
it('fails with unkonwn access_token', function (done) { it('fails with unkonwn access_token', function (done) {
request.get(SIMPLE_AUTH_ORIGIN + '/api/v1/logout') superagent.get(SIMPLE_AUTH_ORIGIN + '/api/v1/logout')
.query({ access_token: accessToken+accessToken }) .query({ access_token: accessToken+accessToken })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.be(null);
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
}); });
it('succeeds', function (done) { it('succeeds', function (done) {
request.get(SIMPLE_AUTH_ORIGIN + '/api/v1/logout') superagent.get(SIMPLE_AUTH_ORIGIN + '/api/v1/logout')
.query({ access_token: accessToken }) .query({ access_token: accessToken })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.be(null); expect(error).to.be(null);
expect(result.statusCode).to.equal(200); expect(result.statusCode).to.equal(200);
request.get(SERVER_URL + '/api/v1/profile') superagent.get(SERVER_URL + '/api/v1/profile')
.query({ access_token: accessToken }) .query({ access_token: accessToken })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.be(null);
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
+76 -82
View File
@@ -10,7 +10,7 @@ var config = require('../../config.js'),
database = require('../../database.js'), database = require('../../database.js'),
tokendb = require('../../tokendb.js'), tokendb = require('../../tokendb.js'),
expect = require('expect.js'), expect = require('expect.js'),
request = require('superagent'), superagent = require('superagent'),
nock = require('nock'), nock = require('nock'),
server = require('../../server.js'), server = require('../../server.js'),
userdb = require('../../userdb.js'); userdb = require('../../userdb.js');
@@ -50,7 +50,7 @@ describe('User API', function () {
after(cleanup); after(cleanup);
it('device is in first time mode', function (done) { it('device is in first time mode', function (done) {
request.get(SERVER_URL + '/api/v1/cloudron/status') superagent.get(SERVER_URL + '/api/v1/cloudron/status')
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(200); expect(res.statusCode).to.equal(200);
expect(res.body.activated).to.not.be.ok(); expect(res.body.activated).to.not.be.ok();
@@ -61,21 +61,21 @@ describe('User API', function () {
it('create admin fails due to missing parameters', function (done) { it('create admin fails due to missing parameters', function (done) {
var scope = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/setup/verify?setupToken=somesetuptoken').reply(200, {}); var scope = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/setup/verify?setupToken=somesetuptoken').reply(200, {});
request.post(SERVER_URL + '/api/v1/cloudron/activate') superagent.post(SERVER_URL + '/api/v1/cloudron/activate')
.query({ setupToken: 'somesetuptoken' }) .query({ setupToken: 'somesetuptoken' })
.send({ username: USERNAME_0 }) .send({ username: USERNAME_0 })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(400); expect(res.statusCode).to.equal(400);
expect(scope.isDone()).to.be.ok(); expect(scope.isDone()).to.be.ok();
done(err); done();
}); });
}); });
it('create admin fails because only POST is allowed', function (done) { it('create admin fails because only POST is allowed', function (done) {
request.get(SERVER_URL + '/api/v1/cloudron/activate') superagent.get(SERVER_URL + '/api/v1/cloudron/activate')
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(404); expect(res.statusCode).to.equal(404);
done(err); done();
}); });
}); });
@@ -83,7 +83,7 @@ describe('User API', function () {
var scope1 = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/setup/verify?setupToken=somesetuptoken').reply(200, {}); var scope1 = nock(config.apiServerOrigin()).get('/api/v1/boxes/' + config.fqdn() + '/setup/verify?setupToken=somesetuptoken').reply(200, {});
var scope2 = nock(config.apiServerOrigin()).post('/api/v1/boxes/' + config.fqdn() + '/setup/done?setupToken=somesetuptoken').reply(201, {}); var scope2 = nock(config.apiServerOrigin()).post('/api/v1/boxes/' + config.fqdn() + '/setup/done?setupToken=somesetuptoken').reply(201, {});
request.post(SERVER_URL + '/api/v1/cloudron/activate') superagent.post(SERVER_URL + '/api/v1/cloudron/activate')
.query({ setupToken: 'somesetuptoken' }) .query({ setupToken: 'somesetuptoken' })
.send({ username: USERNAME_0, password: PASSWORD, email: EMAIL }) .send({ username: USERNAME_0, password: PASSWORD, email: EMAIL })
.end(function (err, res) { .end(function (err, res) {
@@ -99,16 +99,16 @@ describe('User API', function () {
}); });
it('device left first time mode', function (done) { it('device left first time mode', function (done) {
request.get(SERVER_URL + '/api/v1/cloudron/status') superagent.get(SERVER_URL + '/api/v1/cloudron/status')
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(200); expect(res.statusCode).to.equal(200);
expect(res.body.activated).to.be.ok(); expect(res.body.activated).to.be.ok();
done(err); done();
}); });
}); });
it('can get userInfo with token', function (done) { it('can get userInfo with token', function (done) {
request.get(SERVER_URL + '/api/v1/users/' + USERNAME_0) superagent.get(SERVER_URL + '/api/v1/users/' + USERNAME_0)
.query({ access_token: token }) .query({ access_token: token })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(200); expect(res.statusCode).to.equal(200);
@@ -119,7 +119,7 @@ describe('User API', function () {
// stash for further use // stash for further use
user_0 = res.body; user_0 = res.body;
done(err); done();
}); });
}); });
@@ -131,10 +131,9 @@ describe('User API', function () {
expect(error).to.not.be.ok(); expect(error).to.not.be.ok();
setTimeout(function () { setTimeout(function () {
request.get(SERVER_URL + '/api/v1/users/' + user_0.username) superagent.get(SERVER_URL + '/api/v1/users/' + user_0.username)
.query({ access_token: token }) .query({ access_token: token })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(); done();
}); });
@@ -143,46 +142,46 @@ describe('User API', function () {
}); });
it('can get userInfo with token', function (done) { it('can get userInfo with token', function (done) {
request.get(SERVER_URL + '/api/v1/users/' + USERNAME_0) superagent.get(SERVER_URL + '/api/v1/users/' + USERNAME_0)
.query({ access_token: token }) .query({ access_token: token })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(200); expect(res.statusCode).to.equal(200);
expect(res.body.username).to.equal(USERNAME_0); expect(res.body.username).to.equal(USERNAME_0);
expect(res.body.email).to.equal(EMAIL); expect(res.body.email).to.equal(EMAIL);
expect(res.body.admin).to.be.ok(); expect(res.body.admin).to.be.ok();
done(err); done();
}); });
}); });
it('cannot get userInfo only with basic auth', function (done) { it('cannot get userInfo only with basic auth', function (done) {
request.get(SERVER_URL + '/api/v1/users/' + USERNAME_0) superagent.get(SERVER_URL + '/api/v1/users/' + USERNAME_0)
.auth(USERNAME_0, PASSWORD) .auth(USERNAME_0, PASSWORD)
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(401); expect(res.statusCode).to.equal(401);
done(err); done();
}); });
}); });
it('cannot get userInfo with invalid token (token length)', function (done) { it('cannot get userInfo with invalid token (token length)', function (done) {
request.get(SERVER_URL + '/api/v1/users/' + USERNAME_0) superagent.get(SERVER_URL + '/api/v1/users/' + USERNAME_0)
.query({ access_token: 'x' + token }) .query({ access_token: 'x' + token })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(401); expect(res.statusCode).to.equal(401);
done(err); done();
}); });
}); });
it('cannot get userInfo with invalid token (wrong token)', function (done) { it('cannot get userInfo with invalid token (wrong token)', function (done) {
request.get(SERVER_URL + '/api/v1/users/' + USERNAME_0) superagent.get(SERVER_URL + '/api/v1/users/' + USERNAME_0)
.query({ access_token: token.toUpperCase() }) .query({ access_token: token.toUpperCase() })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(401); expect(res.statusCode).to.equal(401);
done(err); done();
}); });
}); });
it('can get userInfo with token in auth header', function (done) { it('can get userInfo with token in auth header', function (done) {
request.get(SERVER_URL + '/api/v1/users/' + USERNAME_0) superagent.get(SERVER_URL + '/api/v1/users/' + USERNAME_0)
.set('Authorization', 'Bearer ' + token) .set('Authorization', 'Bearer ' + token)
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(200); expect(res.statusCode).to.equal(200);
@@ -191,30 +190,30 @@ describe('User API', function () {
expect(res.body.admin).to.be.ok(); expect(res.body.admin).to.be.ok();
expect(res.body.password).to.not.be.ok(); expect(res.body.password).to.not.be.ok();
expect(res.body.salt).to.not.be.ok(); expect(res.body.salt).to.not.be.ok();
done(err); done();
}); });
}); });
it('cannot get userInfo with invalid token in auth header', function (done) { it('cannot get userInfo with invalid token in auth header', function (done) {
request.get(SERVER_URL + '/api/v1/users/' + USERNAME_0) superagent.get(SERVER_URL + '/api/v1/users/' + USERNAME_0)
.set('Authorization', 'Bearer ' + 'x' + token) .set('Authorization', 'Bearer ' + 'x' + token)
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(401); expect(res.statusCode).to.equal(401);
done(err); done();
}); });
}); });
it('cannot get userInfo with invalid token (wrong token)', function (done) { it('cannot get userInfo with invalid token (wrong token)', function (done) {
request.get(SERVER_URL + '/api/v1/users/' + USERNAME_0) superagent.get(SERVER_URL + '/api/v1/users/' + USERNAME_0)
.set('Authorization', 'Bearer ' + 'x' + token.toUpperCase()) .set('Authorization', 'Bearer ' + 'x' + token.toUpperCase())
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(401); expect(res.statusCode).to.equal(401);
done(err); done();
}); });
}); });
it('create second user succeeds', function (done) { it('create second user succeeds', function (done) {
request.post(SERVER_URL + '/api/v1/users') superagent.post(SERVER_URL + '/api/v1/users')
.query({ access_token: token }) .query({ access_token: token })
.send({ username: USERNAME_1, email: EMAIL_1 }) .send({ username: USERNAME_1, email: EMAIL_1 })
.end(function (err, res) { .end(function (err, res) {
@@ -228,90 +227,86 @@ describe('User API', function () {
it('set second user as admin succeeds', function (done) { it('set second user as admin succeeds', function (done) {
// TODO is USERNAME_1 in body and url redundant? // TODO is USERNAME_1 in body and url redundant?
request.post(SERVER_URL + '/api/v1/users/' + USERNAME_1 + '/admin') superagent.post(SERVER_URL + '/api/v1/users/' + USERNAME_1 + '/admin')
.query({ access_token: token }) .query({ access_token: token })
.send({ username: USERNAME_1, admin: true }) .send({ username: USERNAME_1, admin: true })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(204); expect(res.statusCode).to.equal(204);
done(err); done();
}); });
}); });
it('remove first user from admins succeeds', function (done) { it('remove first user from admins succeeds', function (done) {
request.post(SERVER_URL + '/api/v1/users/' + USERNAME_0 + '/admin') superagent.post(SERVER_URL + '/api/v1/users/' + USERNAME_0 + '/admin')
.query({ access_token: token_1 }) .query({ access_token: token_1 })
.send({ username: USERNAME_0, admin: false }) .send({ username: USERNAME_0, admin: false })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(204); expect(res.statusCode).to.equal(204);
done(err); done();
}); });
}); });
it('remove second user by first, now normal, user fails', function (done) { it('remove second user by first, now normal, user fails', function (done) {
request.del(SERVER_URL + '/api/v1/users/' + USERNAME_1) superagent.del(SERVER_URL + '/api/v1/users/' + USERNAME_1)
.query({ access_token: token }) .query({ access_token: token })
.send({ password: PASSWORD }) .send({ password: PASSWORD })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(403); expect(res.statusCode).to.equal(403);
done(err); done();
}); });
}); });
it('remove second user from admins and thus last admin fails', function (done) { it('remove second user from admins and thus last admin fails', function (done) {
request.post(SERVER_URL + '/api/v1/users/' + USERNAME_1 + '/admin') superagent.post(SERVER_URL + '/api/v1/users/' + USERNAME_1 + '/admin')
.query({ access_token: token_1 }) .query({ access_token: token_1 })
.send({ username: USERNAME_1, admin: false }) .send({ username: USERNAME_1, admin: false })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(403); expect(res.statusCode).to.equal(403);
done(err); done();
}); });
}); });
it('reset first user as admin succeeds', function (done) { it('reset first user as admin succeeds', function (done) {
request.post(SERVER_URL + '/api/v1/users/' + USERNAME_0 + '/admin') superagent.post(SERVER_URL + '/api/v1/users/' + USERNAME_0 + '/admin')
.query({ access_token: token_1 }) .query({ access_token: token_1 })
.send({ username: USERNAME_0, admin: true }) .send({ username: USERNAME_0, admin: true })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(204); expect(res.statusCode).to.equal(204);
done(err); done();
}); });
}); });
it('create user missing username fails', function (done) { it('create user missing username fails', function (done) {
request.post(SERVER_URL + '/api/v1/users') superagent.post(SERVER_URL + '/api/v1/users')
.query({ access_token: token }) .query({ access_token: token })
.send({ email: EMAIL_2 }) .send({ email: EMAIL_2 })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(); done();
}); });
}); });
it('create user missing email fails', function (done) { it('create user missing email fails', function (done) {
request.post(SERVER_URL + '/api/v1/users') superagent.post(SERVER_URL + '/api/v1/users')
.query({ access_token: token }) .query({ access_token: token })
.send({ username: USERNAME_2 }) .send({ username: USERNAME_2 })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(); done();
}); });
}); });
it('create second and third user', function (done) { it('create second and third user', function (done) {
request.post(SERVER_URL + '/api/v1/users') superagent.post(SERVER_URL + '/api/v1/users')
.query({ access_token: token }) .query({ access_token: token })
.send({ username: USERNAME_2, email: EMAIL_2 }) .send({ username: USERNAME_2, email: EMAIL_2 })
.end(function (error, res) { .end(function (error, res) {
expect(error).to.not.be.ok();
expect(res.statusCode).to.equal(201); expect(res.statusCode).to.equal(201);
request.post(SERVER_URL + '/api/v1/users') superagent.post(SERVER_URL + '/api/v1/users')
.query({ access_token: token }) .query({ access_token: token })
.send({ username: USERNAME_3, email: EMAIL_3 }) .send({ username: USERNAME_3, email: EMAIL_3 })
.end(function (error, res) { .end(function (error, res) {
expect(error).to.not.be.ok();
expect(res.statusCode).to.equal(201); expect(res.statusCode).to.equal(201);
// HACK to get a token for second user (passwords are generated and the user should have gotten a password setup link...) // HACK to get a token for second user (passwords are generated and the user should have gotten a password setup link...)
@@ -321,10 +316,9 @@ describe('User API', function () {
}); });
it('second user userInfo', function (done) { it('second user userInfo', function (done) {
request.get(SERVER_URL + '/api/v1/users/' + USERNAME_2) superagent.get(SERVER_URL + '/api/v1/users/' + USERNAME_2)
.query({ access_token: token_1 }) .query({ access_token: token_1 })
.end(function (error, result) { .end(function (error, result) {
expect(error).to.be(null);
expect(result.statusCode).to.equal(200); expect(result.statusCode).to.equal(200);
expect(result.body.username).to.equal(USERNAME_2); expect(result.body.username).to.equal(USERNAME_2);
expect(result.body.email).to.equal(EMAIL_2); expect(result.body.email).to.equal(EMAIL_2);
@@ -335,17 +329,17 @@ describe('User API', function () {
}); });
it('create user with same username should fail', function (done) { it('create user with same username should fail', function (done) {
request.post(SERVER_URL + '/api/v1/users') superagent.post(SERVER_URL + '/api/v1/users')
.query({ access_token: token }) .query({ access_token: token })
.send({ username: USERNAME_2, email: EMAIL }) .send({ username: USERNAME_2, email: EMAIL })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(409); expect(res.statusCode).to.equal(409);
done(err); done();
}); });
}); });
it('list users', function (done) { it('list users', function (done) {
request.get(SERVER_URL + '/api/v1/users') superagent.get(SERVER_URL + '/api/v1/users')
.query({ access_token: token_2 }) .query({ access_token: token_2 })
.end(function (error, res) { .end(function (error, res) {
expect(error).to.be(null); expect(error).to.be(null);
@@ -367,106 +361,106 @@ describe('User API', function () {
}); });
it('user removes himself is not allowed', function (done) { it('user removes himself is not allowed', function (done) {
request.del(SERVER_URL + '/api/v1/users/' + USERNAME_0) superagent.del(SERVER_URL + '/api/v1/users/' + USERNAME_0)
.query({ access_token: token }) .query({ access_token: token })
.send({ password: PASSWORD }) .send({ password: PASSWORD })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(403); expect(res.statusCode).to.equal(403);
done(err); done();
}); });
}); });
it('admin cannot remove normal user without giving a password', function (done) { it('admin cannot remove normal user without giving a password', function (done) {
request.del(SERVER_URL + '/api/v1/users/' + USERNAME_3) superagent.del(SERVER_URL + '/api/v1/users/' + USERNAME_3)
.query({ access_token: token }) .query({ access_token: token })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(400); expect(res.statusCode).to.equal(400);
done(err); done();
}); });
}); });
it('admin cannot remove normal user with empty password', function (done) { it('admin cannot remove normal user with empty password', function (done) {
request.del(SERVER_URL + '/api/v1/users/' + USERNAME_3) superagent.del(SERVER_URL + '/api/v1/users/' + USERNAME_3)
.query({ access_token: token }) .query({ access_token: token })
.send({ password: '' }) .send({ password: '' })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(403); expect(res.statusCode).to.equal(403);
done(err); done();
}); });
}); });
it('admin cannot remove normal user with giving wrong password', function (done) { it('admin cannot remove normal user with giving wrong password', function (done) {
request.del(SERVER_URL + '/api/v1/users/' + USERNAME_3) superagent.del(SERVER_URL + '/api/v1/users/' + USERNAME_3)
.query({ access_token: token }) .query({ access_token: token })
.send({ password: PASSWORD + PASSWORD }) .send({ password: PASSWORD + PASSWORD })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(403); expect(res.statusCode).to.equal(403);
done(err); done();
}); });
}); });
it('admin removes normal user', function (done) { it('admin removes normal user', function (done) {
request.del(SERVER_URL + '/api/v1/users/' + USERNAME_3) superagent.del(SERVER_URL + '/api/v1/users/' + USERNAME_3)
.query({ access_token: token }) .query({ access_token: token })
.send({ password: PASSWORD }) .send({ password: PASSWORD })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(204); expect(res.statusCode).to.equal(204);
done(err); done();
}); });
}); });
it('admin removes himself should not be allowed', function (done) { it('admin removes himself should not be allowed', function (done) {
request.del(SERVER_URL + '/api/v1/users/' + USERNAME_0) superagent.del(SERVER_URL + '/api/v1/users/' + USERNAME_0)
.query({ access_token: token }) .query({ access_token: token })
.send({ password: PASSWORD }) .send({ password: PASSWORD })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(403); expect(res.statusCode).to.equal(403);
done(err); done();
}); });
}); });
// Change email // Change email
it('change email fails due to missing token', function (done) { it('change email fails due to missing token', function (done) {
request.put(SERVER_URL + '/api/v1/users/' + USERNAME_0) superagent.put(SERVER_URL + '/api/v1/users/' + USERNAME_0)
.send({ password: PASSWORD, email: EMAIL_0_NEW }) .send({ password: PASSWORD, email: EMAIL_0_NEW })
.end(function (error, result) { .end(function (error, result) {
expect(result.statusCode).to.equal(401); expect(result.statusCode).to.equal(401);
done(error); done();
}); });
}); });
it('change email fails due to missing password', function (done) { it('change email fails due to missing password', function (done) {
request.put(SERVER_URL + '/api/v1/users/' + USERNAME_0) superagent.put(SERVER_URL + '/api/v1/users/' + USERNAME_0)
.query({ access_token: token }) .query({ access_token: token })
.send({ email: EMAIL_0_NEW }) .send({ email: EMAIL_0_NEW })
.end(function (error, result) { .end(function (error, result) {
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(error); done();
}); });
}); });
it('change email fails due to wrong password', function (done) { it('change email fails due to wrong password', function (done) {
request.put(SERVER_URL + '/api/v1/users/' + USERNAME_0) superagent.put(SERVER_URL + '/api/v1/users/' + USERNAME_0)
.query({ access_token: token }) .query({ access_token: token })
.send({ password: PASSWORD+PASSWORD, email: EMAIL_0_NEW }) .send({ password: PASSWORD+PASSWORD, email: EMAIL_0_NEW })
.end(function (error, result) { .end(function (error, result) {
expect(result.statusCode).to.equal(403); expect(result.statusCode).to.equal(403);
done(error); done();
}); });
}); });
it('change email fails due to invalid email', function (done) { it('change email fails due to invalid email', function (done) {
request.put(SERVER_URL + '/api/v1/users/' + USERNAME_0) superagent.put(SERVER_URL + '/api/v1/users/' + USERNAME_0)
.query({ access_token: token }) .query({ access_token: token })
.send({ password: PASSWORD, email: 'foo@bar' }) .send({ password: PASSWORD, email: 'foo@bar' })
.end(function (error, result) { .end(function (error, result) {
expect(result.statusCode).to.equal(400); expect(result.statusCode).to.equal(400);
done(error); done();
}); });
}); });
it('change email succeeds', function (done) { it('change email succeeds', function (done) {
request.put(SERVER_URL + '/api/v1/users/' + USERNAME_0) superagent.put(SERVER_URL + '/api/v1/users/' + USERNAME_0)
.query({ access_token: token }) .query({ access_token: token })
.send({ password: PASSWORD, email: EMAIL_0_NEW }) .send({ password: PASSWORD, email: EMAIL_0_NEW })
.end(function (error, result) { .end(function (error, result) {
@@ -477,52 +471,52 @@ describe('User API', function () {
// Change password // Change password
it('change password fails due to missing current password', function (done) { it('change password fails due to missing current password', function (done) {
request.post(SERVER_URL + '/api/v1/users/' + USERNAME_0 + '/password') superagent.post(SERVER_URL + '/api/v1/users/' + USERNAME_0 + '/password')
.query({ access_token: token }) .query({ access_token: token })
.send({ newPassword: 'some wrong password' }) .send({ newPassword: 'some wrong password' })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(400); expect(res.statusCode).to.equal(400);
done(err); done();
}); });
}); });
it('change password fails due to missing new password', function (done) { it('change password fails due to missing new password', function (done) {
request.post(SERVER_URL + '/api/v1/users/' + USERNAME_0 + '/password') superagent.post(SERVER_URL + '/api/v1/users/' + USERNAME_0 + '/password')
.query({ access_token: token }) .query({ access_token: token })
.send({ password: PASSWORD }) .send({ password: PASSWORD })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(400); expect(res.statusCode).to.equal(400);
done(err); done();
}); });
}); });
it('change password fails due to wrong password', function (done) { it('change password fails due to wrong password', function (done) {
request.post(SERVER_URL + '/api/v1/users/' + USERNAME_0 + '/password') superagent.post(SERVER_URL + '/api/v1/users/' + USERNAME_0 + '/password')
.query({ access_token: token }) .query({ access_token: token })
.send({ password: 'some wrong password', newPassword: 'newpassword' }) .send({ password: 'some wrong password', newPassword: 'newpassword' })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(403); expect(res.statusCode).to.equal(403);
done(err); done();
}); });
}); });
it('change password fails due to invalid password', function (done) { it('change password fails due to invalid password', function (done) {
request.post(SERVER_URL + '/api/v1/users/' + USERNAME_0 + '/password') superagent.post(SERVER_URL + '/api/v1/users/' + USERNAME_0 + '/password')
.query({ access_token: token }) .query({ access_token: token })
.send({ password: PASSWORD, newPassword: 'five' }) .send({ password: PASSWORD, newPassword: 'five' })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(400); expect(res.statusCode).to.equal(400);
done(err); done();
}); });
}); });
it('change password succeeds', function (done) { it('change password succeeds', function (done) {
request.post(SERVER_URL + '/api/v1/users/' + USERNAME_0 + '/password') superagent.post(SERVER_URL + '/api/v1/users/' + USERNAME_0 + '/password')
.query({ access_token: token }) .query({ access_token: token })
.send({ password: PASSWORD, newPassword: 'new_password' }) .send({ password: PASSWORD, newPassword: 'new_password' })
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(204); expect(res.statusCode).to.equal(204);
done(err); done();
}); });
}); });
}); });
+2
View File
@@ -182,6 +182,8 @@ function doTask(appId, taskName, callback) {
// NOTE: if you change container name here, fix addons.js to return correct container names // NOTE: if you change container name here, fix addons.js to return correct container names
docker.createSubcontainer(app, app.id + '-' + taskName, [ '/bin/sh', '-c', gState[appId].schedulerConfig[taskName].command ], { } /* options */, function (error, container) { docker.createSubcontainer(app, app.id + '-' + taskName, [ '/bin/sh', '-c', gState[appId].schedulerConfig[taskName].command ], { } /* options */, function (error, container) {
if (error) return callback(error);
appState.containerIds[taskName] = container.id; appState.containerIds[taskName] = container.id;
saveState(gState); saveState(gState);
+2
View File
@@ -10,6 +10,7 @@ exports = module.exports = {
var assert = require('assert'), var assert = require('assert'),
async = require('async'), async = require('async'),
auth = require('./auth.js'), auth = require('./auth.js'),
certificates = require('./certificates.js'),
cloudron = require('./cloudron.js'), cloudron = require('./cloudron.js'),
cron = require('./cron.js'), cron = require('./cron.js'),
config = require('./config.js'), config = require('./config.js'),
@@ -235,6 +236,7 @@ function start(callback) {
auth.initialize, auth.initialize,
database.initialize, database.initialize,
cloudron.initialize, // keep this here because it reads activation state that others depend on cloudron.initialize, // keep this here because it reads activation state that others depend on
certificates.installAdminCertificate, // keep this before cron to block heartbeats until cert is ready
taskmanager.initialize, taskmanager.initialize,
mailer.initialize, mailer.initialize,
cron.initialize, cron.initialize,
+33 -115
View File
@@ -26,37 +26,31 @@ exports = module.exports = {
getBackupConfig: getBackupConfig, getBackupConfig: getBackupConfig,
setBackupConfig: setBackupConfig, setBackupConfig: setBackupConfig,
getTlsConfig: getTlsConfig,
setTlsConfig: setTlsConfig,
getDefaultSync: getDefaultSync, getDefaultSync: getDefaultSync,
getAll: getAll, getAll: getAll,
validateCertificate: validateCertificate,
setCertificate: setCertificate,
setAdminCertificate: setAdminCertificate,
AUTOUPDATE_PATTERN_KEY: 'autoupdate_pattern', AUTOUPDATE_PATTERN_KEY: 'autoupdate_pattern',
TIME_ZONE_KEY: 'time_zone', TIME_ZONE_KEY: 'time_zone',
CLOUDRON_NAME_KEY: 'cloudron_name', CLOUDRON_NAME_KEY: 'cloudron_name',
DEVELOPER_MODE_KEY: 'developer_mode', DEVELOPER_MODE_KEY: 'developer_mode',
DNS_CONFIG_KEY: 'dns_config', DNS_CONFIG_KEY: 'dns_config',
BACKUP_CONFIG_KEY: 'backup_config', BACKUP_CONFIG_KEY: 'backup_config',
TLS_CONFIG_KEY: 'tls_config',
events: new (require('events').EventEmitter)() events: new (require('events').EventEmitter)()
}; };
var assert = require('assert'), var assert = require('assert'),
config = require('./config.js'), config = require('./config.js'),
constants = require('./constants.js'),
CronJob = require('cron').CronJob, CronJob = require('cron').CronJob,
DatabaseError = require('./databaseerror.js'), DatabaseError = require('./databaseerror.js'),
ejs = require('ejs'),
fs = require('fs'),
path = require('path'),
paths = require('./paths.js'), paths = require('./paths.js'),
safe = require('safetydance'), safe = require('safetydance'),
settingsdb = require('./settingsdb.js'), settingsdb = require('./settingsdb.js'),
shell = require('./shell.js'),
util = require('util'), util = require('util'),
x509 = require('x509'),
_ = require('underscore'); _ = require('underscore');
var gDefaults = (function () { var gDefaults = (function () {
@@ -67,13 +61,11 @@ var gDefaults = (function () {
result[exports.DEVELOPER_MODE_KEY] = false; result[exports.DEVELOPER_MODE_KEY] = false;
result[exports.DNS_CONFIG_KEY] = { }; result[exports.DNS_CONFIG_KEY] = { };
result[exports.BACKUP_CONFIG_KEY] = { }; result[exports.BACKUP_CONFIG_KEY] = { };
result[exports.TLS_CONFIG_KEY] = { provider: 'caas' };
return result; return result;
})(); })();
var NGINX_APPCONFIG_EJS = fs.readFileSync(__dirname + '/../setup/start/nginx/appconfig.ejs', { encoding: 'utf8' }),
RELOAD_NGINX_CMD = path.join(__dirname, 'scripts/reloadnginx.sh');
if (config.TEST) { if (config.TEST) {
// avoid noisy warnings during npm test // avoid noisy warnings during npm test
exports.events.setMaxListeners(100); exports.events.setMaxListeners(100);
@@ -101,7 +93,6 @@ util.inherits(SettingsError, Error);
SettingsError.INTERNAL_ERROR = 'Internal Error'; SettingsError.INTERNAL_ERROR = 'Internal Error';
SettingsError.NOT_FOUND = 'Not Found'; SettingsError.NOT_FOUND = 'Not Found';
SettingsError.BAD_FIELD = 'Bad Field'; SettingsError.BAD_FIELD = 'Bad Field';
SettingsError.INVALID_CERT = 'Invalid certificate';
function setAutoupdatePattern(pattern, callback) { function setAutoupdatePattern(pattern, callback) {
assert.strictEqual(typeof pattern, 'string'); assert.strictEqual(typeof pattern, 'string');
@@ -276,6 +267,34 @@ function setDnsConfig(dnsConfig, callback) {
}); });
} }
function getTlsConfig(callback) {
assert.strictEqual(typeof callback, 'function');
settingsdb.get(exports.TLS_CONFIG_KEY, function (error, value) {
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(null, gDefaults[exports.TLS_CONFIG_KEY]);
if (error) return callback(new SettingsError(SettingsError.INTERNAL_ERROR, error));
callback(null, JSON.parse(value)); // provider
});
}
function setTlsConfig(tlsConfig, callback) {
assert.strictEqual(typeof tlsConfig, 'object');
assert.strictEqual(typeof callback, 'function');
if (tlsConfig.provider !== 'caas' && tlsConfig.provider.indexOf('le-') !== 0) {
return callback(new SettingsError(SettingsError.BAD_FIELD, 'provider must be caas or le-*'));
}
settingsdb.set(exports.TLS_CONFIG_KEY, JSON.stringify(tlsConfig), function (error) {
if (error) return callback(new SettingsError(SettingsError.INTERNAL_ERROR, error));
exports.events.emit(exports.TLS_CONFIG_KEY, tlsConfig);
callback(null);
});
}
function getBackupConfig(callback) { function getBackupConfig(callback) {
assert.strictEqual(typeof callback, 'function'); assert.strictEqual(typeof callback, 'function');
@@ -322,104 +341,3 @@ function getAll(callback) {
callback(null, result); callback(null, result);
}); });
} }
// note: https://tools.ietf.org/html/rfc4346#section-7.4.2 (certificate_list) requires that the
// servers certificate appears first (and not the intermediate cert)
function validateCertificate(cert, key, fqdn) {
assert(cert === null || typeof cert === 'string');
assert(key === null || typeof key === 'string');
assert.strictEqual(typeof fqdn, 'string');
if (cert === null && key === null) return null;
if (!cert && key) return new Error('missing cert');
if (cert && !key) return new Error('missing key');
var content;
try {
content = x509.parseCert(cert);
} catch (e) {
return new Error('invalid cert: ' + e.message);
}
// check expiration
if (content.notAfter < new Date()) return new Error('cert expired');
function matchesDomain(domain) {
if (domain === fqdn) return true;
if (domain.indexOf('*') === 0 && domain.slice(2) === fqdn.slice(fqdn.indexOf('.') + 1)) return true;
return false;
}
// check domain
var domains = content.altNames.concat(content.subject.commonName);
if (!domains.some(matchesDomain)) return new Error(util.format('cert is not valid for this domain. Expecting %s in %j', fqdn, domains));
// http://httpd.apache.org/docs/2.0/ssl/ssl_faq.html#verify
var certModulus = safe.child_process.execSync('openssl x509 -noout -modulus', { encoding: 'utf8', input: cert });
var keyModulus = safe.child_process.execSync('openssl rsa -noout -modulus', { encoding: 'utf8', input: key });
if (certModulus !== keyModulus) return new Error('key does not match the cert');
return null;
}
function setCertificate(cert, key, callback) {
assert.strictEqual(typeof cert, 'string');
assert.strictEqual(typeof key, 'string');
assert.strictEqual(typeof callback, 'function');
var error = validateCertificate(cert, key, '*.' + config.fqdn());
if (error) return callback(new SettingsError(SettingsError.INVALID_CERT, error.message));
// backup the cert
if (!safe.fs.writeFileSync(path.join(paths.APP_CERTS_DIR, 'host.cert'), cert)) return callback(new SettingsError(SettingsError.INTERNAL_ERROR, safe.error.message));
if (!safe.fs.writeFileSync(path.join(paths.APP_CERTS_DIR, 'host.key'), key)) return callback(new SettingsError(SettingsError.INTERNAL_ERROR, safe.error.message));
// copy over fallback cert
if (!safe.fs.writeFileSync(path.join(paths.NGINX_CERT_DIR, 'host.cert'), cert)) return callback(new SettingsError(SettingsError.INTERNAL_ERROR, safe.error.message));
if (!safe.fs.writeFileSync(path.join(paths.NGINX_CERT_DIR, 'host.key'), key)) return callback(new SettingsError(SettingsError.INTERNAL_ERROR, safe.error.message));
shell.sudo('setCertificate', [ RELOAD_NGINX_CMD ], function (error) {
if (error) return callback(new SettingsError(SettingsError.INTERNAL_ERROR, error));
return callback(null);
});
}
function setAdminCertificate(cert, key, callback) {
assert.strictEqual(typeof cert, 'string');
assert.strictEqual(typeof key, 'string');
assert.strictEqual(typeof callback, 'function');
var sourceDir = path.resolve(__dirname, '..');
var endpoint = 'admin';
var vhost = config.appFqdn(constants.ADMIN_LOCATION);
var certFilePath = path.join(paths.APP_CERTS_DIR, 'admin.cert');
var keyFilePath = path.join(paths.APP_CERTS_DIR, 'admin.key');
var error = validateCertificate(cert, key, vhost);
if (error) return callback(new SettingsError(SettingsError.INVALID_CERT, error.message));
// backup the cert
if (!safe.fs.writeFileSync(certFilePath, cert)) return callback(new SettingsError(SettingsError.INTERNAL_ERROR, safe.error.message));
if (!safe.fs.writeFileSync(keyFilePath, key)) return callback(new SettingsError(SettingsError.INTERNAL_ERROR, safe.error.message));
var data = {
sourceDir: sourceDir,
adminOrigin: config.adminOrigin(),
vhost: vhost,
endpoint: endpoint,
certFilePath: certFilePath,
keyFilePath: keyFilePath
};
var nginxConf = ejs.render(NGINX_APPCONFIG_EJS, data);
var nginxConfigFilename = path.join(paths.NGINX_APPCONFIG_DIR, 'admin.conf');
if (!safe.fs.writeFileSync(nginxConfigFilename, nginxConf)) return callback(safe.error);
shell.sudo('setAdminCertificate', [ RELOAD_NGINX_CMD ], function (error) {
if (error) return callback(new SettingsError(SettingsError.INTERNAL_ERROR, error));
return callback(null);
});
}
+2 -2
View File
@@ -24,7 +24,7 @@ function getBackupCredentials(backupConfig, callback) {
var url = config.apiServerOrigin() + '/api/v1/boxes/' + config.fqdn() + '/awscredentials'; var url = config.apiServerOrigin() + '/api/v1/boxes/' + config.fqdn() + '/awscredentials';
superagent.post(url).query({ token: backupConfig.token }).end(function (error, result) { superagent.post(url).query({ token: backupConfig.token }).end(function (error, result) {
if (error) return callback(error); if (error && !error.response) return callback(error);
if (result.statusCode !== 201) return callback(new Error(result.text)); if (result.statusCode !== 201) return callback(new Error(result.text));
if (!result.body || !result.body.credentials) return callback(new Error('Unexpected response')); if (!result.body || !result.body.credentials) return callback(new Error('Unexpected response'));
@@ -49,7 +49,7 @@ function getAllPaged(backupConfig, page, perPage, callback) {
var url = config.apiServerOrigin() + '/api/v1/boxes/' + config.fqdn() + '/backups'; var url = config.apiServerOrigin() + '/api/v1/boxes/' + config.fqdn() + '/backups';
superagent.get(url).query({ token: backupConfig.token }).end(function (error, result) { superagent.get(url).query({ token: backupConfig.token }).end(function (error, result) {
if (error) return callback(error); if (error && !error.response) return callback(error);
if (result.statusCode !== 200) return callback(new Error(result.text)); if (result.statusCode !== 200) return callback(new Error(result.text));
if (!result.body || !util.isArray(result.body.backups)) return callback(new Error('Unexpected response')); if (!result.body || !util.isArray(result.body.backups)) return callback(new Error('Unexpected response'));
+4 -3
View File
@@ -97,10 +97,11 @@ function startAppTask(appId) {
var pid = gActiveTasks[appId].pid; var pid = gActiveTasks[appId].pid;
debug('Started task of %s pid: %s', appId, pid); debug('Started task of %s pid: %s', appId, pid);
gActiveTasks[appId].once('exit', function (code) { gActiveTasks[appId].once('exit', function (code, signal) {
debug('Task for %s pid %s completed with status %s', appId, pid, code); debug('Task for %s pid %s completed with status %s', appId, pid, code);
if (code && code !== 50) { // apptask crashed if (code === null /* signal */ || (code !== 0 && code !== 50)) { // apptask crashed
appdb.update(appId, { installationState: appdb.ISTATE_ERROR, installationProgress: 'Apptask crashed with code ' + code }, NOOP_CALLBACK); debug('Apptask crashed with code %s and signal %s', code, signal);
appdb.update(appId, { installationState: appdb.ISTATE_ERROR, installationProgress: 'Apptask crashed with code ' + code + ' and signal ' + signal }, NOOP_CALLBACK);
} }
delete gActiveTasks[appId]; delete gActiveTasks[appId];
locker.unlock(locker.OP_APPTASK); // unlock event will trigger next task locker.unlock(locker.OP_APPTASK); // unlock event will trigger next task
+8 -7
View File
@@ -85,7 +85,8 @@ describe('apptask', function () {
async.series([ async.series([
database.initialize, database.initialize,
appdb.add.bind(null, APP.id, APP.appStoreId, APP.manifest, APP.location, APP.portBindings, APP.accessRestriction, APP.oauthProxy), appdb.add.bind(null, APP.id, APP.appStoreId, APP.manifest, APP.location, APP.portBindings, APP.accessRestriction, APP.oauthProxy),
settings.setDnsConfig.bind(null, { provider: 'route53', accessKeyId: 'accessKeyId', secretAccessKey: 'secretAccessKey', endpoint: 'http://localhost:5353' }) settings.setDnsConfig.bind(null, { provider: 'route53', accessKeyId: 'accessKeyId', secretAccessKey: 'secretAccessKey', endpoint: 'http://localhost:5353' }),
settings.setTlsConfig.bind(null, { provider: 'caas' })
], done); ], done);
}); });
@@ -97,12 +98,12 @@ describe('apptask', function () {
apptask.initialize(done); apptask.initialize(done);
}); });
it('free port', function (done) { it('reserve port', function (done) {
apptask._getFreePort(function (error, port) { apptask._reserveHttpPort(APP, function (error) {
expect(error).to.be(null); expect(error).to.not.be.ok();
expect(port).to.be.a('number'); expect(APP.httpPort).to.be.a('number');
var client = net.connect(port); var client = net.connect(APP.httpPort);
client.on('connect', function () { done(new Error('Port is not free:' + port)); }); client.on('connect', function () { done(new Error('Port is not free:' + APP.httpPort)); });
client.on('error', function (error) { done(); }); client.on('error', function (error) { done(); });
}); });
}); });
+90
View File
@@ -0,0 +1,90 @@
/* jslint node:true */
/* global it:false */
/* global describe:false */
/* global before:false */
/* global after:false */
'use strict';
var certificates = require('../certificates.js'),
expect = require('expect.js');
describe('Certificates', function () {
describe('validateCertificate', function () {
/*
Generate these with:
openssl genrsa -out server.key 512
openssl req -new -key server.key -out server.csr -subj "/C=DE/ST=Berlin/L=Berlin/O=Nebulon/OU=CTO/CN=baz.foobar.com"
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
*/
// foobar.com
var validCert0 = '-----BEGIN CERTIFICATE-----\nMIIBujCCAWQCCQCjLyTKzAJ4FDANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJE\nRTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xEDAOBgNVBAoMB05l\nYnVsb24xDDAKBgNVBAsMA0NUTzETMBEGA1UEAwwKZm9vYmFyLmNvbTAeFw0xNTEw\nMjgxMjM5MjZaFw0xNjEwMjcxMjM5MjZaMGQxCzAJBgNVBAYTAkRFMQ8wDQYDVQQI\nDAZCZXJsaW4xDzANBgNVBAcMBkJlcmxpbjEQMA4GA1UECgwHTmVidWxvbjEMMAoG\nA1UECwwDQ1RPMRMwEQYDVQQDDApmb29iYXIuY29tMFwwDQYJKoZIhvcNAQEBBQAD\nSwAwSAJBAMeYofgwHeNVmGkGe0gj4dnX2ciifDi7X2K/oVHp7mxuHjGMSYP9Z7b6\n+mu0IMf4OedwXStHBeO8mwjKxZmE7p8CAwEAATANBgkqhkiG9w0BAQsFAANBAJI7\nFUUHXjR63UFk8pgxp0c7hEGqj4VWWGsmo8oZnnX8jGVmQDKbk8o3MtDujfqupmMR\nMo7tSAFlG7zkm3GYhpw=\n-----END CERTIFICATE-----';
var validKey0 = '-----BEGIN RSA PRIVATE KEY-----\nMIIBOwIBAAJBAMeYofgwHeNVmGkGe0gj4dnX2ciifDi7X2K/oVHp7mxuHjGMSYP9\nZ7b6+mu0IMf4OedwXStHBeO8mwjKxZmE7p8CAwEAAQJBAJS59Sb8o6i8JT9NJxvQ\nMQCkSJGqEaosZJ0uccSZ7aE48v+H7HiPzXAueitohcEif2Wp1EZ1RbRMURhznNiZ\neLECIQDxxqhakO6wc7H68zmpRXJ5ZxGUNbM24AMtpONAtEw9iwIhANNWtp6P74OV\ntvfOmtubbqw768fmGskFCOcp5oF8oF29AiBkTAf9AhCyjFwyAYJTEScq67HkLN66\njfVjkvpfFixmfwIgI+xldmZ5DCDyzQSthg7RrS0yUvRmMS1N6h1RNUl96PECIQDl\nit4lFcytbqNo1PuBZvzQE+plCjiJqXHYo3WCst1Jbg==\n-----END RSA PRIVATE KEY-----';
// *.foobar.com
var validCert1 = '-----BEGIN CERTIFICATE-----\nMIIBvjCCAWgCCQCg957GWuHtbzANBgkqhkiG9w0BAQsFADBmMQswCQYDVQQGEwJE\nRTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xEDAOBgNVBAoMB05l\nYnVsb24xDDAKBgNVBAsMA0NUTzEVMBMGA1UEAwwMKi5mb29iYXIuY29tMB4XDTE1\nMTAyODEzMDI1MFoXDTE2MTAyNzEzMDI1MFowZjELMAkGA1UEBhMCREUxDzANBgNV\nBAgMBkJlcmxpbjEPMA0GA1UEBwwGQmVybGluMRAwDgYDVQQKDAdOZWJ1bG9uMQww\nCgYDVQQLDANDVE8xFTATBgNVBAMMDCouZm9vYmFyLmNvbTBcMA0GCSqGSIb3DQEB\nAQUAA0sAMEgCQQC0FKf07ZWMcABFlZw+GzXK9EiZrlJ1lpnu64RhN99z7MXRr8cF\nnZVgY3jgatuyR5s3WdzUvye2eJ0rNicl2EZJAgMBAAEwDQYJKoZIhvcNAQELBQAD\nQQAw4bteMZAeJWl2wgNLw+wTwAH96E0jyxwreCnT5AxJLmgimyQ0XOF4FsssdRFj\nxD9WA+rktelBodJyPeTDNhIh\n-----END CERTIFICATE-----';
var validKey1 = '-----BEGIN RSA PRIVATE KEY-----\nMIIBOQIBAAJBALQUp/TtlYxwAEWVnD4bNcr0SJmuUnWWme7rhGE333PsxdGvxwWd\nlWBjeOBq27JHmzdZ3NS/J7Z4nSs2JyXYRkkCAwEAAQJALV2eykcoC48TonQEPmkg\nbhaIS57syw67jMLsQImQ02UABKzqHPEKLXPOZhZPS9hsC/hGIehwiYCXMUlrl+WF\nAQIhAOntBI6qaecNjAAVG7UbZclMuHROUONmZUF1KNq6VyV5AiEAxRLkfHWy52CM\njOQrX347edZ30f4QczvugXwsyuU9A1ECIGlGZ8Sk4OBA8n6fAUcyO06qnmCJVlHg\npTUeOvKk5c9RAiBs28+8dCNbrbhVhx/yQr9FwNM0+ttJW/yWJ+pyNQhr0QIgJTT6\nxwCWYOtbioyt7B9l+ENy3AMSO3Uq+xmIKkvItK4=\n-----END RSA PRIVATE KEY-----';
// baz.foobar.com
var validCert2 = '-----BEGIN CERTIFICATE-----\nMIIBwjCCAWwCCQDIKtL9RCDCkDANBgkqhkiG9w0BAQsFADBoMQswCQYDVQQGEwJE\nRTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xEDAOBgNVBAoMB05l\nYnVsb24xDDAKBgNVBAsMA0NUTzEXMBUGA1UEAwwOYmF6LmZvb2Jhci5jb20wHhcN\nMTUxMDI4MTMwNTMzWhcNMTYxMDI3MTMwNTMzWjBoMQswCQYDVQQGEwJERTEPMA0G\nA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xEDAOBgNVBAoMB05lYnVsb24x\nDDAKBgNVBAsMA0NUTzEXMBUGA1UEAwwOYmF6LmZvb2Jhci5jb20wXDANBgkqhkiG\n9w0BAQEFAANLADBIAkEAw7UWW/VoQePv2l92l3XcntZeyw1nBiHxk1axZwC6auOW\n2/zfA//Tg7fv4q5qKnV1n/71IiMAheeFcpfogY5rTwIDAQABMA0GCSqGSIb3DQEB\nCwUAA0EAtluL6dGNfOdNkzoO/UwzRaIvEm2reuqe+Ik4WR/k+DJ4igrmRCQqXwjW\nJaGYsFWsuk3QLOWQ9YgCKlcIYd+1/A==\n-----END CERTIFICATE-----';
var validKey2 = '-----BEGIN RSA PRIVATE KEY-----\nMIIBOQIBAAJBAMO1Flv1aEHj79pfdpd13J7WXssNZwYh8ZNWsWcAumrjltv83wP/\n04O37+Kuaip1dZ/+9SIjAIXnhXKX6IGOa08CAwEAAQJAUPD3Y2cXDJFaJQXwhWnw\nqhzdLbvITUgCor5rNr+dWhE2MopGPpRHiabA1PeWEPx8CfblyTZGd8KUR/2W1c0r\naQIhAP4ZxB3+uhuzzMfyRrn/khr12pFn/FCIDbwnDbyUxLrTAiEAxSuVOFs+Mupt\nYCz/pPrDCx3eid0wyXRObbkLHOxJiBUCIBTp5fxaBNNW3xnt1OhmIo5Zgd3J4zh1\nmjvMMxM8Y1zFAiAxOP0qsZSoj1+41+MGY9fXaaCJ2F96m3+M4tpEYTTGNQIgdESZ\nz+hzHBeYVbWJpIR8uaNkx7wveUF90FpipXyeTsA=\n-----END RSA PRIVATE KEY-----';
it('allows both null', function () {
expect(certificates.validateCertificate(null, null, 'foobar.com')).to.be(null);
});
it('does not allow only cert', function () {
expect(certificates.validateCertificate('cert', null, 'foobar.com')).to.be.an(Error);
});
it('does not allow only key', function () {
expect(certificates.validateCertificate(null, 'key', 'foobar.com')).to.be.an(Error);
});
it('does not allow empty string for cert', function () {
expect(certificates.validateCertificate('', 'key', 'foobar.com')).to.be.an(Error);
});
it('does not allow empty string for key', function () {
expect(certificates.validateCertificate('cert', '', 'foobar.com')).to.be.an(Error);
});
it('does not allow invalid cert', function () {
expect(certificates.validateCertificate('someinvalidcert', validKey0, 'foobar.com')).to.be.an(Error);
});
it('does not allow invalid key', function () {
expect(certificates.validateCertificate(validCert0, 'invalidkey', 'foobar.com')).to.be.an(Error);
});
it('does not allow cert without matching domain', function () {
expect(certificates.validateCertificate(validCert0, validKey0, 'cloudron.io')).to.be.an(Error);
});
it('allows valid cert with matching domain', function () {
expect(certificates.validateCertificate(validCert0, validKey0, 'foobar.com')).to.be(null);
});
it('allows valid cert with matching domain (wildcard)', function () {
expect(certificates.validateCertificate(validCert1, validKey1, 'abc.foobar.com')).to.be(null);
});
it('does now allow cert without matching domain (wildcard)', function () {
expect(certificates.validateCertificate(validCert1, validKey1, 'foobar.com')).to.be.an(Error);
expect(certificates.validateCertificate(validCert1, validKey1, 'bar.abc.foobar.com')).to.be.an(Error);
});
it('allows valid cert with matching domain (subdomain)', function () {
expect(certificates.validateCertificate(validCert2, validKey2, 'baz.foobar.com')).to.be(null);
});
it('does not allow cert without matching domain (subdomain)', function () {
expect(certificates.validateCertificate(validCert0, validKey0, 'baz.foobar.com')).to.be.an(Error);
});
it('does not allow invalid cert/key tuple', function () {
expect(certificates.validateCertificate(validCert0, validKey1, 'foobar.com')).to.be.an(Error);
});
});
});
+25 -29
View File
@@ -11,7 +11,7 @@ var progress = require('../progress.js'),
database = require('../database.js'), database = require('../database.js'),
expect = require('expect.js'), expect = require('expect.js'),
nock = require('nock'), nock = require('nock'),
request = require('superagent'), superagent = require('superagent'),
server = require('../server.js'); server = require('../server.js');
var SERVER_URL = 'http://localhost:' + config.get('port'); var SERVER_URL = 'http://localhost:' + config.get('port');
@@ -46,7 +46,7 @@ describe('Server', function () {
}); });
it('is reachable', function (done) { it('is reachable', function (done) {
request.get(SERVER_URL + '/api/v1/cloudron/status', function (err, res) { superagent.get(SERVER_URL + '/api/v1/cloudron/status', function (err, res) {
expect(res.statusCode).to.equal(200); expect(res.statusCode).to.equal(200);
done(err); done(err);
}); });
@@ -79,32 +79,32 @@ describe('Server', function () {
}); });
}); });
it('random bad requests', function (done) { it('random bad superagents', function (done) {
request.get(SERVER_URL + '/random', function (err, res) { superagent.get(SERVER_URL + '/random', function (err, res) {
expect(err).to.not.be.ok(); expect(err).to.be.ok();
expect(res.statusCode).to.equal(404); expect(res.statusCode).to.equal(404);
done(err); done();
}); });
}); });
it('version', function (done) { it('version', function (done) {
request.get(SERVER_URL + '/api/v1/cloudron/status', function (err, res) { superagent.get(SERVER_URL + '/api/v1/cloudron/status', function (err, res) {
expect(err).to.not.be.ok(); expect(err).to.not.be.ok();
expect(res.statusCode).to.equal(200); expect(res.statusCode).to.equal(200);
expect(res.body.version).to.equal('0.5.0'); expect(res.body.version).to.equal('0.5.0');
done(err); done();
}); });
}); });
it('status route is GET', function (done) { it('status route is GET', function (done) {
request.post(SERVER_URL + '/api/v1/cloudron/status') superagent.post(SERVER_URL + '/api/v1/cloudron/status')
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(404); expect(res.statusCode).to.equal(404);
request.get(SERVER_URL + '/api/v1/cloudron/status') superagent.get(SERVER_URL + '/api/v1/cloudron/status')
.end(function (err, res) { .end(function (err, res) {
expect(res.statusCode).to.equal(200); expect(res.statusCode).to.equal(200);
done(err); done();
}); });
}); });
}); });
@@ -122,18 +122,16 @@ describe('Server', function () {
}); });
it('config fails due missing token', function (done) { it('config fails due missing token', function (done) {
request.get(SERVER_URL + '/api/v1/cloudron/config', function (err, res) { superagent.get(SERVER_URL + '/api/v1/cloudron/config', function (err, res) {
expect(err).to.not.be.ok();
expect(res.statusCode).to.equal(401); expect(res.statusCode).to.equal(401);
done(err); done();
}); });
}); });
it('config fails due wrong token', function (done) { it('config fails due wrong token', function (done) {
request.get(SERVER_URL + '/api/v1/cloudron/config').query({ access_token: 'somewrongtoken' }).end(function (err, res) { superagent.get(SERVER_URL + '/api/v1/cloudron/config').query({ access_token: 'somewrongtoken' }).end(function (err, res) {
expect(err).to.not.be.ok();
expect(res.statusCode).to.equal(401); expect(res.statusCode).to.equal(401);
done(err); done();
}); });
}); });
}); });
@@ -150,8 +148,7 @@ describe('Server', function () {
}); });
it('succeeds with no progress', function (done) { it('succeeds with no progress', function (done) {
request.get(SERVER_URL + '/api/v1/cloudron/progress', function (error, result) { superagent.get(SERVER_URL + '/api/v1/cloudron/progress', function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(200); expect(result.statusCode).to.equal(200);
expect(result.body.update).to.be(null); expect(result.body.update).to.be(null);
expect(result.body.backup).to.be(null); expect(result.body.backup).to.be(null);
@@ -162,8 +159,7 @@ describe('Server', function () {
it('succeeds with update progress', function (done) { it('succeeds with update progress', function (done) {
progress.set(progress.UPDATE, 13, 'This is some status string'); progress.set(progress.UPDATE, 13, 'This is some status string');
request.get(SERVER_URL + '/api/v1/cloudron/progress', function (error, result) { superagent.get(SERVER_URL + '/api/v1/cloudron/progress', function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(200); expect(result.statusCode).to.equal(200);
expect(result.body.update).to.be.an('object'); expect(result.body.update).to.be.an('object');
expect(result.body.update.percent).to.be.a('number'); expect(result.body.update.percent).to.be.a('number');
@@ -179,8 +175,7 @@ describe('Server', function () {
it('succeeds with no progress after clearing the update', function (done) { it('succeeds with no progress after clearing the update', function (done) {
progress.clear(progress.UPDATE); progress.clear(progress.UPDATE);
request.get(SERVER_URL + '/api/v1/cloudron/progress', function (error, result) { superagent.get(SERVER_URL + '/api/v1/cloudron/progress', function (error, result) {
expect(error).to.not.be.ok();
expect(result.statusCode).to.equal(200); expect(result.statusCode).to.equal(200);
expect(result.body.update).to.be(null); expect(result.body.update).to.be(null);
expect(result.body.backup).to.be(null); expect(result.body.backup).to.be(null);
@@ -211,8 +206,9 @@ describe('Server', function () {
}); });
it('is not reachable anymore', function (done) { it('is not reachable anymore', function (done) {
request.get(SERVER_URL + '/api/v1/cloudron/status', function (error, result) { superagent.get(SERVER_URL + '/api/v1/cloudron/status', function (error, result) {
expect(error).to.not.be(null); expect(error).to.not.be(null);
expect(!error.response).to.be.ok();
done(); done();
}); });
}); });
@@ -226,15 +222,15 @@ describe('Server', function () {
}); });
it('responds to OPTIONS', function (done) { it('responds to OPTIONS', function (done) {
request('OPTIONS', SERVER_URL + '/api/v1/cloudron/status') superagent('OPTIONS', SERVER_URL + '/api/v1/cloudron/status')
.set('Access-Control-Request-Method', 'GET') .set('Access-Control-Request-Method', 'GET')
.set('Access-Control-Request-Headers', 'accept, origin, x-requested-with') .set('Access-Control-Request-Headers', 'accept, origin, x-superagented-with')
.set('Origin', 'http://localhost') .set('Origin', 'http://localhost')
.end(function (res) { .end(function (error, res) {
expect(res.headers['access-control-allow-methods']).to.be('GET, PUT, DELETE, POST, OPTIONS'); expect(res.headers['access-control-allow-methods']).to.be('GET, PUT, DELETE, POST, OPTIONS');
expect(res.headers['access-control-allow-credentials']).to.be('true'); expect(res.headers['access-control-allow-credentials']).to.be('true');
expect(res.headers['access-control-allow-headers']).to.be('accept, origin, x-requested-with'); // mirrored from request expect(res.headers['access-control-allow-headers']).to.be('accept, origin, x-superagented-with'); // mirrored from superagent
expect(res.headers['access-control-allow-origin']).to.be('http://localhost'); // mirrors from request expect(res.headers['access-control-allow-origin']).to.be('http://localhost'); // mirrors from superagent
done(); done();
}); });
}); });
+15 -81
View File
@@ -100,6 +100,21 @@ describe('Settings', function () {
}); });
}); });
it('can set tls config', function (done) {
settings.setTlsConfig({ provider: 'caas' }, function (error) {
expect(error).to.be(null);
done();
});
});
it('can get tls config', function (done) {
settings.getTlsConfig(function (error, dnsConfig) {
expect(error).to.be(null);
expect(dnsConfig.provider).to.be('caas');
done();
});
});
it('can set backup config', function (done) { it('can set backup config', function (done) {
settings.setBackupConfig({ provider: 'caas', token: 'TOKEN' }, function (error) { settings.setBackupConfig({ provider: 'caas', token: 'TOKEN' }, function (error) {
expect(error).to.be(null); expect(error).to.be(null);
@@ -126,85 +141,4 @@ describe('Settings', function () {
}); });
}); });
}); });
describe('validateCertificate', function () {
before(setup);
after(cleanup);
/*
Generate these with:
openssl genrsa -out server.key 512
openssl req -new -key server.key -out server.csr -subj "/C=DE/ST=Berlin/L=Berlin/O=Nebulon/OU=CTO/CN=baz.foobar.com"
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
*/
// foobar.com
var validCert0 = '-----BEGIN CERTIFICATE-----\nMIIBujCCAWQCCQCjLyTKzAJ4FDANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJE\nRTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xEDAOBgNVBAoMB05l\nYnVsb24xDDAKBgNVBAsMA0NUTzETMBEGA1UEAwwKZm9vYmFyLmNvbTAeFw0xNTEw\nMjgxMjM5MjZaFw0xNjEwMjcxMjM5MjZaMGQxCzAJBgNVBAYTAkRFMQ8wDQYDVQQI\nDAZCZXJsaW4xDzANBgNVBAcMBkJlcmxpbjEQMA4GA1UECgwHTmVidWxvbjEMMAoG\nA1UECwwDQ1RPMRMwEQYDVQQDDApmb29iYXIuY29tMFwwDQYJKoZIhvcNAQEBBQAD\nSwAwSAJBAMeYofgwHeNVmGkGe0gj4dnX2ciifDi7X2K/oVHp7mxuHjGMSYP9Z7b6\n+mu0IMf4OedwXStHBeO8mwjKxZmE7p8CAwEAATANBgkqhkiG9w0BAQsFAANBAJI7\nFUUHXjR63UFk8pgxp0c7hEGqj4VWWGsmo8oZnnX8jGVmQDKbk8o3MtDujfqupmMR\nMo7tSAFlG7zkm3GYhpw=\n-----END CERTIFICATE-----';
var validKey0 = '-----BEGIN RSA PRIVATE KEY-----\nMIIBOwIBAAJBAMeYofgwHeNVmGkGe0gj4dnX2ciifDi7X2K/oVHp7mxuHjGMSYP9\nZ7b6+mu0IMf4OedwXStHBeO8mwjKxZmE7p8CAwEAAQJBAJS59Sb8o6i8JT9NJxvQ\nMQCkSJGqEaosZJ0uccSZ7aE48v+H7HiPzXAueitohcEif2Wp1EZ1RbRMURhznNiZ\neLECIQDxxqhakO6wc7H68zmpRXJ5ZxGUNbM24AMtpONAtEw9iwIhANNWtp6P74OV\ntvfOmtubbqw768fmGskFCOcp5oF8oF29AiBkTAf9AhCyjFwyAYJTEScq67HkLN66\njfVjkvpfFixmfwIgI+xldmZ5DCDyzQSthg7RrS0yUvRmMS1N6h1RNUl96PECIQDl\nit4lFcytbqNo1PuBZvzQE+plCjiJqXHYo3WCst1Jbg==\n-----END RSA PRIVATE KEY-----';
// *.foobar.com
var validCert1 = '-----BEGIN CERTIFICATE-----\nMIIBvjCCAWgCCQCg957GWuHtbzANBgkqhkiG9w0BAQsFADBmMQswCQYDVQQGEwJE\nRTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xEDAOBgNVBAoMB05l\nYnVsb24xDDAKBgNVBAsMA0NUTzEVMBMGA1UEAwwMKi5mb29iYXIuY29tMB4XDTE1\nMTAyODEzMDI1MFoXDTE2MTAyNzEzMDI1MFowZjELMAkGA1UEBhMCREUxDzANBgNV\nBAgMBkJlcmxpbjEPMA0GA1UEBwwGQmVybGluMRAwDgYDVQQKDAdOZWJ1bG9uMQww\nCgYDVQQLDANDVE8xFTATBgNVBAMMDCouZm9vYmFyLmNvbTBcMA0GCSqGSIb3DQEB\nAQUAA0sAMEgCQQC0FKf07ZWMcABFlZw+GzXK9EiZrlJ1lpnu64RhN99z7MXRr8cF\nnZVgY3jgatuyR5s3WdzUvye2eJ0rNicl2EZJAgMBAAEwDQYJKoZIhvcNAQELBQAD\nQQAw4bteMZAeJWl2wgNLw+wTwAH96E0jyxwreCnT5AxJLmgimyQ0XOF4FsssdRFj\nxD9WA+rktelBodJyPeTDNhIh\n-----END CERTIFICATE-----';
var validKey1 = '-----BEGIN RSA PRIVATE KEY-----\nMIIBOQIBAAJBALQUp/TtlYxwAEWVnD4bNcr0SJmuUnWWme7rhGE333PsxdGvxwWd\nlWBjeOBq27JHmzdZ3NS/J7Z4nSs2JyXYRkkCAwEAAQJALV2eykcoC48TonQEPmkg\nbhaIS57syw67jMLsQImQ02UABKzqHPEKLXPOZhZPS9hsC/hGIehwiYCXMUlrl+WF\nAQIhAOntBI6qaecNjAAVG7UbZclMuHROUONmZUF1KNq6VyV5AiEAxRLkfHWy52CM\njOQrX347edZ30f4QczvugXwsyuU9A1ECIGlGZ8Sk4OBA8n6fAUcyO06qnmCJVlHg\npTUeOvKk5c9RAiBs28+8dCNbrbhVhx/yQr9FwNM0+ttJW/yWJ+pyNQhr0QIgJTT6\nxwCWYOtbioyt7B9l+ENy3AMSO3Uq+xmIKkvItK4=\n-----END RSA PRIVATE KEY-----';
// baz.foobar.com
var validCert2 = '-----BEGIN CERTIFICATE-----\nMIIBwjCCAWwCCQDIKtL9RCDCkDANBgkqhkiG9w0BAQsFADBoMQswCQYDVQQGEwJE\nRTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xEDAOBgNVBAoMB05l\nYnVsb24xDDAKBgNVBAsMA0NUTzEXMBUGA1UEAwwOYmF6LmZvb2Jhci5jb20wHhcN\nMTUxMDI4MTMwNTMzWhcNMTYxMDI3MTMwNTMzWjBoMQswCQYDVQQGEwJERTEPMA0G\nA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xEDAOBgNVBAoMB05lYnVsb24x\nDDAKBgNVBAsMA0NUTzEXMBUGA1UEAwwOYmF6LmZvb2Jhci5jb20wXDANBgkqhkiG\n9w0BAQEFAANLADBIAkEAw7UWW/VoQePv2l92l3XcntZeyw1nBiHxk1axZwC6auOW\n2/zfA//Tg7fv4q5qKnV1n/71IiMAheeFcpfogY5rTwIDAQABMA0GCSqGSIb3DQEB\nCwUAA0EAtluL6dGNfOdNkzoO/UwzRaIvEm2reuqe+Ik4WR/k+DJ4igrmRCQqXwjW\nJaGYsFWsuk3QLOWQ9YgCKlcIYd+1/A==\n-----END CERTIFICATE-----';
var validKey2 = '-----BEGIN RSA PRIVATE KEY-----\nMIIBOQIBAAJBAMO1Flv1aEHj79pfdpd13J7WXssNZwYh8ZNWsWcAumrjltv83wP/\n04O37+Kuaip1dZ/+9SIjAIXnhXKX6IGOa08CAwEAAQJAUPD3Y2cXDJFaJQXwhWnw\nqhzdLbvITUgCor5rNr+dWhE2MopGPpRHiabA1PeWEPx8CfblyTZGd8KUR/2W1c0r\naQIhAP4ZxB3+uhuzzMfyRrn/khr12pFn/FCIDbwnDbyUxLrTAiEAxSuVOFs+Mupt\nYCz/pPrDCx3eid0wyXRObbkLHOxJiBUCIBTp5fxaBNNW3xnt1OhmIo5Zgd3J4zh1\nmjvMMxM8Y1zFAiAxOP0qsZSoj1+41+MGY9fXaaCJ2F96m3+M4tpEYTTGNQIgdESZ\nz+hzHBeYVbWJpIR8uaNkx7wveUF90FpipXyeTsA=\n-----END RSA PRIVATE KEY-----';
it('allows both null', function () {
expect(settings.validateCertificate(null, null, 'foobar.com')).to.be(null);
});
it('does not allow only cert', function () {
expect(settings.validateCertificate('cert', null, 'foobar.com')).to.be.an(Error);
});
it('does not allow only key', function () {
expect(settings.validateCertificate(null, 'key', 'foobar.com')).to.be.an(Error);
});
it('does not allow empty string for cert', function () {
expect(settings.validateCertificate('', 'key', 'foobar.com')).to.be.an(Error);
});
it('does not allow empty string for key', function () {
expect(settings.validateCertificate('cert', '', 'foobar.com')).to.be.an(Error);
});
it('does not allow invalid cert', function () {
expect(settings.validateCertificate('someinvalidcert', validKey0, 'foobar.com')).to.be.an(Error);
});
it('does not allow invalid key', function () {
expect(settings.validateCertificate(validCert0, 'invalidkey', 'foobar.com')).to.be.an(Error);
});
it('does not allow cert without matching domain', function () {
expect(settings.validateCertificate(validCert0, validKey0, 'cloudron.io')).to.be.an(Error);
});
it('allows valid cert with matching domain', function () {
expect(settings.validateCertificate(validCert0, validKey0, 'foobar.com')).to.be(null);
});
it('allows valid cert with matching domain (wildcard)', function () {
expect(settings.validateCertificate(validCert1, validKey1, 'abc.foobar.com')).to.be(null);
});
it('does now allow cert without matching domain (wildcard)', function () {
expect(settings.validateCertificate(validCert1, validKey1, 'foobar.com')).to.be.an(Error);
expect(settings.validateCertificate(validCert1, validKey1, 'bar.abc.foobar.com')).to.be.an(Error);
});
it('allows valid cert with matching domain (subdomain)', function () {
expect(settings.validateCertificate(validCert2, validKey2, 'baz.foobar.com')).to.be(null);
});
it('does not allow cert without matching domain (subdomain)', function () {
expect(settings.validateCertificate(validCert0, validKey0, 'baz.foobar.com')).to.be.an(Error);
});
it('does not allow invalid cert/key tuple', function () {
expect(settings.validateCertificate(validCert0, validKey1, 'foobar.com')).to.be.an(Error);
});
});
}); });
+3 -3
View File
@@ -53,7 +53,7 @@ function getAppUpdates(callback) {
.timeout(10 * 1000) .timeout(10 * 1000)
.end(function (error, result) { .end(function (error, result) {
if (error) return callback(error); if (error && !error.response) return callback(error);
if (result.statusCode !== 200 || !result.body.appVersions) { if (result.statusCode !== 200 || !result.body.appVersions) {
return callback(new Error(util.format('Error checking app update: %s %s', result.statusCode, result.text))); return callback(new Error(util.format('Error checking app update: %s %s', result.statusCode, result.text)));
@@ -88,8 +88,8 @@ function getBoxUpdates(callback) {
.get(config.get('boxVersionsUrl')) .get(config.get('boxVersionsUrl'))
.timeout(10 * 1000) .timeout(10 * 1000)
.end(function (error, result) { .end(function (error, result) {
if (error) return callback(error); if (error && !error.response) return callback(error);
if (result.status !== 200) return callback(new Error(util.format('Bad status: %s %s', result.status, result.text))); if (result.statusCode !== 200) return callback(new Error(util.format('Bad status: %s %s', result.statusCode, result.text)));
var versions = safe.JSON.parse(result.text); var versions = safe.JSON.parse(result.text);
+89
View File
@@ -0,0 +1,89 @@
/* jslint node:true */
'use strict';
exports = module.exports = waitForDns;
var assert = require('assert'),
async = require('async'),
attempt = require('attempt'),
debug = require('debug')('box:src/waitfordns'),
dns = require('native-dns');
// the first arg to callback is not an error argument; this is required for async.every
function isChangeSynced(domain, ip, nameserver, callback) {
assert.strictEqual(typeof domain, 'string');
assert.strictEqual(typeof ip, 'string');
assert.strictEqual(typeof nameserver, 'string');
assert.strictEqual(typeof callback, 'function');
// ns records cannot have cname
dns.resolve4(nameserver, function (error, nsIps) {
if (error || !nsIps || nsIps.length === 0) return callback(false);
async.every(nsIps, function (nsIp, iteratorCallback) {
var req = dns.Request({
question: dns.Question({ name: domain, type: 'A' }),
server: { address: nsIp },
timeout: 5000
});
req.on('timeout', function () { return iteratorCallback(false); });
req.on('message', function (error, message) {
if (error || !message.answer || message.answer.length === 0) return iteratorCallback(false);
debug('isChangeSynced: ns: %s (%s), name:%s Actual:%j Expecting:%s', nameserver, nsIp, domain, message.answer[0], ip);
if (message.answer[0].address !== ip) return iteratorCallback(false);
iteratorCallback(true); // done
});
req.send();
}, callback);
});
}
// check if IP change has propagated to every nameserver
function waitForDns(domain, ip, zoneName, options, callback) {
assert.strictEqual(typeof domain, 'string');
assert.strictEqual(typeof ip, 'string');
assert.strictEqual(typeof zoneName, 'string');
var defaultOptions = {
retryInterval: 5000,
retries: Infinity
};
if (typeof options === 'function') {
callback = options;
options = defaultOptions;
} else {
assert.strictEqual(typeof options, 'object');
assert.strictEqual(typeof callback, 'function');
}
debug('waitForDNS: domain %s to be %s in zone %s.', domain, ip, zoneName);
attempt(function (attempts) {
var callback = this; // gross
debug('waitForDNS: %s attempt %s.', domain, attempts);
dns.resolveNs(zoneName, function (error, nameservers) {
if (error || !nameservers) return callback(error || new Error('Unable to get nameservers'));
async.every(nameservers, isChangeSynced.bind(null, domain, ip), function (synced) {
debug('waitForDNS: %s %s ns: %j', domain, synced ? 'done' : 'not done', nameservers);
callback(synced ? null : new Error('ETRYAGAIN'));
});
});
}, { interval: options.retryInterval, retries: options.retries }, function (error) {
if (error) return callback(error);
debug('waitForDNS: %s done.', domain);
callback(null);
});
}
+1 -1
View File
@@ -32,7 +32,7 @@ function backupDone(filename, app, appBackupIds, callback) {
}; };
superagent.post(url).send(data).query({ token: config.token() }).end(function (error, result) { superagent.post(url).send(data).query({ token: config.token() }).end(function (error, result) {
if (error) return callback(error); if (error && !error.response) return callback(error);
if (result.statusCode !== 200) return callback(new Error(result.text)); if (result.statusCode !== 200) return callback(new Error(result.text));
if (!result.body) return callback(new Error('Unexpected response')); if (!result.body) return callback(new Error('Unexpected response'));
+19 -1
View File
@@ -119,6 +119,24 @@
</div> </div>
</div> </div>
<!-- Modal error app -->
<div class="modal fade" id="appErrorModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">{{ appError.app.location }}</h4>
</div>
<div class="modal-body">
<p>There was an error installing this app</p>
<p>{{appError.app.installationProgress}}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">OK</button>
</div>
</div>
</div>
</div>
<!-- Modal uninstall app --> <!-- Modal uninstall app -->
<div class="modal fade" id="appUninstallModal" tabindex="-1" role="dialog"> <div class="modal fade" id="appUninstallModal" tabindex="-1" role="dialog">
<div class="modal-dialog"> <div class="modal-dialog">
@@ -219,7 +237,7 @@
<div class="row animateMeOpacity ng-hide" ng-show="installedApps.length > 0"> <div class="row animateMeOpacity ng-hide" ng-show="installedApps.length > 0">
<div class="col-sm-1 grid-item" ng-repeat="app in installedApps"> <div class="col-sm-1 grid-item" ng-repeat="app in installedApps">
<div style="background-color: white;" class="highlight grid-item-content"> <div style="background-color: white;" class="highlight grid-item-content">
<a ng-href="{{app | applicationLink}}" target="_blank"> <a ng-href="{{app | applicationLink}}" ng-click="(app | installError) === true && showError(app)" target="_blank">
<div class="grid-item-top"> <div class="grid-item-top">
<div class="row"> <div class="row">
<div class="col-xs-12 text-center" style="padding-left: 5px; padding-right: 5px;"> <div class="col-xs-12 text-center" style="padding-left: 5px; padding-right: 5px;">
+15
View File
@@ -1,4 +1,5 @@
/* global ISTATES:false */ /* global ISTATES:false */
/* global HSTATES:false */
'use strict'; 'use strict';
@@ -40,6 +41,10 @@ angular.module('Application').controller('AppsController', ['$scope', '$location
password: '' password: ''
}; };
$scope.appError = {
app: {}
};
$scope.appUpdate = { $scope.appUpdate = {
busy: false, busy: false,
error: {}, error: {},
@@ -201,6 +206,16 @@ angular.module('Application').controller('AppsController', ['$scope', '$location
}); });
}; };
$scope.showError = function (app) {
$scope.reset();
$scope.appError.app = app;
$('#appErrorModal').modal('show');
return false; // prevent propagation and default
};
$scope.showRestore = function (app) { $scope.showRestore = function (app) {
$scope.reset(); $scope.reset();
+1 -1
View File
@@ -65,7 +65,7 @@
<form name="defaultCertForm" ng-submit="setDefaultCert()"> <form name="defaultCertForm" ng-submit="setDefaultCert()">
<fieldset> <fieldset>
<label class="control-label" for="defaultCertInput">Fallback Certificate</label> <label class="control-label" for="defaultCertInput">Fallback Certificate</label>
<p>This certificate has to be wildcard certificates and will be used for all apps, which were not configured to use a specific certificate.</p> <p>A wildcard certificate that will be used for apps installed without a specific certificate.</p>
<div class="has-error text-center" ng-show="defaultCert.error">{{ defaultCert.error }}</div> <div class="has-error text-center" ng-show="defaultCert.error">{{ defaultCert.error }}</div>
<div class="text-success text-center" ng-show="defaultCert.success"><b>Upload successful</b></div> <div class="text-success text-center" ng-show="defaultCert.success"><b>Upload successful</b></div>
<div class="form-group" ng-class="{ 'has-error': (!defaultCert.cert.$dirty && defaultCert.error) }"> <div class="form-group" ng-class="{ 'has-error': (!defaultCert.cert.$dirty && defaultCert.error) }">