UNPKG

@datalayer/core

Version:

[![Datalayer](https://assets.datalayer.tech/datalayer-25.svg)](https://datalayer.io)

169 lines (168 loc) 9.61 kB
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; /* * Copyright (c) 2023-2025 Datalayer, Inc. * Distributed under the terms of the Modified BSD License. */ import { useEffect, useState } from 'react'; import { PageConfig, URLExt } from '@jupyterlab/coreutils'; import { EyeIcon, EyeClosedIcon, MarkGithubIcon } from '@primer/octicons-react'; import { Button, FormControl, Heading, Link, PageLayout, TextInput, } from '@primer/react'; import { Box } from '@datalayer/primer-addons'; import { asUser, IAMProvidersSpecs } from '../../models'; import { useIAMStore } from '../../state'; import { CenteredSpinner } from '../display'; import { isInsideJupyterLab, validateLength } from '../../utils'; import { useNavigate, useCache, useToast, useIAM } from '../../hooks'; import { LoginToken } from './LoginToken'; export const Login = (props) => { const { heading, homeRoute, loginRoute, showEmailLogin = true, showGitHubLogin = true, showTokenLogin = true, } = props; const { useLogin, useOAuth2AuthorizationURL } = useCache({ loginRoute }); const loginMutation = useLogin(); const getOAuth2URLMutation = useOAuth2AuthorizationURL(); const { loginAndNavigate, setLogin } = useIAM(); const { externalToken, iamProvidersAuthorizationURL, addIAMProviderAuthorizationURL, iamRunUrl, logout, checkIAMToken, } = useIAMStore(); const { enqueueToast } = useToast(); const navigate = useNavigate(); const [loading, setLoading] = useState(false); const [loadingWithToken, setLoadingWithToken] = useState(-1); const [formValues, setFormValues] = useState({ handle: undefined, password: undefined, }); const [validationResult, setValidationResult] = useState({ handle: undefined, password: undefined, }); const [passwordVisibility, setPasswordVisibility] = useState(false); useEffect(() => { const initIAMProvider = (iamProvider) => { const callbackURI = isInsideJupyterLab() ? URLExt.join(PageConfig.getBaseUrl(), iamProvider.oauth2CallbackServerRoute) : location.protocol + '//' + location.hostname + ':' + location.port + iamProvider.oauth2CallbackUIRoute; const queryArgs = { provider: iamProvider.name, callback_uri: callbackURI, }; /* const xsrfTokenMatch = document.cookie.match('\\b_xsrf=([^;]*)\\b'); if (xsrfTokenMatch) { queryArgs['xsrf'] = xsrfTokenMatch[1]; } */ getOAuth2URLMutation.mutate(queryArgs, { onSuccess: authUrl => { if (authUrl) { addIAMProviderAuthorizationURL(iamProvider.name, authUrl); } else { console.error(`Failed to get the Login URL from Datalayer IAM for provider ${iamProvider.name}.`); } }, }); }; if (!iamProvidersAuthorizationURL[IAMProvidersSpecs.GitHub.name]) { initIAMProvider(IAMProvidersSpecs.GitHub); } if (!iamProvidersAuthorizationURL[IAMProvidersSpecs.LinkedIn.name]) { initIAMProvider(IAMProvidersSpecs.LinkedIn); } }, [iamRunUrl, iamProvidersAuthorizationURL, addIAMProviderAuthorizationURL]); useEffect(() => { if (externalToken) { setLoadingWithToken(1); loginAndNavigate(externalToken, logout, checkIAMToken, navigate, homeRoute) .catch(error => { console.debug('Failed to login with token from cookie..', error); enqueueToast('Failed to check authentication.', { variant: 'error' }); }) .finally(() => { setLoadingWithToken(-1); }); } }, [externalToken]); const handleKeyDown = (event) => { if (event.key === 'Enter') { submit(); } }; const handleHandleChange = (event) => { setFormValues(prevFormValues => ({ ...prevFormValues, handle: event.target.value, })); }; const handlePasswordChange = (event) => { setFormValues(prevFormValues => ({ ...prevFormValues, password: event.target.value, })); }; const submit = async () => { if (loading || validationResult.handle !== '' || validationResult.password !== '') { return; } setLoading(true); loginMutation.mutate({ handle: formValues.handle, password: formValues.password, }, { onSuccess: (resp) => { if (resp.success) { setValidationResult({ handle: '', password: '' }); const user = asUser(resp.user); const token = resp.token; setLogin(user, token); navigate(homeRoute); } else { enqueueToast('Failed to login. Check your username and password.', { variant: 'warning', }); setValidationResult({ handle: '', password: 'Invalid credentials', }); console.debug(`Failed to login: ${resp.message}`, resp.errors ?? ''); } }, onSettled: () => { setLoading(false); }, }); }; useEffect(() => { setValidationResult({ ...validationResult, handle: formValues.handle === undefined ? undefined : validateLength(formValues.handle, 1) ? '' : 'Your username may not be empty.', password: formValues.password === undefined ? undefined : validateLength(formValues.password, 1) ? '' : 'Your password may not be empty.', }); }, [formValues]); return (_jsxs(PageLayout, { containerWidth: "medium", padding: "normal", style: { overflow: 'visible', minHeight: 'calc(100vh - 45px)' }, children: [_jsx(PageLayout.Header, { children: _jsx(Heading, { children: heading || 'Login to Datalayer' }) }), _jsx(PageLayout.Content, { children: loadingWithToken < 0 ? (_jsx(_Fragment, { children: _jsxs(Box, { display: "flex", children: [showEmailLogin && (_jsxs(Box, { sx: { label: { marginTop: 2 }, paddingRight: '10%' }, children: [_jsx(Box, { mt: 5, children: _jsxs(FormControl, { required: true, children: [_jsx(FormControl.Label, { children: "Your username" }), _jsx(TextInput, { autoFocus: true, placeholder: "Your username", value: formValues.handle, onChange: handleHandleChange, onKeyDown: handleKeyDown }), validationResult.handle && (_jsx(FormControl.Validation, { variant: validationResult.handle ? 'error' : 'success', children: validationResult.handle }))] }) }), _jsx(Box, { children: _jsxs(FormControl, { required: true, children: [_jsx(FormControl.Label, { children: "Your password" }), _jsx(TextInput, { placeholder: "Your password", type: passwordVisibility ? 'text' : 'password', value: formValues.password, onChange: handlePasswordChange, onKeyDown: handleKeyDown, trailingAction: _jsx(TextInput.Action, { onClick: () => { setPasswordVisibility(!passwordVisibility); }, icon: passwordVisibility ? EyeClosedIcon : EyeIcon, "aria-label": passwordVisibility ? 'Hide password' : 'Show password', sx: { color: 'var(--fgColor-muted)' } }), sx: { overflow: 'visible' } }), validationResult.password && (_jsx(FormControl.Validation, { variant: validationResult.password ? 'error' : 'success', children: validationResult.password }))] }) }), _jsxs(Box, { mt: 5, children: [_jsx(Button, { variant: "primary", disabled: loading || validationResult.handle !== '' || validationResult.password !== '', onClick: submit, children: loading ? 'Login…' : heading ? 'Login with Datalayer' : 'Login' }), _jsx(Box, { pt: 6 }), _jsx(Link, { href: "https://datalayer.app/password", target: "_blank", children: "Forgot password?" })] })] })), _jsx(Box, { children: _jsxs(Box, { display: "flex", flexDirection: "column", sx: { margin: 'auto' }, children: [showGitHubLogin && iamProvidersAuthorizationURL[IAMProvidersSpecs.GitHub.name] && (_jsx(Button, { leadingVisual: MarkGithubIcon, href: iamProvidersAuthorizationURL[IAMProvidersSpecs.GitHub.name], as: "a", style: { margin: '10px 0' }, children: "Login with GitHub" })), showTokenLogin && (_jsx(LoginToken, { homeRoute: homeRoute, style: { margin: '10px 0' } }))] }) })] }) })) : loadingWithToken ? (_jsx(CenteredSpinner, { message: "Checking authentication\u2026" })) : (_jsx(_Fragment, {})) })] })); }; export default Login;