✨ feat: add lark login
This commit is contained in:
parent
53cb2122a0
commit
1cc76c8939
1
web/berry/src/assets/images/icons/lark.svg
Normal file
1
web/berry/src/assets/images/icons/lark.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 5.4 KiB |
@ -48,6 +48,28 @@ const useLogin = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const larkLogin = async (code, state) => {
|
||||||
|
try {
|
||||||
|
const res = await API.get(`/api/oauth/lark?code=${code}&state=${state}`);
|
||||||
|
const { success, message, data } = res.data;
|
||||||
|
if (success) {
|
||||||
|
if (message === 'bind') {
|
||||||
|
showSuccess('绑定成功!');
|
||||||
|
navigate('/panel');
|
||||||
|
} else {
|
||||||
|
dispatch({ type: LOGIN, payload: data });
|
||||||
|
localStorage.setItem('user', JSON.stringify(data));
|
||||||
|
showSuccess('登录成功!');
|
||||||
|
navigate('/panel');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { success, message };
|
||||||
|
} catch (err) {
|
||||||
|
// 请求失败,设置错误信息
|
||||||
|
return { success: false, message: '' };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const wechatLogin = async (code) => {
|
const wechatLogin = async (code) => {
|
||||||
try {
|
try {
|
||||||
const res = await API.get(`/api/oauth/wechat?code=${code}`);
|
const res = await API.get(`/api/oauth/wechat?code=${code}`);
|
||||||
@ -72,7 +94,7 @@ const useLogin = () => {
|
|||||||
navigate('/');
|
navigate('/');
|
||||||
};
|
};
|
||||||
|
|
||||||
return { login, logout, githubLogin, wechatLogin };
|
return { login, logout, githubLogin, wechatLogin, larkLogin };
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useLogin;
|
export default useLogin;
|
||||||
|
@ -8,6 +8,7 @@ import MinimalLayout from 'layout/MinimalLayout';
|
|||||||
const AuthLogin = Loadable(lazy(() => import('views/Authentication/Auth/Login')));
|
const AuthLogin = Loadable(lazy(() => import('views/Authentication/Auth/Login')));
|
||||||
const AuthRegister = Loadable(lazy(() => import('views/Authentication/Auth/Register')));
|
const AuthRegister = Loadable(lazy(() => import('views/Authentication/Auth/Register')));
|
||||||
const GitHubOAuth = Loadable(lazy(() => import('views/Authentication/Auth/GitHubOAuth')));
|
const GitHubOAuth = Loadable(lazy(() => import('views/Authentication/Auth/GitHubOAuth')));
|
||||||
|
const LarkOAuth = Loadable(lazy(() => import('views/Authentication/Auth/LarkOAuth')));
|
||||||
const ForgetPassword = Loadable(lazy(() => import('views/Authentication/Auth/ForgetPassword')));
|
const ForgetPassword = Loadable(lazy(() => import('views/Authentication/Auth/ForgetPassword')));
|
||||||
const ResetPassword = Loadable(lazy(() => import('views/Authentication/Auth/ResetPassword')));
|
const ResetPassword = Loadable(lazy(() => import('views/Authentication/Auth/ResetPassword')));
|
||||||
const Home = Loadable(lazy(() => import('views/Home')));
|
const Home = Loadable(lazy(() => import('views/Home')));
|
||||||
@ -48,6 +49,10 @@ const OtherRoutes = {
|
|||||||
path: '/oauth/github',
|
path: '/oauth/github',
|
||||||
element: <GitHubOAuth />
|
element: <GitHubOAuth />
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/oauth/lark',
|
||||||
|
element: <LarkOAuth />
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/404',
|
path: '/404',
|
||||||
element: <NotFoundView />
|
element: <NotFoundView />
|
||||||
|
@ -91,6 +91,13 @@ export async function onGitHubOAuthClicked(github_client_id, openInNewTab = fals
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function onLarkOAuthClicked(lark_client_id) {
|
||||||
|
const state = await getOAuthState();
|
||||||
|
if (!state) return;
|
||||||
|
let redirect_uri = `${window.location.origin}/oauth/lark`;
|
||||||
|
window.open(`https://open.feishu.cn/open-apis/authen/v1/index?redirect_uri=${redirect_uri}&app_id=${lark_client_id}&state=${state}`);
|
||||||
|
}
|
||||||
|
|
||||||
export function isAdmin() {
|
export function isAdmin() {
|
||||||
let user = localStorage.getItem('user');
|
let user = localStorage.getItem('user');
|
||||||
if (!user) return false;
|
if (!user) return false;
|
||||||
|
94
web/berry/src/views/Authentication/Auth/LarkOAuth.js
Normal file
94
web/berry/src/views/Authentication/Auth/LarkOAuth.js
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { showError } from 'utils/common';
|
||||||
|
import useLogin from 'hooks/useLogin';
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import { Grid, Stack, Typography, useMediaQuery, CircularProgress } from '@mui/material';
|
||||||
|
|
||||||
|
// project imports
|
||||||
|
import AuthWrapper from '../AuthWrapper';
|
||||||
|
import AuthCardWrapper from '../AuthCardWrapper';
|
||||||
|
import Logo from 'ui-component/Logo';
|
||||||
|
|
||||||
|
// assets
|
||||||
|
|
||||||
|
// ================================|| AUTH3 - LOGIN ||================================ //
|
||||||
|
|
||||||
|
const LarkOAuth = () => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const matchDownSM = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
|
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const [prompt, setPrompt] = useState('处理中...');
|
||||||
|
const { larkLogin } = useLogin();
|
||||||
|
|
||||||
|
let navigate = useNavigate();
|
||||||
|
|
||||||
|
const sendCode = async (code, state, count) => {
|
||||||
|
const { success, message } = await larkLogin(code, state);
|
||||||
|
if (!success) {
|
||||||
|
if (message) {
|
||||||
|
showError(message);
|
||||||
|
}
|
||||||
|
if (count === 0) {
|
||||||
|
setPrompt(`操作失败,重定向至登录界面中...`);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||||
|
navigate('/login');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
setPrompt(`出现错误,第 ${count} 次重试中...`);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||||
|
await sendCode(code, state, count);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let code = searchParams.get('code');
|
||||||
|
let state = searchParams.get('state');
|
||||||
|
sendCode(code, state, 0).then();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AuthWrapper>
|
||||||
|
<Grid container direction="column" justifyContent="flex-end">
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Grid container justifyContent="center" alignItems="center" sx={{ minHeight: 'calc(100vh - 136px)' }}>
|
||||||
|
<Grid item sx={{ m: { xs: 1, sm: 3 }, mb: 0 }}>
|
||||||
|
<AuthCardWrapper>
|
||||||
|
<Grid container spacing={2} alignItems="center" justifyContent="center">
|
||||||
|
<Grid item sx={{ mb: 3 }}>
|
||||||
|
<Link to="#">
|
||||||
|
<Logo />
|
||||||
|
</Link>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Grid container direction={matchDownSM ? 'column-reverse' : 'row'} alignItems="center" justifyContent="center">
|
||||||
|
<Grid item>
|
||||||
|
<Stack alignItems="center" justifyContent="center" spacing={1}>
|
||||||
|
<Typography color={theme.palette.primary.main} gutterBottom variant={matchDownSM ? 'h3' : 'h2'}>
|
||||||
|
飞书 登录
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} container direction="column" justifyContent="center" alignItems="center" style={{ height: '200px' }}>
|
||||||
|
<CircularProgress />
|
||||||
|
<Typography variant="h3" paddingTop={'20px'}>
|
||||||
|
{prompt}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</AuthCardWrapper>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</AuthWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LarkOAuth;
|
@ -35,7 +35,8 @@ import VisibilityOff from '@mui/icons-material/VisibilityOff';
|
|||||||
|
|
||||||
import Github from 'assets/images/icons/github.svg';
|
import Github from 'assets/images/icons/github.svg';
|
||||||
import Wechat from 'assets/images/icons/wechat.svg';
|
import Wechat from 'assets/images/icons/wechat.svg';
|
||||||
import { onGitHubOAuthClicked } from 'utils/common';
|
import Lark from 'assets/images/icons/lark.svg';
|
||||||
|
import { onGitHubOAuthClicked, onLarkOAuthClicked } from 'utils/common';
|
||||||
|
|
||||||
// ============================|| FIREBASE - LOGIN ||============================ //
|
// ============================|| FIREBASE - LOGIN ||============================ //
|
||||||
|
|
||||||
@ -49,7 +50,7 @@ const LoginForm = ({ ...others }) => {
|
|||||||
// const [checked, setChecked] = useState(true);
|
// const [checked, setChecked] = useState(true);
|
||||||
|
|
||||||
let tripartiteLogin = false;
|
let tripartiteLogin = false;
|
||||||
if (siteInfo.github_oauth || siteInfo.wechat_login) {
|
if (siteInfo.github_oauth || siteInfo.wechat_login || siteInfo.lark_client_id) {
|
||||||
tripartiteLogin = true;
|
tripartiteLogin = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,6 +122,29 @@ const LoginForm = ({ ...others }) => {
|
|||||||
<WechatModal open={openWechat} handleClose={handleWechatClose} wechatLogin={wechatLogin} qrCode={siteInfo.wechat_qrcode} />
|
<WechatModal open={openWechat} handleClose={handleWechatClose} wechatLogin={wechatLogin} qrCode={siteInfo.wechat_qrcode} />
|
||||||
</Grid>
|
</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>
|
||||||
|
)}
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
|
@ -12,7 +12,8 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogActions,
|
DialogActions,
|
||||||
Divider
|
Divider,
|
||||||
|
SvgIcon
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import Grid from '@mui/material/Unstable_Grid2';
|
import Grid from '@mui/material/Unstable_Grid2';
|
||||||
import SubCard from 'ui-component/cards/SubCard';
|
import SubCard from 'ui-component/cards/SubCard';
|
||||||
@ -20,12 +21,13 @@ import { IconBrandWechat, IconBrandGithub, IconMail } from '@tabler/icons-react'
|
|||||||
import Label from 'ui-component/Label';
|
import Label from 'ui-component/Label';
|
||||||
import { API } from 'utils/api';
|
import { API } from 'utils/api';
|
||||||
import { showError, showSuccess } from 'utils/common';
|
import { showError, showSuccess } from 'utils/common';
|
||||||
import { onGitHubOAuthClicked } from 'utils/common';
|
import { onGitHubOAuthClicked, onLarkOAuthClicked } from 'utils/common';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import WechatModal from 'views/Authentication/AuthForms/WechatModal';
|
import WechatModal from 'views/Authentication/AuthForms/WechatModal';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import EmailModal from './component/EmailModal';
|
import EmailModal from './component/EmailModal';
|
||||||
import Turnstile from 'react-turnstile';
|
import Turnstile from 'react-turnstile';
|
||||||
|
import { ReactComponent as Lark } from 'assets/images/icons/lark.svg';
|
||||||
|
|
||||||
const validationSchema = Yup.object().shape({
|
const validationSchema = Yup.object().shape({
|
||||||
username: Yup.string().required('用户名 不能为空').min(3, '用户名 不能小于 3 个字符'),
|
username: Yup.string().required('用户名 不能为空').min(3, '用户名 不能小于 3 个字符'),
|
||||||
@ -137,6 +139,9 @@ export default function Profile() {
|
|||||||
<Label variant="ghost" color={inputs.email ? 'primary' : 'default'}>
|
<Label variant="ghost" color={inputs.email ? 'primary' : 'default'}>
|
||||||
<IconMail /> {inputs.email || '未绑定'}
|
<IconMail /> {inputs.email || '未绑定'}
|
||||||
</Label>
|
</Label>
|
||||||
|
<Label variant="ghost" color={inputs.lark_id ? 'primary' : 'default'}>
|
||||||
|
<SvgIcon component={Lark} inheritViewBox="0 0 24 24" /> {inputs.lark_id || '未绑定'}
|
||||||
|
</Label>
|
||||||
</Stack>
|
</Stack>
|
||||||
<SubCard title="个人信息">
|
<SubCard title="个人信息">
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
@ -205,6 +210,13 @@ export default function Profile() {
|
|||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
)}
|
)}
|
||||||
|
{status.lark_client_id && !inputs.lark_id && (
|
||||||
|
<Grid xs={12} md={4}>
|
||||||
|
<Button variant="contained" onClick={() => onLarkOAuthClicked(status.lark_client_id)}>
|
||||||
|
绑定 飞书 账号
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
<Grid xs={12} md={4}>
|
<Grid xs={12} md={4}>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
|
@ -31,6 +31,8 @@ const SystemSetting = () => {
|
|||||||
GitHubOAuthEnabled: '',
|
GitHubOAuthEnabled: '',
|
||||||
GitHubClientId: '',
|
GitHubClientId: '',
|
||||||
GitHubClientSecret: '',
|
GitHubClientSecret: '',
|
||||||
|
LarkClientId: '',
|
||||||
|
LarkClientSecret: '',
|
||||||
Notice: '',
|
Notice: '',
|
||||||
SMTPServer: '',
|
SMTPServer: '',
|
||||||
SMTPPort: '',
|
SMTPPort: '',
|
||||||
@ -138,7 +140,9 @@ const SystemSetting = () => {
|
|||||||
name === 'TurnstileSecretKey' ||
|
name === 'TurnstileSecretKey' ||
|
||||||
name === 'EmailDomainWhitelist' ||
|
name === 'EmailDomainWhitelist' ||
|
||||||
name === 'MessagePusherAddress' ||
|
name === 'MessagePusherAddress' ||
|
||||||
name === 'MessagePusherToken'
|
name === 'MessagePusherToken' ||
|
||||||
|
name === 'LarkClientId' ||
|
||||||
|
name === 'LarkClientSecret'
|
||||||
) {
|
) {
|
||||||
setInputs((inputs) => ({ ...inputs, [name]: value }));
|
setInputs((inputs) => ({ ...inputs, [name]: value }));
|
||||||
} else {
|
} else {
|
||||||
@ -212,6 +216,15 @@ const SystemSetting = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const submitLarkOAuth = async () => {
|
||||||
|
if (originInputs['LarkClientId'] !== inputs.LarkClientId) {
|
||||||
|
await updateOption('LarkClientId', inputs.LarkClientId);
|
||||||
|
}
|
||||||
|
if (originInputs['LarkClientSecret'] !== inputs.LarkClientSecret && inputs.LarkClientSecret !== '') {
|
||||||
|
await updateOption('LarkClientSecret', inputs.LarkClientSecret);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Stack spacing={2}>
|
<Stack spacing={2}>
|
||||||
@ -486,6 +499,61 @@ const SystemSetting = () => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</SubCard>
|
</SubCard>
|
||||||
|
<SubCard
|
||||||
|
title="配置飞书授权登录"
|
||||||
|
subTitle={
|
||||||
|
<span>
|
||||||
|
{' '}
|
||||||
|
用以支持通过飞书进行登录注册,
|
||||||
|
<a href="https://open.feishu.cn/app" target="_blank" rel="noreferrer">
|
||||||
|
点击此处
|
||||||
|
</a>
|
||||||
|
管理你的飞书应用
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Grid container spacing={{ xs: 3, sm: 2, md: 4 }}>
|
||||||
|
<Grid xs={12}>
|
||||||
|
<Alert severity="info" sx={{ wordWrap: 'break-word' }}>
|
||||||
|
主页链接填 <code>{inputs.ServerAddress}</code>
|
||||||
|
,重定向 URL 填 <code>{`${inputs.ServerAddress}/oauth/lark`}</code>
|
||||||
|
</Alert>
|
||||||
|
</Grid>
|
||||||
|
<Grid xs={12} md={6}>
|
||||||
|
<FormControl fullWidth>
|
||||||
|
<InputLabel htmlFor="LarkClientId">App ID</InputLabel>
|
||||||
|
<OutlinedInput
|
||||||
|
id="LarkClientId"
|
||||||
|
name="LarkClientId"
|
||||||
|
value={inputs.LarkClientId || ''}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
label="App ID"
|
||||||
|
placeholder="输入 App ID"
|
||||||
|
disabled={loading}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</Grid>
|
||||||
|
<Grid xs={12} md={6}>
|
||||||
|
<FormControl fullWidth>
|
||||||
|
<InputLabel htmlFor="LarkClientSecret">App Secret</InputLabel>
|
||||||
|
<OutlinedInput
|
||||||
|
id="LarkClientSecret"
|
||||||
|
name="LarkClientSecret"
|
||||||
|
value={inputs.LarkClientSecret || ''}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
label="App Secret"
|
||||||
|
placeholder="敏感信息不会发送到前端显示"
|
||||||
|
disabled={loading}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</Grid>
|
||||||
|
<Grid xs={12}>
|
||||||
|
<Button variant="contained" onClick={submitLarkOAuth}>
|
||||||
|
保存飞书 OAuth 设置
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</SubCard>
|
||||||
<SubCard
|
<SubCard
|
||||||
title="配置 WeChat Server"
|
title="配置 WeChat Server"
|
||||||
subTitle={
|
subTitle={
|
||||||
|
Loading…
Reference in New Issue
Block a user