Skip to content

Commit

Permalink
Reload OIDC token when needed (#995)
Browse files Browse the repository at this point in the history
  • Loading branch information
andresmgot authored Mar 26, 2019
1 parent 3961c90 commit cf23ed5
Show file tree
Hide file tree
Showing 29 changed files with 296 additions and 136 deletions.
23 changes: 23 additions & 0 deletions dashboard/src/actions/auth.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ let store: any;

beforeEach(() => {
const state: IAuthState = {
sessionExpired: false,
authenticated: false,
authenticating: false,
oidcAuthenticated: false,
Expand Down Expand Up @@ -105,10 +106,32 @@ describe("OIDC authentication", () => {
payload: { authenticated: true, oidc: true },
type: getType(actions.auth.setAuthenticated),
},
{
payload: { sessionExpired: false },
type: getType(actions.auth.setSessionExpired),
},
];

return store.dispatch(actions.auth.tryToAuthenticateWithOIDC()).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});

it("expires the session and logs out ", () => {
Auth.usingOIDCToken = jest.fn(() => true);
const expectedActions = [
{
payload: { sessionExpired: true },
type: getType(actions.auth.setSessionExpired),
},
{
payload: { authenticated: false, oidc: false },
type: getType(actions.auth.setAuthenticated),
},
];

return store.dispatch(actions.auth.expireSession()).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});
});
18 changes: 17 additions & 1 deletion dashboard/src/actions/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ export const authenticationError = createAction("AUTHENTICATION_ERROR", resolve
return (errorMsg: string) => resolve(errorMsg);
});

const allActions = [setAuthenticated, authenticating, authenticationError];
export const setSessionExpired = createAction("SET_AUTHENTICATION_SESSION_EXPIRED", resolve => {
return (sessionExpired: boolean) => resolve({ sessionExpired });
});

const allActions = [setAuthenticated, authenticating, authenticationError, setSessionExpired];

export type AuthAction = ActionType<typeof allActions[number]>;

Expand All @@ -28,6 +32,9 @@ export function authenticate(
await Auth.validateToken(token);
Auth.setAuthToken(token, oidc);
dispatch(setAuthenticated(true, oidc));
if (oidc) {
dispatch(setSessionExpired(false));
}
} catch (e) {
dispatch(authenticationError(e.toString()));
}
Expand All @@ -41,6 +48,15 @@ export function logout(): ThunkAction<Promise<void>, IStoreState, null, AuthActi
};
}

export function expireSession(): ThunkAction<Promise<void>, IStoreState, null, AuthAction> {
return async dispatch => {
if (Auth.usingOIDCToken()) {
dispatch(setSessionExpired(true));
}
dispatch(logout());
};
}

export function tryToAuthenticateWithOIDC(): ThunkAction<
Promise<void>,
IStoreState,
Expand Down
53 changes: 21 additions & 32 deletions dashboard/src/actions/charts.test.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,28 @@
import axios from "axios";
import configureMockStore from "redux-mock-store";
import thunk from "redux-thunk";
import { getType } from "typesafe-actions";
import { axios } from "../shared/AxiosInstance";

import actions from ".";
import { NotFoundError } from "../shared/types";

const mockStore = configureMockStore([thunk]);
jest.mock("axios");
const axiosGetMock = axios.get as jest.Mock;

let axiosGetMock = jest.fn();
let store: any;
let fetchMock: jest.Mock;
let response: any;

beforeEach(() => {
store = mockStore();
fetchMock = jest.fn(() => {
axiosGetMock.mockImplementation(() => {
return {
ok: true,
json: () => {
return { data: response };
status: 200,
data: {
data: response,
},
};
});
window.fetch = fetchMock;
axiosGetMock.mockImplementation(() => {
return { data: response };
});
axios.get = axiosGetMock;
});

afterEach(() => {
Expand All @@ -43,24 +38,22 @@ describe("fetchCharts", () => {
];
await store.dispatch(actions.charts.fetchCharts("foo"));
expect(store.getActions()).toEqual(expectedActions);
expect(fetchMock.mock.calls[0][0]).toBe("api/chartsvc/v1/charts/foo");
expect(axiosGetMock.mock.calls[0][0]).toBe("api/chartsvc/v1/charts/foo");
});

it("returns a 404 error", async () => {
const expectedActions = [
{ type: getType(actions.charts.requestCharts) },
{ type: getType(actions.charts.errorChart), payload: new NotFoundError("not found") },
];
fetchMock = jest.fn(() => {
axiosGetMock = jest.fn(() => {
return {
ok: false,
status: 404,
json: () => {
return { data: "not found" };
},
data: "not found",
};
});
window.fetch = fetchMock;
axios.get = axiosGetMock;
await store.dispatch(actions.charts.fetchCharts("foo"));
expect(store.getActions()).toEqual(expectedActions);
});
Expand All @@ -70,16 +63,14 @@ describe("fetchCharts", () => {
{ type: getType(actions.charts.requestCharts) },
{ type: getType(actions.charts.errorChart), payload: new Error("something went wrong") },
];
fetchMock = jest.fn(() => {
axiosGetMock = jest.fn(() => {
return {
ok: false,
status: 500,
json: () => {
return { data: "something went wrong" };
},
data: "something went wrong",
};
});
window.fetch = fetchMock;
axios.get = axiosGetMock;
await store.dispatch(actions.charts.fetchCharts("foo"));
expect(store.getActions()).toEqual(expectedActions);
});
Expand All @@ -94,7 +85,7 @@ describe("fetchChartVersions", () => {
];
await store.dispatch(actions.charts.fetchChartVersions("foo"));
expect(store.getActions()).toEqual(expectedActions);
expect(fetchMock.mock.calls[0][0]).toBe("api/chartsvc/v1/charts/foo/versions");
expect(axiosGetMock.mock.calls[0][0]).toBe("api/chartsvc/v1/charts/foo/versions");
});
});

Expand All @@ -107,7 +98,7 @@ describe("getChartVersion", () => {
];
await store.dispatch(actions.charts.getChartVersion("foo", "1.0.0"));
expect(store.getActions()).toEqual(expectedActions);
expect(fetchMock.mock.calls[0][0]).toBe("api/chartsvc/v1/charts/foo/versions/1.0.0");
expect(axiosGetMock.mock.calls[0][0]).toBe("api/chartsvc/v1/charts/foo/versions/1.0.0");
});
});

Expand All @@ -121,7 +112,7 @@ describe("fetchChartVersionsAndSelectVersion", () => {
];
await store.dispatch(actions.charts.fetchChartVersionsAndSelectVersion("foo", "1.0.0"));
expect(store.getActions()).toEqual(expectedActions);
expect(fetchMock.mock.calls[0][0]).toBe("api/chartsvc/v1/charts/foo/versions");
expect(axiosGetMock.mock.calls[0][0]).toBe("api/chartsvc/v1/charts/foo/versions");
});

it("returns a not found error", async () => {
Expand All @@ -130,18 +121,16 @@ describe("fetchChartVersionsAndSelectVersion", () => {
{ type: getType(actions.charts.requestCharts) },
{ type: getType(actions.charts.errorChart), payload: new NotFoundError("not found") },
];
fetchMock = jest.fn(() => {
axiosGetMock = jest.fn(() => {
return {
ok: false,
status: 404,
json: () => {
return { data: "not found" };
},
data: "not found",
};
});
window.fetch = fetchMock;
axios.get = axiosGetMock;
await store.dispatch(actions.charts.fetchChartVersionsAndSelectVersion("foo", "1.0.0"));
expect(store.getActions()).toEqual(expectedActions);
expect(fetchMock.mock.calls[0][0]).toBe("api/chartsvc/v1/charts/foo/versions");
expect(axiosGetMock.mock.calls[0][0]).toBe("api/chartsvc/v1/charts/foo/versions");
});
});
11 changes: 6 additions & 5 deletions dashboard/src/actions/charts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Dispatch } from "redux";
import { ThunkAction } from "redux-thunk";
import { ActionType, createAction } from "typesafe-actions";

import { axios } from "../shared/AxiosInstance";
import Chart from "../shared/Chart";
import { IChart, IChartVersion, IStoreState, NotFoundError } from "../shared/types";
import * as url from "../shared/url";
Expand Down Expand Up @@ -54,17 +55,17 @@ export type ChartsAction = ActionType<typeof allActions[number]>;

async function httpGet(dispatch: Dispatch, targetURL: string): Promise<any> {
try {
const response = await fetch(targetURL);
const json = await response.json();
if (!response.ok) {
const error = json.data || response.statusText;
const response = await axios.get(targetURL);
// If non-2XX response (not ok)
if (response.status < 200 || response.status > 299) {
const error = response.data || response.statusText;
if (response.status === 404) {
dispatch(errorChart(new NotFoundError(error)));
} else {
dispatch(errorChart(new Error(error)));
}
} else {
return json.data;
return response.data.data;
}
} catch (e) {
dispatch(errorChart(e));
Expand Down
12 changes: 6 additions & 6 deletions dashboard/src/actions/repos.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import thunk from "redux-thunk";
import { getType } from "typesafe-actions";
import actions from ".";
import { AppRepository } from "../shared/AppRepository";
import { axios } from "../shared/AxiosInstance";
import Secret from "../shared/Secret";
import { IAppRepository, NotFoundError } from "../shared/types";

Expand All @@ -12,6 +13,7 @@ const mockStore = configureMockStore([thunk]);

let store: any;
const appRepo = { spec: { resyncRequests: 10000 } };
axios.get = jest.fn();

beforeEach(() => {
store = mockStore({ config: { namespace: "my-namespace" } });
Expand All @@ -29,6 +31,8 @@ beforeEach(() => {
Secret.create = jest.fn();
});

afterEach(jest.resetAllMocks);

// Regular action creators
interface ITestCase {
name: string;
Expand Down Expand Up @@ -347,10 +351,6 @@ describe("installRepo", () => {
});

describe("checkChart", () => {
window.fetch = jest.fn().mockImplementationOnce(() => {
return { ok: true };
});

it("dispatches requestRepo and receivedRepo if no error", async () => {
const expectedActions = [
{
Expand All @@ -367,8 +367,8 @@ describe("checkChart", () => {
});

it("dispatches requestRepo and errorChart if error fetching", async () => {
window.fetch = jest.fn().mockImplementationOnce(() => {
return { ok: false };
axios.get = jest.fn(() => {
throw new Error();
});

const expectedActions = [
Expand Down
7 changes: 4 additions & 3 deletions dashboard/src/actions/repos.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ThunkAction } from "redux-thunk";
import { ActionType, createAction } from "typesafe-actions";
import { AppRepository } from "../shared/AppRepository";
import { axios } from "../shared/AxiosInstance";
import Secret from "../shared/Secret";
import * as url from "../shared/url";
import { errorChart } from "./charts";
Expand Down Expand Up @@ -193,10 +194,10 @@ export function checkChart(
} = getState();
dispatch(requestRepo());
const appRepository = await AppRepository.get(repo, namespace);
const res = await fetch(url.api.charts.listVersions(`${repo}/${chartName}`));
if (res.ok) {
try {
await axios.get(url.api.charts.listVersions(`${repo}/${chartName}`));
dispatch(receiveRepo(appRepository));
} else {
} catch (e) {
dispatch(
errorChart(new NotFoundError(`Chart ${chartName} not found in the repository ${repo}.`)),
);
Expand Down
11 changes: 11 additions & 0 deletions dashboard/src/components/PrivateRoute/PrivateRoute.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class MockComponent extends React.Component {}
it("redirects to the /login route if not authenticated", () => {
const wrapper = shallow(
<PrivateRoute
sessionExpired={false}
authenticated={false}
path="/test"
component={MockComponent}
Expand All @@ -44,6 +45,7 @@ it("redirects to the /login route if not authenticated", () => {
it("renders the given component when authenticated", () => {
const wrapper = shallow(
<PrivateRoute
sessionExpired={false}
authenticated={true}
path="/test"
component={MockComponent}
Expand All @@ -54,3 +56,12 @@ it("renders the given component when authenticated", () => {
const wrapper2 = shallow(<RenderMethod {...emptyRouteComponentProps} />);
expect(wrapper2.find(MockComponent).exists()).toBe(true);
});

it("renders modal to reload the page if the session is expired", () => {
const wrapper = shallow(
<PrivateRoute sessionExpired={true} authenticated={false} {...emptyRouteComponentProps} />,
);
const renderization: JSX.Element = wrapper.prop("render")();
expect(renderization.type.toString()).toContain("Modal");
expect(renderization.props.isOpen).toBe(true);
});
Loading

0 comments on commit cf23ed5

Please sign in to comment.