Verified Commit 06d6eb53 authored by Sébastiaan Versteeg's avatar Sébastiaan Versteeg
Browse files

Logout user on invalid token when opening app

parent 2df0d10b
import { import {
url, apiUrl, defaultProfileImage, apiRequest,
tokenSelector, loggedInSelector, apiRequest, ServerError, apiUrl,
defaultProfileImage,
loggedInSelector,
ServerError,
tokenSelector,
url,
} from '../../app/utils/url'; } from '../../app/utils/url';
const fetchPromiseResult = {
status: 200,
json: () => Promise.resolve('responseJson'),
clone: global.fetch,
};
global.fetch = jest.fn().mockReturnValue( global.fetch = jest.fn().mockReturnValue(
Promise.resolve({ status: 200, json: () => 'responseJson' })); Promise.resolve(fetchPromiseResult));
fetchPromiseResult.clone = global.fetch;
jest.mock('react-native-locale-detector', () => 'en'); jest.mock('react-native-locale-detector', () => 'en');
...@@ -63,7 +74,11 @@ describe('url helper', () => { ...@@ -63,7 +74,11 @@ describe('url helper', () => {
it('should throw a server error', () => { it('should throw a server error', () => {
expect.assertions(1); expect.assertions(1);
const response = { status: 404, json: () => 'responseJson' }; const response = {
status: 404,
json: () => Promise.resolve('responseJson'),
clone: () => ({ status: 404 }),
};
global.fetch.mockReturnValue(Promise.resolve(response)); global.fetch.mockReturnValue(Promise.resolve(response));
return apiRequest('route', {}, null) return apiRequest('route', {}, null)
.catch(e => expect(e).toEqual(new ServerError('Invalid status code: 404', response))); .catch(e => expect(e).toEqual(new ServerError('Invalid status code: 404', response)));
...@@ -71,7 +86,11 @@ describe('url helper', () => { ...@@ -71,7 +86,11 @@ describe('url helper', () => {
it('should return an empty response on status 204', () => { it('should return an empty response on status 204', () => {
expect.assertions(1); expect.assertions(1);
const response = { status: 204, json: () => 'responseJson' }; const response = {
status: 204,
json: () => Promise.resolve('responseJson'),
clone: () => ({ status: 204 }),
};
global.fetch.mockReturnValue(Promise.resolve(response)); global.fetch.mockReturnValue(Promise.resolve(response));
return apiRequest('route', {}, null) return apiRequest('route', {}, null)
.then(res => expect(res).toEqual({})); .then(res => expect(res).toEqual({}));
......
export const LOGIN = 'LOGIN_LOGIN'; export const LOGIN = 'LOGIN_LOGIN';
export const SUCCESS = 'LOGIN_SUCCESS'; export const SUCCESS = 'LOGIN_SUCCESS';
export const LOGOUT = 'LOGIN_LOGOUT'; export const LOGOUT = 'LOGIN_LOGOUT';
export const TOKEN_INVALID = 'LOGIN_TOKEN_INVALID';
export const PROFILE = 'LOGIN_PROFILE'; export const PROFILE = 'LOGIN_PROFILE';
export const PROFILE_SUCCESS = 'LOGIN_PROFILE_SUCCESS'; export const PROFILE_SUCCESS = 'LOGIN_PROFILE_SUCCESS';
...@@ -16,6 +17,10 @@ export function logout() { ...@@ -16,6 +17,10 @@ export function logout() {
return { type: LOGOUT }; return { type: LOGOUT };
} }
export function tokenInvalid() {
return { type: TOKEN_INVALID };
}
export function profile(token) { export function profile(token) {
return { type: PROFILE, payload: { token } }; return { type: PROFILE, payload: { token } };
} }
......
...@@ -62,6 +62,7 @@ export default function navigate(state = initialState, action = {}) { ...@@ -62,6 +62,7 @@ export default function navigate(state = initialState, action = {}) {
drawerOpen: action.payload.drawerOpen, drawerOpen: action.payload.drawerOpen,
}; };
} }
case loginActions.TOKEN_INVALID:
case loginActions.LOGOUT: { case loginActions.LOGOUT: {
return initialState; return initialState;
} }
......
...@@ -23,6 +23,7 @@ export default function session(state = initialState, action = {}) { ...@@ -23,6 +23,7 @@ export default function session(state = initialState, action = {}) {
displayName: action.payload.displayName, displayName: action.payload.displayName,
photo: action.payload.photo, photo: action.payload.photo,
}; };
case loginActions.TOKEN_INVALID:
case loginActions.LOGOUT: case loginActions.LOGOUT:
return initialState; return initialState;
default: default:
......
...@@ -52,6 +52,11 @@ const logout = function* logout() { ...@@ -52,6 +52,11 @@ const logout = function* logout() {
Snackbar.show({ title: 'Logout successful' }); Snackbar.show({ title: 'Logout successful' });
}; };
const tokenInvalid = function* tokenInvalid() {
yield call(AsyncStorage.clear);
yield put(pushNotificationsActions.invalidate());
};
const profile = function* profile(action) { const profile = function* profile(action) {
const { token } = action.payload; const { token } = action.payload;
...@@ -81,6 +86,7 @@ const loginSaga = function* loginSaga() { ...@@ -81,6 +86,7 @@ const loginSaga = function* loginSaga() {
yield takeEvery(loginActions.LOGIN, login); yield takeEvery(loginActions.LOGIN, login);
yield takeEvery(loginActions.LOGOUT, logout); yield takeEvery(loginActions.LOGOUT, logout);
yield takeEvery(loginActions.PROFILE, profile); yield takeEvery(loginActions.PROFILE, profile);
yield takeEvery(loginActions.TOKEN_INVALID, tokenInvalid);
}; };
export default loginSaga; export default loginSaga;
...@@ -24,6 +24,9 @@ const welcome = function* welcome() { ...@@ -24,6 +24,9 @@ const welcome = function* welcome() {
const response = yield call(apiRequest, 'events', data, params); const response = yield call(apiRequest, 'events', data, params);
yield put(welcomeActions.success(response.results)); yield put(welcomeActions.success(response.results));
} catch (error) { } catch (error) {
if (error.name === 'TokenInvalidError') {
yield put(loginActions.tokenInvalid());
}
yield put(welcomeActions.failure()); yield put(welcomeActions.failure());
} }
}; };
......
...@@ -15,11 +15,34 @@ export const loggedInSelector = state => state.navigation.loggedIn; ...@@ -15,11 +15,34 @@ export const loggedInSelector = state => state.navigation.loggedIn;
export class ServerError extends Error { export class ServerError extends Error {
constructor(message, response) { constructor(message, response) {
super(message); super(message);
this.name = this.constructor.name; this.name = 'ServerError';
this.response = response; this.response = response;
} }
} }
export class TokenInvalidError extends Error {
constructor(response) {
super('Invalid token');
this.name = 'TokenInvalidError';
this.response = response;
}
}
const detectInvalidToken = (response) => {
const responseCopy = response.clone();
return response.json().then((json) => {
if (response.status === 403) {
const contentLang = response.headers.get('content-language');
if ((contentLang === 'en' && json.detail === 'Invalid token.') ||
(contentLang === 'nl' && json.detail === 'Ongeldige token.')) {
throw new TokenInvalidError(responseCopy);
}
}
return responseCopy;
});
};
export const apiRequest = (route, fetchOpts, params) => { export const apiRequest = (route, fetchOpts, params) => {
const requestOptions = fetchOpts; const requestOptions = fetchOpts;
if (!requestOptions.headers) { if (!requestOptions.headers) {
...@@ -35,6 +58,7 @@ export const apiRequest = (route, fetchOpts, params) => { ...@@ -35,6 +58,7 @@ export const apiRequest = (route, fetchOpts, params) => {
} }
return fetch(`${apiUrl}/${route}/${query}`, requestOptions) return fetch(`${apiUrl}/${route}/${query}`, requestOptions)
.then(detectInvalidToken)
.then((response) => { .then((response) => {
if (response.status >= 400 && response.status <= 500) { if (response.status >= 400 && response.status <= 500) {
throw new ServerError(`Invalid status code: ${response.status}`, response); throw new ServerError(`Invalid status code: ${response.status}`, response);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment