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 421. Fix account selection #448

Merged
merged 7 commits into from
Aug 8, 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
55 changes: 29 additions & 26 deletions src/app/auth/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export default function Login() {
},
body: JSON.stringify(body),
})
if (res.status === 200 || res.status === 401) {
if (res.status === 200 || res.status === 401 || res.status === 206) {
const auth: AuthViewModel = await res.json()
setAuthViewModel(auth)
if (auth.status === 'success') {
Expand All @@ -42,6 +42,32 @@ export default function Login() {
}
};

const getMultipleAccountsViewModel = (res: Response): AuthViewModel => {
const multiple_accounts = res.headers.get('X-Rucio-Auth-Accounts')
if (multiple_accounts === null) {
return {
status: 'error',
message: 'Cannot retrieve X-Rucio-Auth-Accounts from response headers',
rucioAccount: '',
rucioAuthType: '',
rucioIdentity: '',
rucioAuthToken: '',
rucioAuthTokenExpires: '',
role: Role.USER
}
}
return {
status: 'multiple_accounts',
message: multiple_accounts,
rucioAccount: '',
rucioAuthType: '',
rucioIdentity: '',
rucioAuthToken: '',
rucioAuthTokenExpires: '',
role: Role.USER
}
}

/**
* 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
Expand Down Expand Up @@ -130,30 +156,8 @@ export default function Login() {
return Promise.resolve(auth)

} else if (res.status === 206) {
const multiple_accounts = res.headers.get('X-Rucio-Auth-Accounts')
if (multiple_accounts === null) {
return Promise.resolve({
status: 'error',
message: 'Cannot retrieve X-Rucio-Auth-Accounts from response headers',
rucioAccount: '',
rucioAuthType: '',
rucioIdentity: '',
rucioAuthToken: '',
rucioAuthTokenExpires: '',
role: Role.USER
})
}
const auth: AuthViewModel = {
status: 'multiple_accounts',
message: multiple_accounts,
rucioAccount: '',
rucioAuthType: '',
rucioIdentity: '',
rucioAuthToken: '',
rucioAuthTokenExpires: '',
role: Role.USER
}
return Promise.resolve(auth)
const viewModel = getMultipleAccountsViewModel(res);
return Promise.resolve(viewModel)
} else if (res.status === 401) {
const auth: AuthViewModel = {
status: 'error',
Expand Down Expand Up @@ -188,7 +192,6 @@ export default function Login() {
*/
const handleX509Session = async (auth: AuthViewModel, rucioAccount: string, shortVOName: string) => {
if (auth.status !== 'success') {
auth.message = 'Cannot set session for x509 login as the login was not successful'
setAuthViewModel(auth)
return
}
Expand Down
164 changes: 92 additions & 72 deletions src/component-library/Pages/Login/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,59 @@ export interface LoginPageProps {
}


interface MultipleAccountsModal {
submit: (account: string | undefined) => Promise<void>,
availableAccounts: string[],
onClose: () => void,
}


const MultipleAccountsModal = ({
submit,
availableAccounts,
onClose
}: MultipleAccountsModal) => {
const [chosenAccount, setChosenAccount] = useState<string | undefined>(undefined);

return <Modal
isOpen={availableAccounts.length !== 0}
onRequestClose={() => {
setChosenAccount(undefined)
onClose()
}}
ariaHideApp={false}
overlayClassName="fixed inset-0 z-40 flex items-center justify-center bg-black bg-opacity-50" // will not work if set with twmerge (uses custom regex)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noice!!

className={twMerge(
"mx-2 max-w-3xl rounded shadow-lg z-50",
"border-2",
"bg-neutral-0 dark:bg-neutral-800",
"flex flex-col space-y-2 p-6",
"justify-center items-center overflow-hidden outline-none focus:outline-none"
)}
contentLabel="Multiaccount Modal"
>
<H2>Select Account</H2>
<P className="text-center py-2">
Multiple accounts are mapped to the passed credentials. Choose one to continue.
</P>
<Dropdown<string>
keys={availableAccounts}
renderFunc={(key: string | undefined) => { return (<p>{key ?? "Select an account"}</p>) }}
handleChange={(key: string | undefined) => { setChosenAccount(key) }}
disableUndefined
/>
<Button
type="submit"
label="Select"
disabled={chosenAccount === undefined}
onClick={async () => {
await submit(chosenAccount);
}}
/>
</Modal>
}


export const Login = ({
loginViewModel,
authViewModel,
Expand All @@ -43,30 +96,43 @@ export const Login = ({

const [username, setUsername] = useState<string>("")
const [password, setPassword] = useState<string>("")
const [account, setAccount] = useState<string | undefined>(undefined)
const [inputAccount, setInputAccount] = useState<string | undefined>(undefined)

const [error, setError] = useState<string | undefined>(undefined)

const [multiaccounts, setMultiaccounts] = useState<string[]>([])
const [multiaccountModal, setMultiaccountModal] = useState<boolean>(false)
const [availableAccounts, setAvailableAccounts] = useState<string[]>([])
const [lastAuthMethod, setLastAuthMethod] = useState<'userpass' | 'x509' | undefined>(undefined)

useEffect(() => {
setMultiaccounts(authViewModel?.rucioMultiAccount?.split(',') ?? [])
}, [authViewModel])
useEffect(() => {
if (multiaccounts.length > 1) {
setMultiaccountModal(true)
} else {
setMultiaccountModal(false)
const handleAuthViewModel = (authViewModel: AuthViewModel) => {
if (authViewModel.status === 'error') {
setError(authViewModel.message)
} else if (authViewModel.status === 'multiple_accounts') {
const accounts = authViewModel.message?.split(',')
setAvailableAccounts(accounts ?? [])
}
}, [multiaccounts])
};

const submitX509 = async (account: string | undefined) => {
const vo = loginViewModel.voList[selectedVOTab] || DefaultVO
const x509AuthViewModel = await handleX509Submit(vo, loginViewModel, account)

if (!x509AuthViewModel) return

setLastAuthMethod('x509')
handleAuthViewModel(x509AuthViewModel)
handleX509Session(x509AuthViewModel, account || "", vo.shortName)
};

const submitUserPass = async (account: string | undefined) => {
handleUserPassSubmit(username, password, loginViewModel.voList[selectedVOTab], account)
setLastAuthMethod('userpass')
return Promise.resolve()
}

useEffect(() => {
if (authViewModel && authViewModel.status === 'error') {
setError(authViewModel.message)
return
}
if (loginViewModel && loginViewModel.status === 'error') {
if (authViewModel) {
handleAuthViewModel(authViewModel)
} else if (loginViewModel && loginViewModel.status === 'error') {
setError(loginViewModel.message)
}
}, [loginViewModel, authViewModel])
Expand All @@ -81,33 +147,11 @@ export const Login = ({
)}
id="root"
>
<Modal
isOpen={multiaccountModal}
onRequestClose={() => { setMultiaccountModal(false) }}
overlayClassName="fixed bg-transparent inset-0 z-0" // will not work if set with twmerge (uses custom regex)
className={twMerge(
"absolute top-32 inset-x-32 rounded shadow",
"border-2",
"bg-neutral-0 dark:bg-neutral-800",
"flex flex-col space-y-2 p-2"
)}
contentLabel="Multiaccount Modal"
>
<H2>Select Account</H2>
<P>Multiple accounts are mapped to the passed credentials. Choose one to continue.</P>
<Dropdown<string>
keys={multiaccounts}
renderFunc={(key: string | undefined) => { return (<p>{key ?? "select an account"}</p>) }}
handleChange={(key: string | undefined) => { setAccount(key) }}
disableUndefined
/>
<Button
type="submit"
label="Select"
disabled={account === undefined}
onClick={() => {setMultiaccountModal(false)}}
/>
</Modal>
<MultipleAccountsModal
submit={lastAuthMethod === 'x509' ? submitX509 : submitUserPass}
availableAccounts={availableAccounts}
onClose={() => setAvailableAccounts([])}
/>
<Collapsible id='login-page-error' showIf={error !== undefined}>
<Alert variant="error" message={error} onClose={
() => {
Expand Down Expand Up @@ -151,24 +195,7 @@ export const Login = ({
</div>
: <></>}

<Button
label="x509"
onClick={async () => {
const vo = loginViewModel.voList[selectedVOTab] || DefaultVO

const x509AuthViewModel = await handleX509Submit(vo, loginViewModel, account)

if (x509AuthViewModel) {
if (x509AuthViewModel.status === 'error') {
setError(x509AuthViewModel.message)
return
}
console.log("x509AuthViewModel", x509AuthViewModel)
console.log("voName", vo.shortName)
await handleX509Session(x509AuthViewModel, account || "", vo.shortName)
}
}}
/>
<Button label="x509" onClick={() => submitX509(inputAccount)}/>

<Button label="Userpass" onClick={() => {
setShowUserPassLoginForm(!showUserPassLoginForm)
Expand Down Expand Up @@ -202,21 +229,14 @@ export const Login = ({
<LabelledInput
label="Account"
idinput="account-input"
updateFunc={(data: string) => { setAccount(data) }}
updateFunc={(data: string) => { setInputAccount(data) }}
/>
</div>
<Button
label="Login"
type="submit"
role="button"
onClick={async () => {
await handleUserPassSubmit(
username,
password,
loginViewModel.voList[selectedVOTab],
account
)
}}
onClick={() => submitUserPass(inputAccount)}
/>
</fieldset>
</form>
Expand All @@ -231,7 +251,7 @@ export const Login = ({
<LabelledInput
label="Account"
idinput="account-input-nouserpass"
updateFunc={(data: string) => { setAccount(data) }}
updateFunc={(data: string) => { setInputAccount(data) }}
/>
</fieldset>
</div>
Expand Down
Loading
Loading