Skip to content

Commit

Permalink
Closes #276: Renew authetication token when expired (#345)
Browse files Browse the repository at this point in the history
  • Loading branch information
tobiangerstein authored Aug 19, 2019
1 parent 6e3bab6 commit 2ba35fa
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 73 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React from 'react'
import { connect } from 'react-redux'
import { authenticationSelectors } from '../../redux/ducks/authentication'
import { authenticationSelectors, authenticationActions } from '../../redux/ducks/authentication'
import Router, { withRouter } from 'next/router';
import { linkPrefix } from '../../lib/configuration';
import { RENEW_TOKEN_TIME_INTERVAL, MIN_TOKEN_EXPIRATION_TIME } from '../../data/constants';

/**
* Handles the routing based on the current authentication (user is logged in/out) status.
Expand All @@ -11,12 +12,18 @@ class AuthenticationRouter extends React.Component {

componentDidMount = () => {
this.checkRoute();
this.intervalId = setInterval(this.triggerRenewTokenInterval, RENEW_TOKEN_TIME_INTERVAL);

}

componentDidUpdate = () => {
this.checkRoute();
}

componentWillUnmount = () => {
clearInterval(this.intervalId);
}

checkRoute = () => {
const { pathname } = this.props.router;

Expand All @@ -31,6 +38,15 @@ class AuthenticationRouter extends React.Component {
}
}

triggerRenewTokenInterval = () => {
if (this.props.isAuthenticated) {
const isSoonExpired = (this.props.tokenExpirationDate * 1000 - Date.now()) < MIN_TOKEN_EXPIRATION_TIME;
if (isSoonExpired) {
this.props.renewToken();
}
}
}

render() {
return (
<>
Expand All @@ -41,9 +57,16 @@ class AuthenticationRouter extends React.Component {
}

function mapStateToProps(state) {
const { token } = state.authentication;

return {
isAuthenticated: authenticationSelectors.isAuthenticated(state)
isAuthenticated: authenticationSelectors.isAuthenticated(state),
tokenExpirationDate: authenticationSelectors.getTokenExpirationDate(state),
}
}

export default withRouter(connect(mapStateToProps, null)(AuthenticationRouter));
const mapDispatchToProps = {
renewToken: authenticationActions.renewToken,
}

export default withRouter(connect(mapStateToProps, mapDispatchToProps)(AuthenticationRouter));
Original file line number Diff line number Diff line change
@@ -1,4 +1,20 @@
/**
* Various constants used by the application.
*/
export const BASE_PAGE_TITLE = "inspectIT Ocelot Configuration Server";

/**
* Default <title> content
*/
export const BASE_PAGE_TITLE = "inspectIT Ocelot Configuration Server";

/**
* Interval in which will be checked, whether the token will be expiring.
*/
export const RENEW_TOKEN_TIME_INTERVAL = 60000;

/**
* Minimum expiration time
* If the expiration time of the current token is shorter than the MIN_TOKEN_EXPIRATION_IIME, the token will be renewed.
*/
export const MIN_TOKEN_EXPIRATION_TIME = 300000;

Original file line number Diff line number Diff line change
@@ -1,80 +1,65 @@
import * as types from "./types";
import {axiosPlain} from '../../../lib/axios-api';

import {configurationActions} from '../configuration';
import { axiosPlain } from "../../../lib/axios-api";
import axiosBearer from "../../../lib/axios-api";
import { configurationActions } from "../configuration";

/**
* Fetches an access token for the given credentials.
*
* @param {string} username
* @param {string} password
*
* @param {string} username
* @param {string} password
*/
export const fetchToken = (username, password) => {
return dispatch => {
dispatch(fetchTokenStarted());
return (dispatch) => {
dispatch({ type: types.FETCH_TOKEN_STARTED });

axiosPlain
.get("/account/token", {
auth: {
username: username,
password: password
}
})
.then(res => {
const token = res.data;
dispatch(fetchTokenSuccess(token, username));
})
.catch(err => {
let message;
const { response } = err;
if (response && response.status == 401) {
message = "The given credentials are not valid.";
} else {
message = err.message;
}
dispatch(fetchTokenFailure(message));
});
};
axiosPlain
.get("/account/token", {
auth: {
username: username,
password: password
}
})
.then(res => {
const token = res.data;
dispatch({ type: types.FETCH_TOKEN_SUCCESS, payload: { token, username } });
})
.catch(err => {
let message;
const { response } = err;
if (response && response.status == 401) {
message = "The given credentials are not valid.";
} else {
message = err.message;
}
dispatch({ type: types.FETCH_TOKEN_FAILURE, payload: { error: message } });
});
};
};

/**
* Is dispatched when the fetching of the access token has been started.
* Renews the access token with the existing token.
*
*/
export const fetchTokenStarted = () => ({
type: types.FETCH_TOKEN_STARTED
});

/**
* Is dispatched if the fetching of the access token was not successful.
*
* @param {*} error
*/
export const fetchTokenFailure = (error) => ({
type: types.FETCH_TOKEN_FAILURE,
payload: {
error
}
});

/**
* Is dispatched when the fetching of the access token was successful.
*
* @param {string} token
*/
export const fetchTokenSuccess = (token, username) => ({
type: types.FETCH_TOKEN_SUCCESS,
payload: {
token,
username
}
});
export const renewToken = () => {
return (dispatch) => {
axiosBearer
.get("/account/token")
.then(res => {
const token = res.data;
dispatch({ type: types.RENEW_TOKEN_SUCCESS, payload: { token } });
})
.catch(err => {
});
};
};

/**
* Logout of the current user.
*/
export const logout = () => {
return dispatch => {
dispatch({type: types.LOGOUT});
dispatch(configurationActions.resetState());
};
};
return dispatch => {
dispatch({ type: types.LOGOUT });
dispatch(configurationActions.resetState());
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as authenticationSelectors from "./selectors";

export {
authenticationActions,
authenticationSelectors
authenticationSelectors,
};

export default reducer;
export default reducer;
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import * as types from "./types";
import { createReducer } from "../../utils";
import { isTokenExpired } from '../../../lib/jwt-utils';
import {authentication as initialState} from '../initial-states';
import { authentication as initialState } from '../initial-states';

const authorizationReducer = createReducer(initialState)({
[types.FETCH_TOKEN_STARTED]: (state, action) => {
return {
...state,
loading: true,
unauthorized: false
};
},
[types.FETCH_TOKEN_FAILURE]: (state, action) => {
Expand Down Expand Up @@ -39,6 +38,14 @@ const authorizationReducer = createReducer(initialState)({
username: null
};
},
[types.RENEW_TOKEN_SUCCESS]: (state, action) => {
const { token } = action.payload;
return {
...state,
error: null,
token
};
},
// SPECIAL REDUCER - dispatched by redux-persist to rehydrate store
["persist/REHYDRATE"]: (state, action) => {
if (!action.payload || !action.payload.authentication) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
export const FETCH_TOKEN_STARTED = 'authorization/FETCH_TOKEN_STARTED';
export const FETCH_TOKEN_FAILURE = 'authorization/FETCH_TOKEN_FAILURE';
export const FETCH_TOKEN_SUCCESS = 'authorization/FETCH_TOKEN_SUCCESS';
export const LOGOUT = 'authorization/LOGOUT';

export const RENEW_TOKEN_SUCCESS = 'authorization/RENEW_TOKEN_SUCCESS';

export const LOGOUT = 'authorization/LOGOUT';

0 comments on commit 2ba35fa

Please sign in to comment.