WIP login page and authentication code

This commit is contained in:
Rick Watson
2019-05-14 22:47:04 +01:00
parent f93804c240
commit c74c287e21
8 changed files with 337 additions and 27 deletions

View File

@ -0,0 +1,34 @@
import * as React from 'react';
import {
Redirect, Route
} from "react-router-dom";
import { withAuthenticationContext } from './Context.js';
import * as Authentication from './Authentication';
import { withNotifier } from '../components/SnackbarNotification';
export class AuthenticatedRoute extends React.Component {
render() {
const { raiseNotification, authenticationContext, component: Component, ...rest } = this.props;
const { location } = this.props;
const renderComponent = (props) => {
if (authenticationContext.jwt) {
return (
<Component {...props} />
);
}
Authentication.storeLoginRedirect(location);
raiseNotification("Please log in to continue.");
return (
<Redirect to='/' />
);
}
return (
<Route {...rest} render={renderComponent} />
);
}
}
export default withNotifier(withAuthenticationContext(AuthenticatedRoute));

View File

@ -0,0 +1,56 @@
import history from '../history';
export const ACCESS_TOKEN = 'access_token';
export const LOGIN_PATHNAME = 'loginPathname';
export const LOGIN_SEARCH = 'loginSearch';
export function storeLoginRedirect(location) {
if (location) {
localStorage.setItem(LOGIN_PATHNAME, location.pathname);
localStorage.setItem(LOGIN_SEARCH, location.search);
}
}
export function clearLoginRedirect() {
localStorage.removeItem(LOGIN_PATHNAME);
localStorage.removeItem(LOGIN_SEARCH);
}
export function fetchLoginRedirect() {
const loginPathname = localStorage.getItem(LOGIN_PATHNAME);
const loginSearch = localStorage.getItem(LOGIN_SEARCH);
clearLoginRedirect();
return {
pathname: loginPathname || "/",
search: (loginPathname && loginSearch) || undefined
};
}
/**
* Wraps the normal fetch routene with one with provides the access token if present.
*/
export function secureFetch(url, params) {
if (localStorage.getItem(ACCESS_TOKEN)) {
params = params || {};
params.headers = params.headers || new Headers();
params.headers.Authorization = 'Bearer ' + localStorage.getItem(ACCESS_TOKEN)
}
return fetch(url, params);
}
/**
* Wraps the normal fetch routene which redirects on 401 response.
*/
export function redirectingSecureFetch(url, params) {
return new Promise(function (resolve, reject) {
secureFetch(url, params).then(response => {
if (response.status === 401) {
history.go("/");
} else {
resolve(response);
}
}).catch(error => {
reject(error);
});
});
}

View File

@ -0,0 +1,91 @@
import * as React from 'react';
import history from '../history'
import { withNotifier } from '../components/SnackbarNotification';
import { ACCESS_TOKEN } from './Authentication';
import { AuthenticationContext } from './Context';
import jwtDecode from 'jwt-decode';
class AuthenticationWrapper extends React.Component {
constructor(props) {
super(props);
this.refresh = this.refresh.bind(this);
this.signIn = this.signIn.bind(this);
this.signOut = this.signOut.bind(this);
this.state = {
context: {
refresh: this.refresh,
signIn: this.signIn,
signOut: this.signOut
},
initialized: false
};
}
componentDidMount() {
this.refresh();
}
render() {
return (
<React.Fragment>
{this.state.initialized ? this.renderContent() : this.renderContentLoading()}
</React.Fragment>
);
}
renderContent() {
return (
<AuthenticationContext.Provider value={this.state.context}>
{this.props.children}
</AuthenticationContext.Provider>
);
}
renderContentLoading() {
return (
<div>THIS IS WHERE THE LOADING MESSAGE GOES</div>
);
}
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('/');
}
} else {
this.setState({ initialized: true });
}
}
signIn(accessToken) {
try {
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('/');
}
}
signOut() {
localStorage.removeItem(ACCESS_TOKEN);
this.setState({
context: {
...this.state.context,
me: undefined
}
});
this.props.raiseNotification("You have signed out.");
history.push('/');
}
}
export default withNotifier(AuthenticationWrapper)

View File

@ -0,0 +1,15 @@
import * as React from "react";
export const AuthenticationContext = React.createContext(
{}
);
export function withAuthenticationContext(Component) {
return function AuthenticationContextComponent(props) {
return (
<AuthenticationContext.Consumer>
{authenticationContext => <Component {...props} authenticationContext={authenticationContext} />}
</AuthenticationContext.Consumer>
);
};
}