parse-dashboard 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,225 @@
1
+ const crypto = require('crypto');
2
+ const inquirer = require('inquirer');
3
+ const OTPAuth = require('otpauth');
4
+ const { copy } = require('./utils.js');
5
+ const phrases = {
6
+ enterPassword: 'Enter a password:',
7
+ enterUsername: 'Enter a username:',
8
+ enterAppName: 'Enter the app name:',
9
+ }
10
+ const getAlgorithm = async () => {
11
+ let { algorithm } = await inquirer.prompt([
12
+ {
13
+ type: 'list',
14
+ name: 'algorithm',
15
+ message: 'Which hashing algorithm do you want to use?',
16
+ default: 'SHA1',
17
+ choices: [
18
+ 'SHA1',
19
+ 'SHA224',
20
+ 'SHA256',
21
+ 'SHA384',
22
+ 'SHA512',
23
+ 'SHA3-224',
24
+ 'SHA3-256',
25
+ 'SHA3-384',
26
+ 'SHA3-512',
27
+ 'Other'
28
+ ]
29
+ }
30
+ ]);
31
+ if (algorithm === 'Other') {
32
+ const result = await inquirer.prompt([
33
+ {
34
+ type: 'input',
35
+ name: 'algorithm',
36
+ message: 'Enter the hashing algorithm you want to use:'
37
+ }
38
+ ]);
39
+ algorithm = result.algorithm;
40
+ }
41
+ const { digits, period } = await inquirer.prompt([
42
+ {
43
+ type: 'number',
44
+ name: 'digits',
45
+ default: 6,
46
+ message: 'Enter the number of digits the one-time password should have:'
47
+ },
48
+ {
49
+ type: 'number',
50
+ name: 'period',
51
+ default: 30,
52
+ message: 'Enter how long the one-time password should be valid (in seconds):'
53
+ }
54
+ ])
55
+ return { algorithm, digits, period};
56
+ };
57
+ const generateSecret = ({ app, username, algorithm, digits, period }) => {
58
+ const secret = new OTPAuth.Secret();
59
+ const totp = new OTPAuth.TOTP({
60
+ issuer: app,
61
+ label: username,
62
+ algorithm,
63
+ digits,
64
+ period,
65
+ secret
66
+ });
67
+ const url = totp.toString();
68
+ return { secret: secret.base32, url };
69
+ };
70
+ const showQR = text => {
71
+ const QRCode = require('qrcode');
72
+ QRCode.toString(text, { type: 'terminal' }, (err, url) => {
73
+ console.log(
74
+ '\n------------------------------------------------------------------------------' +
75
+ `\n\n${url}`
76
+ );
77
+ });
78
+ };
79
+
80
+ const showInstructions = ({ app, username, passwordCopied, secret, url, encrypt, config }) => {
81
+ let orderCounter = 0;
82
+ const getOrder = () => {
83
+ orderCounter++;
84
+ return orderCounter;
85
+ }
86
+ console.log(
87
+ '------------------------------------------------------------------------------' +
88
+ '\n\nFollow these steps to complete the set-up:'
89
+ );
90
+
91
+ console.log(
92
+ `\n${getOrder()}. Add the following settings for user "${username}" ${app ? `in app "${app}" ` : '' }to the Parse Dashboard configuration.` +
93
+ `\n\n ${JSON.stringify(config)}`
94
+ );
95
+
96
+ if (passwordCopied) {
97
+ console.log(
98
+ `\n${getOrder()}. Securely store the generated login password that has been copied to your clipboard.`
99
+ );
100
+ }
101
+
102
+ if (secret) {
103
+ console.log(
104
+ `\n${getOrder()}. Open the authenticator app to scan the QR code above or enter this secret code:` +
105
+ `\n\n ${secret}` +
106
+ '\n\n If the secret code generates incorrect one-time passwords, try this alternative:' +
107
+ `\n\n ${url}` +
108
+ `\n\n${getOrder()}. Destroy any records of the QR code and the secret code to secure the account.`
109
+ );
110
+ }
111
+
112
+ if (encrypt) {
113
+ console.log(
114
+ `\n${getOrder()}. Make sure that "useEncryptedPasswords" is set to "true" in your dashboard configuration.` +
115
+ '\n You chose to generate an encrypted password for this user.' +
116
+ '\n Any existing users with non-encrypted passwords will require newly created, encrypted passwords.'
117
+ );
118
+ }
119
+ console.log(
120
+ '\n------------------------------------------------------------------------------\n'
121
+ );
122
+ }
123
+
124
+ module.exports = {
125
+ async createUser() {
126
+ const data = {};
127
+
128
+ console.log('');
129
+ const { username, password } = await inquirer.prompt([
130
+ {
131
+ type: 'input',
132
+ name: 'username',
133
+ message: phrases.enterUsername
134
+ },
135
+ {
136
+ type: 'confirm',
137
+ name: 'password',
138
+ message: 'Do you want to auto-generate a password?'
139
+ }
140
+ ]);
141
+ data.user = username;
142
+ if (!password) {
143
+ const { password } = await inquirer.prompt([
144
+ {
145
+ type: 'password',
146
+ name: 'password',
147
+ message: phrases.enterPassword
148
+ }
149
+ ]);
150
+ data.pass = password;
151
+ } else {
152
+ const password = crypto.randomBytes(20).toString('base64');
153
+ data.pass = password;
154
+ }
155
+ const { mfa, encrypt } = await inquirer.prompt([
156
+ {
157
+ type: 'confirm',
158
+ name: 'encrypt',
159
+ message: 'Should the password be encrypted? (strongly recommended, otherwise it is stored in clear-text)'
160
+ },
161
+ {
162
+ type: 'confirm',
163
+ name: 'mfa',
164
+ message: 'Do you want to enable multi-factor authentication?'
165
+ }
166
+ ]);
167
+ if (encrypt) {
168
+ // Copy the raw password to clipboard
169
+ copy(data.pass);
170
+
171
+ // Encrypt password
172
+ const bcrypt = require('bcryptjs');
173
+ const salt = bcrypt.genSaltSync(10);
174
+ data.pass = bcrypt.hashSync(data.pass, salt);
175
+ }
176
+ if (mfa) {
177
+ const { app } = await inquirer.prompt([
178
+ {
179
+ type: 'input',
180
+ name: 'app',
181
+ message: phrases.enterAppName
182
+ }
183
+ ]);
184
+ const { algorithm, digits, period } = await getAlgorithm();
185
+ const { secret, url } = generateSecret({ app, username, algorithm, digits, period });
186
+ data.mfa = secret;
187
+ data.app = app;
188
+ data.url = url;
189
+ if (algorithm !== 'SHA1') {
190
+ data.mfaAlgorithm = algorithm;
191
+ }
192
+ showQR(data.url);
193
+ }
194
+
195
+ const config = { mfa: data.mfa, user: data.user, pass: data.pass };
196
+ showInstructions({ app: data.app, username, passwordCopied: true, secret: data.mfa, url: data.url, encrypt, config });
197
+ },
198
+ async createMFA() {
199
+ console.log('');
200
+ const { username, app } = await inquirer.prompt([
201
+ {
202
+ type: 'input',
203
+ name: 'username',
204
+ message:
205
+ 'Enter the username for which you want to enable multi-factor authentication:'
206
+ },
207
+ {
208
+ type: 'input',
209
+ name: 'app',
210
+ message: phrases.enterAppName
211
+ }
212
+ ]);
213
+ const { algorithm, digits, period } = await getAlgorithm();
214
+
215
+ const { url, secret } = generateSecret({ app, username, algorithm, digits, period });
216
+ showQR(url);
217
+
218
+ // Compose config
219
+ const config = { mfa: secret };
220
+ if (algorithm !== 'SHA1') {
221
+ config.mfaAlgorithm = algorithm;
222
+ }
223
+ showInstructions({ app, username, secret, url, config });
224
+ }
225
+ };
@@ -0,0 +1,7 @@
1
+ module.exports = {
2
+ copy(text) {
3
+ const proc = require('child_process').spawn('pbcopy');
4
+ proc.stdin.write(text);
5
+ proc.stdin.end();
6
+ }
7
+ }
@@ -0,0 +1,6 @@
1
+ const { createUser, createMFA } = require('./CLI/mfa');
2
+
3
+ module.exports = {
4
+ createUser,
5
+ createMFA
6
+ };
@@ -0,0 +1,229 @@
1
+ 'use strict';
2
+ const express = require('express');
3
+ const path = require('path');
4
+ const packageJson = require('package-json');
5
+ const csrf = require('csurf');
6
+ const Authentication = require('./Authentication.js');
7
+ var fs = require('fs');
8
+
9
+ const currentVersionFeatures = require('../package.json').parseDashboardFeatures;
10
+
11
+ var newFeaturesInLatestVersion = [];
12
+ packageJson('parse-dashboard', 'latest').then(latestPackage => {
13
+ if (latestPackage.parseDashboardFeatures instanceof Array) {
14
+ newFeaturesInLatestVersion = latestPackage.parseDashboardFeatures.filter(feature => {
15
+ return currentVersionFeatures.indexOf(feature) === -1;
16
+ });
17
+ }
18
+ });
19
+
20
+ function getMount(mountPath) {
21
+ mountPath = mountPath || '';
22
+ if (!mountPath.endsWith('/')) {
23
+ mountPath += '/';
24
+ }
25
+ return mountPath;
26
+ }
27
+
28
+ function checkIfIconsExistForApps(apps, iconsFolder) {
29
+ for (var i in apps) {
30
+ var currentApp = apps[i];
31
+ var iconName = currentApp.iconName;
32
+ var path = iconsFolder + '/' + iconName;
33
+
34
+ fs.stat(path, function(err) {
35
+ if (err) {
36
+ if ('ENOENT' == err.code) {// file does not exist
37
+ console.warn('Icon with file name: ' + iconName +' couldn\'t be found in icons folder!');
38
+ } else {
39
+ console.log(
40
+ 'An error occurd while checking for icons, please check permission!');
41
+ }
42
+ } else {
43
+ //every thing was ok so for example you can read it and send it to client
44
+ }
45
+ } );
46
+ }
47
+ }
48
+
49
+ module.exports = function(config, options) {
50
+ options = options || {};
51
+ var app = express();
52
+ // Serve public files.
53
+ app.use(express.static(path.join(__dirname,'public')));
54
+
55
+ // Allow setting via middleware
56
+ if (config.trustProxy && app.disabled('trust proxy')) {
57
+ app.enable('trust proxy');
58
+ }
59
+
60
+ // wait for app to mount in order to get mountpath
61
+ app.on('mount', function() {
62
+ const mountPath = getMount(app.mountpath);
63
+ const users = config.users;
64
+ const useEncryptedPasswords = config.useEncryptedPasswords ? true : false;
65
+ const authInstance = new Authentication(users, useEncryptedPasswords, mountPath);
66
+ authInstance.initialize(app, { cookieSessionSecret: options.cookieSessionSecret });
67
+
68
+ // CSRF error handler
69
+ app.use(function (err, req, res, next) {
70
+ if (err.code !== 'EBADCSRFTOKEN') return next(err)
71
+
72
+ // handle CSRF token errors here
73
+ res.status(403)
74
+ res.send('form tampered with')
75
+ });
76
+
77
+ // Serve the configuration.
78
+ app.get('/parse-dashboard-config.json', function(req, res) {
79
+ let apps = config.apps.map((app) => Object.assign({}, app)); // make a copy
80
+ let response = {
81
+ apps: apps,
82
+ newFeaturesInLatestVersion: newFeaturesInLatestVersion,
83
+ };
84
+
85
+ //Based on advice from Doug Wilson here:
86
+ //https://github.com/expressjs/express/issues/2518
87
+ const requestIsLocal =
88
+ req.connection.remoteAddress === '127.0.0.1' ||
89
+ req.connection.remoteAddress === '::ffff:127.0.0.1' ||
90
+ req.connection.remoteAddress === '::1';
91
+ if (!options.dev && !requestIsLocal) {
92
+ if (!req.secure && !options.allowInsecureHTTP) {
93
+ //Disallow HTTP requests except on localhost, to prevent the master key from being transmitted in cleartext
94
+ return res.send({ success: false, error: 'Parse Dashboard can only be remotely accessed via HTTPS' });
95
+ }
96
+
97
+ if (!users) {
98
+ //Accessing the dashboard over the internet can only be done with username and password
99
+ return res.send({ success: false, error: 'Configure a user to access Parse Dashboard remotely' });
100
+ }
101
+ }
102
+ const authentication = req.user;
103
+
104
+ const successfulAuth = authentication && authentication.isAuthenticated;
105
+ const appsUserHasAccess = authentication && authentication.appsUserHasAccessTo;
106
+ const isReadOnly = authentication && authentication.isReadOnly;
107
+ // User is full read-only, replace the masterKey by the read-only one
108
+ if (isReadOnly) {
109
+ response.apps = response.apps.map((app) => {
110
+ app.masterKey = app.readOnlyMasterKey;
111
+ if (!app.masterKey) {
112
+ throw new Error('You need to provide a readOnlyMasterKey to use read-only features.');
113
+ }
114
+ return app;
115
+ });
116
+ }
117
+
118
+ if (successfulAuth) {
119
+ if (appsUserHasAccess) {
120
+ // Restric access to apps defined in user dictionary
121
+ // If they didn't supply any app id, user will access all apps
122
+ response.apps = response.apps.filter(function (app) {
123
+ return appsUserHasAccess.find(appUserHasAccess => {
124
+ const isSame = app.appId === appUserHasAccess.appId;
125
+ if (isSame && appUserHasAccess.readOnly) {
126
+ app.masterKey = app.readOnlyMasterKey;
127
+ }
128
+ return isSame;
129
+ })
130
+ });
131
+ }
132
+ // They provided correct auth
133
+ return res.json(response);
134
+ }
135
+
136
+ if (users) {
137
+ //They provided incorrect auth
138
+ return res.sendStatus(401);
139
+ }
140
+
141
+ //They didn't provide auth, and have configured the dashboard to not need auth
142
+ //(ie. didn't supply usernames and passwords)
143
+ if (requestIsLocal || options.dev) {
144
+ //Allow no-auth access on localhost only, if they have configured the dashboard to not need auth
145
+ return res.json(response);
146
+ }
147
+ //We shouldn't get here. Fail closed.
148
+ res.send({ success: false, error: 'Something went wrong.' });
149
+ });
150
+
151
+ // Serve the app icons. Uses the optional `iconsFolder` parameter as
152
+ // directory name, that was setup in the config file.
153
+ // We are explicitly not using `__dirpath` here because one may be
154
+ // running parse-dashboard from globally installed npm.
155
+ if (config.iconsFolder) {
156
+ try {
157
+ var stat = fs.statSync(config.iconsFolder);
158
+ if (stat.isDirectory()) {
159
+ app.use('/appicons', express.static(config.iconsFolder));
160
+ //Check also if the icons really exist
161
+ checkIfIconsExistForApps(config.apps, config.iconsFolder);
162
+ }
163
+ } catch (e) {
164
+ // Directory doesn't exist or something.
165
+ console.warn('Iconsfolder at path: ' + config.iconsFolder +
166
+ ' not found!');
167
+ }
168
+ }
169
+
170
+ app.get('/login', csrf(), function(req, res) {
171
+ if (!users || (req.user && req.user.isAuthenticated)) {
172
+ return res.redirect(`${mountPath}apps`);
173
+ }
174
+
175
+ let errors = req.flash('error');
176
+ if (errors && errors.length) {
177
+ errors = `<div id="login_errors" style="display: none;">
178
+ ${errors.join(' ')}
179
+ </div>`
180
+ }
181
+ res.send(`<!DOCTYPE html>
182
+ <head>
183
+ <link rel="shortcut icon" type="image/x-icon" href="${mountPath}favicon.ico" />
184
+ <base href="${mountPath}"/>
185
+ <script>
186
+ PARSE_DASHBOARD_PATH = "${mountPath}";
187
+ </script>
188
+ </head>
189
+ <html>
190
+ <title>Parse Dashboard</title>
191
+ <body>
192
+ <div id="login_mount"></div>
193
+ ${errors}
194
+ <script id="csrf" type="application/json">"${req.csrfToken()}"</script>
195
+ <script src="${mountPath}bundles/login.bundle.js"></script>
196
+ </body>
197
+ </html>
198
+ `);
199
+ });
200
+
201
+ // For every other request, go to index.html. Let client-side handle the rest.
202
+ app.get('/*', function(req, res) {
203
+ if (users && (!req.user || !req.user.isAuthenticated)) {
204
+ return res.redirect(`${mountPath}login`);
205
+ }
206
+ if (users && req.user && req.user.matchingUsername ) {
207
+ res.append('username', req.user.matchingUsername);
208
+ }
209
+ res.send(`<!DOCTYPE html>
210
+ <head>
211
+ <link rel="shortcut icon" type="image/x-icon" href="${mountPath}favicon.ico" />
212
+ <base href="${mountPath}"/>
213
+ <script>
214
+ PARSE_DASHBOARD_PATH = "${mountPath}";
215
+ </script>
216
+ </head>
217
+ <html>
218
+ <title>Parse Dashboard</title>
219
+ <body>
220
+ <div id="browser_mount"></div>
221
+ <script src="${mountPath}bundles/dashboard.bundle.js"></script>
222
+ </body>
223
+ </html>
224
+ `);
225
+ });
226
+ });
227
+
228
+ return app;
229
+ }
@@ -0,0 +1,199 @@
1
+ /*
2
+ * Copyright (c) 2016-present, Parse, LLC
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the license found in the LICENSE file in
6
+ * the root directory of this source tree.
7
+ */
8
+ // Command line tool for npm start
9
+ 'use strict'
10
+ const path = require('path');
11
+ const jsonFile = require('json-file-plus');
12
+ const express = require('express');
13
+ const parseDashboard = require('./app');
14
+ const CLIHelper = require('./CLIHelper.js');
15
+
16
+ const program = require('commander');
17
+ program.option('--appId [appId]', 'the app Id of the app you would like to manage.');
18
+ program.option('--masterKey [masterKey]', 'the master key of the app you would like to manage.');
19
+ program.option('--serverURL [serverURL]', 'the server url of the app you would like to manage.');
20
+ program.option('--graphQLServerURL [graphQLServerURL]', 'the GraphQL server url of the app you would like to manage.');
21
+ program.option('--dev', 'Enable development mode. This will disable authentication and allow non HTTPS connections. DO NOT ENABLE IN PRODUCTION SERVERS');
22
+ program.option('--appName [appName]', 'the name of the app you would like to manage. Optional.');
23
+ program.option('--config [config]', 'the path to the configuration file');
24
+ program.option('--host [host]', 'the host to run parse-dashboard');
25
+ program.option('--port [port]', 'the port to run parse-dashboard');
26
+ program.option('--mountPath [mountPath]', 'the mount path to run parse-dashboard');
27
+ program.option('--allowInsecureHTTP [allowInsecureHTTP]', 'set this flag when you are running the dashboard behind an HTTPS load balancer or proxy with early SSL termination.');
28
+ program.option('--sslKey [sslKey]', 'the path to the SSL private key.');
29
+ program.option('--sslCert [sslCert]', 'the path to the SSL certificate.');
30
+ program.option('--trustProxy [trustProxy]', 'set this flag when you are behind a front-facing proxy, such as when hosting on Heroku. Uses X-Forwarded-* headers to determine the client\'s connection and IP address.');
31
+ program.option('--cookieSessionSecret [cookieSessionSecret]', 'set the cookie session secret, defaults to a random string. You should set that value if you want sessions to work across multiple server, or across restarts');
32
+ program.option('--createUser', 'helper tool to allow you to generate secure user passwords and secrets. Use this on trusted devices only.');
33
+ program.option('--createMFA', 'helper tool to allow you to generate multi-factor authentication secrets.');
34
+
35
+ program.parse(process.argv);
36
+
37
+ for (const key in program) {
38
+ const func = CLIHelper[key];
39
+ if (func && typeof func === 'function') {
40
+ func();
41
+ return;
42
+ }
43
+ }
44
+
45
+ const host = program.host || process.env.HOST || '0.0.0.0';
46
+ const port = program.port || process.env.PORT || 4040;
47
+ const mountPath = program.mountPath || process.env.MOUNT_PATH || '/';
48
+ const allowInsecureHTTP = program.allowInsecureHTTP || process.env.PARSE_DASHBOARD_ALLOW_INSECURE_HTTP;
49
+ const cookieSessionSecret = program.cookieSessionSecret || process.env.PARSE_DASHBOARD_COOKIE_SESSION_SECRET;
50
+ const trustProxy = program.trustProxy || process.env.PARSE_DASHBOARD_TRUST_PROXY;
51
+ const dev = program.dev;
52
+
53
+ if (trustProxy && allowInsecureHTTP) {
54
+ console.log('Set only trustProxy *or* allowInsecureHTTP, not both. Only one is needed to handle being behind a proxy.');
55
+ process.exit(-1);
56
+ }
57
+
58
+ let explicitConfigFileProvided = !!program.config;
59
+ let configFile = null;
60
+ let configFromCLI = null;
61
+ let configServerURL = program.serverURL || process.env.PARSE_DASHBOARD_SERVER_URL;
62
+ let configGraphQLServerURL = program.graphQLServerURL || process.env.PARSE_DASHBOARD_GRAPHQL_SERVER_URL;
63
+ let configMasterKey = program.masterKey || process.env.PARSE_DASHBOARD_MASTER_KEY;
64
+ let configAppId = program.appId || process.env.PARSE_DASHBOARD_APP_ID;
65
+ let configAppName = program.appName || process.env.PARSE_DASHBOARD_APP_NAME;
66
+ let configUserId = program.userId || process.env.PARSE_DASHBOARD_USER_ID;
67
+ let configUserPassword = program.userPassword || process.env.PARSE_DASHBOARD_USER_PASSWORD;
68
+ let configSSLKey = program.sslKey || process.env.PARSE_DASHBOARD_SSL_KEY;
69
+ let configSSLCert = program.sslCert || process.env.PARSE_DASHBOARD_SSL_CERT;
70
+
71
+ function handleSIGs(server) {
72
+ const signals = {
73
+ 'SIGINT': 2,
74
+ 'SIGTERM': 15
75
+ };
76
+ function shutdown(signal, value) {
77
+ server.close(function () {
78
+ console.log('server stopped by ' + signal);
79
+ process.exit(128 + value);
80
+ });
81
+ }
82
+ Object.keys(signals).forEach(function (signal) {
83
+ process.on(signal, function () {
84
+ shutdown(signal, signals[signal]);
85
+ });
86
+ });
87
+ }
88
+
89
+ if (!program.config && !process.env.PARSE_DASHBOARD_CONFIG) {
90
+ if (configServerURL && configMasterKey && configAppId) {
91
+ configFromCLI = {
92
+ data: {
93
+ apps: [
94
+ {
95
+ appId: configAppId,
96
+ serverURL: configServerURL,
97
+ masterKey: configMasterKey,
98
+ appName: configAppName,
99
+ },
100
+ ]
101
+ }
102
+ };
103
+ if (configGraphQLServerURL) {
104
+ configFromCLI.data.apps[0].graphQLServerURL = configGraphQLServerURL;
105
+ }
106
+ if (configUserId && configUserPassword) {
107
+ configFromCLI.data.users = [
108
+ {
109
+ user: configUserId,
110
+ pass: configUserPassword,
111
+ }
112
+ ];
113
+ }
114
+ } else if (!configServerURL && !configMasterKey && !configAppName) {
115
+ configFile = path.join(__dirname, 'parse-dashboard-config.json');
116
+ }
117
+ } else if (!program.config && process.env.PARSE_DASHBOARD_CONFIG) {
118
+ configFromCLI = {
119
+ data: JSON.parse(process.env.PARSE_DASHBOARD_CONFIG)
120
+ };
121
+ } else {
122
+ configFile = program.config;
123
+ if (program.appId || program.serverURL || program.masterKey || program.appName || program.graphQLServerURL) {
124
+ console.log('You must provide either a config file or other CLI options (appName, appId, masterKey, serverURL, and graphQLServerURL); not both.');
125
+ process.exit(3);
126
+ }
127
+ }
128
+
129
+ let p = null;
130
+ let configFilePath = null;
131
+ if (configFile) {
132
+ p = jsonFile(configFile);
133
+ configFilePath = path.dirname(configFile);
134
+ } else if (configFromCLI) {
135
+ p = Promise.resolve(configFromCLI);
136
+ } else {
137
+ //Failed to load default config file.
138
+ console.log('You must provide either a config file or an app ID, Master Key, and server URL. See parse-dashboard --help for details.');
139
+ process.exit(4);
140
+ }
141
+ p.then(config => {
142
+ config.data.apps.forEach(app => {
143
+ if (!app.appName) {
144
+ app.appName = app.appId;
145
+ }
146
+ });
147
+
148
+ if (config.data.iconsFolder && configFilePath) {
149
+ config.data.iconsFolder = path.join(configFilePath, config.data.iconsFolder);
150
+ }
151
+
152
+ const app = express();
153
+
154
+ if (allowInsecureHTTP || trustProxy || dev) app.enable('trust proxy');
155
+
156
+ config.data.trustProxy = trustProxy;
157
+ let dashboardOptions = { allowInsecureHTTP, cookieSessionSecret, dev };
158
+ app.use(mountPath, parseDashboard(config.data, dashboardOptions));
159
+ let server;
160
+ if(!configSSLKey || !configSSLCert){
161
+ // Start the server.
162
+ server = app.listen(port, host, function () {
163
+ console.log(`The dashboard is now available at http://${server.address().address}:${server.address().port}${mountPath}`);
164
+ });
165
+ } else {
166
+ // Start the server using SSL.
167
+ var fs = require('fs');
168
+ var privateKey = fs.readFileSync(configSSLKey);
169
+ var certificate = fs.readFileSync(configSSLCert);
170
+
171
+ server = require('https').createServer({
172
+ key: privateKey,
173
+ cert: certificate
174
+ }, app).listen(port, host, function () {
175
+ console.log(`The dashboard is now available at https://${server.address().address}:${server.address().port}${mountPath}`);
176
+ });
177
+ }
178
+ handleSIGs(server);
179
+ }, error => {
180
+ if (error instanceof SyntaxError) {
181
+ console.log('Your config file contains invalid JSON. Exiting.');
182
+ process.exit(1);
183
+ } else if (error.code === 'ENOENT') {
184
+ if (explicitConfigFileProvided) {
185
+ console.log('Your config file is missing. Exiting.');
186
+ process.exit(2);
187
+ } else {
188
+ console.log('You must provide either a config file or required CLI options (app ID, Master Key, and server URL); not both.');
189
+ process.exit(3);
190
+ }
191
+ } else {
192
+ console.log('There was a problem with your config. Exiting.');
193
+ process.exit(-1);
194
+ }
195
+ })
196
+ .catch(error => {
197
+ console.log('There was a problem loading the dashboard. Exiting.', error);
198
+ process.exit(-1);
199
+ });
@@ -0,0 +1,14 @@
1
+ {
2
+ "apps": [
3
+ {
4
+ "serverURL": "http://localhost:1338/parse",
5
+ "appId": "hello",
6
+ "masterKey": "world",
7
+ "appName": "",
8
+ "iconName": "",
9
+ "primaryBackgroundColor": "",
10
+ "secondaryBackgroundColor": ""
11
+ }
12
+ ],
13
+ "iconsFolder": "icons"
14
+ }