add security form, begin work on routing
This commit is contained in:
parent
353b46c675
commit
6e5b35978a
@ -1 +1 @@
|
|||||||
REACT_APP_ENDPOINT_ROOT=http://192.168.0.16/rest/
|
REACT_APP_ENDPOINT_ROOT=http://192.168.0.11/rest/
|
||||||
|
@ -31,7 +31,7 @@ class AppRouting extends Component {
|
|||||||
<AuthenticatedRoute exact path="/ap-configuration" component={APConfiguration} />
|
<AuthenticatedRoute exact path="/ap-configuration" component={APConfiguration} />
|
||||||
<AuthenticatedRoute exact path="/ntp-configuration" component={NTPConfiguration} />
|
<AuthenticatedRoute exact path="/ntp-configuration" component={NTPConfiguration} />
|
||||||
<AuthenticatedRoute exact path="/ota-configuration" component={OTAConfiguration} />
|
<AuthenticatedRoute exact path="/ota-configuration" component={OTAConfiguration} />
|
||||||
<AuthenticatedRoute exact path="/security" component={Security} />
|
<AuthenticatedRoute exact path="/security/*" component={Security} />
|
||||||
<Redirect to="/" />
|
<Redirect to="/" />
|
||||||
</Switch>
|
</Switch>
|
||||||
</AuthenticationWrapper>
|
</AuthenticationWrapper>
|
||||||
|
@ -137,7 +137,7 @@ class MenuAppBar extends React.Component {
|
|||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText primary="OTA Updates" />
|
<ListItemText primary="OTA Updates" />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem button component={Link} to='/security'>
|
<ListItem button component={Link} to='/security/'>
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<LockIcon />
|
<LockIcon />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
|
@ -3,6 +3,7 @@ import React, { Component } from 'react';
|
|||||||
import { USERS_ENDPOINT } from '../constants/Endpoints';
|
import { USERS_ENDPOINT } from '../constants/Endpoints';
|
||||||
import { restComponent } from '../components/RestComponent';
|
import { restComponent } from '../components/RestComponent';
|
||||||
import ManageUsersForm from '../forms/ManageUsersForm';
|
import ManageUsersForm from '../forms/ManageUsersForm';
|
||||||
|
import SectionContent from '../components/SectionContent';
|
||||||
|
|
||||||
class ManageUsers extends Component {
|
class ManageUsers extends Component {
|
||||||
|
|
||||||
@ -13,6 +14,7 @@ class ManageUsers extends Component {
|
|||||||
render() {
|
render() {
|
||||||
const { data, fetched, errorMessage } = this.props;
|
const { data, fetched, errorMessage } = this.props;
|
||||||
return (
|
return (
|
||||||
|
<SectionContent title="Manage Users">
|
||||||
<ManageUsersForm
|
<ManageUsersForm
|
||||||
userData={data}
|
userData={data}
|
||||||
userDataFetched={fetched}
|
userDataFetched={fetched}
|
||||||
@ -22,6 +24,7 @@ class ManageUsers extends Component {
|
|||||||
setData={this.props.setData}
|
setData={this.props.setData}
|
||||||
handleValueChange={this.props.handleValueChange}
|
handleValueChange={this.props.handleValueChange}
|
||||||
/>
|
/>
|
||||||
|
</SectionContent>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,15 +1,35 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import { Redirect, Switch } from 'react-router-dom'
|
||||||
|
|
||||||
|
import Tabs from '@material-ui/core/Tabs';
|
||||||
|
import Tab from '@material-ui/core/Tab';
|
||||||
|
|
||||||
|
import AuthenticatedRoute from '../authentication/AuthenticatedRoute';
|
||||||
import MenuAppBar from '../components/MenuAppBar';
|
import MenuAppBar from '../components/MenuAppBar';
|
||||||
import ManageUsers from './ManageUsers';
|
import ManageUsers from './ManageUsers';
|
||||||
|
import SecuritySettings from './SecuritySettings';
|
||||||
|
|
||||||
class Security extends Component {
|
class Security extends Component {
|
||||||
|
|
||||||
|
handleTabChange = (event, path) => {
|
||||||
|
this.props.history.push(path);
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<MenuAppBar sectionTitle="Security">
|
<MenuAppBar sectionTitle="Security">
|
||||||
<ManageUsers />
|
<Tabs value={this.props.match.url} onChange={this.handleTabChange} indicatorColor="primary" textColor="primary" variant="fullWidth">
|
||||||
|
<Tab value="/security/users" label="Manage Users" />
|
||||||
|
<Tab value="/security/settings" label="Security Settings" />
|
||||||
|
</Tabs>
|
||||||
|
<Switch>
|
||||||
|
<AuthenticatedRoute exact={true} path="/security/users" component={ManageUsers} />
|
||||||
|
<AuthenticatedRoute exact={true} path="/security/settings" component={SecuritySettings} />
|
||||||
|
<Redirect to="/security/users" />
|
||||||
|
</Switch>
|
||||||
</MenuAppBar>
|
</MenuAppBar>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Security
|
export default Security;
|
||||||
|
32
interface/src/containers/SecuritySettings.js
Normal file
32
interface/src/containers/SecuritySettings.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
import { USERS_ENDPOINT } from '../constants/Endpoints';
|
||||||
|
import { restComponent } from '../components/RestComponent';
|
||||||
|
import SecuritySettingsForm from '../forms/SecuritySettingsForm';
|
||||||
|
import SectionContent from '../components/SectionContent';
|
||||||
|
|
||||||
|
class SecuritySettings extends Component {
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { data, fetched, errorMessage } = this.props;
|
||||||
|
return (
|
||||||
|
<SectionContent title="Security Settings">
|
||||||
|
<SecuritySettingsForm
|
||||||
|
securitySettings={data}
|
||||||
|
securitySettingsFetched={fetched}
|
||||||
|
errorMessage={errorMessage}
|
||||||
|
onSubmit={this.props.saveData}
|
||||||
|
onReset={this.props.loadData}
|
||||||
|
handleValueChange={this.props.handleValueChange}
|
||||||
|
/>
|
||||||
|
</SectionContent>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default restComponent(USERS_ENDPOINT, SecuritySettings);
|
@ -21,7 +21,6 @@ import CloseIcon from '@material-ui/icons/Close';
|
|||||||
import CheckIcon from '@material-ui/icons/Check';
|
import CheckIcon from '@material-ui/icons/Check';
|
||||||
import IconButton from '@material-ui/core/IconButton';
|
import IconButton from '@material-ui/core/IconButton';
|
||||||
|
|
||||||
import SectionContent from '../components/SectionContent';
|
|
||||||
import UserForm from './UserForm';
|
import UserForm from './UserForm';
|
||||||
import { withAuthenticationContext } from '../authentication/Context';
|
import { withAuthenticationContext } from '../authentication/Context';
|
||||||
|
|
||||||
@ -138,8 +137,6 @@ class ManageUsersForm extends React.Component {
|
|||||||
const { classes, userData, userDataFetched, errorMessage, onReset } = this.props;
|
const { classes, userData, userDataFetched, errorMessage, onReset } = this.props;
|
||||||
const { user, creating } = this.state;
|
const { user, creating } = this.state;
|
||||||
return (
|
return (
|
||||||
<SectionContent title="Manage Users">
|
|
||||||
{
|
|
||||||
!userDataFetched ?
|
!userDataFetched ?
|
||||||
<div className={classes.loadingSettings}>
|
<div className={classes.loadingSettings}>
|
||||||
<LinearProgress className={classes.loadingSettingsDetails} />
|
<LinearProgress className={classes.loadingSettingsDetails} />
|
||||||
@ -185,7 +182,7 @@ class ManageUsersForm extends React.Component {
|
|||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={2} />
|
<TableCell colSpan={2} />
|
||||||
<TableCell align="center">
|
<TableCell align="center">
|
||||||
<Button variant="contained" color="secondary" className={classes.button} onClick={this.createUser}>
|
<Button variant="contained" color="secondary" onClick={this.createUser}>
|
||||||
Add User
|
Add User
|
||||||
</Button>
|
</Button>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
@ -195,7 +192,7 @@ class ManageUsersForm extends React.Component {
|
|||||||
{
|
{
|
||||||
this.noAdminConfigured() &&
|
this.noAdminConfigured() &&
|
||||||
<Typography component="div" variant="body1">
|
<Typography component="div" variant="body1">
|
||||||
<Box bgcolor="error.main" color="error.contrastText" p={2} m={1}>
|
<Box bgcolor="error.main" color="error.contrastText" p={2} m={2}>
|
||||||
You must have at least one admin user configured.
|
You must have at least one admin user configured.
|
||||||
</Box>
|
</Box>
|
||||||
</Typography>
|
</Typography>
|
||||||
@ -209,7 +206,6 @@ class ManageUsersForm extends React.Component {
|
|||||||
</ValidatorForm>
|
</ValidatorForm>
|
||||||
{
|
{
|
||||||
user &&
|
user &&
|
||||||
|
|
||||||
<UserForm
|
<UserForm
|
||||||
user={user}
|
user={user}
|
||||||
creating={creating}
|
creating={creating}
|
||||||
@ -219,27 +215,23 @@ class ManageUsersForm extends React.Component {
|
|||||||
handleCheckboxChange={this.handleUserCheckboxChange}
|
handleCheckboxChange={this.handleUserCheckboxChange}
|
||||||
uniqueUsername={this.uniqueUsername}
|
uniqueUsername={this.uniqueUsername}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
}
|
}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
:
|
:
|
||||||
<SectionContent title="Manage Users">
|
<div className={classes.loadingSettings}>
|
||||||
<Typography variant="h4" className={classes.loadingSettingsDetails}>
|
<Typography variant="h4" className={classes.loadingSettingsDetails}>
|
||||||
{errorMessage}
|
{errorMessage}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Button variant="contained" color="secondary" className={classes.button} onClick={onReset}>
|
<Button variant="contained" color="secondary" className={classes.button} onClick={onReset}>
|
||||||
Reset
|
Reset
|
||||||
</Button>
|
</Button>
|
||||||
</SectionContent>
|
</div>
|
||||||
}
|
|
||||||
</SectionContent>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ManageUsersForm.propTypes = {
|
ManageUsersForm.propTypes = {
|
||||||
authenticationContext: PropTypes.object.isRequired,
|
|
||||||
classes: PropTypes.object.isRequired,
|
classes: PropTypes.object.isRequired,
|
||||||
userData: PropTypes.object,
|
userData: PropTypes.object,
|
||||||
userDataFetched: PropTypes.bool.isRequired,
|
userDataFetched: PropTypes.bool.isRequired,
|
||||||
@ -247,7 +239,8 @@ ManageUsersForm.propTypes = {
|
|||||||
onSubmit: PropTypes.func.isRequired,
|
onSubmit: PropTypes.func.isRequired,
|
||||||
onReset: PropTypes.func.isRequired,
|
onReset: PropTypes.func.isRequired,
|
||||||
setData: PropTypes.func.isRequired,
|
setData: PropTypes.func.isRequired,
|
||||||
handleValueChange: PropTypes.func.isRequired
|
handleValueChange: PropTypes.func.isRequired,
|
||||||
|
authenticationContext: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withAuthenticationContext(withStyles(styles)(ManageUsersForm));
|
export default withAuthenticationContext(withStyles(styles)(ManageUsersForm));
|
||||||
|
97
interface/src/forms/SecuritySettingsForm.js
Normal file
97
interface/src/forms/SecuritySettingsForm.js
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { ValidatorForm } from 'react-material-ui-form-validator';
|
||||||
|
|
||||||
|
import { withStyles } from '@material-ui/core/styles';
|
||||||
|
import Button from '@material-ui/core/Button';
|
||||||
|
import LinearProgress from '@material-ui/core/LinearProgress';
|
||||||
|
import Typography from '@material-ui/core/Typography';
|
||||||
|
import Box from '@material-ui/core/Box';
|
||||||
|
|
||||||
|
import PasswordValidator from '../components/PasswordValidator';
|
||||||
|
import { withAuthenticationContext } from '../authentication/Context';
|
||||||
|
|
||||||
|
const styles = theme => ({
|
||||||
|
loadingSettings: {
|
||||||
|
margin: theme.spacing.unit,
|
||||||
|
},
|
||||||
|
loadingSettingsDetails: {
|
||||||
|
margin: theme.spacing.unit * 4,
|
||||||
|
textAlign: "center"
|
||||||
|
},
|
||||||
|
textField: {
|
||||||
|
width: "100%"
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
marginRight: theme.spacing.unit * 2,
|
||||||
|
marginTop: theme.spacing.unit * 2,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
class SecuritySettingsForm extends React.Component {
|
||||||
|
|
||||||
|
onSubmit = () => {
|
||||||
|
this.props.onSubmit();
|
||||||
|
this.props.authenticationContext.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { classes, securitySettingsFetched, securitySettings, errorMessage, handleValueChange, onReset } = this.props;
|
||||||
|
return (
|
||||||
|
!securitySettingsFetched ?
|
||||||
|
<div className={classes.loadingSettings}>
|
||||||
|
<LinearProgress className={classes.loadingSettingsDetails} />
|
||||||
|
<Typography variant="h4" className={classes.loadingSettingsDetails}>
|
||||||
|
Loading...
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
securitySettings ?
|
||||||
|
<ValidatorForm onSubmit={this.onSubmit} ref="SecuritySettingsForm">
|
||||||
|
<PasswordValidator
|
||||||
|
validators={['required', 'matchRegexp:^.{0,64}$']}
|
||||||
|
errorMessages={['JWT Secret Required', 'JWT Secret must be 64 characters or less']}
|
||||||
|
name="jwt_secret"
|
||||||
|
label="JWT Secret"
|
||||||
|
className={classes.textField}
|
||||||
|
value={securitySettings.jwt_secret}
|
||||||
|
onChange={handleValueChange('jwt_secret')}
|
||||||
|
margin="normal"
|
||||||
|
/>
|
||||||
|
<Typography component="div" variant="body1">
|
||||||
|
<Box bgcolor="primary.main" color="primary.contrastText" p={2} m={2}>
|
||||||
|
If you modify the JWT Secret, all users will be logged out.
|
||||||
|
</Box>
|
||||||
|
</Typography>
|
||||||
|
<Button variant="contained" color="primary" className={classes.button} type="submit">
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
<Button variant="contained" color="secondary" className={classes.button} onClick={onReset}>
|
||||||
|
Reset
|
||||||
|
</Button>
|
||||||
|
</ValidatorForm>
|
||||||
|
:
|
||||||
|
<div className={classes.loadingSettings}>
|
||||||
|
<Typography variant="h4" className={classes.loadingSettingsDetails}>
|
||||||
|
{errorMessage}
|
||||||
|
</Typography>
|
||||||
|
<Button variant="contained" color="secondary" className={classes.button} onClick={onReset}>
|
||||||
|
Reset
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SecuritySettingsForm.propTypes = {
|
||||||
|
classes: PropTypes.object.isRequired,
|
||||||
|
securitySettingsFetched: PropTypes.bool.isRequired,
|
||||||
|
securitySettings: PropTypes.object,
|
||||||
|
errorMessage: PropTypes.string,
|
||||||
|
onSubmit: PropTypes.func.isRequired,
|
||||||
|
onReset: PropTypes.func.isRequired,
|
||||||
|
handleValueChange: PropTypes.func.isRequired,
|
||||||
|
authenticationContext: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withAuthenticationContext(withStyles(styles)(SecuritySettingsForm));
|
@ -44,7 +44,7 @@ class UserForm extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<ValidatorForm onSubmit={onDoneEditing} ref={this.formRef}>
|
<ValidatorForm onSubmit={onDoneEditing} ref={this.formRef}>
|
||||||
<Dialog onClose={onCancelEditing} aria-labelledby="user-form-dialog-title" open={true} scroll="paper">
|
<Dialog onClose={onCancelEditing} aria-labelledby="user-form-dialog-title" open={true} scroll="paper">
|
||||||
<DialogTitle id="user-form-dialog-title">{creating ? 'Create' : 'Modify'} User</DialogTitle>
|
<DialogTitle id="user-form-dialog-title">{creating ? 'Add' : 'Modify'} User</DialogTitle>
|
||||||
<DialogContent dividers={true}>
|
<DialogContent dividers={true}>
|
||||||
<TextValidator
|
<TextValidator
|
||||||
validators={creating ? ['required', 'uniqueUsername', 'matchRegexp:^[a-zA-Z0-9_\\.]{1,24}$'] : []}
|
validators={creating ? ['required', 'uniqueUsername', 'matchRegexp:^[a-zA-Z0-9_\\.]{1,24}$'] : []}
|
||||||
|
@ -6,6 +6,7 @@ SecurityManager::~SecurityManager() {}
|
|||||||
void SecurityManager::readFromJsonObject(JsonObject& root) {
|
void SecurityManager::readFromJsonObject(JsonObject& root) {
|
||||||
// secret
|
// secret
|
||||||
_jwtSecret = root["jwt_secret"] | DEFAULT_JWT_SECRET;
|
_jwtSecret = root["jwt_secret"] | DEFAULT_JWT_SECRET;
|
||||||
|
_jwtHandler.setSecret(_jwtSecret);
|
||||||
|
|
||||||
// users
|
// users
|
||||||
_users.clear();
|
_users.clear();
|
||||||
@ -34,9 +35,6 @@ void SecurityManager::writeToJsonObject(JsonObject& root) {
|
|||||||
void SecurityManager::begin() {
|
void SecurityManager::begin() {
|
||||||
// read config
|
// read config
|
||||||
readFromFS();
|
readFromFS();
|
||||||
|
|
||||||
// configure secret
|
|
||||||
jwtHandler.setSecret(_jwtSecret);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Authentication SecurityManager::authenticateRequest(AsyncWebServerRequest *request) {
|
Authentication SecurityManager::authenticateRequest(AsyncWebServerRequest *request) {
|
||||||
@ -53,7 +51,7 @@ Authentication SecurityManager::authenticateRequest(AsyncWebServerRequest *reque
|
|||||||
|
|
||||||
Authentication SecurityManager::authenticateJWT(String jwt) {
|
Authentication SecurityManager::authenticateJWT(String jwt) {
|
||||||
DynamicJsonDocument payloadDocument(MAX_JWT_SIZE);
|
DynamicJsonDocument payloadDocument(MAX_JWT_SIZE);
|
||||||
jwtHandler.parseJWT(jwt, payloadDocument);
|
_jwtHandler.parseJWT(jwt, payloadDocument);
|
||||||
if (payloadDocument.is<JsonObject>()) {
|
if (payloadDocument.is<JsonObject>()) {
|
||||||
JsonObject parsedPayload = payloadDocument.as<JsonObject>();
|
JsonObject parsedPayload = payloadDocument.as<JsonObject>();
|
||||||
String username = parsedPayload["username"];
|
String username = parsedPayload["username"];
|
||||||
@ -91,5 +89,5 @@ String SecurityManager::generateJWT(User *user) {
|
|||||||
DynamicJsonDocument _jsonDocument(MAX_JWT_SIZE);
|
DynamicJsonDocument _jsonDocument(MAX_JWT_SIZE);
|
||||||
JsonObject payload = _jsonDocument.to<JsonObject>();
|
JsonObject payload = _jsonDocument.to<JsonObject>();
|
||||||
populateJWTPayload(payload, user);
|
populateJWTPayload(payload, user);
|
||||||
return jwtHandler.buildJWT(payload);
|
return _jwtHandler.buildJWT(payload);
|
||||||
}
|
}
|
||||||
|
@ -93,7 +93,7 @@ class SecurityManager : public SettingsService {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
// jwt handler
|
// jwt handler
|
||||||
ArduinoJsonJWT jwtHandler = ArduinoJsonJWT(DEFAULT_JWT_SECRET);
|
ArduinoJsonJWT _jwtHandler = ArduinoJsonJWT(DEFAULT_JWT_SECRET);
|
||||||
|
|
||||||
// access point settings
|
// access point settings
|
||||||
String _jwtSecret;
|
String _jwtSecret;
|
||||||
|
Loading…
Reference in New Issue
Block a user