Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature 425. Multiple account sign in and account switching #451

Merged
merged 15 commits into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions src/app/auth/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ export default function Login() {

/**
* Sends a request directly to the x509 endpoint of rucio auth server to retrieve a RucioAuthTOken using x509 client certificate provided via the browser
* @param vo
* @param account
* @param vo
* @param account
* @returns {@link AuthViewModel} indicating the status of the request
*/
const handleX509Submit = async (vo: VO, loginViewModel: LoginViewModel, account?: string | undefined): Promise<AuthViewModel> => {
Expand Down Expand Up @@ -232,9 +232,6 @@ export default function Login() {
.then((res) => res.json())
.then((loginViewModel: LoginViewModel) => {
setViewModel(loginViewModel)
if (loginViewModel.isLoggedIn) {
router.push(redirectURL)
}
}
)
}, []);
Expand Down
4 changes: 4 additions & 0 deletions src/component-library/Demos/01_0_Login_MultiVO.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ export const ABasicLogin: Story = {
multiVOEnabled: false,
voList: [voAtlas, voCMS],
isLoggedIn: false,
accountsAvailable: undefined,
accountActive: undefined,
rucioAuthHost: 'https://rucio.cern.ch',
},
authViewModel: {
Expand All @@ -92,6 +94,8 @@ export const ABasicMultiVOLogin: Story = {
multiVOEnabled: true,
voList: [voAtlas, voCMS, voLHCb],
isLoggedIn: false,
accountsAvailable: undefined,
accountActive: undefined,
rucioAuthHost: 'https://rucio.cern.ch',
},
authViewModel: {
Expand Down
2 changes: 2 additions & 0 deletions src/component-library/Demos/01_1_Login.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ export const Playbook_InitLogin: Story = {
multiVOEnabled: true,
voList: [voAtlas, voCMS],
isLoggedIn: false,
accountsAvailable: undefined,
accountActive: undefined,
rucioAuthHost: 'https://rucio.cern.ch',
},
authViewModel: {
Expand Down
2 changes: 2 additions & 0 deletions src/component-library/Demos/01_1_Login_OIDC.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ export const AMultiVOOIDCEnabledLogin: Story = {
multiVOEnabled: true,
voList: [voAtlas, voCMS, voLHCb],
isLoggedIn: false,
accountActive: undefined,
accountsAvailable: undefined,
rucioAuthHost: 'https://rucio.cern.ch',
},
authViewModel: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,16 @@ export const Playbook_Multi_Account: Story = {
oidcProviders: [cernOIDCProvider],
multiVOEnabled: true,
voList: [voAtlas, voCMS],
accountsAvailable: undefined,
accountActive: undefined,
isLoggedIn: false,
rucioAuthHost: 'https://rucio.cern.ch',
},
authViewModel: {
status: "success",
message: "",
status: "multiple_accounts",
message: "mayank,ddmadmin,tester",
rucioAccount: "",
rucioMultiAccount: "mayank,ddmadmin",
rucioMultiAccount: "",
rucioAuthType: "",
rucioAuthToken: "",
rucioIdentity: "",
Expand Down
189 changes: 117 additions & 72 deletions src/component-library/Pages/Layout/AccountDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,77 @@
import { twMerge } from "tailwind-merge"
import { ForwardedRef, forwardRef, useState } from "react"
import { HiCog, HiSwitchHorizontal, HiLogout } from "react-icons/hi"
import {twMerge} from "tailwind-merge"
import {ForwardedRef, forwardRef, useState} from "react"
import {HiSwitchHorizontal, HiLogout, HiUserAdd} from "react-icons/hi"
import Link from "next/link"
import {useRouter} from "next/navigation";

const AccountList = (props: { accountList: string[] }) => {
return <div className="flex flex-col">
{
props.accountList.map((account, index) => {
return (
<a
className={twMerge(
"text-text-600 hover:bg-neutral-200 hover:cursor-pointer",
"dark:text-text-300 dark:hover:bg-neutral-600",
"flex items-center justify-between py-2 px-1 space-x-4",
"text-right"
)}
key={index}
href={`/api/auth/switch?account=${account}&&callbackUrl=/dashboard`}
>
<HiSwitchHorizontal className="text-2xl text-text-900 dark:text-text-100 shrink-0"/>
<span>
<span>Switch to </span>
<b className="text-text-800 dark:text-text-100">{account}</b>
</span>
</a>
)
})
}
</div>
};

const SignOutOfAllButton = () => {
const router = useRouter();

const signOut = async () => {
const request = new Request('/api/auth/logout', {
method: 'POST'
})
//TODO: handle errors
await fetch(request)
router.push('/auth/login')
};

return <div
className={twMerge(
"text-text-800 hover:bg-base-warning-200 hover:cursor-pointer",
"dark:text-text-100 dark:hover:bg-base-warning-600",
"flex items-center justify-between py-2 px-1 space-x-4",
"text-right",
)}
onClick={() => signOut()}
>
<span>Sign <b>out</b> of all accounts</span>
<HiLogout className="dark:text-text-100 text-2xl text-text-900 shrink-0"/>
</div>
}

const SignIntoButton = () => {
return <Link
className={twMerge(
"text-text-800 hover:bg-base-success-200 hover:cursor-pointer",
"dark:text-text-100 dark:hover:bg-base-success-600",
"flex items-center justify-between py-2 px-1 space-x-4",
"text-right",
)}
href="/auth/login"
prefetch={false}
>
<span>Sign <b>in to</b> another account</span>
<HiUserAdd className="dark:text-text-100 text-2xl text-text-900 shrink-0"/>
</Link>
}

export const AccountDropdown = forwardRef(function AccountDropdown
(
Expand All @@ -12,79 +82,54 @@ export const AccountDropdown = forwardRef(function AccountDropdown
},
ref: ForwardedRef<HTMLDivElement>
) {
return (
const hasAccountChoice = props.accountsPossible.length !== 1;
return (

<div
className={twMerge("flex flex-col p-2 sm:w-fit",
"rounded-md border shadow-md",
props.isProfileOpen ? "visible" : "invisible",
"absolute top-10 right-0",
"divide-y",
"bg-neutral-100 dark:bg-neutral-800",
"z-[100]"
)}
onMouseEnter={e => e.preventDefault()}
ref={ref}
>
<Link
className={twMerge(
"text-text-600 hover:bg-neutral-200 hover:cursor-pointer",
"dark:text-text-300 dark:hover:bg-neutral-600",
"flex items-center justify-between py-2 px-1 space-x-4",
"text-right"
<div
className={twMerge("flex flex-col p-2 sm:w-fit",
"rounded-md border shadow-md",
props.isProfileOpen ? "visible" : "invisible",
"absolute top-10 right-0",
"divide-y",
"bg-neutral-100 dark:bg-neutral-800",
"z-[100]",
"w-64 sm:w-96"
)}
href="/accountsettings"
prefetch={false}
onMouseEnter={e => e.preventDefault()}
ref={ref}
>
<HiCog className="text-3xl text-text-600 dark:text-text-100 shrink-0" />
<span>
<span>Settings for </span>
<b className="text-text-800 dark:text-text-100">{props.accountActive}</b>
<div
className={twMerge(
"text-text-600 hover:cursor-pointer",
"dark:text-text-300",
"flex justify-between items-center py-4 px-1 space-x-4",
"text-right"
)}
>
<span className="text-xl">
<span>Hello, </span>
<b className="text-text-800 dark:text-text-100">{props.accountActive}</b>!
</span>
</Link>
<div
className="flex flex-col"
>
{
(props.accountsPossible.filter(
(account) => account !== props.accountActive
)).map((account, index) => {
return (
<Link
className={twMerge(
"text-text-600 hover:bg-neutral-200 hover:cursor-pointer",
"dark:text-text-300 dark:hover:bg-neutral-600",
"flex items-center justify-between py-2 px-1 space-x-4",
"text-right"
)}
key={index}
href="/api/account/switch"
prefetch={false}
>
<HiSwitchHorizontal className="text-2xl text-text-900 dark:text-text-100 shrink-0" />
<span>
<span>Switch to </span>
<b className="text-text-800 dark:text-text-100">{account}</b>
</span>
</Link>
)
})
<div
className={twMerge(
"bg-neutral-200 hover:bg-base-warning-600",
"p-1",
"rounded-md"
)}
>
{/* Using the <a> tag here prevents a bug with response caching */}
{/* eslint-disable-next-line @next/next/no-html-link-for-pages */}
<a href="/api/auth/logout?callbackUrl=/dashboard">
<HiLogout className="text-2xl text-text-900 shrink-0"/>
</a>
</div>
</div>
{hasAccountChoice &&
<AccountList accountList={props.accountsPossible.filter(account => account !== props.accountActive)}/>
}
<SignIntoButton/>
{hasAccountChoice && <SignOutOfAllButton/>}
</div>
<Link
className={twMerge(
"text-text-800 hover:bg-base-warning-200 hover:cursor-pointer",
"dark:text-text-100 dark:hover:bg-base-warning-600",
"flex items-center justify-between py-2 px-1 space-x-4",
"text-right",
"w-64 sm:w-96"
)}
href="/api/auth/logout"
prefetch={false}
>
<b>Logout</b> <HiLogout className="text-3xl text-text-900 shrink-0" />
</Link>
</div>
)
}
)
}
)
2 changes: 2 additions & 0 deletions src/component-library/Pages/Login/Login.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ LoginPage.args = {
multiVOEnabled: true,
voList: [voAtlas, voCMS],
isLoggedIn: false,
accountsAvailable: undefined,
accountActive: undefined,
rucioAuthHost: 'https://rucio.cern.ch',
},
authViewModel: {
Expand Down
Loading
Loading