2015-07-20 00:09:47 -07:00
'use strict' ;
exports = module . exports = {
2020-10-27 17:11:50 -07:00
getApp ,
2021-08-20 09:19:44 -07:00
listByUser ,
2020-10-27 17:11:50 -07:00
getAppIcon ,
install ,
uninstall ,
restore ,
importApp ,
2020-12-06 19:38:50 -08:00
exportApp ,
2020-10-27 17:11:50 -07:00
backup ,
update ,
getLogs ,
getLogStream ,
listBackups ,
repair ,
2015-07-20 00:09:47 -07:00
2020-10-27 17:11:50 -07:00
setAccessRestriction ,
setLabel ,
setTags ,
setIcon ,
setMemoryLimit ,
setCpuShares ,
setAutomaticBackup ,
setAutomaticUpdate ,
setReverseProxyConfig ,
setCertificate ,
setDebugMode ,
setEnvironment ,
setMailbox ,
setLocation ,
setDataDir ,
2020-10-28 19:42:48 -07:00
setMounts ,
2019-09-08 16:57:08 -07:00
2020-10-27 17:11:50 -07:00
stop ,
start ,
restart ,
exec ,
execWebSocket ,
2016-06-17 17:12:55 -05:00
2020-10-27 17:11:50 -07:00
clone ,
2017-08-18 20:45:52 -07:00
2020-10-27 17:11:50 -07:00
uploadFile ,
downloadFile ,
2020-03-29 17:11:10 -07:00
2020-12-22 17:19:26 -08:00
2020-10-27 17:11:50 -07:00
load
2015-07-20 00:09:47 -07:00
} ;
2021-08-30 09:27:35 -07:00
const apps = require ( '../apps.js' ) ,
2015-07-20 00:09:47 -07:00
assert = require ( 'assert' ) ,
2019-03-25 15:07:06 -07:00
auditSource = require ( '../auditsource.js' ) ,
2019-10-24 10:39:47 -07:00
BoxError = require ( '../boxerror.js' ) ,
2015-07-20 00:09:47 -07:00
debug = require ( 'debug' ) ( 'box:routes/apps' ) ,
HttpError = require ( 'connect-lastmile' ) . HttpError ,
HttpSuccess = require ( 'connect-lastmile' ) . HttpSuccess ,
safe = require ( 'safetydance' ) ,
2020-03-29 16:24:04 -07:00
users = require ( '../users.js' ) ,
2017-08-17 11:29:13 +02:00
WebSocket = require ( 'ws' ) ;
2015-07-20 00:09:47 -07:00
2021-08-20 09:19:44 -07:00
async function load ( req , res , next ) {
2015-07-20 00:09:47 -07:00
assert . strictEqual ( typeof req . params . id , 'string' ) ;
2021-08-23 15:44:23 -07:00
const [ error , result ] = await safe ( apps . get ( req . params . id ) ) ;
2021-08-20 09:19:44 -07:00
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
2021-08-30 09:27:35 -07:00
if ( ! result ) return next ( new HttpError ( 404 , 'App not found' ) ) ;
2015-07-20 00:09:47 -07:00
2021-09-20 22:39:35 -07:00
req . app = result ;
2020-03-29 17:11:10 -07:00
2021-08-20 09:19:44 -07:00
next ( ) ;
2015-07-20 00:09:47 -07:00
}
2020-03-29 17:11:10 -07:00
function getApp ( req , res , next ) {
2021-09-20 22:39:35 -07:00
assert . strictEqual ( typeof req . app , 'object' ) ;
2020-03-29 17:11:10 -07:00
2021-09-20 22:39:35 -07:00
next ( new HttpSuccess ( 200 , apps . removeInternalFields ( req . app ) ) ) ;
2020-03-29 17:11:10 -07:00
}
2021-08-20 09:19:44 -07:00
async function listByUser ( req , res , next ) {
2016-02-25 12:20:11 +01:00
assert . strictEqual ( typeof req . user , 'object' ) ;
2021-08-20 09:19:44 -07:00
let [ error , result ] = await safe ( apps . listByUser ( req . user ) ) ;
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
2018-04-26 20:07:03 -07:00
2021-08-20 09:19:44 -07:00
result = result . map ( apps . removeRestrictedFields ) ;
2015-07-20 00:09:47 -07:00
2021-08-20 09:19:44 -07:00
next ( new HttpSuccess ( 200 , { apps : result } ) ) ;
2015-07-20 00:09:47 -07:00
}
2021-08-20 09:19:44 -07:00
async function getAppIcon ( req , res , next ) {
2021-09-20 22:39:35 -07:00
assert . strictEqual ( typeof req . app , 'object' ) ;
2015-07-20 00:09:47 -07:00
2021-09-20 22:39:35 -07:00
const [ error , icon ] = await safe ( apps . getIcon ( req . app , { original : req . query . original } ) ) ;
2021-08-20 09:19:44 -07:00
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
2019-05-17 09:47:11 -07:00
2021-08-20 09:19:44 -07:00
res . send ( icon ) ;
2015-07-20 00:09:47 -07:00
}
2021-08-20 09:19:44 -07:00
async function install ( req , res , next ) {
2015-07-20 00:09:47 -07:00
assert . strictEqual ( typeof req . body , 'object' ) ;
2020-03-29 16:24:04 -07:00
const data = req . body ;
2015-07-20 00:09:47 -07:00
2016-06-04 01:07:43 -07:00
// atleast one
2016-06-04 19:19:00 -07:00
if ( 'manifest' in data && typeof data . manifest !== 'object' ) return next ( new HttpError ( 400 , 'manifest must be an object' ) ) ;
if ( 'appStoreId' in data && typeof data . appStoreId !== 'string' ) return next ( new HttpError ( 400 , 'appStoreId must be a string' ) ) ;
2016-06-04 01:07:43 -07:00
if ( ! data . manifest && ! data . appStoreId ) return next ( new HttpError ( 400 , 'appStoreId or manifest is required' ) ) ;
2016-06-03 23:22:38 -07:00
// required
2015-07-20 00:09:47 -07:00
if ( typeof data . location !== 'string' ) return next ( new HttpError ( 400 , 'location is required' ) ) ;
2017-11-02 22:17:44 +01:00
if ( typeof data . domain !== 'string' ) return next ( new HttpError ( 400 , 'domain is required' ) ) ;
2015-10-16 15:11:54 +02:00
if ( typeof data . accessRestriction !== 'object' ) return next ( new HttpError ( 400 , 'accessRestriction is required' ) ) ;
2016-06-03 23:22:38 -07:00
// optional
if ( ( 'portBindings' in data ) && typeof data . portBindings !== 'object' ) return next ( new HttpError ( 400 , 'portBindings must be an object' ) ) ;
2015-07-20 00:09:47 -07:00
if ( 'icon' in data && typeof data . icon !== 'string' ) return next ( new HttpError ( 400 , 'icon is not a string' ) ) ;
2016-06-04 18:30:05 -07:00
2019-03-22 07:48:31 -07:00
if ( 'label' in data && typeof data . label !== 'string' ) return next ( new HttpError ( 400 , 'label must be a string' ) ) ;
2016-02-11 17:00:21 +01:00
if ( 'memoryLimit' in data && typeof data . memoryLimit !== 'number' ) return next ( new HttpError ( 400 , 'memoryLimit is not a number' ) ) ;
2016-06-04 18:30:05 -07:00
2016-11-11 10:55:44 +05:30
if ( 'sso' in data && typeof data . sso !== 'boolean' ) return next ( new HttpError ( 400 , 'sso must be a boolean' ) ) ;
2017-08-16 14:12:07 -07:00
if ( 'enableBackup' in data && typeof data . enableBackup !== 'boolean' ) return next ( new HttpError ( 400 , 'enableBackup must be a boolean' ) ) ;
2018-12-07 09:03:28 -08:00
if ( 'enableAutomaticUpdate' in data && typeof data . enableAutomaticUpdate !== 'boolean' ) return next ( new HttpError ( 400 , 'enableAutomaticUpdate must be a boolean' ) ) ;
2016-09-06 21:21:56 -07:00
2017-01-20 05:48:25 -08:00
if ( ( 'debugMode' in data ) && typeof data . debugMode !== 'object' ) return next ( new HttpError ( 400 , 'debugMode must be an object' ) ) ;
2017-01-19 11:20:24 -08:00
2018-09-04 16:21:10 -07:00
if ( 'alternateDomains' in data ) {
if ( ! Array . isArray ( data . alternateDomains ) ) return next ( new HttpError ( 400 , 'alternateDomains must be an array' ) ) ;
if ( data . alternateDomains . some ( function ( d ) { return ( typeof d . domain !== 'string' || typeof d . subdomain !== 'string' ) ; } ) ) return next ( new HttpError ( 400 , 'alternateDomains array must contain objects with domain and subdomain strings' ) ) ;
}
2021-01-18 17:26:26 -08:00
if ( 'aliasDomains' in data ) {
if ( ! Array . isArray ( data . aliasDomains ) ) return next ( new HttpError ( 400 , 'aliasDomains must be an array' ) ) ;
if ( data . aliasDomains . some ( function ( d ) { return ( typeof d . domain !== 'string' || typeof d . subdomain !== 'string' ) ; } ) ) return next ( new HttpError ( 400 , 'aliasDomains array must contain objects with domain and subdomain strings' ) ) ;
}
2018-10-11 14:07:43 -07:00
if ( 'env' in data ) {
if ( ! data . env || typeof data . env !== 'object' ) return next ( new HttpError ( 400 , 'env must be an object' ) ) ;
if ( Object . keys ( data . env ) . some ( function ( key ) { return typeof data . env [ key ] !== 'string' ; } ) ) return next ( new HttpError ( 400 , 'env must contain values as strings' ) ) ;
}
2019-09-16 09:31:34 -07:00
if ( 'overwriteDns' in req . body && typeof req . body . overwriteDns !== 'boolean' ) return next ( new HttpError ( 400 , 'overwriteDns must be boolean' ) ) ;
2021-02-24 14:49:30 -08:00
if ( 'skipDnsSetup' in req . body && typeof req . body . skipDnsSetup !== 'boolean' ) return next ( new HttpError ( 400 , 'skipDnsSetup must be boolean' ) ) ;
2021-06-16 23:09:30 -07:00
if ( 'enableMailbox' in req . body && typeof req . body . enableMailbox !== 'boolean' ) return next ( new HttpError ( 400 , 'enableMailbox must be boolean' ) ) ;
2019-09-16 09:31:34 -07:00
2021-08-20 09:19:44 -07:00
let [ error , result ] = await safe ( apps . downloadManifest ( data . appStoreId , data . manifest ) ) ;
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
2015-07-20 00:09:47 -07:00
2021-08-20 09:19:44 -07:00
if ( safe . query ( result . manifest , 'addons.docker' ) && req . user . role !== users . ROLE _OWNER ) return next ( new HttpError ( 403 , '"owner" role is required to install app with docker addon' ) ) ;
2020-03-29 16:24:04 -07:00
2021-08-20 09:19:44 -07:00
data . appStoreId = result . appStoreId ;
data . manifest = result . manifest ;
2020-03-29 16:24:04 -07:00
2021-08-20 09:19:44 -07:00
[ error , result ] = await safe ( apps . install ( data , auditSource . fromRequest ( req ) ) ) ;
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
next ( new HttpSuccess ( 202 , { id : result . id , taskId : result . taskId } ) ) ;
2015-07-20 00:09:47 -07:00
}
2021-08-20 09:19:44 -07:00
async function setAccessRestriction ( req , res , next ) {
2019-09-08 16:57:08 -07:00
assert . strictEqual ( typeof req . body , 'object' ) ;
2021-09-20 22:39:35 -07:00
assert . strictEqual ( typeof req . app , 'object' ) ;
2019-09-08 16:57:08 -07:00
if ( typeof req . body . accessRestriction !== 'object' ) return next ( new HttpError ( 400 , 'accessRestriction must be an object' ) ) ;
2021-09-20 22:39:35 -07:00
const [ error ] = await safe ( apps . setAccessRestriction ( req . app , req . body . accessRestriction , auditSource . fromRequest ( req ) ) ) ;
2021-08-20 09:19:44 -07:00
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
2019-09-08 16:57:08 -07:00
2021-08-20 09:19:44 -07:00
next ( new HttpSuccess ( 200 , { } ) ) ;
2019-09-08 16:57:08 -07:00
}
2021-08-20 09:19:44 -07:00
async function setLabel ( req , res , next ) {
2019-09-08 16:57:08 -07:00
assert . strictEqual ( typeof req . body , 'object' ) ;
2021-09-20 22:39:35 -07:00
assert . strictEqual ( typeof req . app , 'object' ) ;
2019-09-08 16:57:08 -07:00
if ( typeof req . body . label !== 'string' ) return next ( new HttpError ( 400 , 'label must be a string' ) ) ;
2021-09-20 22:39:35 -07:00
const [ error ] = await safe ( apps . setLabel ( req . app , req . body . label , auditSource . fromRequest ( req ) ) ) ;
2021-08-20 09:19:44 -07:00
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
2019-09-08 16:57:08 -07:00
2021-08-20 09:19:44 -07:00
next ( new HttpSuccess ( 200 , { } ) ) ;
2019-09-08 16:57:08 -07:00
}
2021-08-20 09:19:44 -07:00
async function setTags ( req , res , next ) {
2019-09-08 16:57:08 -07:00
assert . strictEqual ( typeof req . body , 'object' ) ;
2021-09-20 22:39:35 -07:00
assert . strictEqual ( typeof req . app , 'object' ) ;
2019-09-08 16:57:08 -07:00
if ( ! Array . isArray ( req . body . tags ) ) return next ( new HttpError ( 400 , 'tags must be an array' ) ) ;
if ( req . body . tags . some ( ( t ) => typeof t !== 'string' ) ) return next ( new HttpError ( 400 , 'tags array must contain strings' ) ) ;
2021-09-20 22:39:35 -07:00
const [ error ] = await safe ( apps . setTags ( req . app , req . body . tags , auditSource . fromRequest ( req ) ) ) ;
2021-08-20 09:19:44 -07:00
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
2019-09-08 16:57:08 -07:00
2021-08-20 09:19:44 -07:00
next ( new HttpSuccess ( 200 , { } ) ) ;
2019-09-08 16:57:08 -07:00
}
2021-08-20 09:19:44 -07:00
async function setIcon ( req , res , next ) {
2019-09-08 16:57:08 -07:00
assert . strictEqual ( typeof req . body , 'object' ) ;
2021-09-20 22:39:35 -07:00
assert . strictEqual ( typeof req . app , 'object' ) ;
2019-09-08 16:57:08 -07:00
if ( req . body . icon !== null && typeof req . body . icon !== 'string' ) return next ( new HttpError ( 400 , 'icon is null or a base-64 image string' ) ) ;
2021-09-20 22:39:35 -07:00
const [ error ] = await safe ( apps . setIcon ( req . app , req . body . icon || null /* empty string means null */ , auditSource . fromRequest ( req ) ) ) ;
2021-08-20 09:19:44 -07:00
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
2019-09-08 16:57:08 -07:00
2021-08-20 09:19:44 -07:00
next ( new HttpSuccess ( 200 , { } ) ) ;
2019-09-08 16:57:08 -07:00
}
2021-08-20 09:19:44 -07:00
async function setMemoryLimit ( req , res , next ) {
2019-09-08 16:57:08 -07:00
assert . strictEqual ( typeof req . body , 'object' ) ;
2021-09-20 22:39:35 -07:00
assert . strictEqual ( typeof req . app , 'object' ) ;
2019-09-08 16:57:08 -07:00
if ( typeof req . body . memoryLimit !== 'number' ) return next ( new HttpError ( 400 , 'memoryLimit is not a number' ) ) ;
2021-09-20 22:39:35 -07:00
const [ error , result ] = await safe ( apps . setMemoryLimit ( req . app , req . body . memoryLimit , auditSource . fromRequest ( req ) ) ) ;
2021-08-20 09:19:44 -07:00
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
2020-01-28 21:30:35 -08:00
2021-08-20 09:19:44 -07:00
next ( new HttpSuccess ( 202 , { taskId : result . taskId } ) ) ;
2020-01-28 21:30:35 -08:00
}
function setCpuShares ( req , res , next ) {
assert . strictEqual ( typeof req . body , 'object' ) ;
2021-09-20 22:39:35 -07:00
assert . strictEqual ( typeof req . app , 'object' ) ;
2020-01-28 21:30:35 -08:00
if ( typeof req . body . cpuShares !== 'number' ) return next ( new HttpError ( 400 , 'cpuShares is not a number' ) ) ;
2021-09-20 22:39:35 -07:00
apps . setCpuShares ( req . app , req . body . cpuShares , auditSource . fromRequest ( req ) , function ( error , result ) {
2020-01-28 21:30:35 -08:00
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
2019-09-08 16:57:08 -07:00
next ( new HttpSuccess ( 202 , { taskId : result . taskId } ) ) ;
} ) ;
}
2021-08-20 09:19:44 -07:00
async function setAutomaticBackup ( req , res , next ) {
2019-09-08 16:57:08 -07:00
assert . strictEqual ( typeof req . body , 'object' ) ;
2021-09-20 22:39:35 -07:00
assert . strictEqual ( typeof req . app , 'object' ) ;
2019-09-08 16:57:08 -07:00
if ( typeof req . body . enable !== 'boolean' ) return next ( new HttpError ( 400 , 'enable must be a boolean' ) ) ;
2021-09-20 22:39:35 -07:00
const [ error ] = await safe ( apps . setAutomaticBackup ( req . app , req . body . enable , auditSource . fromRequest ( req ) ) ) ;
2021-08-20 09:19:44 -07:00
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
2019-09-08 16:57:08 -07:00
2021-08-20 09:19:44 -07:00
next ( new HttpSuccess ( 200 , { } ) ) ;
2019-09-08 16:57:08 -07:00
}
2021-08-20 09:19:44 -07:00
async function setAutomaticUpdate ( req , res , next ) {
2019-09-08 16:57:08 -07:00
assert . strictEqual ( typeof req . body , 'object' ) ;
2021-09-20 22:39:35 -07:00
assert . strictEqual ( typeof req . app , 'object' ) ;
2019-09-08 16:57:08 -07:00
if ( typeof req . body . enable !== 'boolean' ) return next ( new HttpError ( 400 , 'enable must be a boolean' ) ) ;
2021-09-20 22:39:35 -07:00
const [ error ] = await safe ( apps . setAutomaticUpdate ( req . app , req . body . enable , auditSource . fromRequest ( req ) ) ) ;
2021-08-20 09:19:44 -07:00
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
2019-09-08 16:57:08 -07:00
2021-08-20 09:19:44 -07:00
next ( new HttpSuccess ( 200 , { } ) ) ;
2019-09-08 16:57:08 -07:00
}
2021-08-17 14:04:29 -07:00
async function setReverseProxyConfig ( req , res , next ) {
2019-09-08 16:57:08 -07:00
assert . strictEqual ( typeof req . body , 'object' ) ;
2021-09-20 22:39:35 -07:00
assert . strictEqual ( typeof req . app , 'object' ) ;
2019-09-08 16:57:08 -07:00
if ( req . body . robotsTxt !== null && typeof req . body . robotsTxt !== 'string' ) return next ( new HttpError ( 400 , 'robotsTxt is not a string' ) ) ;
2019-10-14 16:59:22 -07:00
if ( req . body . csp !== null && typeof req . body . csp !== 'string' ) return next ( new HttpError ( 400 , 'csp is not a string' ) ) ;
2019-10-13 18:22:03 -07:00
2021-09-20 22:39:35 -07:00
const [ error ] = await safe ( apps . setReverseProxyConfig ( req . app , req . body , auditSource . fromRequest ( req ) ) ) ;
2021-08-17 14:04:29 -07:00
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
2019-09-08 16:57:08 -07:00
2021-08-17 14:04:29 -07:00
next ( new HttpSuccess ( 200 , { } ) ) ;
2019-09-08 16:57:08 -07:00
}
2021-08-17 14:04:29 -07:00
async function setCertificate ( req , res , next ) {
2019-09-08 16:57:08 -07:00
assert . strictEqual ( typeof req . body , 'object' ) ;
2021-09-20 22:39:35 -07:00
assert . strictEqual ( typeof req . app , 'object' ) ;
2019-09-08 16:57:08 -07:00
2021-05-05 10:34:22 -07:00
if ( typeof req . body . location !== 'string' ) return next ( new HttpError ( 400 , 'location must be string' ) ) ; // location may be an empty string
if ( ! req . body . domain ) return next ( new HttpError ( 400 , 'domain is required' ) ) ;
if ( typeof req . body . domain !== 'string' ) return next ( new HttpError ( 400 , 'domain must be string' ) ) ;
2019-09-08 16:57:08 -07:00
if ( req . body . key !== null && typeof req . body . cert !== 'string' ) return next ( new HttpError ( 400 , 'cert must be a string' ) ) ;
if ( req . body . cert !== null && typeof req . body . key !== 'string' ) return next ( new HttpError ( 400 , 'key must be a string' ) ) ;
if ( req . body . cert && ! req . body . key ) return next ( new HttpError ( 400 , 'key must be provided' ) ) ;
if ( ! req . body . cert && req . body . key ) return next ( new HttpError ( 400 , 'cert must be provided' ) ) ;
2021-09-20 22:39:35 -07:00
const [ error ] = await safe ( apps . setCertificate ( req . app , req . body , auditSource . fromRequest ( req ) ) ) ;
2021-08-17 14:04:29 -07:00
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
2019-09-08 16:57:08 -07:00
2021-08-17 14:04:29 -07:00
next ( new HttpSuccess ( 200 , { } ) ) ;
2019-09-08 16:57:08 -07:00
}
2021-08-20 09:19:44 -07:00
async function setEnvironment ( req , res , next ) {
2019-09-08 16:57:08 -07:00
assert . strictEqual ( typeof req . body , 'object' ) ;
2021-09-20 22:39:35 -07:00
assert . strictEqual ( typeof req . app , 'object' ) ;
2019-09-08 16:57:08 -07:00
if ( ! req . body . env || typeof req . body . env !== 'object' ) return next ( new HttpError ( 400 , 'env must be an object' ) ) ;
2019-09-09 15:35:02 -07:00
if ( Object . keys ( req . body . env ) . some ( ( key ) => typeof req . body . env [ key ] !== 'string' ) ) return next ( new HttpError ( 400 , 'env must contain values as strings' ) ) ;
2019-09-08 16:57:08 -07:00
2021-09-20 22:39:35 -07:00
const [ error , result ] = await safe ( apps . setEnvironment ( req . app , req . body . env , auditSource . fromRequest ( req ) ) ) ;
2021-08-20 09:19:44 -07:00
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
2019-09-08 16:57:08 -07:00
2021-08-20 09:19:44 -07:00
next ( new HttpSuccess ( 202 , { taskId : result . taskId } ) ) ;
2019-09-08 16:57:08 -07:00
}
2021-08-20 09:19:44 -07:00
async function setDebugMode ( req , res , next ) {
2019-09-08 16:57:08 -07:00
assert . strictEqual ( typeof req . body , 'object' ) ;
2021-09-20 22:39:35 -07:00
assert . strictEqual ( typeof req . app , 'object' ) ;
2019-09-08 16:57:08 -07:00
if ( req . body . debugMode !== null && typeof req . body . debugMode !== 'object' ) return next ( new HttpError ( 400 , 'debugMode must be an object' ) ) ;
2021-09-20 22:39:35 -07:00
const [ error , result ] = await safe ( apps . setDebugMode ( req . app , req . body . debugMode , auditSource . fromRequest ( req ) ) ) ;
2021-08-20 09:19:44 -07:00
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
2019-09-08 16:57:08 -07:00
2021-08-20 09:19:44 -07:00
next ( new HttpSuccess ( 202 , { taskId : result . taskId } ) ) ;
2019-09-08 16:57:08 -07:00
}
2021-08-20 09:19:44 -07:00
async function setMailbox ( req , res , next ) {
2019-09-08 16:57:08 -07:00
assert . strictEqual ( typeof req . body , 'object' ) ;
2021-09-20 22:39:35 -07:00
assert . strictEqual ( typeof req . app , 'object' ) ;
2019-09-08 16:57:08 -07:00
2021-03-16 22:38:59 -07:00
if ( typeof req . body . enable !== 'boolean' ) return next ( new HttpError ( 400 , 'enable must be a boolean' ) ) ;
if ( req . body . enable ) {
if ( req . body . mailboxName !== null && typeof req . body . mailboxName !== 'string' ) return next ( new HttpError ( 400 , 'mailboxName must be a string' ) ) ;
if ( typeof req . body . mailboxDomain !== 'string' ) return next ( new HttpError ( 400 , 'mailboxDomain must be a string' ) ) ;
}
2019-09-08 16:57:08 -07:00
2021-09-20 22:39:35 -07:00
const [ error , result ] = await safe ( apps . setMailbox ( req . app , req . body , auditSource . fromRequest ( req ) ) ) ;
2021-08-20 09:19:44 -07:00
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
2019-09-08 16:57:08 -07:00
2021-08-20 09:19:44 -07:00
next ( new HttpSuccess ( 202 , { taskId : result . taskId } ) ) ;
2019-09-08 16:57:08 -07:00
}
2021-08-20 09:19:44 -07:00
async function setLocation ( req , res , next ) {
2019-09-08 16:57:08 -07:00
assert . strictEqual ( typeof req . body , 'object' ) ;
2021-09-20 22:39:35 -07:00
assert . strictEqual ( typeof req . app , 'object' ) ;
2019-09-08 16:57:08 -07:00
2019-10-03 16:23:01 +02:00
if ( typeof req . body . location !== 'string' ) return next ( new HttpError ( 400 , 'location must be string' ) ) ; // location may be an empty string
2019-09-08 16:57:08 -07:00
if ( ! req . body . domain ) return next ( new HttpError ( 400 , 'domain is required' ) ) ;
if ( typeof req . body . domain !== 'string' ) return next ( new HttpError ( 400 , 'domain must be string' ) ) ;
if ( 'portBindings' in req . body && typeof req . body . portBindings !== 'object' ) return next ( new HttpError ( 400 , 'portBindings must be an object' ) ) ;
if ( 'alternateDomains' in req . body ) {
if ( ! Array . isArray ( req . body . alternateDomains ) ) return next ( new HttpError ( 400 , 'alternateDomains must be an array' ) ) ;
if ( req . body . alternateDomains . some ( function ( d ) { return ( typeof d . domain !== 'string' || typeof d . subdomain !== 'string' ) ; } ) ) return next ( new HttpError ( 400 , 'alternateDomains array must contain objects with domain and subdomain strings' ) ) ;
}
2021-01-18 17:26:26 -08:00
if ( 'aliasDomains' in req . body ) {
if ( ! Array . isArray ( req . body . aliasDomains ) ) return next ( new HttpError ( 400 , 'aliasDomains must be an array' ) ) ;
if ( req . body . aliasDomains . some ( function ( d ) { return ( typeof d . domain !== 'string' || typeof d . subdomain !== 'string' ) ; } ) ) return next ( new HttpError ( 400 , 'aliasDomains array must contain objects with domain and subdomain strings' ) ) ;
}
2019-09-10 15:23:47 -07:00
if ( 'overwriteDns' in req . body && typeof req . body . overwriteDns !== 'boolean' ) return next ( new HttpError ( 400 , 'overwriteDns must be boolean' ) ) ;
2021-02-24 14:49:30 -08:00
if ( 'skipDnsSetup' in req . body && typeof req . body . skipDnsSetup !== 'boolean' ) return next ( new HttpError ( 400 , 'skipDnsSetup must be boolean' ) ) ;
2019-09-10 15:23:47 -07:00
2021-09-20 22:39:35 -07:00
const [ error , result ] = await safe ( apps . setLocation ( req . app , req . body , auditSource . fromRequest ( req ) ) ) ;
2021-08-20 09:19:44 -07:00
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
2019-09-08 16:57:08 -07:00
2021-08-20 09:19:44 -07:00
next ( new HttpSuccess ( 202 , { taskId : result . taskId } ) ) ;
2019-09-08 16:57:08 -07:00
}
2021-08-20 09:19:44 -07:00
async function setDataDir ( req , res , next ) {
2019-09-08 16:57:08 -07:00
assert . strictEqual ( typeof req . body , 'object' ) ;
2021-09-20 22:39:35 -07:00
assert . strictEqual ( typeof req . app , 'object' ) ;
2019-09-08 16:57:08 -07:00
2019-09-09 16:37:59 -07:00
if ( req . body . dataDir !== null && typeof req . body . dataDir !== 'string' ) return next ( new HttpError ( 400 , 'dataDir must be a string' ) ) ;
2019-09-08 16:57:08 -07:00
2021-09-20 22:39:35 -07:00
const [ error , result ] = await safe ( apps . setDataDir ( req . app , req . body . dataDir , auditSource . fromRequest ( req ) ) ) ;
2021-08-20 09:19:44 -07:00
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
2019-09-08 16:57:08 -07:00
2021-08-20 09:19:44 -07:00
next ( new HttpSuccess ( 202 , { taskId : result . taskId } ) ) ;
2019-09-08 16:57:08 -07:00
}
2021-08-20 09:19:44 -07:00
async function repair ( req , res , next ) {
2019-09-19 17:04:11 -07:00
assert . strictEqual ( typeof req . body , 'object' ) ;
2021-09-20 22:39:35 -07:00
assert . strictEqual ( typeof req . app , 'object' ) ;
2019-09-19 17:04:11 -07:00
2019-09-21 19:45:55 -07:00
const data = req . body ;
2019-11-23 18:35:51 -08:00
if ( 'manifest' in data ) {
2020-02-11 21:05:01 -08:00
if ( ! data . manifest || typeof data . manifest !== 'object' ) return next ( new HttpError ( 400 , 'manifest must be an object' ) ) ;
2020-03-29 16:24:04 -07:00
2020-03-29 20:12:59 -07:00
if ( safe . query ( data . manifest , 'addons.docker' ) && req . user . role !== users . ROLE _OWNER ) return next ( new HttpError ( 403 , '"owner" role is required to repair app with docker addon' ) ) ;
2020-02-11 21:05:01 -08:00
}
if ( 'dockerImage' in data ) {
if ( ! data . dockerImage || typeof data . dockerImage !== 'string' ) return next ( new HttpError ( 400 , 'dockerImage must be a string' ) ) ;
2019-09-21 19:45:55 -07:00
}
2021-09-20 22:39:35 -07:00
const [ error , result ] = await safe ( apps . repair ( req . app , data , auditSource . fromRequest ( req ) ) ) ;
2021-08-20 09:19:44 -07:00
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
2019-09-19 17:04:11 -07:00
2021-08-20 09:19:44 -07:00
next ( new HttpSuccess ( 202 , { taskId : result . taskId } ) ) ;
2019-09-19 17:04:11 -07:00
}
2021-07-14 11:07:19 -07:00
async function restore ( req , res , next ) {
2016-06-13 10:08:58 -07:00
assert . strictEqual ( typeof req . body , 'object' ) ;
2021-09-20 22:39:35 -07:00
assert . strictEqual ( typeof req . app , 'object' ) ;
2015-07-20 00:09:47 -07:00
2021-07-14 11:07:19 -07:00
const data = req . body ;
2016-06-13 10:08:58 -07:00
2019-12-05 21:15:09 -08:00
if ( ! data . backupId || typeof data . backupId !== 'string' ) return next ( new HttpError ( 400 , 'backupId must be non-empty string' ) ) ;
2016-06-13 10:13:54 -07:00
2021-09-20 22:39:35 -07:00
const [ error , result ] = await safe ( apps . restore ( req . app , data . backupId , auditSource . fromRequest ( req ) ) ) ;
2021-07-14 11:07:19 -07:00
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
2015-07-20 00:09:47 -07:00
2021-07-14 11:07:19 -07:00
next ( new HttpSuccess ( 202 , { taskId : result . taskId } ) ) ;
2015-07-20 00:09:47 -07:00
}
2021-08-20 09:19:44 -07:00
async function importApp ( req , res , next ) {
2019-10-15 09:16:29 -07:00
assert . strictEqual ( typeof req . body , 'object' ) ;
2021-09-20 22:39:35 -07:00
assert . strictEqual ( typeof req . app , 'object' ) ;
2019-10-15 09:16:29 -07:00
2021-08-20 09:19:44 -07:00
const data = req . body ;
2019-10-15 09:16:29 -07:00
2019-12-04 18:54:25 -08:00
if ( 'backupId' in data ) { // if not provided, we import in-place
if ( typeof data . backupId !== 'string' ) return next ( new HttpError ( 400 , 'backupId must be string' ) ) ;
if ( typeof data . backupFormat !== 'string' ) return next ( new HttpError ( 400 , 'backupFormat must be string' ) ) ;
if ( 'backupConfig' in data && typeof data . backupConfig !== 'object' ) return next ( new HttpError ( 400 , 'backupConfig must be an object' ) ) ;
const backupConfig = req . body . backupConfig ;
if ( req . body . backupConfig ) {
if ( typeof backupConfig . provider !== 'string' ) return next ( new HttpError ( 400 , 'provider is required' ) ) ;
2020-05-12 10:31:51 -07:00
if ( 'password' in backupConfig && typeof backupConfig . password !== 'string' ) return next ( new HttpError ( 400 , 'password must be a string' ) ) ;
2019-12-04 18:54:25 -08:00
if ( 'acceptSelfSignedCerts' in backupConfig && typeof backupConfig . acceptSelfSignedCerts !== 'boolean' ) return next ( new HttpError ( 400 , 'format must be a boolean' ) ) ;
// testing backup config can take sometime
req . clearTimeout ( ) ;
}
}
2019-10-15 09:16:29 -07:00
2021-09-20 22:39:35 -07:00
const [ error , result ] = await safe ( apps . importApp ( req . app , data , auditSource . fromRequest ( req ) ) ) ;
2021-08-20 09:19:44 -07:00
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
2019-10-15 09:16:29 -07:00
2021-08-20 09:19:44 -07:00
next ( new HttpSuccess ( 202 , { taskId : result . taskId } ) ) ;
2019-10-15 09:16:29 -07:00
}
2021-08-20 09:19:44 -07:00
async function exportApp ( req , res , next ) {
2020-12-06 19:38:50 -08:00
assert . strictEqual ( typeof req . body , 'object' ) ;
2021-09-20 22:39:35 -07:00
assert . strictEqual ( typeof req . app , 'object' ) ;
2020-12-06 19:38:50 -08:00
2021-09-20 22:39:35 -07:00
const [ error , result ] = await safe ( apps . exportApp ( req . app , { } , auditSource . fromRequest ( req ) ) ) ;
2021-08-20 09:19:44 -07:00
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
2020-12-06 19:38:50 -08:00
2021-08-20 09:19:44 -07:00
next ( new HttpSuccess ( 202 , { taskId : result . taskId } ) ) ;
2020-12-06 19:38:50 -08:00
}
2021-08-20 09:19:44 -07:00
async function clone ( req , res , next ) {
2016-06-17 17:12:55 -05:00
assert . strictEqual ( typeof req . body , 'object' ) ;
2021-09-20 22:39:35 -07:00
assert . strictEqual ( typeof req . app , 'object' ) ;
2016-06-17 17:12:55 -05:00
var data = req . body ;
if ( typeof data . backupId !== 'string' ) return next ( new HttpError ( 400 , 'backupId must be a string' ) ) ;
if ( typeof data . location !== 'string' ) return next ( new HttpError ( 400 , 'location is required' ) ) ;
2017-11-02 22:17:44 +01:00
if ( typeof data . domain !== 'string' ) return next ( new HttpError ( 400 , 'domain is required' ) ) ;
2016-06-17 17:12:55 -05:00
if ( ( 'portBindings' in data ) && typeof data . portBindings !== 'object' ) return next ( new HttpError ( 400 , 'portBindings must be an object' ) ) ;
2019-09-16 09:31:34 -07:00
if ( 'overwriteDns' in req . body && typeof req . body . overwriteDns !== 'boolean' ) return next ( new HttpError ( 400 , 'overwriteDns must be boolean' ) ) ;
2021-02-24 14:49:30 -08:00
if ( 'skipDnsSetup' in req . body && typeof req . body . skipDnsSetup !== 'boolean' ) return next ( new HttpError ( 400 , 'skipDnsSetup must be boolean' ) ) ;
2019-09-16 09:31:34 -07:00
2021-09-20 22:39:35 -07:00
const [ error , result ] = await safe ( apps . clone ( req . app , data , req . user , auditSource . fromRequest ( req ) ) ) ;
2021-08-20 09:19:44 -07:00
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
2016-06-17 17:12:55 -05:00
2021-08-20 09:19:44 -07:00
next ( new HttpSuccess ( 201 , { id : result . id , taskId : result . taskId } ) ) ;
2016-06-17 17:12:55 -05:00
}
2021-08-20 09:19:44 -07:00
async function backup ( req , res , next ) {
2021-09-20 22:39:35 -07:00
assert . strictEqual ( typeof req . app , 'object' ) ;
2015-07-20 00:09:47 -07:00
2021-09-20 22:39:35 -07:00
const [ error , result ] = await safe ( apps . backup ( req . app ) ) ;
2021-08-20 09:19:44 -07:00
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
2015-07-20 00:09:47 -07:00
2021-08-20 09:19:44 -07:00
next ( new HttpSuccess ( 202 , { taskId : result . taskId } ) ) ;
2015-07-20 00:09:47 -07:00
}
2021-08-20 09:19:44 -07:00
async function uninstall ( req , res , next ) {
2021-09-20 22:39:35 -07:00
assert . strictEqual ( typeof req . app , 'object' ) ;
2015-07-20 00:09:47 -07:00
2021-09-20 22:39:35 -07:00
const [ error , result ] = await safe ( apps . uninstall ( req . app , auditSource . fromRequest ( req ) ) ) ;
2021-08-20 09:19:44 -07:00
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
2015-07-20 00:09:47 -07:00
2021-08-20 09:19:44 -07:00
next ( new HttpSuccess ( 202 , { taskId : result . taskId } ) ) ;
2015-07-20 00:09:47 -07:00
}
2021-08-20 09:19:44 -07:00
async function start ( req , res , next ) {
2021-09-20 22:39:35 -07:00
assert . strictEqual ( typeof req . app , 'object' ) ;
2015-07-20 00:09:47 -07:00
2021-09-20 22:39:35 -07:00
const [ error , result ] = await safe ( apps . start ( req . app , auditSource . fromRequest ( req ) ) ) ;
2021-08-20 09:19:44 -07:00
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
2015-07-20 00:09:47 -07:00
2021-08-20 09:19:44 -07:00
next ( new HttpSuccess ( 202 , { taskId : result . taskId } ) ) ;
2015-07-20 00:09:47 -07:00
}
2021-08-20 09:19:44 -07:00
async function stop ( req , res , next ) {
2021-09-20 22:39:35 -07:00
assert . strictEqual ( typeof req . app , 'object' ) ;
2015-07-20 00:09:47 -07:00
2021-09-20 22:39:35 -07:00
const [ error , result ] = await safe ( apps . stop ( req . app , auditSource . fromRequest ( req ) ) ) ;
2021-08-20 09:19:44 -07:00
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
2015-07-20 00:09:47 -07:00
2021-08-20 09:19:44 -07:00
next ( new HttpSuccess ( 202 , { taskId : result . taskId } ) ) ;
2015-07-20 00:09:47 -07:00
}
2021-08-20 09:19:44 -07:00
async function restart ( req , res , next ) {
2021-09-20 22:39:35 -07:00
assert . strictEqual ( typeof req . app , 'object' ) ;
2019-12-20 10:29:29 -08:00
2021-09-20 22:39:35 -07:00
const [ error , result ] = await safe ( apps . restart ( req . app , auditSource . fromRequest ( req ) ) ) ;
2021-08-20 09:19:44 -07:00
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
2019-12-20 10:29:29 -08:00
2021-08-20 09:19:44 -07:00
next ( new HttpSuccess ( 202 , { taskId : result . taskId } ) ) ;
2019-12-20 10:29:29 -08:00
}
2021-08-20 09:19:44 -07:00
async function update ( req , res , next ) {
2015-07-20 00:09:47 -07:00
assert . strictEqual ( typeof req . body , 'object' ) ;
2021-09-20 22:39:35 -07:00
assert . strictEqual ( typeof req . app , 'object' ) ;
2015-07-20 00:09:47 -07:00
2021-08-20 09:19:44 -07:00
const data = req . body ;
2015-07-20 00:09:47 -07:00
2016-06-04 19:19:00 -07:00
// atleast one
if ( 'manifest' in data && typeof data . manifest !== 'object' ) return next ( new HttpError ( 400 , 'manifest must be an object' ) ) ;
if ( 'appStoreId' in data && typeof data . appStoreId !== 'string' ) return next ( new HttpError ( 400 , 'appStoreId must be a string' ) ) ;
if ( ! data . manifest && ! data . appStoreId ) return next ( new HttpError ( 400 , 'appStoreId or manifest is required' ) ) ;
2019-09-26 13:06:15 -07:00
if ( 'skipBackup' in data && typeof data . skipBackup !== 'boolean' ) return next ( new HttpError ( 400 , 'skipBackup must be a boolean' ) ) ;
2015-07-20 00:09:47 -07:00
if ( 'force' in data && typeof data . force !== 'boolean' ) return next ( new HttpError ( 400 , 'force must be a boolean' ) ) ;
2021-08-20 09:19:44 -07:00
let [ error , result ] = await safe ( apps . downloadManifest ( data . appStoreId , data . manifest ) ) ;
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
2015-07-20 00:09:47 -07:00
2021-08-20 09:19:44 -07:00
const { appStoreId , manifest } = result ;
2020-03-29 16:24:04 -07:00
2021-08-20 09:19:44 -07:00
if ( safe . query ( manifest , 'addons.docker' ) && req . user . role !== users . ROLE _OWNER ) return next ( new HttpError ( 403 , '"owner" role is required to update app with docker addon' ) ) ;
2020-03-29 16:24:04 -07:00
2021-08-20 09:19:44 -07:00
data . appStoreId = appStoreId ;
data . manifest = manifest ;
2021-09-20 22:39:35 -07:00
[ error , result ] = await safe ( apps . updateApp ( req . app , data , auditSource . fromRequest ( req ) ) ) ;
2021-08-20 09:19:44 -07:00
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
next ( new HttpSuccess ( 202 , { taskId : result . taskId } ) ) ;
2015-07-20 00:09:47 -07:00
}
// this route is for streaming logs
function getLogStream ( req , res , next ) {
2021-09-20 22:39:35 -07:00
assert . strictEqual ( typeof req . app , 'object' ) ;
2015-07-20 00:09:47 -07:00
2019-01-08 12:10:53 -08:00
var lines = 'lines' in req . query ? parseInt ( req . query . lines , 10 ) : 10 ; // we ignore last-event-id
2015-11-02 11:20:50 -08:00
if ( isNaN ( lines ) ) return next ( new HttpError ( 400 , 'lines must be a valid number' ) ) ;
2015-07-20 00:09:47 -07:00
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' ) ) ;
2017-04-19 21:43:29 -07:00
var options = {
lines : lines ,
2019-01-08 12:10:53 -08:00
follow : true ,
format : 'json'
2017-04-19 21:43:29 -07:00
} ;
2021-09-20 22:39:35 -07:00
apps . getLogs ( req . app , options , function ( error , logStream ) {
2019-10-24 18:05:14 -07:00
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
2015-07-20 00:09:47 -07:00
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 ) ;
2019-04-24 16:08:30 -07:00
res . write ( sse ( obj . realtimeTimestamp , JSON . stringify ( obj ) ) ) ; // send timestamp as id
2015-07-20 00:09:47 -07:00
} ) ;
logStream . on ( 'end' , res . end . bind ( res ) ) ;
logStream . on ( 'error' , res . end . bind ( res , null ) ) ;
} ) ;
}
function getLogs ( req , res , next ) {
2021-09-20 22:39:35 -07:00
assert . strictEqual ( typeof req . app , 'object' ) ;
2015-07-20 00:09:47 -07:00
2019-01-08 12:10:53 -08:00
var lines = 'lines' in req . query ? parseInt ( req . query . lines , 10 ) : 10 ;
2015-11-02 11:20:50 -08:00
if ( isNaN ( lines ) ) return next ( new HttpError ( 400 , 'lines must be a number' ) ) ;
2017-04-18 20:32:57 -07:00
var options = {
lines : lines ,
follow : false ,
2019-01-08 12:10:53 -08:00
format : req . query . format || 'json'
2017-04-18 20:32:57 -07:00
} ;
2021-09-20 22:39:35 -07:00
apps . getLogs ( req . app , options , function ( error , logStream ) {
2019-10-24 18:05:14 -07:00
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
2015-07-20 00:09:47 -07:00
res . writeHead ( 200 , {
'Content-Type' : 'application/x-logs' ,
2021-09-20 22:39:35 -07:00
'Content-Disposition' : ` attachment; filename=" ${ req . app . id } .log" ` ,
2015-07-20 00:09:47 -07:00
'Cache-Control' : 'no-cache' ,
'X-Accel-Buffering' : 'no' // disable nginx buffering
} ) ;
logStream . pipe ( res ) ;
} ) ;
}
2016-05-22 21:07:05 -07:00
function demuxStream ( stream , stdin ) {
var header = null ;
stream . on ( 'readable' , function ( ) {
header = header || stream . read ( 4 ) ;
while ( header !== null ) {
var length = header . readUInt32BE ( 0 ) ;
if ( length === 0 ) {
header = null ;
return stdin . end ( ) ; // EOF
}
var payload = stream . read ( length ) ;
if ( payload === null ) break ;
stdin . write ( payload ) ;
header = stream . read ( 4 ) ;
}
} ) ;
}
2021-08-25 19:41:46 -07:00
async function exec ( req , res , next ) {
2021-09-20 22:39:35 -07:00
assert . strictEqual ( typeof req . app , 'object' ) ;
2015-07-20 00:09:47 -07:00
2021-08-25 19:41:46 -07:00
let cmd = null ;
2015-07-20 00:09:47 -07:00
if ( req . query . cmd ) {
cmd = safe . JSON . parse ( req . query . cmd ) ;
2021-05-02 11:26:08 -07:00
if ( ! Array . isArray ( cmd ) || cmd . length < 1 ) return next ( new HttpError ( 400 , 'cmd must be array with atleast size 1' ) ) ;
2015-07-20 00:09:47 -07:00
}
2021-08-25 19:41:46 -07:00
const columns = req . query . columns ? parseInt ( req . query . columns , 10 ) : null ;
2015-07-20 00:09:47 -07:00
if ( isNaN ( columns ) ) return next ( new HttpError ( 400 , 'columns must be a number' ) ) ;
2021-08-25 19:41:46 -07:00
const rows = req . query . rows ? parseInt ( req . query . rows , 10 ) : null ;
2015-07-20 00:09:47 -07:00
if ( isNaN ( rows ) ) return next ( new HttpError ( 400 , 'rows must be a number' ) ) ;
2021-08-25 19:41:46 -07:00
const tty = req . query . tty === 'true' ;
2020-03-29 18:40:49 -07:00
2021-09-20 22:39:35 -07:00
if ( safe . query ( req . app , 'manifest.addons.docker' ) && req . user . role !== users . ROLE _OWNER ) return next ( new HttpError ( 403 , '"owner" role is requied to exec app with docker addon' ) ) ;
2016-01-18 11:16:06 -08:00
2020-04-08 09:18:58 -07:00
// in a badly configured reverse proxy, we might be here without an upgrade
if ( req . headers [ 'upgrade' ] !== 'tcp' ) return next ( new HttpError ( 404 , 'exec requires TCP upgrade' ) ) ;
2021-09-20 22:39:35 -07:00
const [ error , duplexStream ] = await safe ( apps . exec ( req . app , { cmd : cmd , rows : rows , columns : columns , tty : tty } ) ) ;
2021-08-25 19:41:46 -07:00
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
2015-07-20 00:09:47 -07:00
2021-08-25 19:41:46 -07:00
req . clearTimeout ( ) ;
res . sendUpgradeHandshake ( ) ;
2015-07-20 00:09:47 -07:00
2021-08-25 19:41:46 -07:00
// When tty is disabled, the duplexStream has 2 separate streams. When enabled, it has stdout/stderr merged.
duplexStream . pipe ( res . socket ) ;
2016-05-22 21:07:05 -07:00
2021-08-25 19:41:46 -07:00
if ( tty ) {
res . socket . pipe ( duplexStream ) ; // in tty mode, the client always waits for server to exit
} else {
demuxStream ( res . socket , duplexStream ) ;
res . socket . on ( 'error' , function ( ) { duplexStream . end ( ) ; } ) ;
res . socket . on ( 'end' , function ( ) { duplexStream . end ( ) ; } ) ;
}
2015-07-20 00:09:47 -07:00
}
2016-01-19 13:35:28 +01:00
2021-08-25 19:41:46 -07:00
async function execWebSocket ( req , res , next ) {
2021-09-20 22:39:35 -07:00
assert . strictEqual ( typeof req . app , 'object' ) ;
2017-08-15 20:00:52 +02:00
2021-08-25 19:41:46 -07:00
let cmd = null ;
2017-08-15 20:00:52 +02:00
if ( req . query . cmd ) {
cmd = safe . JSON . parse ( req . query . cmd ) ;
2021-05-02 11:26:08 -07:00
if ( ! Array . isArray ( cmd ) || cmd . length < 1 ) return next ( new HttpError ( 400 , 'cmd must be array with atleast size 1' ) ) ;
2017-08-15 20:00:52 +02:00
}
2021-08-25 19:41:46 -07:00
const columns = req . query . columns ? parseInt ( req . query . columns , 10 ) : null ;
2017-08-17 09:29:15 +02:00
if ( isNaN ( columns ) ) return next ( new HttpError ( 400 , 'columns must be a number' ) ) ;
2017-08-15 20:00:52 +02:00
2021-08-25 19:41:46 -07:00
const rows = req . query . rows ? parseInt ( req . query . rows , 10 ) : null ;
2017-08-17 09:29:15 +02:00
if ( isNaN ( rows ) ) return next ( new HttpError ( 400 , 'rows must be a number' ) ) ;
2017-08-15 20:00:52 +02:00
2021-08-25 19:41:46 -07:00
const tty = req . query . tty === 'true' ? true : false ;
2017-08-15 20:00:52 +02:00
2020-04-08 09:18:58 -07:00
// in a badly configured reverse proxy, we might be here without an upgrade
if ( req . headers [ 'upgrade' ] !== 'websocket' ) return next ( new HttpError ( 404 , 'exec requires websocket' ) ) ;
2021-09-20 22:39:35 -07:00
const [ error , duplexStream ] = await safe ( apps . exec ( req . app , { cmd : cmd , rows : rows , columns : columns , tty : tty } ) ) ;
2021-08-25 19:41:46 -07:00
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
2017-08-15 20:00:52 +02:00
2021-08-25 19:41:46 -07:00
req . clearTimeout ( ) ;
res . handleUpgrade ( function ( ws ) {
duplexStream . on ( 'end' , function ( ) { ws . close ( ) ; } ) ;
duplexStream . on ( 'close' , function ( ) { ws . close ( ) ; } ) ;
duplexStream . on ( 'error' , function ( error ) {
debug ( 'duplexStream error:' , error ) ;
} ) ;
duplexStream . on ( 'data' , function ( data ) {
if ( ws . readyState !== WebSocket . OPEN ) return ;
ws . send ( data . toString ( ) ) ;
} ) ;
ws . on ( 'error' , function ( error ) {
debug ( 'websocket error:' , error ) ;
} ) ;
ws . on ( 'message' , function ( msg ) {
duplexStream . write ( msg ) ;
} ) ;
ws . on ( 'close' , function ( ) {
// Clean things up, if any?
2017-08-15 20:00:52 +02:00
} ) ;
} ) ;
}
2021-07-14 11:07:19 -07:00
async function listBackups ( req , res , next ) {
2021-09-20 22:39:35 -07:00
assert . strictEqual ( typeof req . app , 'object' ) ;
2016-01-19 13:35:28 +01:00
2021-07-14 11:07:19 -07:00
const page = typeof req . query . page !== 'undefined' ? parseInt ( req . query . page ) : 1 ;
2016-03-08 08:57:28 -08:00
if ( ! page || page < 0 ) return next ( new HttpError ( 400 , 'page query param has to be a postive number' ) ) ;
2021-07-14 11:07:19 -07:00
const perPage = typeof req . query . per _page !== 'undefined' ? parseInt ( req . query . per _page ) : 25 ;
2016-03-08 08:57:28 -08:00
if ( ! perPage || perPage < 0 ) return next ( new HttpError ( 400 , 'per_page query param has to be a postive number' ) ) ;
2021-09-20 22:39:35 -07:00
const [ error , result ] = await safe ( apps . listBackups ( req . app , page , perPage ) ) ;
2021-07-14 11:07:19 -07:00
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
2016-01-19 13:35:28 +01:00
2021-07-14 11:07:19 -07:00
next ( new HttpSuccess ( 200 , { backups : result } ) ) ;
2016-01-19 13:35:28 +01:00
}
2017-08-18 20:45:52 -07:00
function uploadFile ( req , res , next ) {
2021-09-20 22:39:35 -07:00
assert . strictEqual ( typeof req . app , 'object' ) ;
2017-08-18 20:45:52 -07:00
if ( typeof req . query . file !== 'string' || ! req . query . file ) return next ( new HttpError ( 400 , 'file query argument must be provided' ) ) ;
if ( ! req . files . file ) return next ( new HttpError ( 400 , 'file must be provided as multipart' ) ) ;
2021-09-20 22:39:35 -07:00
apps . uploadFile ( req . app , req . files . file . path , req . query . file , function ( error ) {
2019-10-24 18:05:14 -07:00
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
2017-08-18 20:45:52 -07:00
next ( new HttpSuccess ( 202 , { } ) ) ;
} ) ;
}
function downloadFile ( req , res , next ) {
2021-09-20 22:39:35 -07:00
assert . strictEqual ( typeof req . app , 'object' ) ;
2017-08-18 20:45:52 -07:00
if ( typeof req . query . file !== 'string' || ! req . query . file ) return next ( new HttpError ( 400 , 'file query argument must be provided' ) ) ;
2021-09-20 22:39:35 -07:00
apps . downloadFile ( req . app , req . query . file , function ( error , stream , info ) {
2019-10-24 18:05:14 -07:00
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
2017-08-18 20:45:52 -07:00
2017-08-20 18:44:26 -07:00
var headers = {
2017-08-18 20:45:52 -07:00
'Content-Type' : 'application/octet-stream' ,
2019-03-04 13:07:14 -08:00
'Content-Disposition' : ` attachment; filename*=utf-8'' ${ encodeURIComponent ( info . filename ) } ` // RFC 2184 section 4
2017-08-20 18:44:26 -07:00
} ;
if ( info . size ) headers [ 'Content-Length' ] = info . size ;
res . writeHead ( 200 , headers ) ;
2017-08-18 20:45:52 -07:00
2017-08-20 18:44:26 -07:00
stream . pipe ( res ) ;
2017-08-18 20:45:52 -07:00
} ) ;
}
2020-04-29 21:55:21 -07:00
2021-08-20 09:19:44 -07:00
async function setMounts ( req , res , next ) {
2020-04-29 21:55:21 -07:00
assert . strictEqual ( typeof req . body , 'object' ) ;
2021-09-20 22:39:35 -07:00
assert . strictEqual ( typeof req . app , 'object' ) ;
2020-04-29 21:55:21 -07:00
2020-10-29 21:58:14 -07:00
if ( ! Array . isArray ( req . body . mounts ) ) return next ( new HttpError ( 400 , 'mounts should be an array' ) ) ;
2020-10-28 19:42:48 -07:00
for ( let m of req . body . mounts ) {
if ( ! m || typeof m !== 'object' ) return next ( new HttpError ( 400 , 'mounts must be an object' ) ) ;
if ( typeof m . volumeId !== 'string' ) return next ( new HttpError ( 400 , 'volumeId must be a string' ) ) ;
if ( typeof m . readOnly !== 'boolean' ) return next ( new HttpError ( 400 , 'readOnly must be a boolean' ) ) ;
2020-04-29 21:55:21 -07:00
}
2021-09-20 22:39:35 -07:00
const [ error , result ] = await safe ( apps . setMounts ( req . app , req . body . mounts , auditSource . fromRequest ( req ) ) ) ;
2021-08-20 09:19:44 -07:00
if ( error ) return next ( BoxError . toHttpError ( error ) ) ;
2020-04-29 21:55:21 -07:00
2021-08-20 09:19:44 -07:00
next ( new HttpSuccess ( 202 , { taskId : result . taskId } ) ) ;
2020-04-29 21:55:21 -07:00
}