Skip to content

Commit

Permalink
feat: Add security checks page (parse-community#2491)
Browse files Browse the repository at this point in the history
  • Loading branch information
dblythy authored Aug 27, 2023
1 parent 3e696f8 commit 103b9c6
Show file tree
Hide file tree
Showing 9 changed files with 175 additions and 26 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/components/Field/Field.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const Field = ({ label, input, labelWidth = 50, labelPadding, height, className
<div className={styles.left} style={{ width: labelWidth + '% ', height: height }}>
{label}
</div>
<div className={styles.right} style={{ marginLeft: labelWidth + '%', height: height }}>
<div className={styles.right} style={{ height: height }}>
{input}
</div>
</div>
Expand Down
14 changes: 4 additions & 10 deletions src/components/Field/Field.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
border-width: 1px 1px 0 1px;
min-height: 80px;
background: white;
display: flex;

&:last-of-type {
border-bottom-width: 1px;
Expand All @@ -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 {
Expand All @@ -46,6 +39,7 @@
display: flex;
justify-content: center;
align-items: center;
flex: 1
}


Expand Down
3 changes: 1 addition & 2 deletions src/components/Label/Label.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ const Label = props => {
return (
<div
className={[styles.label, fieldStyles.centered].join(' ')}
style={{ padding: '0 ' + padding }}
>
style={{ padding: '0 ' + padding, ...props.style }}>
<div className={styles.text}>{props.text}</div>
{props.description ? <div className={styles.description}>{props.description}</div> : null}
</div>
Expand Down
16 changes: 9 additions & 7 deletions src/dashboard/Dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.

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

const SettingsRoute = (
<Route element={<SettingsData />}>
<Route path="dashboard" element={<DashboardSettings />} />
<Route path="general" element={<GeneralSettings />} />
<Route path="keys" element={<SecuritySettings />} />
<Route path="users" element={<UsersSettings />} />
<Route path="push" element={<PushSettings />} />
<Route path="hosting" element={<HostingSettings />} />
<Route index element={<Navigate replace to="dashboard" />} />
<Route path='dashboard' element={<DashboardSettings />} />
<Route path='security' element={<Security />} />
<Route path='general' element={<GeneralSettings />} />
<Route path='keys' element={<SecuritySettings />} />
<Route path='users' element={<UsersSettings />} />
<Route path='push' element={<PushSettings />} />
<Route path='hosting' element={<HostingSettings />} />
<Route index element={<Navigate replace to='dashboard' />} />
</Route>
);

Expand Down
17 changes: 11 additions & 6 deletions src/dashboard/DashboardView.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/*
Expand Down
125 changes: 125 additions & 0 deletions src/dashboard/Settings/Security/Security.react.js
Original file line number Diff line number Diff line change
@@ -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 (
<Toolbar section="Settings" subsection="Security">
<Button color="white" value="Reload" onClick={() => this.reload()} />
</Toolbar>
);
}

renderRow(security) {
return (
<tr key={JSON.stringify(security)}>
<td className={styles.tableData} style={security.header ? {fontWeight: 'bold'} : {}} width={'20%'}>
{security.check}
</td>
<td className={styles.tableData} width={'5%'}>
{security.i !== undefined ? '' : (security.status === 'success' ? '✅' : '❌')}
</td>
<td className={styles.tableData} width={'37.5%'}>
{security.issue}
</td>
<td className={styles.tableData} width={'37.5%'}>
{security.solution}
</td>
</tr>
);
}

renderHeaders() {
return [
<TableHeader width={20} key="Check">
Check
</TableHeader>,
<TableHeader width={5} key="Status">
Status
</TableHeader>,
<TableHeader width={37.5} key="Issue">
Issue
</TableHeader>,
<TableHeader width={37.5} key="Solution">
Solution
</TableHeader>,
];
}

renderEmpty() {
return <EmptyState title="Security" description={<span>{this.state.error}</span>} 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 || {} });
}
}
4 changes: 4 additions & 0 deletions src/dashboard/Settings/Security/Security.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@import 'stylesheets/globals.scss';
.tableData {
white-space: normal !important;
}
2 changes: 2 additions & 0 deletions src/lib/ParseApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -75,6 +76,7 @@ export default class ParseApp {
this.graphQLServerURL = graphQLServerURL;
this.columnPreference = columnPreference;
this.scripts = scripts;
this.enableSecurityChecks = !!enableSecurityChecks;

if (!supportedPushLocales) {
console.warn(
Expand Down

0 comments on commit 103b9c6

Please sign in to comment.