Skip to content

Commit 7a65202

Browse files
JeremyPleaseflovilmart
authored andcommitted
Use mountPath for all log in and log out redirects (#605)
* Use mountPath for all log in and log out redirects Fixes #604 * Fix e2e tests * Revert "Fix e2e tests" This reverts commit 7f37275. * Add more e2e tests to check handling of different mount paths * Wait for dashboard to mount in order to get proper/reliable mountpath * Remove commented out old getMount code
1 parent 09c6084 commit 7a65202

File tree

6 files changed

+181
-157
lines changed

6 files changed

+181
-157
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
### NEXT RELEASE
44

5+
* Fix: Use mountPath for all log in and log out redirects
56
* _Contributing to this repo? Add info about your change here to be included in next release_
67

78
### 1.0.20

Parse-Dashboard/Authentication.js

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@ var LocalStrategy = require('passport-local').Strategy;
66

77
/**
88
* Constructor for Authentication class
9-
*
9+
*
1010
* @class Authentication
1111
* @param {Object[]} validUsers
1212
* @param {boolean} useEncryptedPasswords
1313
*/
14-
function Authentication(validUsers, useEncryptedPasswords) {
14+
function Authentication(validUsers, useEncryptedPasswords, mountPath) {
1515
this.validUsers = validUsers;
1616
this.useEncryptedPasswords = useEncryptedPasswords || false;
17+
this.mountPath = mountPath;
1718
}
1819

1920
function initialize(app) {
@@ -57,21 +58,21 @@ function initialize(app) {
5758
app.post('/login',
5859
csrf(),
5960
passport.authenticate('local', {
60-
successRedirect: '/apps',
61-
failureRedirect: '/login',
61+
successRedirect: `${self.mountPath}apps`,
62+
failureRedirect: `${self.mountPath}login`,
6263
failureFlash : true
6364
})
6465
);
6566

6667
app.get('/logout', function(req, res){
6768
req.logout();
68-
res.redirect('/login');
69+
res.redirect(`${self.mountPath}login`);
6970
});
7071
}
7172

7273
/**
7374
* Authenticates the `userToTest`
74-
*
75+
*
7576
* @param {Object} userToTest
7677
* @returns {Object} Object with `isAuthenticated` and `appsUserHasAccessTo` properties
7778
*/
@@ -97,7 +98,7 @@ function authenticate(userToTest, usernameOnly) {
9798

9899
return isAuthenticated;
99100
}) ? true : false;
100-
101+
101102
return {
102103
isAuthenticated,
103104
matchingUsername,

Parse-Dashboard/app.js

Lines changed: 136 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,8 @@ packageJson('parse-dashboard', 'latest').then(latestPackage => {
1717
}
1818
});
1919

20-
function getMount(req) {
21-
let url = req.url;
22-
let originalUrl = req.originalUrl;
23-
var mountPathLength = req.originalUrl.length - req.url.length;
24-
var mountPath = req.originalUrl.slice(0, mountPathLength);
20+
function getMount(mountPath) {
21+
mountPath = mountPath || '';
2522
if (!mountPath.endsWith('/')) {
2623
mountPath += '/';
2724
}
@@ -59,149 +56,152 @@ module.exports = function(config, allowInsecureHTTP) {
5956
app.enable('trust proxy');
6057
}
6158

62-
const users = config.users;
63-
const useEncryptedPasswords = config.useEncryptedPasswords ? true : false;
64-
const authInstance = new Authentication(users, useEncryptedPasswords);
65-
authInstance.initialize(app);
66-
67-
// CSRF error handler
68-
app.use(function (err, req, res, next) {
69-
if (err.code !== 'EBADCSRFTOKEN') return next(err)
70-
71-
// handle CSRF token errors here
72-
res.status(403)
73-
res.send('form tampered with')
74-
});
75-
76-
// Serve the configuration.
77-
app.get('/parse-dashboard-config.json', function(req, res) {
78-
let response = {
79-
apps: config.apps,
80-
newFeaturesInLatestVersion: newFeaturesInLatestVersion,
81-
};
82-
83-
//Based on advice from Doug Wilson here:
84-
//https://github.com/expressjs/express/issues/2518
85-
const requestIsLocal =
86-
req.connection.remoteAddress === '127.0.0.1' ||
87-
req.connection.remoteAddress === '::ffff:127.0.0.1' ||
88-
req.connection.remoteAddress === '::1';
89-
if (!requestIsLocal && !req.secure && !allowInsecureHTTP) {
90-
//Disallow HTTP requests except on localhost, to prevent the master key from being transmitted in cleartext
91-
return res.send({ success: false, error: 'Parse Dashboard can only be remotely accessed via HTTPS' });
92-
}
59+
// wait for app to mount in order to get mountpath
60+
app.on('mount', function() {
61+
const mountPath = getMount(app.mountpath);
62+
const users = config.users;
63+
const useEncryptedPasswords = config.useEncryptedPasswords ? true : false;
64+
const authInstance = new Authentication(users, useEncryptedPasswords, mountPath);
65+
authInstance.initialize(app);
66+
67+
// CSRF error handler
68+
app.use(function (err, req, res, next) {
69+
if (err.code !== 'EBADCSRFTOKEN') return next(err)
70+
71+
// handle CSRF token errors here
72+
res.status(403)
73+
res.send('form tampered with')
74+
});
9375

94-
if (!requestIsLocal && !users) {
95-
//Accessing the dashboard over the internet can only be done with username and password
96-
return res.send({ success: false, error: 'Configure a user to access Parse Dashboard remotely' });
97-
}
76+
// Serve the configuration.
77+
app.get('/parse-dashboard-config.json', function(req, res) {
78+
let response = {
79+
apps: config.apps,
80+
newFeaturesInLatestVersion: newFeaturesInLatestVersion,
81+
};
82+
83+
//Based on advice from Doug Wilson here:
84+
//https://github.com/expressjs/express/issues/2518
85+
const requestIsLocal =
86+
req.connection.remoteAddress === '127.0.0.1' ||
87+
req.connection.remoteAddress === '::ffff:127.0.0.1' ||
88+
req.connection.remoteAddress === '::1';
89+
if (!requestIsLocal && !req.secure && !allowInsecureHTTP) {
90+
//Disallow HTTP requests except on localhost, to prevent the master key from being transmitted in cleartext
91+
return res.send({ success: false, error: 'Parse Dashboard can only be remotely accessed via HTTPS' });
92+
}
9893

99-
const authentication = req.user;
94+
if (!requestIsLocal && !users) {
95+
//Accessing the dashboard over the internet can only be done with username and password
96+
return res.send({ success: false, error: 'Configure a user to access Parse Dashboard remotely' });
97+
}
10098

101-
const successfulAuth = authentication && authentication.isAuthenticated;
102-
const appsUserHasAccess = authentication && authentication.appsUserHasAccessTo;
99+
const authentication = req.user;
100+
101+
const successfulAuth = authentication && authentication.isAuthenticated;
102+
const appsUserHasAccess = authentication && authentication.appsUserHasAccessTo;
103+
104+
if (successfulAuth) {
105+
if (appsUserHasAccess) {
106+
// Restric access to apps defined in user dictionary
107+
// If they didn't supply any app id, user will access all apps
108+
response.apps = response.apps.filter(function (app) {
109+
return appsUserHasAccess.find(appUserHasAccess => {
110+
return app.appId == appUserHasAccess.appId
111+
})
112+
});
113+
}
114+
// They provided correct auth
115+
return res.json(response);
116+
}
103117

104-
if (successfulAuth) {
105-
if (appsUserHasAccess) {
106-
// Restric access to apps defined in user dictionary
107-
// If they didn't supply any app id, user will access all apps
108-
response.apps = response.apps.filter(function (app) {
109-
return appsUserHasAccess.find(appUserHasAccess => {
110-
return app.appId == appUserHasAccess.appId
111-
})
112-
});
118+
if (users) {
119+
//They provided incorrect auth
120+
return res.sendStatus(401);
113121
}
114-
// They provided correct auth
115-
return res.json(response);
116-
}
117122

118-
if (users) {
119-
//They provided incorrect auth
120-
return res.sendStatus(401);
121-
}
123+
//They didn't provide auth, and have configured the dashboard to not need auth
124+
//(ie. didn't supply usernames and passwords)
125+
if (requestIsLocal) {
126+
//Allow no-auth access on localhost only, if they have configured the dashboard to not need auth
127+
return res.json(response);
128+
}
129+
//We shouldn't get here. Fail closed.
130+
res.send({ success: false, error: 'Something went wrong.' });
131+
});
122132

123-
//They didn't provide auth, and have configured the dashboard to not need auth
124-
//(ie. didn't supply usernames and passwords)
125-
if (requestIsLocal) {
126-
//Allow no-auth access on localhost only, if they have configured the dashboard to not need auth
127-
return res.json(response);
133+
// Serve the app icons. Uses the optional `iconsFolder` parameter as
134+
// directory name, that was setup in the config file.
135+
// We are explicitly not using `__dirpath` here because one may be
136+
// running parse-dashboard from globally installed npm.
137+
if (config.iconsFolder) {
138+
try {
139+
var stat = fs.statSync(config.iconsFolder);
140+
if (stat.isDirectory()) {
141+
app.use('/appicons', express.static(config.iconsFolder));
142+
//Check also if the icons really exist
143+
checkIfIconsExistForApps(config.apps, config.iconsFolder);
144+
}
145+
} catch (e) {
146+
// Directory doesn't exist or something.
147+
console.warn("Iconsfolder at path: " + config.iconsFolder +
148+
" not found!");
149+
}
128150
}
129-
//We shouldn't get here. Fail closed.
130-
res.send({ success: false, error: 'Something went wrong.' });
131-
});
132151

133-
// Serve the app icons. Uses the optional `iconsFolder` parameter as
134-
// directory name, that was setup in the config file.
135-
// We are explicitly not using `__dirpath` here because one may be
136-
// running parse-dashboard from globally installed npm.
137-
if (config.iconsFolder) {
138-
try {
139-
var stat = fs.statSync(config.iconsFolder);
140-
if (stat.isDirectory()) {
141-
app.use('/appicons', express.static(config.iconsFolder));
142-
//Check also if the icons really exist
143-
checkIfIconsExistForApps(config.apps, config.iconsFolder);
152+
app.get('/login', csrf(), function(req, res) {
153+
if (!users || (req.user && req.user.isAuthenticated)) {
154+
return res.redirect(`${mountPath}apps`);
144155
}
145-
} catch (e) {
146-
// Directory doesn't exist or something.
147-
console.warn("Iconsfolder at path: " + config.iconsFolder +
148-
" not found!");
149-
}
150-
}
151156

152-
app.get('/login', csrf(), function(req, res) {
153-
if (!users || (req.user && req.user.isAuthenticated)) {
154-
return res.redirect('/apps');
155-
}
156-
let mountPath = getMount(req);
157-
let errors = req.flash('error');
158-
if (errors && errors.length) {
159-
errors = `<div id="login_errors" style="display: none;">
160-
${errors.join(' ')}
161-
</div>`
162-
}
163-
res.send(`<!DOCTYPE html>
164-
<head>
165-
<link rel="shortcut icon" type="image/x-icon" href="${mountPath}favicon.ico" />
166-
<base href="${mountPath}"/>
167-
<script>
168-
PARSE_DASHBOARD_PATH = "${mountPath}";
169-
</script>
170-
</head>
171-
<html>
172-
<title>Parse Dashboard</title>
173-
<body>
174-
<div id="login_mount"></div>
175-
${errors}
176-
<script id="csrf" type="application/json">"${req.csrfToken()}"</script>
177-
<script src="${mountPath}bundles/login.bundle.js"></script>
178-
</body>
179-
</html>
180-
`);
181-
});
157+
let errors = req.flash('error');
158+
if (errors && errors.length) {
159+
errors = `<div id="login_errors" style="display: none;">
160+
${errors.join(' ')}
161+
</div>`
162+
}
163+
res.send(`<!DOCTYPE html>
164+
<head>
165+
<link rel="shortcut icon" type="image/x-icon" href="${mountPath}favicon.ico" />
166+
<base href="${mountPath}"/>
167+
<script>
168+
PARSE_DASHBOARD_PATH = "${mountPath}";
169+
</script>
170+
</head>
171+
<html>
172+
<title>Parse Dashboard</title>
173+
<body>
174+
<div id="login_mount"></div>
175+
${errors}
176+
<script id="csrf" type="application/json">"${req.csrfToken()}"</script>
177+
<script src="${mountPath}bundles/login.bundle.js"></script>
178+
</body>
179+
</html>
180+
`);
181+
});
182182

183-
// For every other request, go to index.html. Let client-side handle the rest.
184-
app.get('/*', function(req, res) {
185-
if (users && (!req.user || !req.user.isAuthenticated)) {
186-
return res.redirect('/login');
187-
}
188-
let mountPath = getMount(req);
189-
res.send(`<!DOCTYPE html>
190-
<head>
191-
<link rel="shortcut icon" type="image/x-icon" href="${mountPath}favicon.ico" />
192-
<base href="${mountPath}"/>
193-
<script>
194-
PARSE_DASHBOARD_PATH = "${mountPath}";
195-
</script>
196-
</head>
197-
<html>
198-
<title>Parse Dashboard</title>
199-
<body>
200-
<div id="browser_mount"></div>
201-
<script src="${mountPath}bundles/dashboard.bundle.js"></script>
202-
</body>
203-
</html>
204-
`);
183+
// For every other request, go to index.html. Let client-side handle the rest.
184+
app.get('/*', function(req, res) {
185+
if (users && (!req.user || !req.user.isAuthenticated)) {
186+
return res.redirect(`${mountPath}login`);
187+
}
188+
res.send(`<!DOCTYPE html>
189+
<head>
190+
<link rel="shortcut icon" type="image/x-icon" href="${mountPath}favicon.ico" />
191+
<base href="${mountPath}"/>
192+
<script>
193+
PARSE_DASHBOARD_PATH = "${mountPath}";
194+
</script>
195+
</head>
196+
<html>
197+
<title>Parse Dashboard</title>
198+
<body>
199+
<div id="browser_mount"></div>
200+
<script src="${mountPath}bundles/dashboard.bundle.js"></script>
201+
</body>
202+
</html>
203+
`);
204+
});
205205
});
206206

207207
return app;

src/components/Sidebar/FooterMenu.react.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import React from 'react';
1212
import styles from 'components/Sidebar/Sidebar.scss';
1313

1414
let host = location.host.split('.');
15-
let urlRoot = location.protocol + '//' + host.slice(host.length - 2).join('.');
15+
let mountPath = window.PARSE_DASHBOARD_PATH;
1616

1717
export default class FooterMenu extends React.Component {
1818
constructor() {
@@ -42,7 +42,7 @@ export default class FooterMenu extends React.Component {
4242
position={this.state.position}
4343
onExternalClick={() => this.setState({ show: false })}>
4444
<div className={styles.popup}>
45-
<a href={`${urlRoot}/logout`}>Log Out <span className={styles.emoji}>👋</span></a>
45+
<a href={`${mountPath}logout`}>Log Out <span className={styles.emoji}>👋</span></a>
4646
<a target='_blank' href='https://www.parse.com/docs/server/guide'>Server Guide <span className={styles.emoji}>📚</span></a>
4747
<a target='_blank' href='https://www.parse.com/help'>Help <span className={styles.emoji}>💊</span></a>
4848
</div>

0 commit comments

Comments
 (0)