|
@@ -1,15 +1,12 @@
|
|
|
import { userAtom } from '@/atoms/user';
|
|
import { userAtom } from '@/atoms/user';
|
|
|
-import { useIntl, useModel } from '@umijs/max';
|
|
|
|
|
-import { Button, Divider, Form, Spin, message } from 'antd';
|
|
|
|
|
|
|
+import { useModel } from '@umijs/max';
|
|
|
|
|
+import { Button, Spin, message } from 'antd';
|
|
|
import { createStyles } from 'antd-style';
|
|
import { createStyles } from 'antd-style';
|
|
|
import { useAtom } from 'jotai';
|
|
import { useAtom } from 'jotai';
|
|
|
-import { useMemo, useState } from 'react';
|
|
|
|
|
|
|
+import { useState } from 'react';
|
|
|
import { flushSync } from 'react-dom';
|
|
import { flushSync } from 'react-dom';
|
|
|
import styled from 'styled-components';
|
|
import styled from 'styled-components';
|
|
|
-import { useLocalAuth } from '../hooks/use-local-auth';
|
|
|
|
|
-import { useSSOAuth } from '../hooks/use-sso-auth';
|
|
|
|
|
import { checkDefaultPage } from '../utils';
|
|
import { checkDefaultPage } from '../utils';
|
|
|
-import LocalUserForm from './local-user-form';
|
|
|
|
|
|
|
|
|
|
const SpinContainer = styled.div`
|
|
const SpinContainer = styled.div`
|
|
|
display: flex;
|
|
display: flex;
|
|
@@ -33,14 +30,6 @@ const Buttons = styled.div`
|
|
|
margin-top: 52px;
|
|
margin-top: 52px;
|
|
|
`;
|
|
`;
|
|
|
|
|
|
|
|
-const BackButton = styled(Button).attrs({
|
|
|
|
|
- type: 'link',
|
|
|
|
|
- size: 'small',
|
|
|
|
|
- block: true
|
|
|
|
|
-})`
|
|
|
|
|
- margin-top: 20px;
|
|
|
|
|
-`;
|
|
|
|
|
-
|
|
|
|
|
const ButtonWrapper = styled(Button).attrs({
|
|
const ButtonWrapper = styled(Button).attrs({
|
|
|
type: 'primary',
|
|
type: 'primary',
|
|
|
block: true
|
|
block: true
|
|
@@ -54,13 +43,6 @@ const ButtonText = styled.span`
|
|
|
gap: 8px;
|
|
gap: 8px;
|
|
|
`;
|
|
`;
|
|
|
|
|
|
|
|
-const DividerWrapper = styled(Divider)`
|
|
|
|
|
- margin-block: 24px !important;
|
|
|
|
|
- .ant-divider-inner-text {
|
|
|
|
|
- color: var(--ant-color-text-secondary);
|
|
|
|
|
- }
|
|
|
|
|
-`;
|
|
|
|
|
-
|
|
|
|
|
const useStyles = createStyles(({ token, css }) => ({
|
|
const useStyles = createStyles(({ token, css }) => ({
|
|
|
errorMessage: css`
|
|
errorMessage: css`
|
|
|
display: flex;
|
|
display: flex;
|
|
@@ -84,15 +66,16 @@ const useStyles = createStyles(({ token, css }) => ({
|
|
|
`
|
|
`
|
|
|
}));
|
|
}));
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * 修改后的登录表单:隐藏本地用户名密码登录,仅显示 SSO 登录按钮
|
|
|
|
|
+ * 点击后直接跳转至 SSO 授权页 (/auth/sso/authorize?redirect=true)
|
|
|
|
|
+ */
|
|
|
const LoginForm = () => {
|
|
const LoginForm = () => {
|
|
|
const [messageApi, contextHolder] = message.useMessage();
|
|
const [messageApi, contextHolder] = message.useMessage();
|
|
|
const { styles } = useStyles();
|
|
const { styles } = useStyles();
|
|
|
const [, setUserInfo] = useAtom(userAtom);
|
|
const [, setUserInfo] = useAtom(userAtom);
|
|
|
const { initialState, setInitialState } = useModel('@@initialState') || {};
|
|
const { initialState, setInitialState } = useModel('@@initialState') || {};
|
|
|
const [authError, setAuthError] = useState<Error | null>(null);
|
|
const [authError, setAuthError] = useState<Error | null>(null);
|
|
|
- const intl = useIntl();
|
|
|
|
|
- const [form] = Form.useForm();
|
|
|
|
|
- const [isPassword, setIsPassword] = useState(false);
|
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
|
|
|
|
const renderWelCome = () => {
|
|
const renderWelCome = () => {
|
|
@@ -131,113 +114,35 @@ const LoginForm = () => {
|
|
|
duration: 5,
|
|
duration: 5,
|
|
|
content: (
|
|
content: (
|
|
|
<div className={styles.errorMessage}>
|
|
<div className={styles.errorMessage}>
|
|
|
- <div className="title">
|
|
|
|
|
- {intl.formatMessage({ id: 'common.login.auth.failed' })}
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <div className="title">登录失败</div>
|
|
|
<div className="message">{error?.message || 'Unknown error'}</div>
|
|
<div className="message">{error?.message || 'Unknown error'}</div>
|
|
|
</div>
|
|
</div>
|
|
|
)
|
|
)
|
|
|
});
|
|
});
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- // local user authentication
|
|
|
|
|
- const { handleLogin, submitLoading } = useLocalAuth({
|
|
|
|
|
- fetchUserInfo,
|
|
|
|
|
- form,
|
|
|
|
|
- onSuccess: async (userInfo) => {
|
|
|
|
|
- setUserInfo(userInfo);
|
|
|
|
|
- if (!userInfo?.require_password_change) {
|
|
|
|
|
- gotoDefaultPage(userInfo);
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
-
|
|
|
|
|
- onError: (error) => {
|
|
|
|
|
- // gpustack handle in the interceptor
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- // SSO hook
|
|
|
|
|
- const SSOAuth = useSSOAuth({
|
|
|
|
|
- fetchUserInfo,
|
|
|
|
|
- onSuccess: (userInfo) => {
|
|
|
|
|
- setUserInfo(userInfo);
|
|
|
|
|
- gotoDefaultPage({});
|
|
|
|
|
- },
|
|
|
|
|
- onLoading: (loading) => {
|
|
|
|
|
- setLoading(loading);
|
|
|
|
|
- },
|
|
|
|
|
- onError: handleOnError
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- const handleLoginWithPassword = () => {
|
|
|
|
|
- setIsPassword(true);
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- const handleLoginWithThirdParty = () => {
|
|
|
|
|
- if (SSOAuth.options.oidc) {
|
|
|
|
|
- SSOAuth.loginWithOIDC();
|
|
|
|
|
- } else if (SSOAuth.options.saml) {
|
|
|
|
|
- SSOAuth.loginWithSAML();
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * SSO 登录:跳转至后端 SSO 授权 URL
|
|
|
|
|
+ * 后端会 302 重定向到统一认证平台登录页
|
|
|
|
|
+ */
|
|
|
|
|
+ const handleSSOLogin = () => {
|
|
|
setLoading(true);
|
|
setLoading(true);
|
|
|
setAuthError(null);
|
|
setAuthError(null);
|
|
|
|
|
+ // 直接跳转至 SSO 授权页
|
|
|
|
|
+ window.location.href = '/auth/sso/authorize?redirect=true';
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- const hasThirdPartyLogin = useMemo(() => {
|
|
|
|
|
- return SSOAuth.options.oidc || SSOAuth.options.saml;
|
|
|
|
|
- }, [SSOAuth.options]);
|
|
|
|
|
-
|
|
|
|
|
- const isThirdPartyAuthHandling = useMemo(() => {
|
|
|
|
|
- return loading && !authError;
|
|
|
|
|
- }, [loading, authError]);
|
|
|
|
|
-
|
|
|
|
|
- const renderLoginButtons = () => {
|
|
|
|
|
- // do not render login buttons if using password login or no third-party login
|
|
|
|
|
- if (!hasThirdPartyLogin || isPassword) return null;
|
|
|
|
|
-
|
|
|
|
|
- return (
|
|
|
|
|
- <Buttons>
|
|
|
|
|
- {SSOAuth.options.oidc && (
|
|
|
|
|
- <ButtonWrapper onClick={SSOAuth.loginWithOIDC}>
|
|
|
|
|
- <ButtonText>
|
|
|
|
|
- {intl.formatMessage(
|
|
|
|
|
- { id: 'common.external.login' },
|
|
|
|
|
- { type: 'SSO' }
|
|
|
|
|
- )}
|
|
|
|
|
- </ButtonText>
|
|
|
|
|
- </ButtonWrapper>
|
|
|
|
|
- )}
|
|
|
|
|
- {SSOAuth.options.saml && (
|
|
|
|
|
- <ButtonWrapper onClick={SSOAuth.loginWithSAML}>
|
|
|
|
|
- <ButtonText>
|
|
|
|
|
- {intl.formatMessage(
|
|
|
|
|
- { id: 'common.external.login' },
|
|
|
|
|
- { type: 'SSO' }
|
|
|
|
|
- )}
|
|
|
|
|
- </ButtonText>
|
|
|
|
|
- </ButtonWrapper>
|
|
|
|
|
- )}
|
|
|
|
|
- <Button type="link" block onClick={handleLoginWithPassword}>
|
|
|
|
|
- <ButtonText>
|
|
|
|
|
- {intl.formatMessage({ id: 'common.login.password' })}
|
|
|
|
|
- </ButtonText>
|
|
|
|
|
- </Button>
|
|
|
|
|
- </Buttons>
|
|
|
|
|
- );
|
|
|
|
|
- };
|
|
|
|
|
|
|
+ const isSSOAuthHandling = loading && !authError;
|
|
|
|
|
|
|
|
return (
|
|
return (
|
|
|
<div>
|
|
<div>
|
|
|
{contextHolder}
|
|
{contextHolder}
|
|
|
<div>
|
|
<div>
|
|
|
- {isThirdPartyAuthHandling ? (
|
|
|
|
|
|
|
+ {isSSOAuthHandling ? (
|
|
|
<SpinContainer>
|
|
<SpinContainer>
|
|
|
{renderWelCome()}
|
|
{renderWelCome()}
|
|
|
<div className="spin">
|
|
<div className="spin">
|
|
|
- <Spin
|
|
|
|
|
- tip={intl.formatMessage({ id: 'common.login.auth' })}
|
|
|
|
|
- size="middle"
|
|
|
|
|
- >
|
|
|
|
|
|
|
+ <Spin tip="正在跳转至统一认证平台..." size="middle">
|
|
|
<div style={{ width: 300 }}></div>
|
|
<div style={{ width: 300 }}></div>
|
|
|
</Spin>
|
|
</Spin>
|
|
|
</div>
|
|
</div>
|
|
@@ -245,23 +150,11 @@ const LoginForm = () => {
|
|
|
) : (
|
|
) : (
|
|
|
<>
|
|
<>
|
|
|
{renderWelCome()}
|
|
{renderWelCome()}
|
|
|
- {renderLoginButtons()}
|
|
|
|
|
- {(!hasThirdPartyLogin || isPassword) && (
|
|
|
|
|
- <LocalUserForm
|
|
|
|
|
- handleLogin={handleLogin}
|
|
|
|
|
- form={form}
|
|
|
|
|
- loading={submitLoading}
|
|
|
|
|
- loginOption={SSOAuth.options}
|
|
|
|
|
- />
|
|
|
|
|
- )}
|
|
|
|
|
- {hasThirdPartyLogin && isPassword && (
|
|
|
|
|
- <BackButton onClick={handleLoginWithThirdParty}>
|
|
|
|
|
- {intl.formatMessage(
|
|
|
|
|
- { id: 'common.external.login' },
|
|
|
|
|
- { type: 'SSO' }
|
|
|
|
|
- )}
|
|
|
|
|
- </BackButton>
|
|
|
|
|
- )}
|
|
|
|
|
|
|
+ <Buttons>
|
|
|
|
|
+ <ButtonWrapper onClick={handleSSOLogin}>
|
|
|
|
|
+ <ButtonText>统一认证平台登录</ButtonText>
|
|
|
|
|
+ </ButtonWrapper>
|
|
|
|
|
+ </Buttons>
|
|
|
</>
|
|
</>
|
|
|
)}
|
|
)}
|
|
|
</div>
|
|
</div>
|