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

Adds Authenticatio/Authorization #35

Merged
merged 8 commits into from
Feb 16, 2020
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
56 changes: 55 additions & 1 deletion app.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ var cookieParser = require('cookie-parser');
var logger = require('morgan');
var cors = require('cors');

// AUTHENTICATION
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var ensureLoggedIn = require('./middlewares/ensure_logged_in');

// ROUTES
var authenticationRouter = require('./routes/authentication');
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var matchesRouter = require('./routes/matchesRouter');
Expand All @@ -24,9 +30,18 @@ app.use(express.json({ limit: '50mb' }));
app.use(cors());
app.use(express.urlencoded({ extended: false, limit: '50mb' }));
app.use(cookieParser());
// app.use(express.static(path.join(__dirname, 'public')));
app.use(require('express-session')({
secret: process.env.APPLICATION_SECRET,
resave: false,
saveUninitialized: false
}));
app.use(passport.initialize());
app.use(passport.session());
app.use(express.static(path.join(__dirname, 'client/build')));

app.use(ensureLoggedIn);

app.use('/', authenticationRouter);
app.use('/', indexRouter);
app.use('/', usersRouter);
app.use('/api', matchesRouter);
Expand Down Expand Up @@ -55,4 +70,43 @@ app.use(function(err, req, res, next) {
res.render('error');
});

// PASSPORT CONFIGURATION
var db = require('./db');
var argon2 = require('argon2');

passport.use(new LocalStrategy(
function(username, password, done) {
db.query('SELECT * FROM users WHERE username = $1 LIMIT 1', [username]).then((result) => {
const user = result.rows[0];

if (!user) {
return done(null, false, 'User not found or credentials not valid');
}

argon2.verify(user.password, password).then((correct) => {
if (correct) {
return done(null, user);
} else {
return done(null, false, 'User not found or credentials not valid');
}
});
})
.catch((err) => {
return done(null, false, 'An unknown error occurred');
});
}
));

passport.serializeUser(function(user, done) {
return done(null, user.user_id);
});

passport.deserializeUser(function(user_id, done) {
db.query('SELECT * FROM users WHERE user_id = $1 LIMIT 1', [user_id]).then((result) => {
return done(null, result.rows[0]);
}).catch((err) => {
return done(err);
});
});

module.exports = app;
120 changes: 80 additions & 40 deletions client/src/App.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import React, { Component } from 'react';
import React, { useContext, Component } from 'react';
import './components/TabNav';
import TabNav from './components/TabNav';
import PitContent from './components/PitContent';
import MatchReportList from './components/MatchReportList';
import AnalystContent from './components/AnalystContent';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import Login from './components/Login';
import Logout from './components/Logout';
import { AuthConsumer, AuthContext, AuthProvider } from './contexts/auth_context';
import { BrowserRouter as Router, Redirect, Route, Switch } from 'react-router-dom';

import 'bootstrap/dist/css/bootstrap.min.css';
//import 'bootstrap/dist/css/bootstrap.css';
import './App.css';
import PitNavigation from './components/PitNavigation';
import MatchContent from './components/MatchContent';
Expand All @@ -26,20 +28,69 @@ window.onunload = event => {
window.scrollTo(0, 0);
};


const ProtectedRoute = ({ component: Component, ...rest }) => {
const authContext = useContext(AuthContext);

return (
<Route { ...rest } render={(props) => (
authContext.isLoggedIn === true
? <Component {...props} />
: <Redirect to={{
pathname: '/login',
state: { from: props.location }
}}/>
)} />
)
};

const AdminRoute = ({ component: Component, ...rest }) => {
const authContext = useContext(AuthContext);

return (
<Route { ...rest } render={(props) => (
authContext.isLoggedIn === true && authContext.user.role === 'admin'
? <Component {...props} />
: <Redirect to={{
pathname: '/login',
state: {
from: props.location,
messages: [
{ type: 'warning', message: 'You must be an admin to access this page' }
]
}
}}/>
)} />
)
}

class App extends Component {
state = {
apiResponse: '',
selectedTab: ''
selectedTab: '',
};

constructor(props) {
super(props);
this.authProvider = React.createRef();
}

componentDidMount() {
this.setState({
selectedTab: localStorage.getItem('selectedTab') || 'match'
});
fetch('/api/isLoggedIn').then((response) => {
if (response.ok) {
response.json().then((user) => {
this.authProvider.current.logInUser(user);
});
} else {
this.authProvider.current.logOutUser();
}
});
}

handleTabSelect = event => {
console.log('event in APP', event);
this.setState({
selectedTab: event
});
Expand All @@ -49,43 +100,32 @@ class App extends Component {

render() {
return (
<div className='App'>
<Router>
<TabNav onClick={this.handleTabSelect} />
<Switch>
<Route path='/pits' exact component={PitNavigation} />
<Route
path='/matches/:competition/:team/:matchNum/'
component={MatchContent}
/>
<Route path='/matches/new' component={MatchContent} />
<Route path='/matches' component={MatchReportList} />
<Route path='/analystHome' component={AnalystContent} />
<Route
path='/pits/:competition/:team'
exact
component={PitContent}
/>
</Switch>
</Router>
</div>
<AuthProvider ref={this.authProvider}>
<div className='App'>
<Router>
<TabNav onClick={this.handleTabSelect} />
<Switch>
<ProtectedRoute path='/pits' exact component={PitNavigation} />
<ProtectedRoute
path='/matches/:competition/:team/:matchNum/'
component={MatchContent}
/>
<AdminRoute path='/matches/new' component={MatchContent} />
<ProtectedRoute path='/matches' component={MatchReportList} />
<ProtectedRoute path='/analystHome' component={AnalystContent} />
<ProtectedRoute
path='/pits/:competition/:team'
exact
component={PitContent}
/>
<Route path='/login' component={Login} />
<Route path='/logout' component={Logout} />
</Switch>
</Router>
</div>
</AuthProvider>
);
}

// render() {
// const App = () => (
// <div>
// <Switch>
// <Route exact path='/' component={TabNav}/>
// </Switch>
// </div>
// )
// return (
// <Switch>
// <App/>
// </Switch>
// )
// }
}

export default App;
3 changes: 3 additions & 0 deletions client/src/Login.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.Login {
text-align: left;
}
113 changes: 113 additions & 0 deletions client/src/components/Login.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import React, { Component } from 'react';

import Container from 'react-bootstrap/Container';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import Form from 'react-bootstrap/Form';
import Button from 'react-bootstrap/Button';
import Alert from 'react-bootstrap/Alert';
import { AuthContext } from '../contexts/auth_context';
import { Redirect } from 'react-router-dom';

import '../Login.css';

class Login extends Component {
static contextType = AuthContext;

state = {
username: '',
password: '',
messages: [],
redirectToReferrer: false
};

constructor(props) {
super(props);
if (props.location && props.location.state && Array.isArray(props.location.state.messages) && props.location.state.messages.length > 0) {
this.state.messages = props.location.state.messages;
}
}

handleUsernameChange = (event) => {
this.setState({ username: event.target.value })
}

handlePasswordChange = (event) => {
this.setState({ password: event.target.value })
}

handleSubmit = (event) => {
event.preventDefault();
fetch('/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ username: this.state.username, password: this.state.password })
}).then((response) => {
if (response.ok) {
response.json().then((user) => {
this.context.logInUser(user);
this.setState({
messages: [],
redirectToReferrer: true
});
});
} else {
this.setState({
messages: [
{ type: 'danger', message: 'Failed to login. Please try again.' }
]
});
}
});
}

render() {
const { from } = this.props.location.state || { from: { pathname: '/' } }
const { redirectToReferrer } = this.state;

if (redirectToReferrer === true) {
return <Redirect to={from} />
}

return (
<Container className='Login'>
<Row>
<Col></Col>
<Col lg={6}>
<h1>Login</h1>
{this.state.messages.map(({ type, message }, index) => (<Alert key={index} variant={type}>{message}</Alert>))}
<Form onSubmit={this.handleSubmit}>
<Form.Group>
<Form.Label>Username</Form.Label>
<Form.Control
type="text"
placeholder="Enter Username"
value={this.state.username }
onChange={this.handleUsernameChange}
/>
</Form.Group>

<Form.Group controlId="formBasicPassword">
<Form.Label>Password</Form.Label>
<Form.Control
type="password"
placeholder="Enter Password"
value={this.state.password}
onChange={this.handlePasswordChange}
/>
</Form.Group>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
</Col>
<Col></Col>
</Row>
</Container>
)
}
}

export default Login;
24 changes: 24 additions & 0 deletions client/src/components/Logout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React, { Component } from 'react';
import { AuthContext } from '../contexts/auth_context';
import { Redirect } from 'react-router-dom';

class Logout extends Component {
static contextType = AuthContext;

componentDidMount() {
fetch('/logout').then((response) => {
this.context.logOutUser();
});
}

render() {
if (this.context.isLoggedIn === false) {
return (
<Redirect to='/login' />
)
}
return null;
}
}

export default Logout;
Loading