"redux-thunk": "2.3.0",
"reselect": "4.0.0",
"shell-quote": "1.6.1",
+ "sinon": "7.3",
+ "ts-mock-imports": "1.2.6",
"tslint-etc": "1.6.0",
"unionize": "2.1.2",
"uuid": "3.3.2",
--- /dev/null
+import { RootState } from '~/store/store';
+
+export const getUserUuid = (state: RootState) => {
+ const user = state.auth.user;
+ if (user) {
+ return user.uuid;
+ } else {
+ return undefined;
+ }
+};
import { collectionAdminActionSet } from '~/views-components/context-menu/action-sets/collection-admin-action-set';
import { processResourceAdminActionSet } from '~/views-components/context-menu/action-sets/process-resource-admin-action-set';
import { projectAdminActionSet } from '~/views-components/context-menu/action-sets/project-admin-action-set';
+import { snackbarActions, SnackbarKind } from "~/store/snackbar/snackbar-actions";
console.log(`Starting arvados [${getBuildInfo()}]`);
store.dispatch(progressIndicatorActions.TOGGLE_WORKING({ id, working }));
},
errorFn: (id, error) => {
- // console.error("Backend error:", error);
- // store.dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Backend error", kind: SnackbarKind.ERROR }));
+ console.error("Backend error:", error);
+ if (error.errors) {
+ store.dispatch(snackbarActions.OPEN_SNACKBAR({
+ message: `${error.errors[0]}`,
+ kind: SnackbarKind.ERROR,
+ hideDuration: 8000
+ }));
+ } else {
+ store.dispatch(snackbarActions.OPEN_SNACKBAR({
+ message: `${error.message}`,
+ kind: SnackbarKind.ERROR,
+ hideDuration: 8000
+ }));
+ }
}
});
const store = configureStore(history, services);
const addChild = (parentId: string, childId: string) => <T>(tree: Tree<T>): Tree<T> => {
+ if (childId === "") {
+ return tree;
+ }
const node = getNode(parentId)(tree);
if (node) {
const children = node.children.some(id => id === childId)
return user ? `${user.firstName} ${user.lastName}` : "";
};
-export interface UserResource extends Resource {
+export interface UserResource extends Resource, User {
kind: ResourceKind.USER;
- email: string;
- username: string;
- firstName: string;
- lastName: string;
- isAdmin: boolean;
- prefs: UserPrefs;
defaultOwnerUuid: string;
- isActive: boolean;
writableBy: string[];
}
//
// SPDX-License-Identifier: AGPL-3.0
-import { getUserFullname, User, UserPrefs, UserResource } from '~/models/user';
+import { getUserFullname, User, UserPrefs } from '~/models/user';
import { AxiosInstance } from "axios";
import { ApiActions } from "~/services/api/api-actions";
import * as uuid from "uuid/v4";
public saveApiToken(token: string) {
localStorage.setItem(API_TOKEN_KEY, token);
- localStorage.setItem(HOME_CLUSTER, token.split('/')[1].substr(0, 5));
+ const sp = token.split('/');
+ if (sp.length == 3) {
+ localStorage.setItem(HOME_CLUSTER, sp[1].substr(0, 5));
+ }
}
public removeApiToken() {
return localStorage.getItem(HOME_CLUSTER) || undefined;
}
- public getUuid() {
- return localStorage.getItem(USER_UUID_KEY) || undefined;
- }
-
- public getOwnerUuid() {
- return localStorage.getItem(USER_OWNER_UUID_KEY) || undefined;
- }
-
- public getIsAdmin(): boolean {
- return localStorage.getItem(USER_IS_ADMIN) === 'true';
- }
-
- public getIsActive(): boolean {
- return localStorage.getItem(USER_IS_ACTIVE) === 'true';
- }
-
- public getUser(): User | undefined {
- const email = localStorage.getItem(USER_EMAIL_KEY);
- const firstName = localStorage.getItem(USER_FIRST_NAME_KEY);
- const lastName = localStorage.getItem(USER_LAST_NAME_KEY);
- const uuid = this.getUuid();
- const ownerUuid = this.getOwnerUuid();
- const isAdmin = this.getIsAdmin();
- const isActive = this.getIsActive();
- const username = localStorage.getItem(USER_USERNAME);
- const prefs = JSON.parse(localStorage.getItem(USER_PREFS) || '{"profile": {}}');
-
- return email && firstName && lastName && uuid && ownerUuid && username && prefs
- ? { email, firstName, lastName, uuid, ownerUuid, isAdmin, isActive, username, prefs }
- : undefined;
- }
-
- public saveUser(user: User | UserResource) {
- localStorage.setItem(USER_EMAIL_KEY, user.email);
- localStorage.setItem(USER_FIRST_NAME_KEY, user.firstName);
- localStorage.setItem(USER_LAST_NAME_KEY, user.lastName);
- localStorage.setItem(USER_UUID_KEY, user.uuid);
- localStorage.setItem(USER_OWNER_UUID_KEY, user.ownerUuid);
- localStorage.setItem(USER_IS_ADMIN, JSON.stringify(user.isAdmin));
- localStorage.setItem(USER_IS_ACTIVE, JSON.stringify(user.isActive));
- localStorage.setItem(USER_USERNAME, user.username);
- localStorage.setItem(USER_PREFS, JSON.stringify(user.prefs));
- }
-
public removeUser() {
localStorage.removeItem(USER_EMAIL_KEY);
localStorage.removeItem(USER_FIRST_NAME_KEY);
});
}
- public getRootUuid() {
- const uuid = this.getOwnerUuid();
- const uuidParts = uuid ? uuid.split('-') : [];
- return uuidParts.length > 1 ? `${uuidParts[0]}-${uuidParts[1]}` : undefined;
- }
-
public getSessions(): Session[] {
try {
const sessions = JSON.parse(localStorage.getItem("sessions") || '');
// SPDX-License-Identifier: AGPL-3.0
import Axios from "axios";
+import { AxiosInstance } from "axios";
import { ApiClientAuthorizationService } from '~/services/api-client-authorization-service/api-client-authorization-service';
import { AuthService } from "./auth-service/auth-service";
import { GroupsService } from "./groups-service/groups-service";
export type ServiceRepository = ReturnType<typeof createServices>;
-export const createServices = (config: Config, actions: ApiActions) => {
- const apiClient = Axios.create();
+export function setAuthorizationHeader(services: ServiceRepository, token: string) {
+ services.apiClient.defaults.headers.common = {
+ Authorization: `Bearer ${token}`
+ };
+ services.webdavClient.defaults.headers = {
+ Authorization: `Bearer ${token}`
+ };
+}
+
+export function removeAuthorizationHeader(services: ServiceRepository) {
+ delete services.apiClient.defaults.headers.common;
+ delete services.webdavClient.defaults.headers.common;
+}
+
+export const createServices = (config: Config, actions: ApiActions, useApiClient?: AxiosInstance) => {
+ // Need to give empty 'headers' object or it will create an
+ // instance with a reference to the global default headers object,
+ // which is very bad because that means setAuthorizationHeader
+ // would update the global default instead of the instance default.
+ const apiClient = useApiClient || Axios.create({ headers: {} });
apiClient.defaults.baseURL = config.baseUrl;
const webdavClient = new WebDAV();
constructor(serverApi: AxiosInstance, actions: ApiActions) {
super(serverApi, "users", actions);
}
+
+ activate(uuid: string) {
+ return CommonResourceService.defaultResponse(
+ this.serverApi
+ .post(this.resourceType + `${uuid}/activate`),
+ this.actions
+ );
+ }
+
+ unsetup(uuid: string) {
+ return CommonResourceService.defaultResponse(
+ this.serverApi
+ .post(this.resourceType + uuid + '/unsetup'),
+ this.actions
+ );
+ }
}
import { Dispatch } from "redux";
import { setBreadcrumbs } from "~/store/breadcrumbs/breadcrumbs-actions";
import { RootState } from "~/store/store";
-import { ServiceRepository } from "~/services/services";
+import { ServiceRepository, createServices, setAuthorizationHeader } from "~/services/services";
import Axios from "axios";
import { getUserFullname, User } from "~/models/user";
import { authActions } from "~/store/auth/auth-action";
import { normalizeURLPath } from "~/common/url";
import { Session, SessionStatus } from "~/models/session";
import { progressIndicatorActions } from "~/store/progress-indicator/progress-indicator-actions";
-import { AuthService, UserDetailsResponse } from "~/services/auth-service/auth-service";
+import { AuthService } from "~/services/auth-service/auth-service";
import { snackbarActions, SnackbarKind } from "~/store/snackbar/snackbar-actions";
import * as jsSHA from "jssha";
return null;
};
-const getUserDetails = async (baseUrl: string, token: string): Promise<UserDetailsResponse> => {
- const resp = await Axios.get<UserDetailsResponse>(`${baseUrl}/users/current`, {
- headers: {
- Authorization: `OAuth2 ${token}`
- }
- });
- return resp.data;
-};
-
const invalidV2Token = "Must be a v2 token";
export const getSaltedToken = (clusterId: string, token: string) => {
Promise<{ user: User; token: string }> => {
const saltedToken = getSaltedToken(config.uuidPrefix, useToken);
- const user = await getUserDetails(config.baseUrl, saltedToken);
+
+ const svc = createServices(config, { progressFn: () => { }, errorFn: () => { } });
+ setAuthorizationHeader(svc, saltedToken);
+
+ const user = await svc.authService.getUserDetails();
return {
- user: {
- firstName: user.first_name,
- lastName: user.last_name,
- uuid: user.uuid,
- ownerUuid: user.owner_uuid,
- email: user.email,
- isAdmin: user.is_admin,
- isActive: user.is_active,
- username: user.username,
- prefs: user.prefs
- },
+ user,
token: saltedToken,
};
};
import { dialogActions } from "~/store/dialog/dialog-actions";
import { Dispatch } from "redux";
import { RootState } from "~/store/store";
+import { getUserUuid } from "~/common/getuser";
import { ServiceRepository } from "~/services/services";
-import {snackbarActions, SnackbarKind} from "~/store/snackbar/snackbar-actions";
+import { snackbarActions, SnackbarKind } from "~/store/snackbar/snackbar-actions";
import { FormErrors, reset, startSubmit, stopSubmit } from "redux-form";
import { KeyType } from "~/models/ssh-key";
import {
getAuthorizedKeysServiceError
} from "~/services/authorized-keys-service/authorized-keys-service";
import { setBreadcrumbs } from "~/store/breadcrumbs/breadcrumbs-actions";
-import {
- authActions,
-} from "~/store/auth/auth-action";
+import { authActions } from "~/store/auth/auth-action";
export const SSH_KEY_CREATE_FORM_NAME = 'sshKeyCreateFormName';
export const SSH_KEY_PUBLIC_KEY_DIALOG = 'sshKeyPublicKeyDialog';
export const createSshKey = (data: SshKeyCreateFormDialogData) =>
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
- const userUuid = getState().auth.user!.uuid;
+ const userUuid = getUserUuid(getState());
const { name, publicKey } = data;
dispatch(startSubmit(SSH_KEY_CREATE_FORM_NAME));
try {
export const loadSshKeysPanel = () =>
async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
try {
- dispatch(setBreadcrumbs([{ label: 'SSH Keys'}]));
+ dispatch(setBreadcrumbs([{ label: 'SSH Keys' }]));
const response = await services.authorizedKeysService.list();
dispatch(authActions.SET_SSH_KEYS(response.items));
} catch (e) {
return;
}
};
-
//
// 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 { 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 { 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 from "axios";
+import MockAdapter from "axios-mock-adapter";
+import { ImportMock } from 'ts-mock-imports';
+import * as servicesModule from "~/services/services";
describe('auth-actions', () => {
- let reducer: (state: AuthState | undefined, action: AuthAction) => any;
+ const axiosInst = Axios.create({ headers: {} });
+ const axiosMock = new MockAdapter(axiosInst);
+
let store: RootStore;
+ let services: ServiceRepository;
const actions: ApiActions = {
progressFn: (id: string, working: boolean) => { },
errorFn: (id: string, message: string) => { }
};
+ let importMocks: any[];
beforeEach(() => {
- store = configureStore(createBrowserHistory(), createServices(mockConfig({}), actions));
+ axiosMock.reset();
+ services = createServices(mockConfig({}), actions, axiosInst);
+ store = configureStore(createBrowserHistory(), services);
localStorage.clear();
- reducer = authReducer(createServices(mockConfig({}), actions));
+ importMocks = [];
+ });
+
+ afterEach(() => {
+ importMocks.map(m => m.restore());
});
- it('should initialise state with user and api token from local storage', () => {
+ it('should initialise state with user and api token from local storage', (done) => {
+
+ 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: {}
+ });
+
+ 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");
- 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",
store.dispatch(initAuth(config));
- expect(store.getState().auth).toEqual({
- apiToken: "token",
- sshKeys: [],
- homeCluster: "zzzzz",
- localCluster: "zzzzz",
- loginCluster: undefined,
- remoteHostsConfig: {
- "zzzzz": {
- "remoteHosts": {
- "xc59z": "xc59z.arvadosapi.com",
- },
- "rootUrl": "https://zzzzz.arvadosapi.com",
- "uuidPrefix": "zzzzz",
- },
- },
- remoteHosts: {
- zzzzz: "zzzzz.arvadosapi.com",
- xc59z: "xc59z.arvadosapi.com"
- },
- sessions: [{
- "active": true,
- "baseUrl": undefined,
- "clusterId": "zzzzz",
- "email": "test@test.com",
- "loggedIn": true,
- "remoteHost": "https://zzzzz.arvadosapi.com",
- "status": 2,
- "token": "token",
- "name": "John Doe"
- "uuid": "zzzzz-tpzed-abcefg",
- }, {
- "active": false,
- "baseUrl": "",
- "clusterId": "xc59z",
- "email": "",
- "loggedIn": false,
- "remoteHost": "xc59z.arvadosapi.com",
- "status": 1,
- "token": "",
- "name": "",
- "uuid": "",
- }],
- user: {
- email: "test@test.com",
- firstName: "John",
- lastName: "Doe",
- uuid: "zzzzz-tpzed-abcefg",
- ownerUuid: "ownerUuid",
- username: "username",
- prefs: {},
- isAdmin: false,
- isActive: true
+ store.subscribe(() => {
+ const auth = store.getState().auth;
+ if (auth.apiToken === "token" &&
+ auth.sessions.length === 2 &&
+ auth.sessions[0].status === 2 &&
+ auth.sessions[1].status === 2
+ ) {
+ try {
+ expect(auth).toEqual({
+ apiToken: "token",
+ sshKeys: [],
+ homeCluster: "zzzzz",
+ localCluster: "zzzzz",
+ loginCluster: undefined,
+ remoteHostsConfig: {
+ "zzzzz": {
+ "remoteHosts": {
+ "xc59z": "xc59z.arvadosapi.com",
+ },
+ "rootUrl": "https://zzzzz.arvadosapi.com",
+ "uuidPrefix": "zzzzz",
+ },
+ },
+ remoteHosts: {
+ zzzzz: "zzzzz.arvadosapi.com",
+ xc59z: "xc59z.arvadosapi.com"
+ },
+ sessions: [{
+ "active": true,
+ "baseUrl": undefined,
+ "clusterId": "zzzzz",
+ "email": "test@test.com",
+ "loggedIn": true,
+ "remoteHost": "https://zzzzz.arvadosapi.com",
+ "status": 2,
+ "token": "token",
+ "name": "John Doe"
+ "uuid": "zzzzz-tpzed-abcefg",
+ }, {
+ "active": false,
+ "baseUrl": "",
+ "clusterId": "xc59z",
+ "email": "",
+ "loggedIn": false,
+ "remoteHost": "xc59z.arvadosapi.com",
+ "status": 2,
+ "token": "",
+ "name": "",
+ "uuid": "",
+ }],
+ 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) {
+ console.log(e);
+ }
}
});
});
+
// TODO: Add remaining action tests
/*
it('should fire external url to login', () => {
import { ofType, unionize, UnionOf } from '~/common/unionize';
import { Dispatch } from "redux";
-import { AxiosInstance } from "axios";
import { RootState } from "../store";
import { ServiceRepository } from "~/services/services";
import { SshKeyResource } from '~/models/ssh-key';
-import { User, UserResource } from "~/models/user";
+import { User } from "~/models/user";
import { Session } from "~/models/session";
import { Config } from '~/common/config';
-import { initSessions } from "~/store/auth/auth-action-session";
-import { cancelLinking } from '~/store/link-account-panel/link-account-panel-actions';
import { matchTokenRoute, matchFedTokenRoute } from '~/routes/routes';
-import { AxiosError } from "axios";
+import { createServices, setAuthorizationHeader } from "~/services/services";
+import { cancelLinking } from '~/store/link-account-panel/link-account-panel-actions';
+import { progressIndicatorActions } from "~/store/progress-indicator/progress-indicator-actions";
+import { WORKBENCH_LOADING_SCREEN } from '~/store/workbench/workbench-actions';
export const authActions = unionize({
- SAVE_API_TOKEN: ofType<string>(),
- SAVE_USER: ofType<UserResource>(),
LOGIN: {},
- LOGOUT: {},
+ LOGOUT: ofType<{ deleteLinkData: boolean }>(),
CONFIG: ofType<{ config: Config }>(),
INIT: ofType<{ user: User, token: string }>(),
USER_DETAILS_REQUEST: {},
REMOTE_CLUSTER_CONFIG: ofType<{ config: Config }>(),
});
-export function setAuthorizationHeader(services: ServiceRepository, token: string) {
- services.apiClient.defaults.headers.common = {
- Authorization: `OAuth2 ${token}`
- };
- services.webdavClient.defaults.headers = {
- Authorization: `OAuth2 ${token}`
- };
-}
-
-function removeAuthorizationHeader(client: AxiosInstance) {
- delete client.defaults.headers.common.Authorization;
-}
-
export const initAuth = (config: Config) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
// Cancel any link account ops in progress unless the user has
// just logged in or there has been a successful link operation
};
const init = (config: Config) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
- const user = services.authService.getUser();
const token = services.authService.getApiToken();
let homeCluster = services.authService.getHomeCluster();
- if (token) {
- setAuthorizationHeader(services, token);
- }
if (homeCluster && !config.remoteHosts[homeCluster]) {
homeCluster = undefined;
}
dispatch(authActions.CONFIG({ config }));
dispatch(authActions.SET_HOME_CLUSTER(config.loginCluster || homeCluster || config.uuidPrefix));
- document.title = `Arvados Workbench (${config.uuidPrefix})`;
- if (token && user) {
- dispatch(authActions.INIT({ user, token }));
- dispatch<any>(initSessions(services.authService, config, user));
- dispatch<any>(getUserDetails()).then((user: User) => {
- dispatch(authActions.INIT({ user, token }));
- }).catch((err: AxiosError) => {
- if (err.response) {
- // Bad token
- if (err.response.status === 401) {
- dispatch<any>(logout());
- }
- }
+
+ if (token && token !== "undefined") {
+ dispatch(progressIndicatorActions.START_WORKING(WORKBENCH_LOADING_SCREEN));
+ dispatch<any>(saveApiToken(token)).then(() => {
+ dispatch(progressIndicatorActions.STOP_WORKING(WORKBENCH_LOADING_SCREEN));
+ }).catch(() => {
+ dispatch(progressIndicatorActions.STOP_WORKING(WORKBENCH_LOADING_SCREEN));
});
}
};
-export const saveApiToken = (token: string) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
- services.authService.saveApiToken(token);
- setAuthorizationHeader(services, token);
- dispatch(authActions.SAVE_API_TOKEN(token));
+export const getConfig = (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Config => {
+ const state = getState().auth;
+ return state.remoteHostsConfig[state.localCluster];
};
-export const saveUser = (user: UserResource) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
- services.authService.saveUser(user);
- dispatch(authActions.SAVE_USER(user));
+export const saveApiToken = (token: string) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise<any> => {
+ const config = dispatch<any>(getConfig);
+ const svc = createServices(config, { progressFn: () => { }, errorFn: () => { } });
+ setAuthorizationHeader(svc, token);
+ return svc.authService.getUserDetails().then((user: User) => {
+ dispatch(authActions.INIT({ user, token }));
+ });
};
export const login = (uuidPrefix: string, homeCluster: string, loginCluster: string,
};
export const logout = (deleteLinkData: boolean = false) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
- if (deleteLinkData) {
- services.linkAccountService.removeAccountToLink();
- }
- services.authService.removeApiToken();
- services.authService.removeUser();
- removeAuthorizationHeader(services.apiClient);
- services.authService.logout();
- dispatch(authActions.LOGOUT());
-};
-
-export const getUserDetails = () => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise<User> => {
- dispatch(authActions.USER_DETAILS_REQUEST());
- return services.authService.getUserDetails().then(user => {
- services.authService.saveUser(user);
- dispatch(authActions.USER_DETAILS_SUCCESS(user));
- return user;
- });
+ dispatch(authActions.LOGOUT({ deleteLinkData }));
};
export type AuthAction = UnionOf<typeof authActions>;
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { Middleware } from "redux";
+import { authActions, } from "./auth-action";
+import { ServiceRepository, setAuthorizationHeader, removeAuthorizationHeader } from "~/services/services";
+import { initSessions } from "~/store/auth/auth-action-session";
+import { User } from "~/models/user";
+import { RootState } from '~/store/store';
+import { progressIndicatorActions } from "~/store/progress-indicator/progress-indicator-actions";
+import { WORKBENCH_LOADING_SCREEN } from '~/store/workbench/workbench-actions';
+
+export const authMiddleware = (services: ServiceRepository): Middleware => store => next => action => {
+ authActions.match(action, {
+ INIT: ({ user, token }) => {
+ next(action);
+ const state: RootState = store.getState();
+
+ if (state.auth.apiToken) {
+ services.authService.saveApiToken(state.auth.apiToken);
+ setAuthorizationHeader(services, state.auth.apiToken);
+ } else {
+ services.authService.removeApiToken();
+ removeAuthorizationHeader(services);
+ }
+
+ store.dispatch<any>(initSessions(services.authService, state.auth.remoteHostsConfig[state.auth.localCluster], user));
+ if (!user.isActive) {
+ store.dispatch(progressIndicatorActions.START_WORKING(WORKBENCH_LOADING_SCREEN));
+ services.userService.activate(user.uuid).then((user: User) => {
+ store.dispatch(authActions.INIT({ user, token }));
+ store.dispatch(progressIndicatorActions.STOP_WORKING(WORKBENCH_LOADING_SCREEN));
+ }).catch(() => {
+ store.dispatch(progressIndicatorActions.STOP_WORKING(WORKBENCH_LOADING_SCREEN));
+ });
+ }
+ },
+ CONFIG: ({ config }) => {
+ document.title = `Arvados Workbench (${config.uuidPrefix})`;
+ next(action);
+ },
+ LOGOUT: ({ deleteLinkData }) => {
+ next(action);
+ if (deleteLinkData) {
+ services.linkAccountService.removeAccountToLink();
+ }
+ services.authService.removeApiToken();
+ services.authService.removeUser();
+ removeAuthorizationHeader(services);
+ services.authService.logout();
+ },
+ default: () => next(action)
+ });
+};
});
});
- it('should save api token', () => {
- const initialState = undefined;
-
- const state = reducer(initialState, authActions.SAVE_API_TOKEN("token"));
- expect(state).toEqual({
- apiToken: "token",
- user: undefined,
- sshKeys: [],
- sessions: [],
- homeCluster: "",
- localCluster: "",
- loginCluster: "",
- remoteHosts: {},
- remoteHostsConfig: {}
- });
- });
-
it('should set user details on success fetch', () => {
const initialState = undefined;
apiToken: undefined,
sshKeys: [],
sessions: [],
- homeCluster: "",
+ homeCluster: "uuid",
localCluster: "",
loginCluster: "",
remoteHosts: {},
// SPDX-License-Identifier: AGPL-3.0
import { authActions, AuthAction } from "./auth-action";
-import { User, UserResource } from "~/models/user";
+import { User } from "~/models/user";
import { ServiceRepository } from "~/services/services";
import { SshKeyResource } from '~/models/ssh-key';
import { Session } from "~/models/session";
export const authReducer = (services: ServiceRepository) => (state = initialState, action: AuthAction) => {
return authActions.match(action, {
- SAVE_API_TOKEN: (token: string) => {
- return { ...state, apiToken: token };
- },
- SAVE_USER: (user: UserResource) => {
- return { ...state, user };
- },
CONFIG: ({ config }) => {
return {
...state,
return { ...state, apiToken: undefined };
},
USER_DETAILS_SUCCESS: (user: User) => {
- return { ...state, user };
+ return { ...state, user, homeCluster: user.uuid.substr(0, 5) };
},
SET_SSH_KEYS: (sshKeys: SshKeyResource[]) => {
return { ...state, sshKeys };
import { Dispatch } from 'redux';
import { RootState } from '~/store/store';
+import { getUserUuid } from "~/common/getuser";
import { Breadcrumb } from '~/components/breadcrumbs/breadcrumbs';
import { getResource } from '~/store/resources/resources';
import { TreePicker } from '../tree-picker/tree-picker';
const path = getState().router.location!.pathname;
const currentUuid = path.split('/')[2];
const uuidKind = extractUuidKind(currentUuid);
-
+
if (uuidKind === ResourceKind.COLLECTION) {
const collectionItem = await services.collectionService.get(currentUuid);
dispatch(setBreadcrumbs(breadcrumbs, collectionItem));
export const setProjectBreadcrumbs = (uuid: string) =>
(dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
const ancestors = getSidePanelTreeNodeAncestorsIds(uuid)(getState().treePicker);
- const rootUuid = services.authService.getUuid();
+ const rootUuid = getUserUuid(getState());
if (uuid === rootUuid || ancestors.find(uuid => uuid === rootUuid)) {
dispatch(setSidePanelBreadcrumbs(uuid));
} else {
import { MiddlewareAPI, Dispatch } from 'redux';
import { DataExplorerMiddlewareService } from '~/store/data-explorer/data-explorer-middleware-service';
import { RootState } from '~/store/store';
+import { getUserUuid } from "~/common/getuser";
import { snackbarActions, SnackbarKind } from '~/store/snackbar/snackbar-actions';
import { getDataExplorer } from '~/store/data-explorer/data-explorer-reducer';
import { resourcesActions } from '~/store/resources/resources-actions';
}
try {
api.dispatch(progressIndicatorActions.START_WORKING(this.getId()));
- const userUuid = api.getState().auth.user!.uuid;
+ const userUuid = getUserUuid(api.getState());
const pathname = api.getState().router.location!.pathname;
const contentAddress = pathname.split('/')[2];
const response = await this.services.collectionService.list({
.addIn('uuid', groupUuids)
.getFilters()
});
- responseUsers.items.map(it=>{
- api.dispatch<any>(ownerNameActions.SET_OWNER_NAME({name: it.uuid === userUuid ? 'User: Me' : `User: ${it.firstName} ${it.lastName}`, uuid: it.uuid}));
+ responseUsers.items.map(it => {
+ api.dispatch<any>(ownerNameActions.SET_OWNER_NAME({ name: it.uuid === userUuid ? 'User: Me' : `User: ${it.firstName} ${it.lastName}`, uuid: it.uuid }));
});
- responseGroups.items.map(it=>{
- api.dispatch<any>(ownerNameActions.SET_OWNER_NAME({name: `Project: ${it.name}`, uuid: it.uuid}));
+ responseGroups.items.map(it => {
+ api.dispatch<any>(ownerNameActions.SET_OWNER_NAME({ name: `Project: ${it.name}`, uuid: it.uuid }));
});
api.dispatch<any>(setBreadcrumbs([{ label: 'Projects', uuid: userUuid }]));
api.dispatch<any>(updateFavorites(response.items.map(item => item.uuid)));
snackbarActions.OPEN_SNACKBAR({
message: 'Could not fetch collection with this content address.',
kind: SnackbarKind.ERROR
- });
\ No newline at end of file
+ });
import { Dispatch } from "redux";
import { reset, startSubmit, stopSubmit, initialize, FormErrors } from 'redux-form';
import { RootState } from '~/store/store';
+import { getUserUuid } from "~/common/getuser";
import { dialogActions } from "~/store/dialog/dialog-actions";
import { ServiceRepository } from '~/services/services';
import { getCommonResourceServiceError, CommonResourceServiceError } from "~/services/common-service/common-resource-service";
const router = getState();
const properties = getState().properties;
if (isItemNotInProject(properties) || !isProjectOrRunProcessRoute(router)) {
- const userUuid = getState().auth.user!.uuid;
+ const userUuid = getUserUuid(getState());
dispatch(initialize(COLLECTION_CREATE_FORM_NAME, { userUuid }));
} else {
dispatch(initialize(COLLECTION_CREATE_FORM_NAME, { ownerUuid }));
import { DataExplorerMiddlewareService } from "~/store/data-explorer/data-explorer-middleware-service";
import { FavoritePanelColumnNames } from "~/views/favorite-panel/favorite-panel";
import { RootState } from "../store";
+import { getUserUuid } from "~/common/getuser";
import { DataColumns } from "~/components/data-table/data-table";
import { ServiceRepository } from "~/services/services";
import { SortDirection } from "~/components/data-table/data-column";
const responseLinks = await this.services.linkService.list({
filters: new FilterBuilder()
.addEqual("linkClass", 'star')
- .addEqual('tailUuid', this.services.authService.getUuid()!)
+ .addEqual('tailUuid', getUserUuid(api.getState()))
.addEqual('tailKind', ResourceKind.USER)
.getFilters()
}).then(results => results);
response.itemsAvailable++;
response.items.push(it);
});
-
+
api.dispatch(progressIndicatorActions.PERSIST_STOP_WORKING(this.getId()));
api.dispatch(resourcesActions.SET_RESOURCES(response.items));
await api.dispatch<any>(loadMissingProcessesInformation(response.items));
import { unionize, ofType, UnionOf } from "~/common/unionize";
import { Dispatch } from "redux";
import { RootState } from "../store";
+import { getUserUuid } from "~/common/getuser";
import { checkFavorite } from "./favorites-reducer";
import { snackbarActions, SnackbarKind } from "../snackbar/snackbar-actions";
import { ServiceRepository } from "~/services/services";
export const toggleFavorite = (resource: { uuid: string; name: string }) =>
(dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise<any> => {
+ const userUuid = getUserUuid(getState());
+ if (!userUuid) {
+ return Promise.reject("No user");
+ }
dispatch(progressIndicatorActions.START_WORKING("toggleFavorite"));
- const userUuid = getState().auth.user!.uuid;
dispatch(favoritesActions.TOGGLE_FAVORITE({ resourceUuid: resource.uuid }));
const isFavorite = checkFavorite(resource.uuid, getState().favorites);
dispatch(snackbarActions.OPEN_SNACKBAR({
export const updateFavorites = (resourceUuids: string[]) =>
(dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
- const userUuid = getState().auth.user!.uuid;
+ const userUuid = getUserUuid(getState());
+ if (!userUuid) {
+ return;
+ }
dispatch(favoritesActions.CHECK_PRESENCE_IN_FAVORITES(resourceUuids));
services.favoriteService
.checkPresenceInFavorites(userUuid, resourceUuids)
import { Dispatch } from "redux";
import { RootState } from "~/store/store";
-import { ServiceRepository } from "~/services/services";
+import { getUserUuid } from "~/common/getuser";
+import { ServiceRepository, createServices, setAuthorizationHeader } from "~/services/services";
import { setBreadcrumbs } from "~/store/breadcrumbs/breadcrumbs-actions";
import { snackbarActions, SnackbarKind } from "~/store/snackbar/snackbar-actions";
import { LinkAccountType, AccountToLink, LinkAccountStatus } from "~/models/link-account";
-import { saveApiToken, saveUser } from "~/store/auth/auth-action";
+import { authActions, getConfig } from "~/store/auth/auth-action";
import { unionize, ofType, UnionOf } from '~/common/unionize';
import { UserResource } from "~/models/user";
import { GroupResource } from "~/models/group";
import { LinkAccountPanelError, OriginatingUser } from "./link-account-panel-reducer";
-import { login, logout, setAuthorizationHeader } from "~/store/auth/auth-action";
+import { login, logout } from "~/store/auth/auth-action";
import { progressIndicatorActions } from "~/store/progress-indicator/progress-indicator-actions";
import { WORKBENCH_LOADING_SCREEN } from '~/store/workbench/workbench-actions';
return LinkAccountPanelError.NONE;
}
+const newServices = (dispatch: Dispatch<any>, token: string) => {
+ const config = dispatch<any>(getConfig);
+ const svc = createServices(config, { progressFn: () => { }, errorFn: () => { } });
+ setAuthorizationHeader(svc, token);
+ return svc;
+};
+
export const checkForLinkStatus = () =>
(dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
const status = services.linkAccountService.getLinkOpStatus();
export const switchUser = (user: UserResource, token: string) =>
(dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
- dispatch(saveUser(user));
- dispatch(saveApiToken(token));
+ dispatch(authActions.INIT({ user, token }));
};
export const linkFailed = () =>
// Use the token of the user we are getting data for. This avoids any admin/non-admin permissions
// issues since a user will always be able to query the api server for their own user data.
- setAuthorizationHeader(services, linkAccountData.token);
- const savedUserResource = await services.userService.get(linkAccountData.userUuid);
- setAuthorizationHeader(services, curToken);
+ const svc = newServices(dispatch, linkAccountData.token);
+ const savedUserResource = await svc.userService.get(linkAccountData.userUuid);
let params: any;
if (linkAccountData.type === LinkAccountType.ACCESS_OTHER_ACCOUNT || linkAccountData.type === LinkAccountType.ACCESS_OTHER_REMOTE_ACCOUNT) {
export const startLinking = (t: LinkAccountType) =>
(dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
- const accountToLink = { type: t, userUuid: services.authService.getUuid(), token: services.authService.getApiToken() } as AccountToLink;
+ const accountToLink = { type: t, userUuid: getUserUuid(getState()), token: services.authService.getApiToken() } as AccountToLink;
services.linkAccountService.saveAccountToLink(accountToLink);
const auth = getState().auth;
const linkAccountData = services.linkAccountService.getAccountToLink();
if (linkAccountData) {
services.linkAccountService.removeAccountToLink();
- setAuthorizationHeader(services, linkAccountData.token);
- user = await services.userService.get(linkAccountData.userUuid);
+ const svc = newServices(dispatch, linkAccountData.token);
+ user = await svc.userService.get(linkAccountData.userUuid);
dispatch(switchUser(user, linkAccountData.token));
services.linkAccountService.saveLinkOpStatus(LinkAccountStatus.CANCELLED);
}
try {
// The merge api links the user sending the request into the user
// specified in the request, so change the authorization header accordingly
- setAuthorizationHeader(services, linkState.userToLinkToken);
- await services.linkAccountService.linkAccounts(linkState.targetUserToken, newGroup.uuid);
+ const svc = newServices(dispatch, linkState.userToLinkToken);
+ await svc.linkAccountService.linkAccounts(linkState.targetUserToken, newGroup.uuid);
dispatch(switchUser(linkState.targetUser, linkState.targetUserToken));
services.linkAccountService.removeAccountToLink();
services.linkAccountService.saveLinkOpStatus(LinkAccountStatus.SUCCESS);
catch (e) {
// If the link operation fails, delete the previously made project
try {
- setAuthorizationHeader(services, linkState.targetUserToken);
- await services.projectService.delete(newGroup.uuid);
+ const svc = newServices(dispatch, linkState.targetUserToken);
+ await svc.projectService.delete(newGroup.uuid);
}
finally {
dispatch(linkFailed());
export const loadMyAccountPanel = () =>
(dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
- dispatch(setBreadcrumbs([{ label: 'User profile'}]));
+ dispatch(setBreadcrumbs([{ label: 'User profile' }]));
};
export const saveEditedUser = (resource: any) =>
async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
try {
await services.userService.update(resource.uuid, resource);
- services.authService.saveUser(resource);
dispatch(authActions.USER_DETAILS_SUCCESS(resource));
dispatch(initialize(MY_ACCOUNT_FORM, resource));
dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Profile has been updated.", hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
- } catch(e) {
+ } catch (e) {
return;
}
};
export const navigateToProcessLogs = compose(push, getProcessLogUrl);
export const navigateToRootProject = (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
- const rootProjectUuid = services.authService.getUuid();
- if (rootProjectUuid) {
- dispatch<any>(navigateTo(rootProjectUuid));
+ const usr = getState().auth.user;
+ if (usr) {
+ dispatch<any>(navigateTo(usr.uuid));
}
};
import { Dispatch } from "redux";
import { RootState } from "~/store/store";
+import { getUserUuid } from "~/common/getuser";
import { ServiceRepository } from "~/services/services";
import { mockProjectResource } from "~/models/test-utils";
import { treePickerActions, receiveTreePickerProjectsData } from "~/store/tree-picker/tree-picker-actions";
import { TreePickerId } from '~/models/tree';
export const resetPickerProjectTree = () => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
- dispatch<any>(treePickerActions.RESET_TREE_PICKER({pickerId: TreePickerId.PROJECTS}));
- dispatch<any>(treePickerActions.RESET_TREE_PICKER({pickerId: TreePickerId.SHARED_WITH_ME}));
- dispatch<any>(treePickerActions.RESET_TREE_PICKER({pickerId: TreePickerId.FAVORITES}));
+ dispatch<any>(treePickerActions.RESET_TREE_PICKER({ pickerId: TreePickerId.PROJECTS }));
+ dispatch<any>(treePickerActions.RESET_TREE_PICKER({ pickerId: TreePickerId.SHARED_WITH_ME }));
+ dispatch<any>(treePickerActions.RESET_TREE_PICKER({ pickerId: TreePickerId.FAVORITES }));
dispatch<any>(initPickerProjectTree());
};
export const initPickerProjectTree = () => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
- const uuid = services.authService.getUuid();
+ const uuid = getUserUuid(getState());
dispatch<any>(getPickerTreeProjects(uuid));
dispatch<any>(getSharedWithMeProjectsPickerTree(uuid));
[mockProjectResource({ uuid, name: kind })],
kind
);
-};
\ No newline at end of file
+};
import { Dispatch } from "redux";
import { reset, startSubmit, stopSubmit, initialize, FormErrors, formValueSelector, change } from 'redux-form';
import { RootState } from '~/store/store';
+import { getUserUuid } from "~/common/getuser";
import { dialogActions } from "~/store/dialog/dialog-actions";
import { getCommonResourceServiceError, CommonResourceServiceError } from '~/services/common-service/common-resource-service';
import { ProjectResource } from '~/models/project';
if (properties.breadcrumbs) {
return Boolean(properties.breadcrumbs[0].label !== 'Projects');
} else {
- return ;
+ return;
}
};
const router = getState();
const properties = getState().properties;
if (isItemNotInProject(properties) || !isProjectOrRunProcessRoute(router)) {
- const userUuid = getState().auth.user!.uuid;
+ const userUuid = getUserUuid(getState());
dispatch(initialize(PROJECT_CREATE_FORM_NAME, { userUuid }));
} else {
dispatch(initialize(PROJECT_CREATE_FORM_NAME, { ownerUuid }));
(dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
const properties = { ...PROJECT_CREATE_FORM_SELECTOR(getState(), 'properties') };
properties[data.key] = data.value;
- dispatch(change(PROJECT_CREATE_FORM_NAME, 'properties', properties ));
+ dispatch(change(PROJECT_CREATE_FORM_NAME, 'properties', properties));
};
export const removePropertyFromCreateProjectForm = (key: string) =>
(dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
const properties = { ...PROJECT_CREATE_FORM_SELECTOR(getState(), 'properties') };
delete properties[key];
- dispatch(change(PROJECT_CREATE_FORM_NAME, 'properties', properties ));
- };
\ No newline at end of file
+ dispatch(change(PROJECT_CREATE_FORM_NAME, 'properties', properties));
+ };
import { startSubmit, stopSubmit, initialize, FormErrors } from 'redux-form';
import { ServiceRepository } from '~/services/services';
import { RootState } from '~/store/store';
+import { getUserUuid } from "~/common/getuser";
import { getCommonResourceServiceError, CommonResourceServiceError } from "~/services/common-service/common-resource-service";
import { MoveToFormDialogData } from '~/store/move-to-dialog/move-to-dialog';
import { resetPickerProjectTree } from '~/store/project-tree-picker/project-tree-picker-actions';
export const moveProject = (resource: MoveToFormDialogData) =>
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
- const userUuid = getState().auth.user!.uuid;
+ const userUuid = getUserUuid(getState());
+ if (!userUuid) {
+ return;
+ }
dispatch(startSubmit(PROJECT_MOVE_FORM_NAME));
try {
const newProject = await services.projectService.update(resource.uuid, { ownerUuid: resource.ownerUuid });
import { Dispatch } from "redux";
import { bindDataExplorerActions } from '~/store/data-explorer/data-explorer-action';
import { RootState } from '~/store/store';
+import { getUserUuid } from "~/common/getuser";
import { ServiceRepository } from "~/services/services";
import { navigateToRepositories } from "~/store/navigation/navigation-action";
import { unionize, ofType, UnionOf } from "~/common/unionize";
export const openRepositoryCreateDialog = () =>
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
- const userUuid = await services.authService.getUuid();
+ const userUuid = getUserUuid(getState());
const user = await services.userService.get(userUuid!);
dispatch(reset(REPOSITORY_CREATE_FORM_NAME));
dispatch(dialogActions.OPEN_DIALOG({ id: REPOSITORY_CREATE_FORM_NAME, data: { user } }));
export const createRepository = (repository: RepositoryResource) =>
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
- const userUuid = await services.authService.getUuid();
+ const userUuid = getUserUuid(getState());
const user = await services.userService.get(userUuid!);
dispatch(startSubmit(REPOSITORY_CREATE_FORM_NAME));
try {
import { unionize, ofType, UnionOf } from "~/common/unionize";
import { ServiceRepository } from "~/services/services";
import { RootState } from '~/store/store';
+import { getUserUuid } from "~/common/getuser";
import { WorkflowResource, getWorkflowInputs, parseWorkflowDefinition } from '~/models/workflow';
import { getFormValues, initialize } from 'redux-form';
import { RUN_PROCESS_BASIC_FORM, RunProcessBasicFormData } from '~/views/run-process-panel/run-process-basic-form';
const basicForm = getFormValues(RUN_PROCESS_BASIC_FORM)(state) as RunProcessBasicFormData;
const inputsForm = getFormValues(RUN_PROCESS_INPUTS_FORM)(state) as WorkflowInputsData;
const advancedForm = getFormValues(RUN_PROCESS_ADVANCED_FORM)(state) as RunProcessAdvancedFormData || DEFAULT_ADVANCED_FORM_VALUES;
- const userUuid = getState().auth.user!.uuid;
+ const userUuid = getUserUuid(getState());
const pathname = getState().runProcessPanel.processPathname;
const { processOwnerUuid, selectedWorkflow } = state.runProcessPanel;
if (selectedWorkflow) {
import { getNode, getNodeAncestorsIds, initTreeNode, TreeNodeStatus } from "~/models/tree";
import { Dispatch } from "redux";
import { RootState } from "~/store/store";
+import { getUserUuid } from "~/common/getuser";
import { ServiceRepository } from "~/services/services";
import { treePickerActions } from "~/store/tree-picker/tree-picker-actions";
import { FilterBuilder } from "~/services/api/filter-builder";
};
export const activateSearchBarTreeBranch = (id: string) =>
- async (dispatch: Dispatch, _: void, services: ServiceRepository) => {
- const ancestors = await services.ancestorsService.ancestors(id, services.authService.getUuid() || '');
+ async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ const userUuid = getUserUuid(getState());
+ if (!userUuid) {
+ return;
+ }
+ const ancestors = await services.ancestorsService.ancestors(id, userUuid);
for (const ancestor of ancestors) {
await dispatch<any>(loadSearchBarTreeProjects(ancestor.uuid));
}));
dispatch(resourcesActions.SET_RESOURCES(items));
};
-
import { Dispatch } from 'redux';
import { treePickerActions } from "~/store/tree-picker/tree-picker-actions";
import { RootState } from '~/store/store';
+import { getUserUuid } from "~/common/getuser";
import { ServiceRepository } from '~/services/services';
import { FilterBuilder } from '~/services/api/filter-builder';
import { resourcesActions } from '~/store/resources/resources-actions';
export const isSidePanelTreeCategory = (id: string) => SIDE_PANEL_CATEGORIES.some(category => category === id);
export const initSidePanelTree = () =>
- (dispatch: Dispatch, _: () => RootState, { authService }: ServiceRepository) => {
- const rootProjectUuid = authService.getUuid() || '';
+ (dispatch: Dispatch, getState: () => RootState, { authService }: ServiceRepository) => {
+ const rootProjectUuid = getUserUuid(getState());
+ if (!rootProjectUuid) {
+ return;
+ }
const nodes = SIDE_PANEL_CATEGORIES.map(id => initTreeNode({ id, value: id }));
const projectsNode = initTreeNode({ id: rootProjectUuid, value: SidePanelTreeCategory.PROJECTS });
const sharedNode = initTreeNode({ id: SidePanelTreeCategory.SHARED_WITH_ME, value: SidePanelTreeCategory.SHARED_WITH_ME });
};
export const activateSidePanelTreeBranch = (id: string) =>
- async (dispatch: Dispatch, _: void, services: ServiceRepository) => {
- const ancestors = await services.ancestorsService.ancestors(id, services.authService.getUuid() || '');
- const isShared = ancestors.every(({ uuid }) => uuid !== services.authService.getUuid());
+ async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ const userUuid = getUserUuid(getState());
+ if (!userUuid) {
+ return;
+ }
+ const ancestors = await services.ancestorsService.ancestors(id, userUuid);
+ const isShared = ancestors.every(({ uuid }) => uuid !== userUuid);
if (isShared) {
await dispatch<any>(loadSidePanelTreeProjects(SidePanelTreeCategory.SHARED_WITH_ME));
}
import { History } from "history";
import { authReducer } from "./auth/auth-reducer";
+import { authMiddleware } from "./auth/auth-middleware";
import { configReducer } from "./config/config-reducer";
import { dataExplorerReducer } from './data-explorer/data-explorer-reducer';
import { detailsPanelReducer } from './details-panel/details-panel-reducer';
import { treePickerReducer } from './tree-picker/tree-picker-reducer';
import { resourcesReducer } from '~/store/resources/resources-reducer';
import { propertiesReducer } from './properties/properties-reducer';
-import { RootState } from './store';
import { fileUploaderReducer } from './file-uploader/file-uploader-reducer';
import { TrashPanelMiddlewareService } from "~/store/trash-panel/trash-panel-middleware-service";
import { TRASH_PANEL_ID } from "~/store/trash-panel/trash-panel-action";
const middlewares: Middleware[] = [
routerMiddleware(history),
thunkMiddleware.withExtraArgument(services),
+ authMiddleware(services),
projectPanelMiddleware,
favoritePanelMiddleware,
trashPanelMiddleware,
listResultsToDataExplorerItemsMeta
} from "../data-explorer/data-explorer-middleware-service";
import { RootState } from "../store";
+import { getUserUuid } from "~/common/getuser";
import { DataColumns } from "~/components/data-table/data-table";
import { ServiceRepository } from "~/services/services";
import { SortDirection } from "~/components/data-table/data-column";
.addOrder(sortDirection, columnName, GroupContentsResourcePrefix.PROJECT);
}
+ const userUuid = getUserUuid(api.getState());
+ if (!userUuid) {
+ return;
+ }
try {
api.dispatch(progressIndicatorActions.START_WORKING(this.getId()));
- const userUuid = this.services.authService.getUuid()!;
const listResults = await this.services.groupsService
.contents(userUuid, {
...dataExplorerToListParams(dataExplorer),
message: 'Could not fetch trash contents.',
kind: SnackbarKind.ERROR
});
-
import { TreeNode, initTreeNode, getNodeDescendants, TreeNodeStatus, getNode, TreePickerId, Tree } from '~/models/tree';
import { Dispatch } from 'redux';
import { RootState } from '~/store/store';
+import { getUserUuid } from "~/common/getuser";
import { ServiceRepository } from '~/services/services';
import { FilterBuilder } from '~/services/api/filter-builder';
import { pipe, values } from 'lodash/fp';
export const initUserProject = (pickerId: string) =>
async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
- const uuid = services.authService.getUuid();
+ const uuid = getUserUuid(getState());
if (uuid) {
dispatch(receiveTreePickerData({
id: '',
};
export const loadUserProject = (pickerId: string, includeCollections = false, includeFiles = false) =>
async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
- const uuid = services.authService.getUuid();
+ const uuid = getUserUuid(getState());
if (uuid) {
dispatch(loadProject({ id: uuid, pickerId, includeCollections, includeFiles }));
}
export const loadFavoritesProject = (params: LoadFavoritesProjectParams) =>
async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
const { pickerId, includeCollections = false, includeFiles = false } = params;
- const uuid = services.authService.getUuid();
+ const uuid = getUserUuid(getState());
if (uuid) {
const filters = pipe(
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
dispatch(treePickerActions.LOAD_TREE_PICKER_NODE({ id, pickerId: TreePickerId.PROJECTS }));
- const ownerUuid = id.length === 0 ? services.authService.getUuid() || '' : id;
+
+ const ownerUuid = id.length === 0 ? getUserUuid(getState()) || '' : id;
const { items } = await services.projectService.list(buildParams(ownerUuid));
dispatch<any>(receiveTreePickerProjectsData(id, items, TreePickerId.PROJECTS));
export const loadFavoriteTreePickerProjects = (id: string) =>
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
- const parentId = services.authService.getUuid() || '';
+ const parentId = getUserUuid(getState()) || '';
if (id === '') {
dispatch(treePickerActions.LOAD_TREE_PICKER_NODE({ id: parentId, pickerId: TreePickerId.FAVORITES }));
export const loadPublicFavoriteTreePickerProjects = (id: string) =>
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
- const parentId = services.authService.getUuid() || '';
+ const parentId = getUserUuid(getState()) || '';
if (id === '') {
dispatch(treePickerActions.LOAD_TREE_PICKER_NODE({ id: parentId, pickerId: TreePickerId.PUBLIC_FAVORITES }));
.addAsc('name')
.getOrder()
};
-};
\ No newline at end of file
+};
import { Dispatch } from "redux";
import { bindDataExplorerActions } from '~/store/data-explorer/data-explorer-action';
import { RootState } from '~/store/store';
+import { getUserUuid } from "~/common/getuser";
import { ServiceRepository } from "~/services/services";
import { dialogActions } from '~/store/dialog/dialog-actions';
import { startSubmit, reset } from "redux-form";
import { UserResource } from "~/models/user";
import { getResource } from '~/store/resources/resources';
import { navigateTo, navigateToUsers, navigateToRootProject } from "~/store/navigation/navigation-action";
-import { saveApiToken } from '~/store/auth/auth-action';
+import { authActions } from '~/store/auth/auth-action';
export const USERS_PANEL_ID = 'usersPanel';
export const USER_ATTRIBUTES_DIALOG = 'userAttributesDialog';
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
const { resources } = getState();
const data = getResource<UserResource>(uuid)(resources);
+ const client = await services.apiClientAuthorizationService.create({ ownerUuid: uuid });
if (data) {
- services.authService.saveUser(data);
+ dispatch<any>(authActions.INIT({ user: data, token: `v2/${client.uuid}/${client.apiToken}` }));
+ location.reload();
+ dispatch<any>(navigateToRootProject);
}
- const client = await services.apiClientAuthorizationService.create({ ownerUuid: uuid });
- dispatch<any>(saveApiToken(`v2/${client.uuid}/${client.apiToken}`));
- location.reload();
- dispatch<any>(navigateToRootProject);
};
export const openUserCreateDialog = () =>
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
- const userUuid = await services.authService.getUuid();
+ const userUuid = getUserUuid(getState());
const user = await services.userService.get(userUuid!);
const virtualMachines = await services.virtualMachineService.list();
dispatch(reset(USER_CREATE_FORM_NAME));
const { resources } = getState();
const data = getResource<UserResource>(uuid)(resources);
const isActive = data!.isActive;
- const newActivity = await services.userService.update(uuid, { isActive: !isActive });
+ let newActivity;
+ if (isActive) {
+ newActivity = await services.userService.unsetup(uuid);
+ } else {
+ newActivity = await services.userService.update(uuid, { isActive: true });
+ }
dispatch<any>(loadUsersPanel());
return newActivity;
};
import { Dispatch } from 'redux';
import { RootState } from "~/store/store";
+import { getUserUuid } from "~/common/getuser";
import { loadDetailsPanel } from '~/store/details-panel/details-panel-action';
import { snackbarActions, SnackbarKind } from '~/store/snackbar/snackbar-actions';
import { favoritePanelActions, loadFavoritePanel } from '~/store/favorite-panel/favorite-panel-action';
export const loadProject = (uuid: string) =>
handleFirstTimeLoad(
async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
- const userUuid = services.authService.getUuid();
+ const userUuid = getUserUuid(getState());
dispatch(setIsProjectPanelTrashed(false));
if (userUuid) {
if (extractUuidKind(uuid) === ResourceKind.USER && userUuid !== uuid) {
export const loadCollection = (uuid: string) =>
handleFirstTimeLoad(
async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
- const userUuid = services.authService.getUuid();
+ const userUuid = getUserUuid(getState());
if (userUuid) {
const match = await loadGroupContentsResource({ uuid, userUuid, services });
match({
import { RouteProps } from "react-router";
import * as React from "react";
import { connect, DispatchProp } from "react-redux";
-import { getUserDetails, saveApiToken } from "~/store/auth/auth-action";
+import { saveApiToken } from "~/store/auth/auth-action";
import { getUrlParameter } from "~/common/url";
import { AuthService } from "~/services/auth-service/auth-service";
import { navigateToRootProject, navigateToLinkAccount } from "~/store/navigation/navigation-action";
-import { User } from "~/models/user";
import { Config } from "~/common/config";
-import { initSessions } from "~/store/auth/auth-action-session";
import { getAccountLinkData } from "~/store/link-account-panel/link-account-panel-actions";
interface ApiTokenProps {
const search = this.props.location ? this.props.location.search : "";
const apiToken = getUrlParameter(search, 'api_token');
const loadMainApp = this.props.loadMainApp;
- this.props.dispatch(saveApiToken(apiToken));
- this.props.dispatch<any>(getUserDetails()).then((user: User) => {
- this.props.dispatch(initSessions(this.props.authService, this.props.config, user));
- }).finally(() => {
+ this.props.dispatch<any>(saveApiToken(apiToken)).finally(() => {
if (loadMainApp) {
if (this.props.dispatch(getAccountLinkData())) {
this.props.dispatch(navigateToLinkAccount);
import { ListItemTextIcon } from "~/components/list-item-text-icon/list-item-text-icon";
import { ProjectIcon, FavoriteIcon, ProjectsIcon, ShareMeIcon, PublicFavoriteIcon } from '~/components/icon/icon';
import { RootState } from "~/store/store";
+import { getUserUuid } from "~/common/getuser";
import { ServiceRepository } from "~/services/services";
import { WrappedFieldProps } from 'redux-form';
import { TreePickerId } from '~/models/tree';
if (pickerId === TreePickerId.PROJECTS) {
dispatch<any>(loadProjectTreePickerProjects(id));
} else if (pickerId === TreePickerId.FAVORITES) {
- dispatch<any>(loadFavoriteTreePickerProjects(id === services.authService.getUuid() ? '' : id));
+ dispatch<any>(loadFavoriteTreePickerProjects(id === getUserUuid(getState()) ? '' : id));
} else if (pickerId === TreePickerId.PUBLIC_FAVORITES) {
- dispatch<any>(loadPublicFavoriteTreePickerProjects(id === services.authService.getUuid() ? '' : id));
+ dispatch<any>(loadPublicFavoriteTreePickerProjects(id === getUserUuid(getState()) ? '' : id));
// TODO: load sharedWithMe
}
} else {
<Typography variant='caption' color='error'>
{props.meta.error}
</Typography>}
- </div>;
\ No newline at end of file
+ </div>;
import { RootState } from '~/store/store';
import { Dispatch } from 'redux';
import { connect } from 'react-redux';
-import { startLinking, cancelLinking, linkAccount, linkAccountPanelActions } from '~/store/link-account-panel/link-account-panel-actions';
+import { startLinking, linkAccount, linkAccountPanelActions, cancelLinking } from '~/store/link-account-panel/link-account-panel-actions';
import { LinkAccountType } from '~/models/link-account';
import {
LinkAccountPanelRoot,
dependencies:
esquery "^1.0.1"
+"@sinonjs/commons@^1", "@sinonjs/commons@^1.3.0", "@sinonjs/commons@^1.4.0":
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.6.0.tgz#ec7670432ae9c8eb710400d112c201a362d83393"
+ integrity sha512-w4/WHG7C4WWFyE5geCieFJF6MZkbW4VAriol5KlmQXpAQdxvV0p26sqNZOW6Qyw6Y0l9K4g+cHvvczR2sEEpqg==
+ dependencies:
+ type-detect "4.0.8"
+
+"@sinonjs/formatio@^3.2.1":
+ version "3.2.2"
+ resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-3.2.2.tgz#771c60dfa75ea7f2d68e3b94c7e888a78781372c"
+ integrity sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==
+ dependencies:
+ "@sinonjs/commons" "^1"
+ "@sinonjs/samsam" "^3.1.0"
+
+"@sinonjs/samsam@^3.1.0", "@sinonjs/samsam@^3.3.1":
+ version "3.3.3"
+ resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-3.3.3.tgz#46682efd9967b259b81136b9f120fd54585feb4a"
+ integrity sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==
+ dependencies:
+ "@sinonjs/commons" "^1.3.0"
+ array-from "^2.1.1"
+ lodash "^4.17.15"
+
+"@sinonjs/text-encoding@^0.7.1":
+ version "0.7.1"
+ resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5"
+ integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==
+
"@types/cheerio@*":
version "0.22.9"
resolved "https://registry.yarnpkg.com/@types/cheerio/-/cheerio-0.22.9.tgz#b5990152604c2ada749b7f88cab3476f21f39d7b"
version "2.1.1"
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.1.tgz#426bb9da84090c1838d812c8150af20a8331e296"
+array-from@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/array-from/-/array-from-2.1.1.tgz#cfe9d8c26628b9dc5aecc62a9f5d8f1f352c1195"
+ integrity sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=
+
array-includes@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.0.3.tgz#184b48f62d92d7452bb31b323165c7f8bd02266d"
address "^1.0.1"
debug "^2.6.0"
-diff@^3.2.0:
+diff@^3.2.0, diff@^3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
pako "~1.0.2"
readable-stream "~2.0.6"
+just-extend@^4.0.2:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.0.2.tgz#f3f47f7dfca0f989c55410a7ebc8854b07108afc"
+ integrity sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==
+
keycode@^2.1.9:
version "2.2.0"
resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.2.0.tgz#3d0af56dc7b8b8e5cba8d0a97f107204eec22b04"
version "4.17.11"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
+lodash@^4.17.15:
+ version "4.17.15"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
+ integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
+
log-symbols@^2.1.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a"
es6-symbol "^3.1.1"
object.assign "^4.1.0"
+lolex@^4.0.1, lolex@^4.1.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/lolex/-/lolex-4.2.0.tgz#ddbd7f6213ca1ea5826901ab1222b65d714b3cd7"
+ integrity sha512-gKO5uExCXvSm6zbF562EvM+rd1kQDnB9AZBbiQVzf1ZmdDpxUSvpnAaVOP83N/31mRK8Ml8/VE8DMvsAZQ+7wg==
+
longest@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097"
version "1.0.0"
resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c"
+nise@^1.4.10:
+ version "1.5.2"
+ resolved "https://registry.yarnpkg.com/nise/-/nise-1.5.2.tgz#b6d29af10e48b321b307e10e065199338eeb2652"
+ integrity sha512-/6RhOUlicRCbE9s+94qCUsyE+pKlVJ5AhIv+jEE7ESKwnbXqulKZ1FYU+XAtHHWE9TinYvAxDUJAb912PwPoWA==
+ dependencies:
+ "@sinonjs/formatio" "^3.2.1"
+ "@sinonjs/text-encoding" "^0.7.1"
+ just-extend "^4.0.2"
+ lolex "^4.1.0"
+ path-to-regexp "^1.7.0"
+
no-case@^2.2.0:
version "2.3.2"
resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac"
version "3.0.2"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
+sinon@7.3:
+ version "7.3.2"
+ resolved "https://registry.yarnpkg.com/sinon/-/sinon-7.3.2.tgz#82dba3a6d85f6d2181e1eca2c10d8657c2161f28"
+ integrity sha512-thErC1z64BeyGiPvF8aoSg0LEnptSaWE7YhdWWbWXgelOyThent7uKOnnEh9zBxDbKixtr5dEko+ws1sZMuFMA==
+ dependencies:
+ "@sinonjs/commons" "^1.4.0"
+ "@sinonjs/formatio" "^3.2.1"
+ "@sinonjs/samsam" "^3.3.1"
+ diff "^3.5.0"
+ lolex "^4.0.1"
+ nise "^1.4.10"
+ supports-color "^5.5.0"
+
slash@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
dependencies:
has-flag "^2.0.0"
-supports-color@^5.1.0, supports-color@^5.3.0, supports-color@^5.4.0:
+supports-color@^5.1.0, supports-color@^5.3.0, supports-color@^5.4.0, supports-color@^5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
dependencies:
loader-utils "^1.0.2"
semver "^5.0.1"
+ts-mock-imports@1.2.6:
+ version "1.2.6"
+ resolved "https://registry.yarnpkg.com/ts-mock-imports/-/ts-mock-imports-1.2.6.tgz#5a98a398c3eadb7f75b6904984bb0ba5f3fbb912"
+ integrity sha512-rZjsIEBWx9a3RGUo4Rhj/hzEGB4GPWJx46fls9EJf4UBsf5SxS2qiozf6dQp0Ym/9LC5MArlXZbZ+93wJzAmjA==
+
tsconfig-paths-webpack-plugin@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-2.0.0.tgz#7652dc684bb3206c8e7e446831ca01cbf4d11772"
dependencies:
prelude-ls "~1.1.2"
+type-detect@4.0.8:
+ version "4.0.8"
+ resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
+ integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
+
type-is@~1.6.16:
version "1.6.16"
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194"