Skip to content

Commit 103b9c6

Browse files
authored
feat: Add security checks page (#2491)
1 parent 3e696f8 commit 103b9c6

File tree

9 files changed

+175
-26
lines changed

9 files changed

+175
-26
lines changed

README.md

+18
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,24 @@ var dashboard = new ParseDashboard({
540540
});
541541
```
542542

543+
## Security Checks
544+
545+
You can view the security status of your Parse Server by enabling the dashboard option `enableSecurityChecks`, and visiting App Settings > Security.
546+
547+
```javascript
548+
const dashboard = new ParseDashboard({
549+
"apps": [
550+
{
551+
"serverURL": "http://localhost:1337/parse",
552+
"appId": "myAppId",
553+
"masterKey": "myMasterKey",
554+
"appName": "MyApp"
555+
"enableSecurityChecks": true
556+
}
557+
],
558+
});
559+
```
560+
543561

544562

545563
### Configuring Basic Authentication

src/components/Field/Field.react.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const Field = ({ label, input, labelWidth = 50, labelPadding, height, className
2626
<div className={styles.left} style={{ width: labelWidth + '% ', height: height }}>
2727
{label}
2828
</div>
29-
<div className={styles.right} style={{ marginLeft: labelWidth + '%', height: height }}>
29+
<div className={styles.right} style={{ height: height }}>
3030
{input}
3131
</div>
3232
</div>

src/components/Field/Field.scss

+4-10
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
border-width: 1px 1px 0 1px;
1515
min-height: 80px;
1616
background: white;
17+
display: flex;
1718

1819
&:last-of-type {
1920
border-bottom-width: 1px;
@@ -25,16 +26,8 @@
2526
}
2627

2728
.left {
28-
position: absolute;
29-
left: 0;
30-
height: 100%;
31-
}
32-
33-
.centered {
34-
@include transform(translateY(-50%));
35-
position: absolute;
36-
width: 100%;
37-
top: 50%;
29+
display: flex;
30+
align-items: center;
3831
}
3932

4033
.right {
@@ -46,6 +39,7 @@
4639
display: flex;
4740
justify-content: center;
4841
align-items: center;
42+
flex: 1
4943
}
5044

5145

src/components/Label/Label.react.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@ const Label = props => {
1515
return (
1616
<div
1717
className={[styles.label, fieldStyles.centered].join(' ')}
18-
style={{ padding: '0 ' + padding }}
19-
>
18+
style={{ padding: '0 ' + padding, ...props.style }}>
2019
<div className={styles.text}>{props.text}</div>
2120
{props.description ? <div className={styles.description}>{props.description}</div> : null}
2221
</div>

src/dashboard/Dashboard.js

+9-7
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
5252
import { Helmet } from 'react-helmet';
5353
import Playground from './Data/Playground/Playground.react';
5454
import DashboardSettings from './Settings/DashboardSettings/DashboardSettings.react';
55+
import Security from './Settings/Security/Security.react';
5556

5657
const ShowSchemaOverview = false; //In progress features. Change false to true to work on this feature.
5758

@@ -213,13 +214,14 @@ export default class Dashboard extends React.Component {
213214

214215
const SettingsRoute = (
215216
<Route element={<SettingsData />}>
216-
<Route path="dashboard" element={<DashboardSettings />} />
217-
<Route path="general" element={<GeneralSettings />} />
218-
<Route path="keys" element={<SecuritySettings />} />
219-
<Route path="users" element={<UsersSettings />} />
220-
<Route path="push" element={<PushSettings />} />
221-
<Route path="hosting" element={<HostingSettings />} />
222-
<Route index element={<Navigate replace to="dashboard" />} />
217+
<Route path='dashboard' element={<DashboardSettings />} />
218+
<Route path='security' element={<Security />} />
219+
<Route path='general' element={<GeneralSettings />} />
220+
<Route path='keys' element={<SecuritySettings />} />
221+
<Route path='users' element={<UsersSettings />} />
222+
<Route path='push' element={<PushSettings />} />
223+
<Route path='hosting' element={<HostingSettings />} />
224+
<Route index element={<Navigate replace to='dashboard' />} />
223225
</Route>
224226
);
225227

src/dashboard/DashboardView.react.js

+11-6
Original file line numberDiff line numberDiff line change
@@ -195,12 +195,17 @@ export default class DashboardView extends React.Component {
195195
}
196196
*/
197197

198-
const settingsSections = [
199-
{
200-
name: 'Dashboard',
201-
link: '/settings/dashboard',
202-
},
203-
];
198+
const settingsSections = [{
199+
name: 'Dashboard',
200+
link: '/settings/dashboard'
201+
}];
202+
203+
if (this.context.enableSecurityChecks) {
204+
settingsSections.push({
205+
name: 'Security',
206+
link: '/settings/security',
207+
})
208+
}
204209

205210
// Settings - nothing remotely like this in parse-server yet. Maybe it will arrive soon.
206211
/*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import TableView from 'dashboard/TableView.react';
2+
import Button from 'components/Button/Button.react';
3+
import EmptyState from 'components/EmptyState/EmptyState.react';
4+
import React from 'react';
5+
import Toolbar from 'components/Toolbar/Toolbar.react';
6+
import styles from './Security.scss';
7+
import Parse from 'parse';
8+
import TableHeader from 'components/Table/TableHeader.react';
9+
10+
export default class Security extends TableView {
11+
constructor() {
12+
super();
13+
this.section = 'App Settings';
14+
this.subsection = 'Security';
15+
this.state = {
16+
loading: false,
17+
data: {},
18+
error: '',
19+
};
20+
}
21+
22+
componentWillMount() {
23+
this.reload();
24+
}
25+
26+
componentWillReceiveProps(nextProps, nextContext) {
27+
if (this.context !== nextContext) {
28+
this.reload();
29+
}
30+
}
31+
32+
renderToolbar() {
33+
return (
34+
<Toolbar section="Settings" subsection="Security">
35+
<Button color="white" value="Reload" onClick={() => this.reload()} />
36+
</Toolbar>
37+
);
38+
}
39+
40+
renderRow(security) {
41+
return (
42+
<tr key={JSON.stringify(security)}>
43+
<td className={styles.tableData} style={security.header ? {fontWeight: 'bold'} : {}} width={'20%'}>
44+
{security.check}
45+
</td>
46+
<td className={styles.tableData} width={'5%'}>
47+
{security.i !== undefined ? '' : (security.status === 'success' ? '✅' : '❌')}
48+
</td>
49+
<td className={styles.tableData} width={'37.5%'}>
50+
{security.issue}
51+
</td>
52+
<td className={styles.tableData} width={'37.5%'}>
53+
{security.solution}
54+
</td>
55+
</tr>
56+
);
57+
}
58+
59+
renderHeaders() {
60+
return [
61+
<TableHeader width={20} key="Check">
62+
Check
63+
</TableHeader>,
64+
<TableHeader width={5} key="Status">
65+
Status
66+
</TableHeader>,
67+
<TableHeader width={37.5} key="Issue">
68+
Issue
69+
</TableHeader>,
70+
<TableHeader width={37.5} key="Solution">
71+
Solution
72+
</TableHeader>,
73+
];
74+
}
75+
76+
renderEmpty() {
77+
return <EmptyState title="Security" description={<span>{this.state.error}</span>} icon="gears" cta="Reload" action={() => this.reload()} />;
78+
}
79+
80+
tableData() {
81+
const data = [];
82+
if (this.state.data.state) {
83+
data.push({
84+
check: 'Overall status',
85+
status: this.state.data.state,
86+
header: true
87+
}),
88+
data.push({i: -1})
89+
}
90+
for (let i = 0; i < this.state.data?.groups?.length; i++) {
91+
const group = this.state.data.groups[i]
92+
data.push({
93+
check: group.name,
94+
status: group.state,
95+
issue: '',
96+
solution: '',
97+
header: true
98+
});
99+
for (const check of group.checks) {
100+
data.push({
101+
check: check.title,
102+
status: check.state,
103+
issue: check.warning,
104+
solution: check.solution,
105+
});
106+
}
107+
if (i !== this.state.data.groups.length - 1) {
108+
data.push({i});
109+
}
110+
}
111+
return data;
112+
}
113+
114+
async reload() {
115+
if (!this.context.enableSecurityChecks) {
116+
this.setState({ error: 'Enable Dashboard option `enableSecurityChecks` to run security check.' });
117+
return;
118+
}
119+
this.setState({ loading: true });
120+
const result = await Parse._request('GET', 'security', {}, { useMasterKey: true }).catch((e) => {
121+
this.setState({ error: e?.message || e });
122+
});
123+
this.setState({ loading: false, data: result?.report || {} });
124+
}
125+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@import 'stylesheets/globals.scss';
2+
.tableData {
3+
white-space: normal !important;
4+
}

src/lib/ParseApp.js

+2
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export default class ParseApp {
4848
columnPreference,
4949
scripts,
5050
classPreference,
51+
enableSecurityChecks
5152
}) {
5253
this.name = appName;
5354
this.createdAt = created_at ? new Date(created_at) : new Date();
@@ -75,6 +76,7 @@ export default class ParseApp {
7576
this.graphQLServerURL = graphQLServerURL;
7677
this.columnPreference = columnPreference;
7778
this.scripts = scripts;
79+
this.enableSecurityChecks = !!enableSecurityChecks;
7880

7981
if (!supportedPushLocales) {
8082
console.warn(

0 commit comments

Comments
 (0)