Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Import data via CSV #2359

Closed
wants to merge 24 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
2a5ec68
ci: bump environment
mtrezza Feb 8, 2022
c247b7a
chore(release): 4.0.0-alpha.16 [skip ci]
semantic-release-bot Feb 10, 2022
ea7d9c0
refactor: bump follow-redirects from 1.14.7 to 1.14.8 (#2039)
dependabot[bot] Feb 12, 2022
bbbe8b1
refactor: upgrade typescript from 4.5.4 to 4.5.5 (#2038)
snyk-bot Feb 12, 2022
a63c7e6
refactor: bump node-sass from 6.0.1 to 7.0.0 (#2037)
dependabot[bot] Feb 12, 2022
665232f
refactor: upgrade otpauth from 7.0.9 to 7.0.10 (#2044)
snyk-bot Feb 17, 2022
4e83a55
refactor: upgrade graphql from 16.2.0 to 16.3.0 (#2043)
snyk-bot Feb 17, 2022
383103c
fix: security upgrade prismjs from 1.26.0 to 1.27.0 (#2047)
snyk-bot Feb 23, 2022
51181b5
chore(release): 4.0.0-alpha.17 [skip ci]
semantic-release-bot Feb 23, 2022
ac75e44
refactor: upgrade @babel/runtime from 7.16.7 to 7.17.0 (#2048)
snyk-bot Feb 24, 2022
f3a4f56
fix: upgrade @babel/runtime from 7.17.0 to 7.17.2 (#2055)
snyk-bot Mar 2, 2022
f47b46b
chore(release): 4.0.0-alpha.18 [skip ci]
semantic-release-bot Mar 2, 2022
743fffa
refactor: upgrade body-parser from 1.19.1 to 1.19.2 (#2057)
snyk-bot Mar 10, 2022
165477d
fix: upgrade express from 4.17.2 to 4.17.3 (#2058)
snyk-bot Mar 10, 2022
7aa47ec
chore(release): 4.0.0-alpha.19 [skip ci]
semantic-release-bot Mar 10, 2022
54d1edf
feat: change string filter description (#2059)
cyb3rko Mar 16, 2022
fde994a
chore(release): 4.0.0-alpha.20 [skip ci]
semantic-release-bot Mar 16, 2022
465e600
fix: upgrade otpauth from 7.0.10 to 7.0.11 (#2061)
snyk-bot Mar 18, 2022
abeef26
chore(release): 4.0.0-alpha.21 [skip ci]
semantic-release-bot Mar 18, 2022
8f69e68
feat: add dashboard option `allowAnonymousUser`
dblythy Mar 23, 2022
a882f51
ci: bump environment
mtrezza Feb 8, 2022
216438f
chore(release): 4.0.0-alpha.16 [skip ci]
semantic-release-bot Feb 10, 2022
749e6e3
feat: add import dialog
dblythy Jan 18, 2023
78dfc29
wip
dblythy Jan 19, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 21 additions & 13 deletions Parse-Dashboard/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,21 +87,29 @@ module.exports = function(config, options) {
newFeaturesInLatestVersion: newFeaturesInLatestVersion,
};

//Based on advice from Doug Wilson here:
//https://github.com/expressjs/express/issues/2518
const requestIsLocal =
req.connection.remoteAddress === '127.0.0.1' ||
req.connection.remoteAddress === '::ffff:127.0.0.1' ||
req.connection.remoteAddress === '::1';
if (!options.dev && !requestIsLocal) {
if (!req.secure && !options.allowInsecureHTTP) {
for (const key in options) {
if (options[key] && !config[key]) {
config[key] = options[key];
}
}
if (!config.dev) {
if (!req.secure && !config.allowInsecureHTTP) {
//Disallow HTTP requests except on localhost, to prevent the master key from being transmitted in cleartext
return res.send({ success: false, error: 'Parse Dashboard can only be remotely accessed via HTTPS' });
return res.send({
success: false,
error:
"Parse Dashboard can only be remotely accessed via HTTPS.",
log: "Configure a user to access Parse Dashboard. If you are running locally, use the --dev parameter to bypass allowInsecureHTTP.",
});
}

if (!users) {
//Accessing the dashboard over the internet can only be done with username and password
return res.send({ success: false, error: 'Configure a user to access Parse Dashboard remotely' });
if (!users && config.allowAnonymousUser) {
//Accessing the dashboard requires users unless allowAnonymousUser is set to `true`
return res.send({
success: false,
error: "Configure a user to access Parse Dashboard.",
log: "Configure a user to access Parse Dashboard. If you are running locally, use the --dev parameter to bypass allowAnonymousUser.",
});
}
}
const authentication = req.user;
Expand Down Expand Up @@ -145,7 +153,7 @@ module.exports = function(config, options) {

//They didn't provide auth, and have configured the dashboard to not need auth
//(ie. didn't supply usernames and passwords)
if (requestIsLocal || options.dev) {
if (config.dev) {
//Allow no-auth access on localhost only, if they have configured the dashboard to not need auth
return res.json(response);
}
Expand Down
177 changes: 164 additions & 13 deletions Parse-Dashboard/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@
*/
// Command line tool for npm start
'use strict'
const path = require('path');
const fs = require('fs');
const express = require('express');
const parseDashboard = require('./app');
const CLIHelper = require('./CLIHelper.js');
const startServer = require('./server');

const program = require('commander');
program.option('--appId [appId]', 'the app Id of the app you would like to manage.');
Expand All @@ -22,27 +25,175 @@ program.option('--host [host]', 'the host to run parse-dashboard');
program.option('--port [port]', 'the port to run parse-dashboard');
program.option('--mountPath [mountPath]', 'the mount path to run parse-dashboard');
program.option('--allowInsecureHTTP [allowInsecureHTTP]', 'set this flag when you are running the dashboard behind an HTTPS load balancer or proxy with early SSL termination.');
program.option('--allowAnonymousUser [allowAnonymousUser]', 'set this to true if you do not require defined users to login. DO NOT ENABLE IN PRODUCTION SERVERS.');
program.option('--sslKey [sslKey]', 'the path to the SSL private key.');
program.option('--sslCert [sslCert]', 'the path to the SSL certificate.');
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.');
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');
program.option('--createUser', 'helper tool to allow you to generate secure user passwords and secrets. Use this on trusted devices only.');
program.option('--createMFA', 'helper tool to allow you to generate multi-factor authentication secrets.');
program.action(async (options) => {
for (const key in options) {
const func = CLIHelper[key];
if (func && typeof func === 'function') {
await func();
process.exit(0);

program.parse(process.argv);

for (const key in program) {
const func = CLIHelper[key];
if (func && typeof func === 'function') {
func();
return;
}
}

const host = program.host || process.env.HOST || '0.0.0.0';
const port = program.port || process.env.PORT || 4040;
const mountPath = program.mountPath || process.env.MOUNT_PATH || '/';
const allowInsecureHTTP = program.allowInsecureHTTP || process.env.PARSE_DASHBOARD_ALLOW_INSECURE_HTTP;
const cookieSessionSecret = program.cookieSessionSecret || process.env.PARSE_DASHBOARD_COOKIE_SESSION_SECRET;
const trustProxy = program.trustProxy || process.env.PARSE_DASHBOARD_TRUST_PROXY;
const dev = program.dev;

if (trustProxy && allowInsecureHTTP) {
console.log('Set only trustProxy *or* allowInsecureHTTP, not both. Only one is needed to handle being behind a proxy.');
process.exit(-1);
}

let explicitConfigFileProvided = !!program.config;
let configFile = null;
let configFromCLI = null;
let configServerURL = program.serverURL || process.env.PARSE_DASHBOARD_SERVER_URL;
let configGraphQLServerURL = program.graphQLServerURL || process.env.PARSE_DASHBOARD_GRAPHQL_SERVER_URL;
let configMasterKey = program.masterKey || process.env.PARSE_DASHBOARD_MASTER_KEY;
let configAppId = program.appId || process.env.PARSE_DASHBOARD_APP_ID;
let configAppName = program.appName || process.env.PARSE_DASHBOARD_APP_NAME;
let configUserId = program.userId || process.env.PARSE_DASHBOARD_USER_ID;
let configUserPassword = program.userPassword || process.env.PARSE_DASHBOARD_USER_PASSWORD;
let configSSLKey = program.sslKey || process.env.PARSE_DASHBOARD_SSL_KEY;
let configSSLCert = program.sslCert || process.env.PARSE_DASHBOARD_SSL_CERT;
const allowAnonymousUser = program.allowAnonymousUser || process.env.PARSE_DASHBOARD_ALLOW_ANONYMOUS_USER

function handleSIGs(server) {
const signals = {
'SIGINT': 2,
'SIGTERM': 15
};
function shutdown(signal, value) {
server.close(function () {
console.log('server stopped by ' + signal);
process.exit(128 + value);
});
}
Object.keys(signals).forEach(function (signal) {
process.on(signal, function () {
shutdown(signal, signals[signal]);
});
});
}

if (!program.config && !process.env.PARSE_DASHBOARD_CONFIG) {
if (configServerURL && configMasterKey && configAppId) {
configFromCLI = {
data: {
apps: [
{
appId: configAppId,
serverURL: configServerURL,
masterKey: configMasterKey,
appName: configAppName,
},
]
}
};
if (configGraphQLServerURL) {
configFromCLI.data.apps[0].graphQLServerURL = configGraphQLServerURL;
}
if (configUserId && configUserPassword) {
configFromCLI.data.users = [
{
user: configUserId,
pass: configUserPassword,
}
];
}
} else if (!configServerURL && !configMasterKey && !configAppName) {
configFile = path.join(__dirname, 'parse-dashboard-config.json');
}
});
} else if (!program.config && process.env.PARSE_DASHBOARD_CONFIG) {
configFromCLI = {
data: JSON.parse(process.env.PARSE_DASHBOARD_CONFIG)
};
} else {
configFile = program.config;
if (program.appId || program.serverURL || program.masterKey || program.appName || program.graphQLServerURL) {
console.log('You must provide either a config file or other CLI options (appName, appId, masterKey, serverURL, and graphQLServerURL); not both.');
process.exit(3);
}
}

async function run() {
await program.parseAsync(process.argv);
const options = program.opts();
let config = null;
let configFilePath = null;
if (configFile) {
try {
config = {
data: JSON.parse(fs.readFileSync(configFile, 'utf8'))
};
configFilePath = path.dirname(configFile);
} catch (error) {
if (error instanceof SyntaxError) {
console.log('Your config file contains invalid JSON. Exiting.');
process.exit(1);
} else if (error.code === 'ENOENT') {
if (explicitConfigFileProvided) {
console.log('Your config file is missing. Exiting.');
process.exit(2);
} else {
console.log('You must provide either a config file or required CLI options (app ID, Master Key, and server URL); not both.');
process.exit(3);
}
} else {
console.log('There was a problem with your config. Exiting.');
process.exit(-1);
}
}
} else if (configFromCLI) {
config = configFromCLI;
} else {
//Failed to load default config file.
console.log('You must provide either a config file or an app ID, Master Key, and server URL. See parse-dashboard --help for details.');
process.exit(4);
}

startServer(options);
config.data.apps.forEach(app => {
if (!app.appName) {
app.appName = app.appId;
}
});

if (config.data.iconsFolder && configFilePath) {
config.data.iconsFolder = path.join(configFilePath, config.data.iconsFolder);
}

run();
const app = express();

if (allowInsecureHTTP || trustProxy || dev) app.enable('trust proxy');

config.data.trustProxy = trustProxy;
let dashboardOptions = { allowInsecureHTTP, cookieSessionSecret, dev, allowAnonymousUser};
app.use(mountPath, parseDashboard(config.data, dashboardOptions));
let server;
if(!configSSLKey || !configSSLCert){
// Start the server.
server = app.listen(port, host, function () {
console.log(`The dashboard is now available at http://${server.address().address}:${server.address().port}${mountPath}`);
});
} else {
// Start the server using SSL.
var privateKey = fs.readFileSync(configSSLKey);
var certificate = fs.readFileSync(configSSLCert);

server = require('https').createServer({
key: privateKey,
cert: certificate
}, app).listen(port, host, function () {
console.log(`The dashboard is now available at https://${server.address().address}:${server.address().port}${mountPath}`);
});
}
handleSIGs(server);
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ The `--dev` parameter disables production-ready security features. This paramete

- allow insecure http connections from anywhere, bypassing the option `allowInsecureHTTP`
- allow the Parse Server `masterKey` to be transmitted in cleartext without encryption
- allow dashboard access without user authentication
- allow dashboard access without user authentication, bypassing the option `allowAnonymousUser`

> ⚠️ Do not use this parameter when deploying Parse Dashboard in a production environment.

Expand Down Expand Up @@ -329,7 +329,7 @@ If you have classes with a lot of columns and you filter them often with the sam
{
"name": "email",
"filterSortToTop": true
}
}
]
}
}
Expand Down Expand Up @@ -452,7 +452,7 @@ With MFA enabled, a user must provide a one-time password that is typically boun

The user requires an authenticator app to generate the one-time password. These apps are provided by many 3rd parties and mostly for free.

If you create a new user by running `parse-dashboard --createUser`, you will be asked whether you want to enable MFA for the new user. To enable MFA for an existing user,
If you create a new user by running `parse-dashboard --createUser`, you will be asked whether you want to enable MFA for the new user. To enable MFA for an existing user,
run `parse-dashboard --createMFA` to generate a `mfa` secret that you then add to the existing user configuration, for example:

```json
Expand Down
Loading