diff --git a/interface/.env.development b/interface/.env.development
index fc000e6..b7c5d93 100644
--- a/interface/.env.development
+++ b/interface/.env.development
@@ -1 +1 @@
-REACT_APP_ENDPOINT_ROOT=http://192.168.0.4/rest/
\ No newline at end of file
+REACT_APP_ENDPOINT_ROOT=http://192.168.0.16/rest/
diff --git a/interface/.env.production b/interface/.env.production
index e2075f0..5f7447a 100644
--- a/interface/.env.production
+++ b/interface/.env.production
@@ -1,2 +1,2 @@
REACT_APP_ENDPOINT_ROOT=/rest/
-GENERATE_SOURCEMAP=false
\ No newline at end of file
+GENERATE_SOURCEMAP=false
diff --git a/interface/src/authentication/Authentication.js b/interface/src/authentication/Authentication.js
index 48fefe8..8606d4a 100644
--- a/interface/src/authentication/Authentication.js
+++ b/interface/src/authentication/Authentication.js
@@ -29,11 +29,13 @@ export function fetchLoginRedirect() {
/**
* Wraps the normal fetch routene with one with provides the access token if present.
*/
-export function secureFetch(url, params) {
- if (localStorage.getItem(ACCESS_TOKEN)) {
+export function authorizedFetch(url, params) {
+ const accessToken = localStorage.getItem(ACCESS_TOKEN);
+ if (accessToken) {
params = params || {};
- params.headers = params.headers || new Headers();
- params.headers.Authorization = 'Bearer ' + localStorage.getItem(ACCESS_TOKEN)
+ params.credentials = 'include';
+ params.headers = params.headers || {};
+ params.headers.Authorization = 'Bearer ' + accessToken;
}
return fetch(url, params);
}
@@ -41,9 +43,9 @@ export function secureFetch(url, params) {
/**
* Wraps the normal fetch routene which redirects on 401 response.
*/
-export function redirectingSecureFetch(url, params) {
+export function redirectingAuthorizedFetch(url, params) {
return new Promise(function (resolve, reject) {
- secureFetch(url, params).then(response => {
+ authorizedFetch(url, params).then(response => {
if (response.status === 401) {
history.go("/");
} else {
diff --git a/interface/src/authentication/AuthenticationWrapper.js b/interface/src/authentication/AuthenticationWrapper.js
index c9cb02a..81b3fa0 100644
--- a/interface/src/authentication/AuthenticationWrapper.js
+++ b/interface/src/authentication/AuthenticationWrapper.js
@@ -1,10 +1,27 @@
import * as React from 'react';
import history from '../history'
import { withNotifier } from '../components/SnackbarNotification';
-
-import { ACCESS_TOKEN } from './Authentication';
+import { VERIFY_AUTHORIZATION_ENDPOINT } from '../constants/Endpoints';
+import { ACCESS_TOKEN, authorizedFetch } from './Authentication';
import { AuthenticationContext } from './Context';
import jwtDecode from 'jwt-decode';
+import CircularProgress from '@material-ui/core/CircularProgress';
+import Typography from '@material-ui/core/Typography';
+import { withStyles } from '@material-ui/core/styles';
+
+const styles = theme => ({
+ loadingPanel: {
+ padding: theme.spacing.unit * 2,
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ height: "100vh",
+ flexDirection: "column"
+ },
+ progress: {
+ margin: theme.spacing.unit * 4,
+ }
+});
class AuthenticationWrapper extends React.Component {
@@ -44,23 +61,30 @@ class AuthenticationWrapper extends React.Component {
}
renderContentLoading() {
+ const { classes } = this.props;
return (
-
THIS IS WHERE THE LOADING MESSAGE GOES
+
+
+
+ Loading...
+
+
);
}
refresh() {
var accessToken = localStorage.getItem(ACCESS_TOKEN);
if (accessToken) {
- try {
- this.setState({ initialized: true, context: { ...this.state.context, jwt: jwtDecode(accessToken) } });
- } catch (err) {
- localStorage.removeItem(ACCESS_TOKEN);
- this.props.raiseNotification("Please log in again.");
- history.push('/');
- }
+ authorizedFetch(VERIFY_AUTHORIZATION_ENDPOINT)
+ .then(response => {
+ const jwt = response.status === 200 ? jwtDecode(accessToken) : undefined;
+ this.setState({ initialized: true, context: { ...this.state.context, jwt } });
+ }).catch(error => {
+ this.setState({ initialized: true, context: { ...this.state.context, jwt: undefined } });
+ this.props.raiseNotification("Error verifying authorization: " + error.message);
+ });
} else {
- this.setState({ initialized: true });
+ this.setState({ initialized: true, context: { ...this.state.context, jwt: undefined } });
}
}
@@ -69,8 +93,8 @@ class AuthenticationWrapper extends React.Component {
this.setState({ context: { ...this.state.context, jwt: jwtDecode(accessToken) } });
localStorage.setItem(ACCESS_TOKEN, accessToken);
} catch (err) {
- this.props.raiseNotification("JWT did not parse.");
- history.push('/');
+ this.setState({ initialized: true, context: { ...this.state.context, jwt: undefined } });
+ this.props.raiseNotification("Failed to parse JWT " + err.message);
}
}
@@ -79,7 +103,7 @@ class AuthenticationWrapper extends React.Component {
this.setState({
context: {
...this.state.context,
- me: undefined
+ jwt: undefined
}
});
this.props.raiseNotification("You have signed out.");
@@ -88,4 +112,4 @@ class AuthenticationWrapper extends React.Component {
}
-export default withNotifier(AuthenticationWrapper)
+export default withStyles(styles)(withNotifier(AuthenticationWrapper))
diff --git a/interface/src/components/SnackbarNotification.js b/interface/src/components/SnackbarNotification.js
index f30c434..c866c0e 100644
--- a/interface/src/components/SnackbarNotification.js
+++ b/interface/src/components/SnackbarNotification.js
@@ -54,7 +54,7 @@ class SnackbarNotification extends React.Component {
open={this.state.open}
autoHideDuration={6000}
onClose={this.handleClose}
- SnackbarContentProps={{
+ ContentProps={{
'aria-describedby': 'message-id',
}}
message={{this.state.message}}
diff --git a/interface/src/constants/Endpoints.js b/interface/src/constants/Endpoints.js
index 6295f29..cc13481 100644
--- a/interface/src/constants/Endpoints.js
+++ b/interface/src/constants/Endpoints.js
@@ -9,4 +9,5 @@ export const LIST_NETWORKS_ENDPOINT = ENDPOINT_ROOT + "listNetworks";
export const WIFI_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "wifiSettings";
export const WIFI_STATUS_ENDPOINT = ENDPOINT_ROOT + "wifiStatus";
export const OTA_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "otaSettings";
-export const SIGN_IN_ENDPOINT = ENDPOINT_ROOT + "signIn";
\ No newline at end of file
+export const SIGN_IN_ENDPOINT = ENDPOINT_ROOT + "signIn";
+export const VERIFY_AUTHORIZATION_ENDPOINT = ENDPOINT_ROOT + "verifyAuthorization";
diff --git a/interface/src/containers/SignInPage.js b/interface/src/containers/SignInPage.js
index 6d98ea9..f03da23 100644
--- a/interface/src/containers/SignInPage.js
+++ b/interface/src/containers/SignInPage.js
@@ -1,5 +1,4 @@
import React, { Component } from 'react';
-
import { withStyles } from '@material-ui/core/styles';
import { TextValidator, ValidatorForm } from 'react-material-ui-form-validator';
import Paper from '@material-ui/core/Paper';
@@ -10,41 +9,42 @@ import ForwardIcon from '@material-ui/icons/Forward';
import { withNotifier } from '../components/SnackbarNotification';
import { SIGN_IN_ENDPOINT } from '../constants/Endpoints';
import { withAuthenticationContext } from '../authentication/Context';
+import PasswordValidator from '../components/PasswordValidator';
-const styles = theme => ({
- loginPage: {
- padding: theme.spacing.unit * 2,
- height: "100vh",
- display: "flex"
- },
- loginPanel: {
- margin: "auto",
- padding: theme.spacing.unit * 2,
- paddingTop: "200px",
- backgroundImage: 'url("/app/icon.png")',
- backgroundRepeat: "no-repeat",
- backgroundPosition: "50% " + theme.spacing.unit * 2 + "px",
- backgroundSize: "auto 150px",
- textAlign: "center"
- },
- extendedIcon: {
- marginRight: theme.spacing.unit,
- },
- 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,
+const styles = theme => {
+ return {
+ loginPage: {
+ display: "flex",
+ height: "100vh",
+ margin: "auto",
+ padding: theme.spacing.unit * 2,
+ justifyContent: "center",
+ flexDirection: "column",
+ maxWidth: theme.breakpoints.values.sm
+ },
+ loginPanel: {
+ textAlign: "center",
+ padding: theme.spacing.unit * 2,
+ paddingTop: "200px",
+ backgroundImage: 'url("/app/icon.png")',
+ backgroundRepeat: "no-repeat",
+ backgroundPosition: "50% " + theme.spacing.unit * 2 + "px",
+ backgroundSize: "auto 150px",
+ width: "100%"
+ },
+ extendedIcon: {
+ marginRight: theme.spacing.unit,
+ },
+ textField: {
+ width: "100%"
+ },
+ button: {
+ marginRight: theme.spacing.unit * 2,
+ marginTop: theme.spacing.unit * 2,
+ }
}
-});
+}
+
class LoginPage extends Component {
@@ -53,7 +53,7 @@ class LoginPage extends Component {
this.state = {
username: '',
password: '',
- fetched: false
+ processing: false
};
}
@@ -64,7 +64,7 @@ class LoginPage extends Component {
onSubmit = () => {
const { username, password } = this.state;
const { authenticationContext } = this.props;
- this.setState({ fetched: false });
+ this.setState({ processing: true });
fetch(SIGN_IN_ENDPOINT, {
method: 'POST',
body: JSON.stringify({ username, password }),
@@ -76,21 +76,22 @@ class LoginPage extends Component {
if (response.status === 200) {
return response.json();
} else if (response.status === 401) {
- throw Error("Login details invalid!");
+ throw Error("Invalid login details.");
} else {
throw Error("Invalid status code: " + response.status);
}
- }).then(json =>{
+ }).then(json => {
authenticationContext.signIn(json.access_token);
+ this.setState({ processing: false });
})
.catch(error => {
this.props.raiseNotification(error.message);
- this.setState({ fetched: true });
+ this.setState({ processing: false });
});
};
render() {
- const { username, password } = this.state;
+ const { username, password, processing } = this.state;
const { classes } = this.props;
return (
@@ -98,6 +99,7 @@ class LoginPage extends Component {
{APP_NAME}
-
-
+
Sign In
diff --git a/platformio.ini b/platformio.ini
index ea7848c..7a152e2 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -19,7 +19,8 @@ monitor_speed = 115200
build_flags=
-D NO_GLOBAL_ARDUINOOTA
-; -D ENABLE_CORS
+ -D ENABLE_CORS
+ -D CORS_ORIGIN=\"http://localhost:3000\"
lib_deps =
NtpClientLib@>=2.5.1,<3.0.0
ArduinoJson@>=6.0.0,<7.0.0
diff --git a/src/main.cpp b/src/main.cpp
index 7c9ec3c..ea0a4c5 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -77,8 +77,9 @@ void setup() {
// Disable CORS if required
#if defined(ENABLE_CORS)
- DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*");
- DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "*");
+ DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", CORS_ORIGIN);
+ DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "Accept, Content-Type, Authorization");
+ DefaultHeaders::Instance().addHeader("Access-Control-Allow-Credentials", "true");
#endif
server.begin();