Feat: Add frontend support for third-party login integration (#7553)

### What problem does this PR solve?

Add frontend support for third-party login integration:

- Used `getLoginChannels` API to fetch available login channels from the
server
- Used `loginWithChannel` function to initiate login based on the
selected channel
- Refactored `useLoginWithGithub` hook to `useOAuthCallback` for
generalized OAuth callback handling
- Updated the login page to dynamically render third-party login buttons
based on the fetched channel list
- Styled third-party login buttons to improve user experience
- Removed unused code snippets

> This PR removes the previously hardcoded GitHub login button. Since
the functionality only worked when `location.host` was equal to
`demo.ragflow.io`, and the authentication logic is now based on
`login.ragflow.io`, this change does not affect the existing logic and
is considered a non-breaking change
---
#### Frontend Screenshot && Backend Configuration


![image](https://github.com/user-attachments/assets/190ad3a5-3718-409a-ad0e-01e7aca39069)

```yaml
# docker/service_conf.yaml.template

# ...
oauth:
  github:
    icon: github
    display_name: "Github"
    # ...

  custom_channel:
    display_name: "OIDC"
    # ...

  custom_channel_2:
    display_name: "OAuth2"
    # ...
```
---
- Related pull requests:
  - #7379
  - #7521 
- Related issues:
  - #3495 

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
- [x] Refactoring
- [x] Performance Improvement
This commit is contained in:
Chaoxi Weng
2025-05-14 12:19:28 +08:00
committed by GitHub
parent d06431f670
commit e7a6a9e47e
7 changed files with 153 additions and 54 deletions

View File

@ -19,6 +19,35 @@
margin: 0 auto;
}
.thirdPartyLoginButton {
margin-top: 10px;
border-top: 1px solid rgba(0, 0, 0, 0.06);
// padding-top: 0px;
.ant-btn {
display: flex;
align-items: center;
justify-content: center;
height: 40px;
font-size: 14px;
border-radius: 4px;
border: 1px solid #d9d9d9;
background: #fff;
color: rgba(0, 0, 0, 0.85);
transition: all 0.3s;
&:hover {
color: #40a9ff;
border-color: #40a9ff;
}
.anticon {
font-size: 16px;
margin-right: 8px;
}
}
}
.loginRight {
display: flex;
align-items: center;

View File

@ -1,13 +1,19 @@
import { useLogin, useRegister } from '@/hooks/login-hooks';
import SvgIcon from '@/components/svg-icon';
import { useAuth } from '@/hooks/auth-hooks';
import {
useLogin,
useLoginChannels,
useLoginWithChannel,
useRegister,
} from '@/hooks/login-hooks';
import { useSystemConfig } from '@/hooks/system-hooks';
import { rsaPsw } from '@/utils';
import { Button, Checkbox, Form, Input } from 'antd';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Icon, useNavigate } from 'umi';
import { useNavigate } from 'umi';
import RightPanel from './right-panel';
import { Domain } from '@/constants/common';
import styles from './index.less';
const Login = () => {
@ -15,11 +21,29 @@ const Login = () => {
const navigate = useNavigate();
const { login, loading: signLoading } = useLogin();
const { register, loading: registerLoading } = useRegister();
const { channels, loading: channelsLoading } = useLoginChannels();
const { login: loginWithChannel, loading: loginWithChannelLoading } =
useLoginWithChannel();
const { t } = useTranslation('translation', { keyPrefix: 'login' });
const loading = signLoading || registerLoading;
const loading =
signLoading ||
registerLoading ||
channelsLoading ||
loginWithChannelLoading;
const { config } = useSystemConfig();
const registerEnabled = config?.registerEnabled !== 0;
const { isLogin } = useAuth();
useEffect(() => {
if (isLogin) {
navigate('/knowledge');
}
}, [isLogin, navigate]);
const handleLoginWithChannel = async (channel: string) => {
await loginWithChannel(channel);
};
const changeTitle = () => {
if (title === 'login' && !registerEnabled) {
return;
@ -65,11 +89,6 @@ const Login = () => {
// wrapperCol: { span: 8 },
};
const toGoogle = () => {
window.location.href =
'https://github.com/login/oauth/authorize?scope=user:email&client_id=302129228f0d96055bee';
};
return (
<div className={styles.loginPage}>
<div className={styles.loginLeft}>
@ -151,39 +170,28 @@ const Login = () => {
>
{title === 'login' ? t('login') : t('continue')}
</Button>
{title === 'login' && (
<>
{/* <Button
block
size="large"
onClick={toGoogle}
style={{ marginTop: 15 }}
>
<div>
<Icon
icon="local:google"
style={{ verticalAlign: 'middle', marginRight: 5 }}
/>
Sign in with Google
</div>
</Button> */}
{location.host === Domain && (
{title === 'login' && channels && channels.length > 0 && (
<div className={styles.thirdPartyLoginButton}>
{channels.map((item) => (
<Button
key={item.channel}
block
size="large"
onClick={toGoogle}
style={{ marginTop: 15 }}
onClick={() => handleLoginWithChannel(item.channel)}
style={{ marginTop: 10 }}
>
<div className="flex items-center">
<Icon
icon="local:github"
style={{ verticalAlign: 'middle', marginRight: 5 }}
<SvgIcon
name={item.icon || 'sso'}
width={20}
height={20}
style={{ marginRight: 5 }}
/>
Sign in with Github
Sign in with {item.display_name}
</div>
</Button>
)}
</>
))}
</div>
)}
</Form>
</div>