mirror of
https://git.rwth-aachen.de/acs/public/villas/web/
synced 2025-03-09 00:00:01 +01:00
Add authentication
Uses jwt tokens, token is stored locally between refreshes
This commit is contained in:
parent
5a1de494fb
commit
5bf9ad9f8d
11 changed files with 374 additions and 14 deletions
|
@ -11,9 +11,15 @@ import request from 'superagent/lib/client';
|
|||
import Promise from 'es6-promise';
|
||||
|
||||
class RestAPI {
|
||||
get(url) {
|
||||
get(url, token) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
request.get(url).set('Access-Control-Allow-Origin', '*').end(function (error, res) {
|
||||
var req = request.get(url).set('Access-Control-Allow-Origin', '*');
|
||||
|
||||
if (token != null) {
|
||||
req.set('x-access-token', token);
|
||||
}
|
||||
|
||||
req.end(function (error, res) {
|
||||
if (res == null || res.status !== 200) {
|
||||
reject(error);
|
||||
} else {
|
||||
|
@ -23,9 +29,15 @@ class RestAPI {
|
|||
});
|
||||
}
|
||||
|
||||
post(url, body) {
|
||||
post(url, body, token) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
request.post(url).send(body).end(function (error, res) {
|
||||
var req = request.post(url).send(body);
|
||||
|
||||
if (token != null) {
|
||||
req.set('x-access-token', token);
|
||||
}
|
||||
|
||||
req.end(function (error, res) {
|
||||
if (res == null || res.status !== 200) {
|
||||
reject(error);
|
||||
} else {
|
||||
|
@ -35,9 +47,15 @@ class RestAPI {
|
|||
});
|
||||
}
|
||||
|
||||
delete(url) {
|
||||
delete(url, token) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
request.delete(url).end(function (error, res) {
|
||||
var req = request.delete(url);
|
||||
|
||||
if (token != null) {
|
||||
req.set('x-access-token', token);
|
||||
}
|
||||
|
||||
req.end(function (error, res) {
|
||||
if (res == null || res.status !== 200) {
|
||||
reject(error);
|
||||
} else {
|
||||
|
@ -47,9 +65,15 @@ class RestAPI {
|
|||
});
|
||||
}
|
||||
|
||||
put(url, body) {
|
||||
put(url, body, token) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
request.put(url).send(body).end(function (error, res) {
|
||||
var req = request.put(url).send(body);
|
||||
|
||||
if (token != null) {
|
||||
req.set('x-access-token', token);
|
||||
}
|
||||
|
||||
req.end(function (error, res) {
|
||||
if (res == null || res.status !== 200) {
|
||||
reject(error);
|
||||
} else {
|
||||
|
|
|
@ -12,7 +12,7 @@ import React, { Component } from 'react';
|
|||
class Footer extends Component {
|
||||
render() {
|
||||
return (
|
||||
<footer>
|
||||
<footer className="app-footer">
|
||||
Copyright © {new Date().getFullYear()}
|
||||
</footer>
|
||||
);
|
||||
|
|
72
src/components/login-form.js
Normal file
72
src/components/login-form.js
Normal file
|
@ -0,0 +1,72 @@
|
|||
/**
|
||||
* File: login-form.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 15.03.2017
|
||||
* Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC
|
||||
* This file is part of VILLASweb. All Rights Reserved. Proprietary and confidential.
|
||||
* Unauthorized copying of this file, via any medium is strictly prohibited.
|
||||
**********************************************************************************/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { Form, Button, FormGroup, FormControl, ControlLabel, Col } from 'react-bootstrap';
|
||||
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
|
||||
class LoginForm extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
username: '',
|
||||
password: ''
|
||||
}
|
||||
}
|
||||
|
||||
login(event) {
|
||||
// prevent from submitting the form since we send an action
|
||||
event.preventDefault();
|
||||
|
||||
// send login action
|
||||
AppDispatcher.dispatch({
|
||||
type: 'users/login',
|
||||
username: this.state.username,
|
||||
password: this.state.password
|
||||
});
|
||||
}
|
||||
|
||||
handleChange(event) {
|
||||
this.setState({ [event.target.id]: event.target.value });
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Form horizontal>
|
||||
<FormGroup controlId="username">
|
||||
<Col componentClass={ControlLabel} sm={2}>
|
||||
Username
|
||||
</Col>
|
||||
<Col sm={10}>
|
||||
<FormControl type="text" placeholder="Username" onChange={(e) => this.handleChange(e)} />
|
||||
</Col>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup controlId="password">
|
||||
<Col componentClass={ControlLabel} sm={2}>
|
||||
Password
|
||||
</Col>
|
||||
<Col sm={10}>
|
||||
<FormControl type="password" placeholder="Password" onChange={(e) => this.handleChange(e)} />
|
||||
</Col>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<Col smOffset={2} sm={10}>
|
||||
<Button type="submit" onClick={(e) => this.login(e)}>Login</Button>
|
||||
</Col>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default LoginForm;
|
|
@ -21,6 +21,7 @@ class SidebarMenu extends Component {
|
|||
<li><Link to="/projects" activeClassName="active">Projects</Link></li>
|
||||
<li><Link to="/simulations" activeClassName="active">Simulations</Link></li>
|
||||
<li><Link to="/simulators" activeClassName="active">Simulators</Link></li>
|
||||
<li><Link to="/logout">Logout</Link></li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -15,6 +15,7 @@ import HTML5Backend from 'react-dnd-html5-backend';
|
|||
import AppDispatcher from '../app-dispatcher';
|
||||
import SimulationStore from '../stores/simulation-store';
|
||||
import SimulatorStore from '../stores/simulator-store';
|
||||
import UserStore from '../stores/user-store';
|
||||
|
||||
import Header from '../components/header';
|
||||
import Footer from '../components/footer';
|
||||
|
@ -24,17 +25,31 @@ import '../styles/app.css';
|
|||
|
||||
class App extends Component {
|
||||
static getStores() {
|
||||
return [ SimulationStore, SimulatorStore ];
|
||||
return [ SimulationStore, SimulatorStore, UserStore ];
|
||||
}
|
||||
|
||||
static calculateState() {
|
||||
return {
|
||||
simulators: SimulatorStore.getState(),
|
||||
simulations: SimulationStore.getState()
|
||||
simulations: SimulationStore.getState(),
|
||||
currentUser: UserStore.getState().currentUser
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
// if token stored locally, request user
|
||||
const token = localStorage.getItem('token');
|
||||
|
||||
if (token != null && token !== '') {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'users/logged-in',
|
||||
token: token
|
||||
});
|
||||
} else {
|
||||
// transition to login page
|
||||
this.props.router.push('/login');
|
||||
}
|
||||
|
||||
// load all simulators and simulations to fetch simulation data
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulators/start-load'
|
||||
|
|
71
src/containers/login.js
Normal file
71
src/containers/login.js
Normal file
|
@ -0,0 +1,71 @@
|
|||
/**
|
||||
* File: login.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 15.03.2017
|
||||
* Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC
|
||||
* This file is part of VILLASweb. All Rights Reserved. Proprietary and confidential.
|
||||
* Unauthorized copying of this file, via any medium is strictly prohibited.
|
||||
**********************************************************************************/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { Container } from 'flux/utils';
|
||||
import { PageHeader } from 'react-bootstrap';
|
||||
|
||||
import LoginForm from '../components/login-form';
|
||||
import Header from '../components/header';
|
||||
import Footer from '../components/footer';
|
||||
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
import UserStore from '../stores/user-store';
|
||||
|
||||
class Login extends Component {
|
||||
static getStores() {
|
||||
return [ UserStore ];
|
||||
}
|
||||
|
||||
static calculateState() {
|
||||
return {
|
||||
currentUser: UserStore.getState().currentUser,
|
||||
token: UserStore.getState().token
|
||||
};
|
||||
}
|
||||
|
||||
componentWillUpdate(nextProps, nextState) {
|
||||
// if token stored locally, request user
|
||||
const token = localStorage.getItem('token');
|
||||
|
||||
if (token != null && token !== '') {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'users/logged-in',
|
||||
token: token
|
||||
});
|
||||
}
|
||||
|
||||
// check if logged in
|
||||
if (nextState.currentUser != null) {
|
||||
// save login in local storage
|
||||
localStorage.setItem('token', this.state.token);
|
||||
|
||||
// transition to index
|
||||
this.props.router.push('/');
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Header />
|
||||
|
||||
<div className="login-container">
|
||||
<PageHeader>Login</PageHeader>
|
||||
|
||||
<LoginForm />
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Container.create(Login);
|
51
src/containers/logout.js
Normal file
51
src/containers/logout.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
* File: logout.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 15.03.2017
|
||||
* Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC
|
||||
* This file is part of VILLASweb. All Rights Reserved. Proprietary and confidential.
|
||||
* Unauthorized copying of this file, via any medium is strictly prohibited.
|
||||
**********************************************************************************/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { Container } from 'flux/utils';
|
||||
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
import UserStore from '../stores/villas-store';
|
||||
|
||||
class Home extends Component {
|
||||
static getStores() {
|
||||
return [ UserStore ];
|
||||
}
|
||||
|
||||
static calculateState() {
|
||||
return {
|
||||
currentUser: UserStore.getState().currentUser
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'users/logout'
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUpdate(nextProps, nextState) {
|
||||
// check if logged out
|
||||
if (nextState.currentUser == null) {
|
||||
// discard login token
|
||||
localStorage.setItem('token', '');
|
||||
|
||||
// transition to login page
|
||||
this.props.router.push('/login');
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<span>Login out</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Container.create(Home);
|
48
src/data-managers/users-data-manager.js
Normal file
48
src/data-managers/users-data-manager.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* File: users-data-manager.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 15.03.2017
|
||||
* Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC
|
||||
* This file is part of VILLASweb. All Rights Reserved. Proprietary and confidential.
|
||||
* Unauthorized copying of this file, via any medium is strictly prohibited.
|
||||
**********************************************************************************/
|
||||
|
||||
import RestDataManager from './rest-data-manager';
|
||||
import RestAPI from '../api/rest-api';
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
|
||||
class UsersDataManager extends RestDataManager {
|
||||
constructor() {
|
||||
super('user', '/users');
|
||||
}
|
||||
|
||||
login(username, password) {
|
||||
RestAPI.post(this.makeURL('/authenticate'), { username: username, password: password }).then(response => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'users/logged-in',
|
||||
token: response.token
|
||||
});
|
||||
}).catch(error => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'users/login-error',
|
||||
error: error
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getCurrentUser(token) {
|
||||
RestAPI.get(this.makeURL('/users/me'), token).then(response => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'users/current-user',
|
||||
user: response
|
||||
});
|
||||
}).catch(error => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'users/current-user-error',
|
||||
error: error
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new UsersDataManager();
|
|
@ -18,6 +18,8 @@ import Simulators from './containers/simulators';
|
|||
import Visualization from './containers/visualization';
|
||||
import Simulations from './containers/simulations';
|
||||
import Simulation from './containers/simulation';
|
||||
import Login from './containers/login';
|
||||
import Logout from './containers/logout';
|
||||
|
||||
class Root extends Component {
|
||||
render() {
|
||||
|
@ -35,7 +37,11 @@ class Root extends Component {
|
|||
|
||||
<Route path='/simulations' component={Simulations} />
|
||||
<Route path='/simulations/:simulation' component={Simulation} />
|
||||
|
||||
<Route path='/logout' component={Logout} />
|
||||
</Route>
|
||||
|
||||
<Route path='/login' component={Login} />
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
|
|
58
src/stores/user-store.js
Normal file
58
src/stores/user-store.js
Normal file
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
* File: user-store.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 15.03.2017
|
||||
* Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC
|
||||
* This file is part of VILLASweb. All Rights Reserved. Proprietary and confidential.
|
||||
* Unauthorized copying of this file, via any medium is strictly prohibited.
|
||||
**********************************************************************************/
|
||||
|
||||
import { ReduceStore } from 'flux/utils';
|
||||
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
import UsersDataManager from '../data-managers/users-data-manager';
|
||||
|
||||
class UserStore extends ReduceStore {
|
||||
constructor() {
|
||||
super(AppDispatcher);
|
||||
}
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
users: [],
|
||||
currentUser: null,
|
||||
token: null
|
||||
};
|
||||
}
|
||||
|
||||
reduce(state, action) {
|
||||
switch (action.type) {
|
||||
case 'users/login':
|
||||
UsersDataManager.login(action.username, action.password);
|
||||
return state;
|
||||
|
||||
case 'users/logout':
|
||||
// delete user and token
|
||||
return { token: null, currentUser: null };
|
||||
|
||||
case 'users/logged-in':
|
||||
// request logged-in user data
|
||||
UsersDataManager.getCurrentUser(action.token);
|
||||
|
||||
return { token: action.token };
|
||||
|
||||
case 'users/current-user':
|
||||
// save logged-in user
|
||||
return { currentUser: action.user };
|
||||
|
||||
case 'users/login-error':
|
||||
console.log(action);
|
||||
return state;
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new UserStore();
|
|
@ -22,7 +22,7 @@ body {
|
|||
font: 16px 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
.app header {
|
||||
.app-header {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
|
||||
|
@ -32,7 +32,7 @@ body {
|
|||
background-color: #fff;
|
||||
}
|
||||
|
||||
.app header h1 {
|
||||
.app-header h1 {
|
||||
width: 100%;
|
||||
|
||||
margin: 0;
|
||||
|
@ -40,7 +40,7 @@ body {
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
.app footer {
|
||||
.app-footer {
|
||||
width: 100%;
|
||||
|
||||
margin-top: 20px;
|
||||
|
@ -91,6 +91,20 @@ body {
|
|||
list-style: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Login form
|
||||
*/
|
||||
.login-container {
|
||||
width: 500px;
|
||||
|
||||
margin: 30px auto;
|
||||
padding: 15px 20px;
|
||||
|
||||
background-color: #fff;
|
||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
|
||||
0 9px 18px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tables
|
||||
*/
|
||||
|
|
Loading…
Add table
Reference in a new issue