//
// SPDX-License-Identifier: AGPL-3.0
-import { authReducer, AuthState } from "./auth-reducer";
-import { AuthAction, initAuth } from "./auth-action";
-import {
- API_TOKEN_KEY,
- USER_EMAIL_KEY,
- USER_FIRST_NAME_KEY,
- USER_LAST_NAME_KEY,
- USER_OWNER_UUID_KEY,
- USER_UUID_KEY,
- USER_IS_ADMIN,
- USER_IS_ACTIVE,
- USER_USERNAME,
- USER_PREFS
-} from "~/services/auth-service/auth-service";
+import { getNewExtraToken, initAuth } from "./auth-action";
+import { API_TOKEN_KEY } from "services/auth-service/auth-service";
import 'jest-localstorage-mock';
-import { createServices } from "~/services/services";
+import { ServiceRepository, createServices } from "services/services";
import { configureStore, RootStore } from "../store";
-import createBrowserHistory from "history/createBrowserHistory";
-import { Config, mockConfig } from '~/common/config';
-import { ApiActions } from "~/services/api/api-actions";
-import { ACCOUNT_LINK_STATUS_KEY} from '~/services/link-account-service/link-account-service';
+import { createBrowserHistory } from "history";
+import { mockConfig } from 'common/config';
+import { ApiActions } from "services/api/api-actions";
+import { ACCOUNT_LINK_STATUS_KEY } from 'services/link-account-service/link-account-service';
+import Axios, { AxiosInstance } from "axios";
+import MockAdapter from "axios-mock-adapter";
+import { ImportMock } from 'ts-mock-imports';
+import * as servicesModule from "services/services";
+import * as authActionSessionModule from "./auth-action-session";
+import { SessionStatus } from "models/session";
+import { getRemoteHostConfig } from "./auth-action-session";
describe('auth-actions', () => {
- let reducer: (state: AuthState | undefined, action: AuthAction) => any;
+ let axiosInst: AxiosInstance;
+ let axiosMock: MockAdapter;
+
let store: RootStore;
+ let services: ServiceRepository;
+ const config: any = {};
const actions: ApiActions = {
progressFn: (id: string, working: boolean) => { },
errorFn: (id: string, message: string) => { }
};
+ let importMocks: any[];
beforeEach(() => {
- store = configureStore(createBrowserHistory(), createServices(mockConfig({}), actions));
+ axiosInst = Axios.create({ headers: {} });
+ axiosMock = new MockAdapter(axiosInst);
+ services = createServices(mockConfig({}), actions, axiosInst);
+ store = configureStore(createBrowserHistory(), services, config);
localStorage.clear();
- reducer = authReducer(createServices(mockConfig({}), actions));
+ importMocks = [];
});
- it('should initialise state with user and api token from local storage', () => {
+ afterEach(() => {
+ importMocks.map(m => m.restore());
+ });
- // Only test the case when a link account operation is not being cancelled
+ it('creates an extra token', async () => {
+ axiosMock
+ .onGet("/users/current")
+ .reply(200, {
+ email: "test@test.com",
+ first_name: "John",
+ last_name: "Doe",
+ uuid: "zzzzz-tpzed-abcefg",
+ owner_uuid: "ownerUuid",
+ is_admin: false,
+ is_active: true,
+ username: "jdoe",
+ prefs: {}
+ })
+ .onGet("/api_client_authorizations/current")
+ .reply(200, {
+ expires_at: "2140-01-01T00:00:00.000Z",
+ api_token: 'extra token',
+ })
+ .onPost("/api_client_authorizations")
+ .replyOnce(200, {
+ uuid: 'zzzzz-gj3su-xxxxxxxxxx',
+ apiToken: 'extra token',
+ })
+ .onPost("/api_client_authorizations")
+ .reply(200, {
+ uuid: 'zzzzz-gj3su-xxxxxxxxxx',
+ apiToken: 'extra additional token',
+ });
+
+ importMocks.push(ImportMock.mockFunction(servicesModule, 'createServices', services));
sessionStorage.setItem(ACCOUNT_LINK_STATUS_KEY, "0");
localStorage.setItem(API_TOKEN_KEY, "token");
- localStorage.setItem(USER_EMAIL_KEY, "test@test.com");
- localStorage.setItem(USER_FIRST_NAME_KEY, "John");
- localStorage.setItem(USER_LAST_NAME_KEY, "Doe");
- localStorage.setItem(USER_UUID_KEY, "zzzzz-tpzed-abcefg");
- localStorage.setItem(USER_USERNAME, "username");
- localStorage.setItem(USER_PREFS, JSON.stringify({}));
- localStorage.setItem(USER_OWNER_UUID_KEY, "ownerUuid");
- localStorage.setItem(USER_IS_ADMIN, JSON.stringify(false));
- localStorage.setItem(USER_IS_ACTIVE, JSON.stringify(true));
const config: any = {
- rootUrl: "https://zzzzz.arvadosapi.com",
+ rootUrl: "https://zzzzz.example.com",
uuidPrefix: "zzzzz",
- remoteHosts: { xc59z: "xc59z.arvadosapi.com" },
+ remoteHosts: { },
+ apiRevision: 12345678,
+ clusterConfig: {
+ Login: { LoginCluster: "" },
+ },
};
- store.dispatch(initAuth(config));
+ // Set up auth, confirm that no extra token was requested
+ await store.dispatch(initAuth(config))
+ expect(store.getState().auth.apiToken).toBeDefined();
+ expect(store.getState().auth.extraApiToken).toBeUndefined();
+
+ // Ask for an extra token
+ await store.dispatch(getNewExtraToken());
+ expect(store.getState().auth.apiToken).toBeDefined();
+ expect(store.getState().auth.extraApiToken).toBeDefined();
+ const extraToken = store.getState().auth.extraApiToken;
+
+ // Ask for a cached extra token
+ await store.dispatch(getNewExtraToken(true));
+ expect(store.getState().auth.extraApiToken).toBe(extraToken);
- expect(store.getState().auth).toEqual({
- apiToken: "token",
- sshKeys: [],
- homeCluster: "zzzzz",
- localCluster: "zzzzz",
- remoteHostsConfig: {},
- remoteHosts: {
- zzzzz: "zzzzz.arvadosapi.com",
- xc59z: "xc59z.arvadosapi.com"
+ // Ask for a new extra token, should make a second api request
+ await store.dispatch(getNewExtraToken(false));
+ expect(store.getState().auth.extraApiToken).toBeDefined();
+ expect(store.getState().auth.extraApiToken).not.toBe(extraToken);
+ });
+
+ it('requests remote token data to login cluster', async () => {
+ const localClusterTokenExpiration = "2020-01-01T00:00:00.000Z";
+ const loginClusterTokenExpiration = "2140-01-01T00:00:00.000Z";
+ axiosMock
+ .onGet("/users/current")
+ .reply(200, {
+ email: "test@test.com",
+ first_name: "John",
+ last_name: "Doe",
+ uuid: "zzzz1-tpzed-abcefg",
+ owner_uuid: "ownerUuid",
+ is_admin: false,
+ is_active: true,
+ username: "jdoe",
+ prefs: {}
+ })
+ .onGet("https://zzzz1.example.com/discovery/v1/apis/arvados/v1/rest")
+ .reply(200, {
+ baseUrl: "https://zzzz1.example.com/arvados/v1",
+ keepWebServiceUrl: "",
+ keepWebInlineServiceUrl: "",
+ remoteHosts: {},
+ rootUrl: "https://zzzz1.example.com",
+ uuidPrefix: "zzzz1",
+ websocketUrl: "",
+ workbenchUrl: "",
+ workbench2Url: "",
+ revision: 12345678
+ })
+ // Local cluster -- cached token
+ .onGet("https://zzzzz.example.com/arvados/v1/api_client_authorizations/current")
+ .reply(200, {
+ uuid: 'zzzz1-gj3su-aaaaaaa',
+ expires_at: localClusterTokenExpiration,
+ api_token: 'tokensecret',
+ })
+ // Login cluster -- authoritative token copy
+ .onGet("https://zzzz1.example.com/arvados/v1/api_client_authorizations/current")
+ .reply(200, {
+ uuid: 'zzzz1-gj3su-aaaaaaa',
+ expires_at: loginClusterTokenExpiration,
+ api_token: 'tokensecret',
+ });
+
+ const config: any = {
+ rootUrl: "https://zzzzz.example.com",
+ uuidPrefix: "zzzzz",
+ remoteHosts: { zzzz1: "zzzz1.example.com" },
+ apiRevision: 12345678,
+ clusterConfig: {
+ Login: { LoginCluster: "zzzz1" },
},
- remoteHostsConfig: {},
- sessions: [{
- "active": true,
- "baseUrl": undefined,
- "clusterId": "zzzzz",
- "email": "test@test.com",
- "loggedIn": true,
- "remoteHost": "https://zzzzz.arvadosapi.com",
- "status": 2,
- "token": "token",
- "username": "John Doe"
- }, {
- "active": false,
- "baseUrl": "",
- "clusterId": "xc59z",
- "email": "",
- "loggedIn": false,
- "remoteHost": "xc59z.arvadosapi.com",
- "status": 0,
- "token": "",
- "username": ""
- }],
- user: {
+ };
+
+ const remoteHostConfig = await getRemoteHostConfig(config.remoteHosts.zzzz1, axiosInst);
+ expect(remoteHostConfig).not.toBeFalsy;
+ services = createServices(remoteHostConfig!, actions, axiosInst);
+
+ importMocks.push(ImportMock.mockFunction(authActionSessionModule, 'getRemoteHostConfig', remoteHostConfig));
+ importMocks.push(ImportMock.mockFunction(servicesModule, 'createServices', services));
+
+ sessionStorage.setItem(ACCOUNT_LINK_STATUS_KEY, "0");
+ localStorage.setItem(API_TOKEN_KEY, "v2/zzzz1-gj3su-aaaaaaa/tokensecret");
+
+ await store.dispatch(initAuth(config));
+ expect(store.getState().auth.apiToken).toBeDefined();
+ expect(localClusterTokenExpiration).not.toBe(loginClusterTokenExpiration);
+ expect(store.getState().auth.apiTokenExpiration).toEqual(new Date(loginClusterTokenExpiration));
+ });
+
+ it('should initialise state with user and api token from local storage', (done) => {
+ axiosMock
+ .onGet("/users/current")
+ .reply(200, {
email: "test@test.com",
- firstName: "John",
- lastName: "Doe",
+ first_name: "John",
+ last_name: "Doe",
uuid: "zzzzz-tpzed-abcefg",
- ownerUuid: "ownerUuid",
- username: "username",
- prefs: {},
- isAdmin: false,
- isActive: true
+ owner_uuid: "ownerUuid",
+ is_admin: false,
+ is_active: true,
+ username: "jdoe",
+ prefs: {}
+ })
+ .onGet("/api_client_authorizations/current")
+ .reply(200, {
+ expires_at: "2140-01-01T00:00:00.000Z"
+ })
+ .onGet("https://xc59z.example.com/discovery/v1/apis/arvados/v1/rest")
+ .reply(200, {
+ baseUrl: "https://xc59z.example.com/arvados/v1",
+ keepWebServiceUrl: "",
+ keepWebInlineServiceUrl: "",
+ remoteHosts: {},
+ rootUrl: "https://xc59z.example.com",
+ uuidPrefix: "xc59z",
+ websocketUrl: "",
+ workbenchUrl: "",
+ workbench2Url: "",
+ revision: 12345678
+ });
+
+ importMocks.push(ImportMock.mockFunction(servicesModule, 'createServices', services));
+
+ // Only test the case when a link account operation is not being cancelled
+ sessionStorage.setItem(ACCOUNT_LINK_STATUS_KEY, "0");
+ localStorage.setItem(API_TOKEN_KEY, "token");
+
+ const config: any = {
+ rootUrl: "https://zzzzz.example.com",
+ uuidPrefix: "zzzzz",
+ remoteHosts: { xc59z: "xc59z.example.com" },
+ apiRevision: 12345678,
+ clusterConfig: {
+ Login: { LoginCluster: "" },
+ },
+ };
+
+ store.dispatch(initAuth(config));
+
+ store.subscribe(() => {
+ const auth = store.getState().auth;
+ if (auth.apiToken === "token" &&
+ auth.sessions.length === 2 &&
+ auth.sessions[0].status === SessionStatus.VALIDATED &&
+ auth.sessions[1].status === SessionStatus.VALIDATED
+ ) {
+ try {
+ expect(auth).toEqual({
+ apiToken: "token",
+ apiTokenExpiration: new Date("2140-01-01T00:00:00.000Z"),
+ apiTokenLocation: "localStorage",
+ config: {
+ apiRevision: 12345678,
+ clusterConfig: {
+ Login: {
+ LoginCluster: "",
+ },
+ },
+ remoteHosts: {
+ "xc59z": "xc59z.example.com",
+ },
+ rootUrl: "https://zzzzz.example.com",
+ uuidPrefix: "zzzzz",
+ },
+ sshKeys: [],
+ extraApiToken: undefined,
+ extraApiTokenExpiration: undefined,
+ homeCluster: "zzzzz",
+ localCluster: "zzzzz",
+ loginCluster: undefined,
+ remoteHostsConfig: {
+ "zzzzz": {
+ "apiRevision": 12345678,
+ "clusterConfig": {
+ "Login": {
+ "LoginCluster": "",
+ },
+ },
+ "remoteHosts": {
+ "xc59z": "xc59z.example.com",
+ },
+ "rootUrl": "https://zzzzz.example.com",
+ "uuidPrefix": "zzzzz",
+ },
+ "xc59z": mockConfig({
+ apiRevision: 12345678,
+ baseUrl: "https://xc59z.example.com/arvados/v1",
+ rootUrl: "https://xc59z.example.com",
+ uuidPrefix: "xc59z"
+ })
+ },
+ remoteHosts: {
+ zzzzz: "zzzzz.example.com",
+ xc59z: "xc59z.example.com"
+ },
+ sessions: [{
+ "active": true,
+ "baseUrl": undefined,
+ "clusterId": "zzzzz",
+ "email": "test@test.com",
+ "loggedIn": true,
+ "remoteHost": "https://zzzzz.example.com",
+ "status": 2,
+ "token": "token",
+ "name": "John Doe",
+ "apiRevision": 12345678,
+ "uuid": "zzzzz-tpzed-abcefg",
+ "userIsActive": true
+ }, {
+ "active": false,
+ "baseUrl": "",
+ "clusterId": "xc59z",
+ "email": "",
+ "loggedIn": false,
+ "remoteHost": "xc59z.example.com",
+ "status": 2,
+ "token": "",
+ "name": "",
+ "uuid": "",
+ "apiRevision": 0,
+ }],
+ user: {
+ email: "test@test.com",
+ firstName: "John",
+ lastName: "Doe",
+ uuid: "zzzzz-tpzed-abcefg",
+ ownerUuid: "ownerUuid",
+ username: "jdoe",
+ prefs: { profile: {} },
+ isAdmin: false,
+ isActive: true
+ }
+ });
+ done();
+ } catch (e) {
+ fail(e);
+ }
}
});
});
+
// TODO: Add remaining action tests
/*
- it('should fire external url to login', () => {
- const initialState = undefined;
- window.location.assign = jest.fn();
- reducer(initialState, authActions.LOGIN());
- expect(window.location.assign).toBeCalledWith(
- `/login?return_to=${window.location.protocol}//${window.location.host}/token`
- );
- });
+ it('should fire external url to login', () => {
+ const initialState = undefined;
+ window.location.assign = jest.fn();
+ reducer(initialState, authActions.LOGIN());
+ expect(window.location.assign).toBeCalledWith(
+ `/login?return_to=${window.location.protocol}//${window.location.host}/token`
+ );
+ });
- it('should fire external url to logout', () => {
- const initialState = undefined;
- window.location.assign = jest.fn();
- reducer(initialState, authActions.LOGOUT());
- expect(window.location.assign).toBeCalledWith(
- `/logout?return_to=${location.protocol}//${location.host}`
- );
- });
- */
+ it('should fire external url to logout', () => {
+ const initialState = undefined;
+ window.location.assign = jest.fn();
+ reducer(initialState, authActions.LOGOUT());
+ expect(window.location.assign).toBeCalledWith(
+ `/logout?return_to=${location.protocol}//${location.host}`
+ );
+ });
+ */
});