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: Add security checks page #2491

Merged
merged 9 commits into from
Aug 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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