- Simplifies user type in the link-account-panel to only be a UserResource, and subsequently removes createdAt from the auth user.
- Switches users back to the originating user when account linking is cancelled after logging on with the second account.
Arvados-DCO-1.1-Signed-off-by: Eric Biagiotti <ebiagiotti@veritasgenetics.com
export interface AccountToLink {
type: LinkAccountType;
- userToken: string;
+ userUuid: string;
+ token: string;
}
prefs: UserPrefs;
isAdmin: boolean;
isActive: boolean;
- createdAt: string;
}
export const getUserFullname = (user?: User) => {
export const USER_IS_ACTIVE = 'isActive';
export const USER_USERNAME = 'username';
export const USER_PREFS = 'prefs';
-export const USER_CREATED_AT = 'createdAt';
export interface UserDetailsResponse {
email: string;
is_admin: boolean;
is_active: boolean;
username: string;
- created_at: string;
prefs: UserPrefs;
}
const isAdmin = this.getIsAdmin();
const isActive = this.getIsActive();
const username = localStorage.getItem(USER_USERNAME);
- const createdAt = localStorage.getItem(USER_CREATED_AT);
const prefs = JSON.parse(localStorage.getItem(USER_PREFS) || '{"profile": {}}');
- return email && firstName && lastName && uuid && ownerUuid && username && createdAt && prefs
- ? { email, firstName, lastName, uuid, ownerUuid, isAdmin, isActive, username, createdAt, prefs }
+ return email && firstName && lastName && uuid && ownerUuid && username && prefs
+ ? { email, firstName, lastName, uuid, ownerUuid, isAdmin, isActive, username, prefs }
: undefined;
}
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_CREATED_AT, user.createdAt);
localStorage.setItem(USER_PREFS, JSON.stringify(user.prefs));
}
localStorage.removeItem(USER_IS_ADMIN);
localStorage.removeItem(USER_IS_ACTIVE);
localStorage.removeItem(USER_USERNAME);
- localStorage.removeItem(USER_CREATED_AT);
localStorage.removeItem(USER_PREFS);
}
isAdmin: resp.data.is_admin,
isActive: resp.data.is_active,
username: resp.data.username,
- createdAt: resp.data.created_at,
prefs
};
})
isAdmin: user.is_admin,
isActive: user.is_active,
username: user.username,
- createdAt: user.created_at,
prefs: user.prefs
},
token: saltedToken
import { RootState } from "../store";
import { ServiceRepository } from "~/services/services";
import { SshKeyResource } from '~/models/ssh-key';
-import { User } from "~/models/user";
+import { User, UserResource } from "~/models/user";
import { Session } from "~/models/session";
import { Config } from '~/common/config';
import { initSessions } from "~/store/auth/auth-action-session";
+import { UserRepositoryCreate } from '~/views-components/dialog-create/dialog-user-create';
export const authActions = unionize({
SAVE_API_TOKEN: ofType<string>(),
+ SAVE_USER: ofType<UserResource>(),
LOGIN: {},
LOGOUT: {},
CONFIG: ofType<{ config: Config }>(),
dispatch(authActions.SAVE_API_TOKEN(token));
};
+export const saveUser = (user: UserResource) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ services.authService.saveUser(user);
+ dispatch(authActions.SAVE_USER(user));
+};
+
export const login = (uuidPrefix: string, homeCluster: string) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
services.authService.login(uuidPrefix, homeCluster);
dispatch(authActions.LOGIN());
username: "username",
prefs: {},
isAdmin: false,
- isActive: true,
- createdAt: "createdAt"
+ isActive: true
};
const state = reducer(initialState, authActions.INIT({ user, token: "token" }));
expect(state).toEqual({
username: "username",
prefs: {},
isAdmin: false,
- isActive: true,
- createdAt: "createdAt"
+ isActive: true
};
const state = reducer(initialState, authActions.USER_DETAILS_SUCCESS(user));
username: "username",
prefs: {},
isAdmin: false,
- isActive: true,
- createdAt: "createdAt"
+ isActive: true
}
});
});
// SPDX-License-Identifier: AGPL-3.0
import { authActions, AuthAction } from "./auth-action";
-import { User } from "~/models/user";
+import { User, UserResource } from "~/models/user";
import { ServiceRepository } from "~/services/services";
import { SshKeyResource } from '~/models/ssh-key';
import { Session } from "~/models/session";
SAVE_API_TOKEN: (token: string) => {
return { ...state, apiToken: token };
},
+ SAVE_USER: (user: UserResource) => {
+ return { ...state, user};
+ },
CONFIG: ({ config }) => {
return {
...state,
import { ServiceRepository } from "~/services/services";
import { setBreadcrumbs } from "~/store/breadcrumbs/breadcrumbs-actions";
import { LinkAccountType, AccountToLink } from "~/models/link-account";
-import { logout } from "~/store/auth/auth-action";
+import { logout, saveApiToken, saveUser } from "~/store/auth/auth-action";
import { unionize, ofType, UnionOf } from '~/common/unionize';
+import { UserResource } from "~/models/user";
+import { navigateToRootProject } from "~/store/navigation/navigation-action";
export const linkAccountPanelActions = unionize({
- LOAD_LINKING: ofType<AccountToLink>(),
+ LOAD_LINKING: ofType<{ user: UserResource | undefined, userToLink: UserResource | undefined }>(),
REMOVE_LINKING: {}
});
(dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
dispatch(setBreadcrumbs([{ label: 'Link account'}]));
- const linkAccountData = services.linkAccountService.getLinkAccount();
- if (linkAccountData) {
- dispatch(linkAccountPanelActions.LOAD_LINKING(linkAccountData));
+ const curUser = getState().auth.user;
+ if (curUser) {
+ services.userService.get(curUser.uuid).then(curUserResource => {
+ const linkAccountData = services.linkAccountService.getLinkAccount();
+ if (linkAccountData) {
+ services.userService.get(linkAccountData.userUuid).then(savedUserResource => {
+ if (linkAccountData.type === LinkAccountType.ADD_OTHER_LOGIN) {
+ dispatch<any>(linkAccountPanelActions.LOAD_LINKING({ userToLink: curUserResource, user: savedUserResource }));
+ }
+ else if (linkAccountData.type === LinkAccountType.ACCESS_OTHER_ACCOUNT) {
+ dispatch<any>(linkAccountPanelActions.LOAD_LINKING({ userToLink: savedUserResource, user: curUserResource }));
+ }
+ else {
+ throw new Error('Invalid link account type.');
+ }
+ });
+ }
+ else {
+ dispatch<any>(linkAccountPanelActions.LOAD_LINKING({ userToLink: undefined, user: curUserResource }));
+ }
+ });
}
};
export const saveAccountLinkData = (t: LinkAccountType) =>
(dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
- const accountToLink = {type: t, userToken: services.authService.getApiToken()} as AccountToLink;
+ const accountToLink = {type: t, userUuid: services.authService.getUuid(), token: services.authService.getApiToken()} as AccountToLink;
services.linkAccountService.saveLinkAccount(accountToLink);
dispatch(logout());
};
export const removeAccountLinkData = () =>
(dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
+ const linkAccountData = services.linkAccountService.getLinkAccount();
services.linkAccountService.removeLinkAccount();
dispatch(linkAccountPanelActions.REMOVE_LINKING());
+ if (linkAccountData) {
+ services.userService.get(linkAccountData.userUuid).then(savedUser => {
+ dispatch(setBreadcrumbs([{ label: ''}]));
+ dispatch<any>(saveUser(savedUser));
+ dispatch<any>(saveApiToken(linkAccountData.token));
+ dispatch<any>(navigateToRootProject);
+ });
+ }
+ };
+
+export const linkAccount = () =>
+ (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
};
\ No newline at end of file
// SPDX-License-Identifier: AGPL-3.0
import { linkAccountPanelActions, LinkAccountPanelAction } from "~/store/link-account-panel/link-account-panel-actions";
-import { AccountToLink } from "~/models/link-account";
+import { UserResource, User } from "~/models/user";
export interface LinkAccountPanelState {
- accountToLink: AccountToLink | undefined;
+ user: UserResource | undefined;
+ userToLink: UserResource | undefined;
}
const initialState = {
- accountToLink: undefined
+ user: undefined,
+ userToLink: undefined
};
export const linkAccountPanelReducer = (state: LinkAccountPanelState = initialState, action: LinkAccountPanelAction) =>
linkAccountPanelActions.match(action, {
default: () => state,
- LOAD_LINKING: (accountToLink) => ({ ...state, accountToLink }),
- REMOVE_LINKING: () => ({...state, accountToLink: undefined})
+ LOAD_LINKING: ({ userToLink, user }) => ({ ...state, user, userToLink }),
+ REMOVE_LINKING: () => ({ ...state, user: undefined, userToLink: undefined })
});
\ No newline at end of file
Grid,
} from '@material-ui/core';
import { ArvadosTheme } from '~/common/custom-theme';
-import { User } from "~/models/user";
+import { User, UserResource } from "~/models/user";
import { LinkAccountType, AccountToLink } from "~/models/link-account";
import { formatDate }from "~/common/formatters";
});
export interface LinkAccountPanelRootDataProps {
- user?: User;
- accountToLink?: AccountToLink;
+ user?: UserResource;
+ userToLink?: UserResource;
}
export interface LinkAccountPanelRootActionProps {
saveAccountLinkData: (type: LinkAccountType) => void;
removeAccountLinkData: () => void;
+ linkAccount: () => void;
+}
+
+function displayUser(user: UserResource, showCreatedAt: boolean = false) {
+ const disp = [];
+ disp.push(<span><b>{user.email}</b> ({user.username}, {user.uuid})</span>);
+ if (showCreatedAt) {
+ disp.push(<span> created on <b>{formatDate(user.createdAt)}</b></span>);
+ }
+ return disp;
}
type LinkAccountPanelRootProps = LinkAccountPanelRootDataProps & LinkAccountPanelRootActionProps & WithStyles<CssRules>;
export const LinkAccountPanelRoot = withStyles(styles) (
- ({classes, user, accountToLink, saveAccountLinkData, removeAccountLinkData}: LinkAccountPanelRootProps) => {
+ ({classes, user, userToLink, saveAccountLinkData, removeAccountLinkData, linkAccount}: LinkAccountPanelRootProps) => {
return <Card className={classes.root}>
<CardContent>
- { user && accountToLink===undefined && <Grid container spacing={24}>
+ { user && userToLink===undefined && <Grid container spacing={24}>
<Grid container item direction="column" spacing={24}>
<Grid item>
- You are currently logged in as <b>{user.email}</b> ({user.username}, {user.uuid}) created on <b>{formatDate(user.createdAt)}</b>
+ You are currently logged in as {displayUser(user, true)}
</Grid>
<Grid item>
You can link Arvados accounts. After linking, either login will take you to the same account.
</Grid>
</Grid>
</Grid>}
- { accountToLink && <Grid container spacing={24}>
+ { userToLink && user && <Grid container spacing={24}>
+ <Grid container item direction="column" spacing={24}>
+ <Grid item>
+ Clicking 'Link accounts' will link {displayUser(userToLink, true)} to {displayUser(user, true)}.
+ </Grid>
+ <Grid item>
+ After linking, logging in as {displayUser(userToLink)} will log you into the same account as {displayUser(user)}.
+ </Grid>
+ <Grid item>
+ Any object owned by {displayUser(userToLink)} will be transfered to {displayUser(user)}.
+ </Grid>
+ </Grid>
<Grid container item direction="row" spacing={24}>
<Grid item>
<Button variant="contained" onClick={() => removeAccountLinkData()}>
</Button>
</Grid>
<Grid item>
- <Button color="primary" variant="contained" onClick={() => {}}>
+ <Button color="primary" variant="contained" onClick={() => linkAccount()}>
Link accounts
</Button>
</Grid>
</Grid>
</Grid> }
</CardContent>
- </Card>;
+ </Card> ;
});
\ No newline at end of file
import { RootState } from '~/store/store';
import { Dispatch } from 'redux';
import { connect } from 'react-redux';
-import { saveAccountLinkData, removeAccountLinkData } from '~/store/link-account-panel/link-account-panel-actions';
+import { saveAccountLinkData, removeAccountLinkData, linkAccount } from '~/store/link-account-panel/link-account-panel-actions';
import { LinkAccountType } from '~/models/link-account';
import {
LinkAccountPanelRoot,
const mapStateToProps = (state: RootState): LinkAccountPanelRootDataProps => {
return {
- user: state.auth.user,
- accountToLink: state.linkAccountPanel.accountToLink
+ user: state.linkAccountPanel.user,
+ userToLink: state.linkAccountPanel.userToLink
};
};
const mapDispatchToProps = (dispatch: Dispatch): LinkAccountPanelRootActionProps => ({
saveAccountLinkData: (type: LinkAccountType) => dispatch<any>(saveAccountLinkData(type)),
- removeAccountLinkData: () => dispatch<any>(removeAccountLinkData())
+ removeAccountLinkData: () => dispatch<any>(removeAccountLinkData()),
+ linkAccount: () => dispatch<any>(linkAccount())
});
export const LinkAccountPanel = connect(mapStateToProps, mapDispatchToProps)(LinkAccountPanelRoot);