mirror of
https://github.com/songquanpeng/one-api.git
synced 2025-10-27 20:03:42 +08:00
* feat: add the ui for configuring the third-party standard OAuth2.0/OIDC. - update SystemSetting.js - add setup ui - add configuration * feat: add the ui for "allow the OAuth 2.0 to login" - update SystemSetting.js * feat: add OAuth 2.0 web ui and its process functions - update common.js - update AuthLogin.js - update config.js * fix: missing "Userinfo" endpoint configuration entry, used by OAuth clients to request user information from the IdP. - update config.js - update SystemSetting.js * feat: updated the icons for Lark and OIDC to match the style of the icons for WeChat, EMail, GitHub. - update lark.svg - new oidc.svg * refactor: Changing OAuth 2.0 to OIDC * feat: add OIDC login method * feat: Add support for OIDC login to the backend * fix: Change the AppId and AppSecret on the Web UI to the standard usage: ClientId, ClientSecret. * feat: Support quick configuration of OIDC through Well-Known Discovery Endpoint * feat: Standardize terminology, add well-known configuration - Change the AppId and AppSecret on the Server End to the standard usage: ClientId, ClientSecret. - add Well-Known configuration to store in database, no actual use in server end but store and display in web ui only
317 lines
11 KiB
JavaScript
317 lines
11 KiB
JavaScript
import { useState } from 'react';
|
|
import { useSelector } from 'react-redux';
|
|
import { Link } from 'react-router-dom';
|
|
|
|
// material-ui
|
|
import { useTheme } from '@mui/material/styles';
|
|
import {
|
|
Box,
|
|
Button,
|
|
Divider,
|
|
FormControl,
|
|
FormHelperText,
|
|
Grid,
|
|
IconButton,
|
|
InputAdornment,
|
|
InputLabel,
|
|
OutlinedInput,
|
|
Stack,
|
|
Typography,
|
|
useMediaQuery
|
|
} from '@mui/material';
|
|
|
|
// third party
|
|
import * as Yup from 'yup';
|
|
import { Formik } from 'formik';
|
|
|
|
// project imports
|
|
import useLogin from 'hooks/useLogin';
|
|
import AnimateButton from 'ui-component/extended/AnimateButton';
|
|
import WechatModal from 'views/Authentication/AuthForms/WechatModal';
|
|
|
|
// assets
|
|
import Visibility from '@mui/icons-material/Visibility';
|
|
import VisibilityOff from '@mui/icons-material/VisibilityOff';
|
|
|
|
import Github from 'assets/images/icons/github.svg';
|
|
import Wechat from 'assets/images/icons/wechat.svg';
|
|
import Lark from 'assets/images/icons/lark.svg';
|
|
import OIDC from 'assets/images/icons/oidc.svg';
|
|
import { onGitHubOAuthClicked, onLarkOAuthClicked, onOidcClicked } from 'utils/common';
|
|
|
|
// ============================|| FIREBASE - LOGIN ||============================ //
|
|
|
|
const LoginForm = ({ ...others }) => {
|
|
const theme = useTheme();
|
|
const { login, wechatLogin } = useLogin();
|
|
const [openWechat, setOpenWechat] = useState(false);
|
|
const matchDownSM = useMediaQuery(theme.breakpoints.down('md'));
|
|
const customization = useSelector((state) => state.customization);
|
|
const siteInfo = useSelector((state) => state.siteInfo);
|
|
// const [checked, setChecked] = useState(true);
|
|
|
|
let tripartiteLogin = false;
|
|
if (siteInfo.github_oauth || siteInfo.wechat_login || siteInfo.lark_client_id || siteInfo.oidc) {
|
|
tripartiteLogin = true;
|
|
}
|
|
|
|
const handleWechatOpen = () => {
|
|
setOpenWechat(true);
|
|
};
|
|
|
|
const handleWechatClose = () => {
|
|
setOpenWechat(false);
|
|
};
|
|
|
|
const [showPassword, setShowPassword] = useState(false);
|
|
const handleClickShowPassword = () => {
|
|
setShowPassword(!showPassword);
|
|
};
|
|
|
|
const handleMouseDownPassword = (event) => {
|
|
event.preventDefault();
|
|
};
|
|
|
|
return (
|
|
<>
|
|
{tripartiteLogin && (
|
|
<Grid container direction="column" justifyContent="center" spacing={2}>
|
|
{siteInfo.github_oauth && (
|
|
<Grid item xs={12}>
|
|
<AnimateButton>
|
|
<Button
|
|
disableElevation
|
|
fullWidth
|
|
onClick={() => onGitHubOAuthClicked(siteInfo.github_client_id)}
|
|
size="large"
|
|
variant="outlined"
|
|
sx={{
|
|
color: 'grey.700',
|
|
backgroundColor: theme.palette.grey[50],
|
|
borderColor: theme.palette.grey[100]
|
|
}}
|
|
>
|
|
<Box sx={{ mr: { xs: 1, sm: 2, width: 20 }, display: 'flex', alignItems: 'center' }}>
|
|
<img src={Github} alt="github" width={25} height={25} style={{ marginRight: matchDownSM ? 8 : 16 }} />
|
|
</Box>
|
|
使用 GitHub 登录
|
|
</Button>
|
|
</AnimateButton>
|
|
</Grid>
|
|
)}
|
|
{siteInfo.wechat_login && (
|
|
<Grid item xs={12}>
|
|
<AnimateButton>
|
|
<Button
|
|
disableElevation
|
|
fullWidth
|
|
onClick={handleWechatOpen}
|
|
size="large"
|
|
variant="outlined"
|
|
sx={{
|
|
color: 'grey.700',
|
|
backgroundColor: theme.palette.grey[50],
|
|
borderColor: theme.palette.grey[100]
|
|
}}
|
|
>
|
|
<Box sx={{ mr: { xs: 1, sm: 2, width: 20 }, display: 'flex', alignItems: 'center' }}>
|
|
<img src={Wechat} alt="Wechat" width={25} height={25} style={{ marginRight: matchDownSM ? 8 : 16 }} />
|
|
</Box>
|
|
使用微信登录
|
|
</Button>
|
|
</AnimateButton>
|
|
<WechatModal open={openWechat} handleClose={handleWechatClose} wechatLogin={wechatLogin} qrCode={siteInfo.wechat_qrcode} />
|
|
</Grid>
|
|
)}
|
|
{siteInfo.lark_client_id && (
|
|
<Grid item xs={12}>
|
|
<AnimateButton>
|
|
<Button
|
|
disableElevation
|
|
fullWidth
|
|
onClick={() => onLarkOAuthClicked(siteInfo.lark_client_id)}
|
|
size="large"
|
|
variant="outlined"
|
|
sx={{
|
|
color: 'grey.700',
|
|
backgroundColor: theme.palette.grey[50],
|
|
borderColor: theme.palette.grey[100]
|
|
}}
|
|
>
|
|
<Box sx={{ mr: { xs: 1, sm: 2, width: 20 }, display: 'flex', alignItems: 'center' }}>
|
|
<img src={Lark} alt="Lark" width={25} height={25} style={{ marginRight: matchDownSM ? 8 : 16 }} />
|
|
</Box>
|
|
使用飞书登录
|
|
</Button>
|
|
</AnimateButton>
|
|
</Grid>
|
|
)}
|
|
{siteInfo.oidc && (
|
|
<Grid item xs={12}>
|
|
<AnimateButton>
|
|
<Button
|
|
disableElevation
|
|
fullWidth
|
|
onClick={() => onOidcClicked(siteInfo.oidc_authorization_endpoint,siteInfo.oidc_client_id)}
|
|
size="large"
|
|
variant="outlined"
|
|
sx={{
|
|
color: 'grey.700',
|
|
backgroundColor: theme.palette.grey[50],
|
|
borderColor: theme.palette.grey[100]
|
|
}}
|
|
>
|
|
<Box sx={{ mr: { xs: 1, sm: 2, width: 20 }, display: 'flex', alignItems: 'center' }}>
|
|
<img src={OIDC} alt="Lark" width={25} height={25} style={{ marginRight: matchDownSM ? 8 : 16 }} />
|
|
</Box>
|
|
使用 OIDC 登录
|
|
</Button>
|
|
</AnimateButton>
|
|
</Grid>
|
|
)}
|
|
<Grid item xs={12}>
|
|
<Box
|
|
sx={{
|
|
alignItems: 'center',
|
|
display: 'flex'
|
|
}}
|
|
>
|
|
<Divider sx={{ flexGrow: 1 }} orientation="horizontal" />
|
|
|
|
<Button
|
|
variant="outlined"
|
|
sx={{
|
|
cursor: 'unset',
|
|
m: 2,
|
|
py: 0.5,
|
|
px: 7,
|
|
borderColor: `${theme.palette.grey[100]} !important`,
|
|
color: `${theme.palette.grey[900]}!important`,
|
|
fontWeight: 500,
|
|
borderRadius: `${customization.borderRadius}px`
|
|
}}
|
|
disableRipple
|
|
disabled
|
|
>
|
|
OR
|
|
</Button>
|
|
|
|
<Divider sx={{ flexGrow: 1 }} orientation="horizontal" />
|
|
</Box>
|
|
</Grid>
|
|
</Grid>
|
|
)}
|
|
|
|
<Formik
|
|
initialValues={{
|
|
username: '',
|
|
password: '',
|
|
submit: null
|
|
}}
|
|
validationSchema={Yup.object().shape({
|
|
username: Yup.string().max(255).required('Username is required'),
|
|
password: Yup.string().max(255).required('Password is required')
|
|
})}
|
|
onSubmit={async (values, { setErrors, setStatus, setSubmitting }) => {
|
|
const { success, message } = await login(values.username, values.password);
|
|
if (success) {
|
|
setStatus({ success: true });
|
|
} else {
|
|
setStatus({ success: false });
|
|
if (message) {
|
|
setErrors({ submit: message });
|
|
}
|
|
}
|
|
setSubmitting(false);
|
|
}}
|
|
>
|
|
{({ errors, handleBlur, handleChange, handleSubmit, isSubmitting, touched, values }) => (
|
|
<form noValidate onSubmit={handleSubmit} {...others}>
|
|
<FormControl fullWidth error={Boolean(touched.username && errors.username)} sx={{ ...theme.typography.customInput }}>
|
|
<InputLabel htmlFor="outlined-adornment-username-login">用户名 / 邮箱</InputLabel>
|
|
<OutlinedInput
|
|
id="outlined-adornment-username-login"
|
|
type="text"
|
|
value={values.username}
|
|
name="username"
|
|
onBlur={handleBlur}
|
|
onChange={handleChange}
|
|
label="用户名"
|
|
inputProps={{ autoComplete: 'username' }}
|
|
/>
|
|
{touched.username && errors.username && (
|
|
<FormHelperText error id="standard-weight-helper-text-username-login">
|
|
{errors.username}
|
|
</FormHelperText>
|
|
)}
|
|
</FormControl>
|
|
|
|
<FormControl fullWidth error={Boolean(touched.password && errors.password)} sx={{ ...theme.typography.customInput }}>
|
|
<InputLabel htmlFor="outlined-adornment-password-login">密码</InputLabel>
|
|
<OutlinedInput
|
|
id="outlined-adornment-password-login"
|
|
type={showPassword ? 'text' : 'password'}
|
|
value={values.password}
|
|
name="password"
|
|
onBlur={handleBlur}
|
|
onChange={handleChange}
|
|
endAdornment={
|
|
<InputAdornment position="end">
|
|
<IconButton
|
|
aria-label="toggle password visibility"
|
|
onClick={handleClickShowPassword}
|
|
onMouseDown={handleMouseDownPassword}
|
|
edge="end"
|
|
size="large"
|
|
>
|
|
{showPassword ? <Visibility /> : <VisibilityOff />}
|
|
</IconButton>
|
|
</InputAdornment>
|
|
}
|
|
label="Password"
|
|
/>
|
|
{touched.password && errors.password && (
|
|
<FormHelperText error id="standard-weight-helper-text-password-login">
|
|
{errors.password}
|
|
</FormHelperText>
|
|
)}
|
|
</FormControl>
|
|
<Stack direction="row" alignItems="center" justifyContent="space-between" spacing={1}>
|
|
{/* <FormControlLabel
|
|
control={
|
|
<Checkbox checked={checked} onChange={(event) => setChecked(event.target.checked)} name="checked" color="primary" />
|
|
}
|
|
label="记住我"
|
|
/> */}
|
|
<Typography
|
|
component={Link}
|
|
to="/reset"
|
|
variant="subtitle1"
|
|
color="primary"
|
|
sx={{ textDecoration: 'none', cursor: 'pointer' }}
|
|
>
|
|
忘记密码?
|
|
</Typography>
|
|
</Stack>
|
|
{errors.submit && (
|
|
<Box sx={{ mt: 3 }}>
|
|
<FormHelperText error>{errors.submit}</FormHelperText>
|
|
</Box>
|
|
)}
|
|
|
|
<Box sx={{ mt: 2 }}>
|
|
<AnimateButton>
|
|
<Button disableElevation disabled={isSubmitting} fullWidth size="large" type="submit" variant="contained" color="primary">
|
|
登录
|
|
</Button>
|
|
</AnimateButton>
|
|
</Box>
|
|
</form>
|
|
)}
|
|
</Formik>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default LoginForm;
|