diff --git a/README.md b/README.md
index c55beb58b9..5c439fbf62 100644
--- a/README.md
+++ b/README.md
@@ -540,6 +540,24 @@ var dashboard = new ParseDashboard({
});
```
+## Security Checks
+
+You can view the security status of your Parse Server by enabling the dashboard option `enableSecurityChecks`, and visiting App Settings > Security.
+
+```javascript
+const dashboard = new ParseDashboard({
+ "apps": [
+ {
+ "serverURL": "http://localhost:1337/parse",
+ "appId": "myAppId",
+ "masterKey": "myMasterKey",
+ "appName": "MyApp"
+ "enableSecurityChecks": true
+ }
+ ],
+});
+```
+
### Configuring Basic Authentication
diff --git a/src/components/Field/Field.react.js b/src/components/Field/Field.react.js
index 7a124f9797..7c9f06b617 100644
--- a/src/components/Field/Field.react.js
+++ b/src/components/Field/Field.react.js
@@ -26,7 +26,7 @@ const Field = ({ label, input, labelWidth = 50, labelPadding, height, className
{label}
-
diff --git a/src/components/Field/Field.scss b/src/components/Field/Field.scss
index 116a490828..2978470710 100644
--- a/src/components/Field/Field.scss
+++ b/src/components/Field/Field.scss
@@ -14,6 +14,7 @@
border-width: 1px 1px 0 1px;
min-height: 80px;
background: white;
+ display: flex;
&:last-of-type {
border-bottom-width: 1px;
@@ -25,16 +26,8 @@
}
.left {
- position: absolute;
- left: 0;
- height: 100%;
-}
-
-.centered {
- @include transform(translateY(-50%));
- position: absolute;
- width: 100%;
- top: 50%;
+ display: flex;
+ align-items: center;
}
.right {
@@ -46,6 +39,7 @@
display: flex;
justify-content: center;
align-items: center;
+ flex: 1
}
diff --git a/src/components/Label/Label.react.js b/src/components/Label/Label.react.js
index 768028fd0e..d48f5d276c 100644
--- a/src/components/Label/Label.react.js
+++ b/src/components/Label/Label.react.js
@@ -15,8 +15,7 @@ const Label = props => {
return (
+ style={{ padding: '0 ' + padding, ...props.style }}>
{props.text}
{props.description ?
{props.description}
: null}
diff --git a/src/dashboard/Dashboard.js b/src/dashboard/Dashboard.js
index ec6e712839..e30a1824f8 100644
--- a/src/dashboard/Dashboard.js
+++ b/src/dashboard/Dashboard.js
@@ -52,6 +52,7 @@ import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import { Helmet } from 'react-helmet';
import Playground from './Data/Playground/Playground.react';
import DashboardSettings from './Settings/DashboardSettings/DashboardSettings.react';
+import Security from './Settings/Security/Security.react';
const ShowSchemaOverview = false; //In progress features. Change false to true to work on this feature.
@@ -213,13 +214,14 @@ export default class Dashboard extends React.Component {
const SettingsRoute = (
}>
- } />
- } />
- } />
- } />
- } />
- } />
- } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
);
diff --git a/src/dashboard/DashboardView.react.js b/src/dashboard/DashboardView.react.js
index d10167d19f..00ceb39223 100644
--- a/src/dashboard/DashboardView.react.js
+++ b/src/dashboard/DashboardView.react.js
@@ -195,12 +195,17 @@ export default class DashboardView extends React.Component {
}
*/
- const settingsSections = [
- {
- name: 'Dashboard',
- link: '/settings/dashboard',
- },
- ];
+ const settingsSections = [{
+ name: 'Dashboard',
+ link: '/settings/dashboard'
+ }];
+
+ if (this.context.enableSecurityChecks) {
+ settingsSections.push({
+ name: 'Security',
+ link: '/settings/security',
+ })
+ }
// Settings - nothing remotely like this in parse-server yet. Maybe it will arrive soon.
/*
diff --git a/src/dashboard/Settings/Security/Security.react.js b/src/dashboard/Settings/Security/Security.react.js
new file mode 100644
index 0000000000..19b0f3ad28
--- /dev/null
+++ b/src/dashboard/Settings/Security/Security.react.js
@@ -0,0 +1,125 @@
+import TableView from 'dashboard/TableView.react';
+import Button from 'components/Button/Button.react';
+import EmptyState from 'components/EmptyState/EmptyState.react';
+import React from 'react';
+import Toolbar from 'components/Toolbar/Toolbar.react';
+import styles from './Security.scss';
+import Parse from 'parse';
+import TableHeader from 'components/Table/TableHeader.react';
+
+export default class Security extends TableView {
+ constructor() {
+ super();
+ this.section = 'App Settings';
+ this.subsection = 'Security';
+ this.state = {
+ loading: false,
+ data: {},
+ error: '',
+ };
+ }
+
+ componentWillMount() {
+ this.reload();
+ }
+
+ componentWillReceiveProps(nextProps, nextContext) {
+ if (this.context !== nextContext) {
+ this.reload();
+ }
+ }
+
+ renderToolbar() {
+ return (
+
+
+ );
+ }
+
+ renderRow(security) {
+ return (
+
+
+ {security.check}
+ |
+
+ {security.i !== undefined ? '' : (security.status === 'success' ? '✅' : '❌')}
+ |
+
+ {security.issue}
+ |
+
+ {security.solution}
+ |
+
+ );
+ }
+
+ renderHeaders() {
+ return [
+
+ Check
+ ,
+
+ Status
+ ,
+
+ Issue
+ ,
+
+ Solution
+ ,
+ ];
+ }
+
+ renderEmpty() {
+ return {this.state.error}} icon="gears" cta="Reload" action={() => this.reload()} />;
+ }
+
+ tableData() {
+ const data = [];
+ if (this.state.data.state) {
+ data.push({
+ check: 'Overall status',
+ status: this.state.data.state,
+ header: true
+ }),
+ data.push({i: -1})
+ }
+ for (let i = 0; i < this.state.data?.groups?.length; i++) {
+ const group = this.state.data.groups[i]
+ data.push({
+ check: group.name,
+ status: group.state,
+ issue: '',
+ solution: '',
+ header: true
+ });
+ for (const check of group.checks) {
+ data.push({
+ check: check.title,
+ status: check.state,
+ issue: check.warning,
+ solution: check.solution,
+ });
+ }
+ if (i !== this.state.data.groups.length - 1) {
+ data.push({i});
+ }
+ }
+ return data;
+ }
+
+ async reload() {
+ if (!this.context.enableSecurityChecks) {
+ this.setState({ error: 'Enable Dashboard option `enableSecurityChecks` to run security check.' });
+ return;
+ }
+ this.setState({ loading: true });
+ const result = await Parse._request('GET', 'security', {}, { useMasterKey: true }).catch((e) => {
+ this.setState({ error: e?.message || e });
+ });
+ this.setState({ loading: false, data: result?.report || {} });
+ }
+}
diff --git a/src/dashboard/Settings/Security/Security.scss b/src/dashboard/Settings/Security/Security.scss
new file mode 100644
index 0000000000..568c7f5ad7
--- /dev/null
+++ b/src/dashboard/Settings/Security/Security.scss
@@ -0,0 +1,4 @@
+ @import 'stylesheets/globals.scss';
+.tableData {
+ white-space: normal !important;
+}
diff --git a/src/lib/ParseApp.js b/src/lib/ParseApp.js
index 0a0434d158..fdbdc196aa 100644
--- a/src/lib/ParseApp.js
+++ b/src/lib/ParseApp.js
@@ -48,6 +48,7 @@ export default class ParseApp {
columnPreference,
scripts,
classPreference,
+ enableSecurityChecks
}) {
this.name = appName;
this.createdAt = created_at ? new Date(created_at) : new Date();
@@ -75,6 +76,7 @@ export default class ParseApp {
this.graphQLServerURL = graphQLServerURL;
this.columnPreference = columnPreference;
this.scripts = scripts;
+ this.enableSecurityChecks = !!enableSecurityChecks;
if (!supportedPushLocales) {
console.warn(