2015-07-20 00:09:47 -07:00
'use strict' ;
exports = module . exports = {
activate : activate ,
2017-01-05 11:52:25 +01:00
dnsSetup : dnsSetup ,
2015-07-20 00:09:47 -07:00
setupTokenAuth : setupTokenAuth ,
2017-04-03 21:27:40 +02:00
providerTokenAuth : providerTokenAuth ,
2015-07-20 00:09:47 -07:00
getStatus : getStatus ,
reboot : reboot ,
2016-06-27 22:24:30 -05:00
migrate : migrate ,
2015-07-20 00:09:47 -07:00
getProgress : getProgress ,
getConfig : getConfig ,
2017-04-07 18:45:36 +02:00
getDisks : getDisks ,
2015-07-20 00:09:47 -07:00
update : update ,
2016-06-07 20:24:41 -07:00
feedback : feedback ,
2017-04-18 15:15:35 -07:00
checkForUpdates : checkForUpdates ,
2017-08-07 16:49:37 +02:00
getLogs : getLogs ,
2017-09-15 14:21:52 +02:00
getLogStream : getLogStream ,
sendTestMail : sendTestMail
2015-07-20 00:09:47 -07:00
} ;
2017-11-14 20:34:25 -08:00
var appstore = require ( '../appstore.js' ) ,
2017-11-16 00:09:55 -08:00
AppstoreError = require ( '../appstore.js' ) . AppstoreError ,
2017-11-14 20:34:25 -08:00
assert = require ( 'assert' ) ,
2016-06-07 20:24:41 -07:00
async = require ( 'async' ) ,
2015-07-20 00:09:47 -07:00
cloudron = require ( '../cloudron.js' ) ,
CloudronError = cloudron . CloudronError ,
2016-04-30 13:56:03 -07:00
config = require ( '../config.js' ) ,
2015-07-20 00:09:47 -07:00
debug = require ( 'debug' ) ( 'box:routes/cloudron' ) ,
HttpError = require ( 'connect-lastmile' ) . HttpError ,
HttpSuccess = require ( 'connect-lastmile' ) . HttpSuccess ,
2016-04-30 13:56:03 -07:00
progress = require ( '../progress.js' ) ,
mailer = require ( '../mailer.js' ) ,
2016-06-07 20:24:41 -07:00
superagent = require ( 'superagent' ) ,
2016-07-02 16:03:21 -05:00
updateChecker = require ( '../updatechecker.js' ) ,
_ = require ( 'underscore' ) ;
2015-07-20 00:09:47 -07:00
2016-05-01 13:15:30 -07:00
function auditSource ( req ) {
2016-05-01 13:29:11 -07:00
var ip = req . headers [ 'x-forwarded-for' ] || req . connection . remoteAddress || null ;
2016-05-01 13:15:30 -07:00
return { ip : ip , username : req . user ? req . user . username : null , userId : req . user ? req . user . id : null } ;
}
2015-07-20 00:09:47 -07:00
function activate ( req , res , next ) {
assert . strictEqual ( typeof req . body , 'object' ) ;
if ( typeof req . body . username !== 'string' ) return next ( new HttpError ( 400 , 'username must be string' ) ) ;
if ( typeof req . body . password !== 'string' ) return next ( new HttpError ( 400 , 'password must be string' ) ) ;
if ( typeof req . body . email !== 'string' ) return next ( new HttpError ( 400 , 'email must be string' ) ) ;
2016-01-20 16:14:21 +01:00
if ( 'displayName' in req . body && typeof req . body . displayName !== 'string' ) return next ( new HttpError ( 400 , 'displayName must be string' ) ) ;
2015-07-20 00:09:47 -07:00
var username = req . body . username ;
var password = req . body . password ;
var email = req . body . email ;
2016-01-19 23:34:49 -08:00
var displayName = req . body . displayName || '' ;
2015-07-20 00:09:47 -07:00
var ip = req . headers [ 'x-forwarded-for' ] || req . connection . remoteAddress ;
debug ( 'activate: username:%s ip:%s' , username , ip ) ;
2016-05-01 13:27:57 -07:00
cloudron . activate ( username , password , email , displayName , ip , auditSource ( req ) , function ( error , info ) {
2015-07-20 00:09:47 -07:00
if ( error && error . reason === CloudronError . ALREADY _PROVISIONED ) return next ( new HttpError ( 409 , 'Already setup' ) ) ;
2016-06-02 00:06:54 -07:00
if ( error && error . reason === CloudronError . BAD _FIELD ) return next ( new HttpError ( 400 , error . message ) ) ;
2015-07-20 00:09:47 -07:00
if ( error ) return next ( new HttpError ( 500 , error ) ) ;
2015-12-29 15:56:37 +01:00
// only in caas case do we have to notify the api server about activation
if ( config . provider ( ) !== 'caas' ) return next ( new HttpSuccess ( 201 , info ) ) ;
2015-07-20 00:09:47 -07:00
// Now let the api server know we got activated
2016-09-12 12:53:51 -07:00
superagent . post ( config . apiServerOrigin ( ) + '/api/v1/boxes/' + config . fqdn ( ) + '/setup/done' ) . query ( { setupToken : req . query . setupToken } )
. timeout ( 30 * 1000 )
. end ( function ( error , result ) {
2017-11-15 18:56:11 -08:00
if ( error && ! error . response ) return next ( new HttpError ( 500 , error ) ) ;
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 !== 201 ) return next ( new HttpError ( 500 , result . text || 'Internal error' ) ) ;
2015-07-20 00:09:47 -07:00
2017-11-15 18:56:11 -08:00
next ( new HttpSuccess ( 201 , info ) ) ;
} ) ;
2015-07-20 00:09:47 -07:00
} ) ;
}
2017-01-05 11:52:25 +01:00
function dnsSetup ( req , res , next ) {
assert . strictEqual ( typeof req . body , 'object' ) ;
if ( typeof req . body . provider !== 'string' ) return next ( new HttpError ( 400 , 'provider is required' ) ) ;
2017-01-10 15:55:31 -08:00
if ( typeof req . body . domain !== 'string' || ! req . body . domain ) return next ( new HttpError ( 400 , 'domain is required' ) ) ;
2017-06-11 22:32:05 -07:00
if ( 'zoneName' in req . body && typeof req . body . zoneName !== 'string' ) return next ( new HttpError ( 400 , 'zoneName must be a string' ) ) ;
cloudron . dnsSetup ( req . body , req . body . domain . toLowerCase ( ) , req . body . zoneName || '' , function ( error ) {
2017-01-12 11:00:46 -08:00
if ( error && error . reason === CloudronError . ALREADY _SETUP ) return next ( new HttpError ( 409 , error . message ) ) ;
if ( error && error . reason === CloudronError . BAD _FIELD ) return next ( new HttpError ( 400 , error . message ) ) ;
2017-01-05 11:52:25 +01:00
if ( error ) return next ( new HttpError ( 500 , error ) ) ;
2017-01-05 17:17:47 -08:00
next ( new HttpSuccess ( 200 ) ) ;
2017-01-05 11:52:25 +01:00
} ) ;
}
2015-07-20 00:09:47 -07:00
function setupTokenAuth ( req , res , next ) {
assert . strictEqual ( typeof req . query , 'object' ) ;
2017-03-14 13:45:04 +01:00
if ( config . provider ( ) === 'caas' ) {
if ( typeof req . query . setupToken !== 'string' || ! req . query . setupToken ) return next ( new HttpError ( 400 , 'setupToken must be a non empty string' ) ) ;
2015-12-29 11:24:45 +01:00
2017-03-14 13:45:04 +01:00
superagent . get ( config . apiServerOrigin ( ) + '/api/v1/boxes/' + config . fqdn ( ) + '/setup/verify' ) . query ( { setupToken : req . query . setupToken } )
2017-11-15 18:56:11 -08:00
. timeout ( 30 * 1000 )
. end ( function ( error , result ) {
if ( error && ! error . response ) return next ( new HttpError ( 500 , error ) ) ;
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 !== 200 ) return next ( new HttpError ( 500 , result . text || 'Internal error' ) ) ;
2017-03-14 13:45:04 +01:00
2017-11-15 18:56:11 -08:00
next ( ) ;
} ) ;
2017-04-03 21:27:40 +02:00
} else {
next ( ) ;
}
}
function providerTokenAuth ( req , res , next ) {
assert . strictEqual ( typeof req . body , 'object' ) ;
if ( config . provider ( ) === 'ami' ) {
if ( typeof req . body . providerToken !== 'string' || ! req . body . providerToken ) return next ( new HttpError ( 400 , 'providerToken must be a non empty string' ) ) ;
2015-07-20 00:09:47 -07:00
2017-03-14 13:45:04 +01:00
superagent . get ( 'http://169.254.169.254/latest/meta-data/instance-id' ) . timeout ( 30 * 1000 ) . end ( function ( error , result ) {
if ( error && ! error . response ) return next ( new HttpError ( 500 , error ) ) ;
if ( result . statusCode !== 200 ) return next ( new HttpError ( 500 , 'Unable to get meta data' ) ) ;
2017-04-03 21:27:40 +02:00
if ( result . text !== req . body . providerToken ) return next ( new HttpError ( 403 , 'Invalid providerToken' ) ) ;
2017-03-14 13:45:04 +01:00
next ( ) ;
} ) ;
} else {
2015-07-20 00:09:47 -07:00
next ( ) ;
2017-03-14 13:45:04 +01:00
}
2015-07-20 00:09:47 -07:00
}
function getStatus ( req , res , next ) {
cloudron . getStatus ( function ( error , status ) {
if ( error ) return next ( new HttpError ( 500 , error ) ) ;
next ( new HttpSuccess ( 200 , status ) ) ;
} ) ;
}
function getProgress ( req , res , next ) {
2017-09-28 11:12:12 -07:00
return next ( new HttpSuccess ( 200 , progress . getAll ( ) ) ) ;
2015-07-20 00:09:47 -07:00
}
function reboot ( req , res , next ) {
// Finish the request, to let the appstore know we triggered the restore it
next ( new HttpSuccess ( 202 , { } ) ) ;
2016-10-03 15:49:47 -07:00
cloudron . reboot ( function ( ) { } ) ;
2015-07-20 00:09:47 -07:00
}
2016-06-27 22:24:30 -05:00
function migrate ( req , res , next ) {
2016-07-04 23:08:51 -05:00
if ( config . provider ( ) !== 'caas' ) return next ( new HttpError ( 422 , 'Cannot use migrate API with this provider' ) ) ;
2016-06-27 22:24:30 -05:00
2016-07-04 23:08:51 -05:00
if ( 'size' in req . body && typeof req . body . size !== 'string' ) return next ( new HttpError ( 400 , 'size must be string' ) ) ;
if ( 'region' in req . body && typeof req . body . region !== 'string' ) return next ( new HttpError ( 400 , 'region must be string' ) ) ;
if ( 'domain' in req . body ) {
if ( typeof req . body . domain !== 'string' ) return next ( new HttpError ( 400 , 'domain must be string' ) ) ;
if ( typeof req . body . provider !== 'string' ) return next ( new HttpError ( 400 , 'provider must be string' ) ) ;
}
2017-06-11 22:32:05 -07:00
if ( 'zoneName' in req . body && typeof req . body . zoneName !== 'string' ) return next ( new HttpError ( 400 , 'zoneName must be string' ) ) ;
2016-07-04 23:08:51 -05:00
debug ( 'Migration requested domain:%s size:%s region:%s' , req . body . domain , req . body . size , req . body . region ) ;
2016-06-27 22:24:30 -05:00
2016-07-02 16:03:21 -05:00
var options = _ . pick ( req . body , 'domain' , 'size' , 'region' ) ;
2016-07-04 23:08:51 -05:00
if ( Object . keys ( options ) . length === 0 ) return next ( new HttpError ( 400 , 'no migrate option provided' ) ) ;
2016-07-02 16:03:21 -05:00
2017-06-01 09:26:03 -07:00
if ( options . domain ) options . domain = options . domain . toLowerCase ( ) ;
2016-07-04 23:28:03 -05:00
cloudron . migrate ( req . body , function ( error ) { // pass req.body because 'domain' can have arbitrary options
2016-06-27 22:24:30 -05:00
if ( error && error . reason === CloudronError . BAD _STATE ) return next ( new HttpError ( 409 , error . message ) ) ;
2016-07-04 23:32:27 -05:00
if ( error && error . reason === CloudronError . BAD _FIELD ) return next ( new HttpError ( 400 , error . message ) ) ;
2016-06-27 22:24:30 -05:00
if ( error ) return next ( new HttpError ( 500 , error ) ) ;
next ( new HttpSuccess ( 202 , { } ) ) ;
} ) ;
}
2015-07-20 00:09:47 -07:00
function getConfig ( req , res , next ) {
cloudron . getConfig ( function ( error , cloudronConfig ) {
if ( error ) return next ( new HttpError ( 500 , error ) ) ;
2017-08-26 13:59:45 -07:00
if ( ! req . user . admin ) {
cloudronConfig = _ . pick ( cloudronConfig , 'apiServerOrigin' , 'webServerOrigin' , 'fqdn' , 'version' , 'progress' , 'isCustomDomain' , 'isDemo' , 'cloudronName' , 'provider' ) ;
}
2015-07-20 00:09:47 -07:00
next ( new HttpSuccess ( 200 , cloudronConfig ) ) ;
} ) ;
}
2017-04-07 18:45:36 +02:00
function getDisks ( req , res , next ) {
cloudron . getDisks ( function ( error , result ) {
if ( error ) return next ( new HttpError ( 500 , error ) ) ;
next ( new HttpSuccess ( 200 , result ) ) ;
} ) ;
}
2015-07-20 00:09:47 -07:00
function update ( req , res , next ) {
// this only initiates the update, progress can be checked via the progress route
2016-05-01 13:15:30 -07:00
cloudron . updateToLatest ( auditSource ( req ) , function ( error ) {
2016-01-14 11:13:00 -08:00
if ( error && error . reason === CloudronError . ALREADY _UPTODATE ) return next ( new HttpError ( 422 , error . message ) ) ;
2015-07-20 00:09:47 -07:00
if ( error && error . reason === CloudronError . BAD _STATE ) return next ( new HttpError ( 409 , error . message ) ) ;
2016-06-20 14:06:58 +02:00
if ( error && error . reason === CloudronError . SELF _UPGRADE _NOT _SUPPORTED ) return next ( new HttpError ( 412 , error . message ) ) ;
2015-07-20 00:09:47 -07:00
if ( error ) return next ( new HttpError ( 500 , error ) ) ;
next ( new HttpSuccess ( 202 , { } ) ) ;
} ) ;
}
2016-06-07 20:24:41 -07:00
function checkForUpdates ( req , res , next ) {
async . series ( [
updateChecker . checkAppUpdates ,
updateChecker . checkBoxUpdates
] , function ( ) {
next ( new HttpSuccess ( 200 , { update : updateChecker . getUpdateInfo ( ) } ) ) ;
} ) ;
}
2015-08-04 14:31:40 +02:00
function feedback ( req , res , next ) {
assert . strictEqual ( typeof req . user , 'object' ) ;
2017-11-14 20:34:25 -08:00
const VALID _TYPES = [ 'feedback' , 'ticket' , 'app_missing' , 'app_error' , 'upgrade_request' ] ;
if ( typeof req . body . type !== 'string' || ! req . body . type ) return next ( new HttpError ( 400 , 'type must be string' ) ) ;
if ( VALID _TYPES . indexOf ( req . body . type ) === - 1 ) return next ( new HttpError ( 400 , 'unknown type' ) ) ;
2015-08-04 16:59:35 +02:00
if ( typeof req . body . subject !== 'string' || ! req . body . subject ) return next ( new HttpError ( 400 , 'subject must be string' ) ) ;
if ( typeof req . body . description !== 'string' || ! req . body . description ) return next ( new HttpError ( 400 , 'description must be string' ) ) ;
2015-08-04 14:31:40 +02:00
2017-11-14 20:34:25 -08:00
appstore . sendFeedback ( _ . extend ( req . body , { email : req . user . alternateEmail || req . user . email , displayName : req . user . displayName } ) , function ( error ) {
2017-11-16 00:09:55 -08:00
if ( error && error . reason === AppstoreError . BILLING _REQUIRED ) return next ( new HttpError ( 402 , 'Login to App Store to create support tickets. You can also email support@cloudron.io' ) ) ;
if ( error ) return next ( new HttpError ( 503 , 'Error contacting cloudron.io. Please email support@cloudron.io' ) ) ;
2017-11-14 20:34:25 -08:00
next ( new HttpSuccess ( 201 , { } ) ) ;
} ) ;
2015-08-04 15:39:14 +02:00
2015-08-04 14:31:40 +02:00
}
2017-04-18 15:15:35 -07:00
function getLogs ( req , res , next ) {
var lines = req . query . lines ? parseInt ( req . query . lines , 10 ) : 100 ;
if ( isNaN ( lines ) ) return next ( new HttpError ( 400 , 'lines must be a number' ) ) ;
var units = req . query . units || 'all' ;
2017-04-18 20:32:57 -07:00
var options = {
lines : lines ,
follow : false ,
units : units . split ( ',' ) ,
format : req . query . format
} ;
cloudron . getLogs ( options , function ( error , logStream ) {
2017-04-18 15:15:35 -07:00
if ( error && error . reason === CloudronError . BAD _FIELD ) return next ( new HttpError ( 404 , 'Invalid type' ) ) ;
if ( error ) return next ( new HttpError ( 500 , error ) ) ;
res . writeHead ( 200 , {
'Content-Type' : 'application/x-logs' ,
'Content-Disposition' : 'attachment; filename="log.txt"' ,
'Cache-Control' : 'no-cache' ,
'X-Accel-Buffering' : 'no' // disable nginx buffering
} ) ;
logStream . pipe ( res ) ;
} ) ;
}
2017-08-07 16:49:37 +02:00
function getLogStream ( req , res , next ) {
var lines = req . query . lines ? parseInt ( req . query . lines , 10 ) : - 10 ; // we ignore last-event-id
if ( isNaN ( lines ) ) return next ( new HttpError ( 400 , 'lines must be a valid number' ) ) ;
var units = req . query . units || 'all' ;
function sse ( id , data ) { return 'id: ' + id + '\ndata: ' + data + '\n\n' ; }
if ( req . headers . accept !== 'text/event-stream' ) return next ( new HttpError ( 400 , 'This API call requires EventStream' ) ) ;
var options = {
lines : lines ,
2017-08-07 18:26:03 +02:00
follow : true ,
2017-08-07 16:49:37 +02:00
units : units . split ( ',' ) ,
format : req . query . format
} ;
cloudron . getLogs ( options , function ( error , logStream ) {
if ( error && error . reason === CloudronError . BAD _FIELD ) return next ( new HttpError ( 404 , 'Invalid type' ) ) ;
if ( error ) return next ( new HttpError ( 500 , error ) ) ;
res . writeHead ( 200 , {
'Content-Type' : 'text/event-stream' ,
'Cache-Control' : 'no-cache' ,
'Connection' : 'keep-alive' ,
'X-Accel-Buffering' : 'no' , // disable nginx buffering
'Access-Control-Allow-Origin' : '*'
} ) ;
res . write ( 'retry: 3000\n' ) ;
res . on ( 'close' , logStream . close ) ;
logStream . on ( 'data' , function ( data ) {
var obj = JSON . parse ( data ) ;
res . write ( sse ( obj . monotonicTimestamp , JSON . stringify ( obj ) ) ) ; // send timestamp as id
} ) ;
logStream . on ( 'end' , res . end . bind ( res ) ) ;
logStream . on ( 'error' , res . end . bind ( res , null ) ) ;
} ) ;
}
2017-09-15 14:21:52 +02:00
function sendTestMail ( req , res , next ) {
assert . strictEqual ( typeof req . body , 'object' ) ;
if ( ! req . body . email || typeof req . body . email !== 'string' ) return next ( new HttpError ( 400 , 'email must be a non-empty string' ) ) ;
mailer . sendTestMail ( req . body . email ) ;
next ( new HttpSuccess ( 202 ) ) ;
}