refactor: Changing OAuth 2.0 to OIDC

This commit is contained in:
OnEvent 2024-08-09 16:44:15 +08:00
parent 80568f2d87
commit a3cb66661d
No known key found for this signature in database
GPG Key ID: 3CDB9068A32B4927
6 changed files with 184 additions and 69 deletions

View File

@ -23,11 +23,11 @@ const config = {
version: '', version: '',
wechat_login: false, wechat_login: false,
wechat_qrcode: '', wechat_qrcode: '',
oauth2: false, oidc: false,
oauth2_app_id: '', oidc_app_id: '',
oauth2_authorization_endpoint: '', oidc_authorization_endpoint: '',
oauth2_token_endpoint: '', oidc_token_endpoint: '',
oauth2_userinfo_endpoint: '', oidc_userinfo_endpoint: '',
} }
}; };

View File

@ -98,12 +98,12 @@ export async function onLarkOAuthClicked(lark_client_id) {
window.open(`https://open.feishu.cn/open-apis/authen/v1/index?redirect_uri=${redirect_uri}&app_id=${lark_client_id}&state=${state}`); window.open(`https://open.feishu.cn/open-apis/authen/v1/index?redirect_uri=${redirect_uri}&app_id=${lark_client_id}&state=${state}`);
} }
export async function onOAuth2Clicked(auth_url, client_id, openInNewTab = false) { export async function onOidcClicked(auth_url, client_id, openInNewTab = false) {
const state = await getOAuthState(); const state = await getOAuthState();
if (!state) return; if (!state) return;
const redirect_uri = `${window.location.origin}/oauth/oidc`; const redirect_uri = `${window.location.origin}/oauth/oidc`;
const response_type = "code"; const response_type = "code";
const scope = "profile email"; const scope = "openid profile email";
const url = `${auth_url}?client_id=${client_id}&redirect_uri=${redirect_uri}&response_type=${response_type}&scope=${scope}&state=${state}`; const url = `${auth_url}?client_id=${client_id}&redirect_uri=${redirect_uri}&response_type=${response_type}&scope=${scope}&state=${state}`;
if (openInNewTab) { if (openInNewTab) {
window.open(url); window.open(url);

View 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 OidcOAuth = () => {
const theme = useTheme();
const matchDownSM = useMediaQuery(theme.breakpoints.down('md'));
const [searchParams] = useSearchParams();
const [prompt, setPrompt] = useState('处理中...');
const { oidcLogin } = useLogin();
let navigate = useNavigate();
const sendCode = async (code, state, count) => {
const { success, message } = await oidcLogin(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'}>
OIDC 登录
</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 OidcOAuth;

View File

@ -36,7 +36,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 Lark from 'assets/images/icons/lark.svg'; import Lark from 'assets/images/icons/lark.svg';
import { onGitHubOAuthClicked, onLarkOAuthClicked, onOAuth2Clicked } from 'utils/common'; import OIDC from 'assets/images/icons/oidc.svg';
import { onGitHubOAuthClicked, onLarkOAuthClicked, onOidcClicked } from 'utils/common';
// ============================|| FIREBASE - LOGIN ||============================ // // ============================|| FIREBASE - LOGIN ||============================ //
@ -50,7 +51,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 || siteInfo.lark_client_id || siteInfo.oauth2) { if (siteInfo.github_oauth || siteInfo.wechat_login || siteInfo.lark_client_id || siteInfo.oidc) {
tripartiteLogin = true; tripartiteLogin = true;
} }
@ -145,13 +146,13 @@ const LoginForm = ({ ...others }) => {
</AnimateButton> </AnimateButton>
</Grid> </Grid>
)} )}
{siteInfo.oauth2 && ( {siteInfo.oidc && (
<Grid item xs={12}> <Grid item xs={12}>
<AnimateButton> <AnimateButton>
<Button <Button
disableElevation disableElevation
fullWidth fullWidth
onClick={() => onOAuth2Clicked(siteInfo.oauth2_authorization_endpoint,siteInfo.oauth2_app_id)} onClick={() => onOidcClicked(siteInfo.oidc_authorization_endpoint,siteInfo.oidc_app_id)}
size="large" size="large"
variant="outlined" variant="outlined"
sx={{ sx={{
@ -161,9 +162,9 @@ const LoginForm = ({ ...others }) => {
}} }}
> >
<Box sx={{ mr: { xs: 1, sm: 2, width: 20 }, display: 'flex', alignItems: 'center' }}> <Box sx={{ mr: { xs: 1, sm: 2, width: 20 }, display: 'flex', alignItems: 'center' }}>
<img src={Wechat} alt="Lark" width={25} height={25} style={{ marginRight: matchDownSM ? 8 : 16 }} /> <img src={OIDC} alt="Lark" width={25} height={25} style={{ marginRight: matchDownSM ? 8 : 16 }} />
</Box> </Box>
使用 OAuth 2.0 登录 使用 OIDC 登录
</Button> </Button>
</AnimateButton> </AnimateButton>
</Grid> </Grid>

View File

@ -20,7 +20,7 @@ import SubCard from 'ui-component/cards/SubCard';
import { IconBrandWechat, IconBrandGithub, IconMail } from '@tabler/icons-react'; 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 { onOidcClicked, showError, showSuccess } from 'utils/common';
import { onGitHubOAuthClicked, onLarkOAuthClicked, copy } from 'utils/common'; import { onGitHubOAuthClicked, onLarkOAuthClicked, copy } 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';
@ -28,6 +28,7 @@ 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'; import { ReactComponent as Lark } from 'assets/images/icons/lark.svg';
import { ReactComponent as OIDC } from 'assets/images/icons/oidc.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 个字符'),
@ -123,6 +124,15 @@ export default function Profile() {
loadUser().then(); loadUser().then();
}, [status]); }, [status]);
function getOidcId(){
if (!inputs.oidc_id) return '';
let oidc_id = inputs.oidc_id;
if (inputs.oidc_id.length > 8) {
oidc_id = inputs.oidc_id.slice(0, 6) + '...' + inputs.oidc_id.slice(-6);
}
return oidc_id;
}
return ( return (
<> <>
<UserCard> <UserCard>
@ -141,6 +151,9 @@ export default function Profile() {
<Label variant="ghost" color={inputs.lark_id ? 'primary' : 'default'}> <Label variant="ghost" color={inputs.lark_id ? 'primary' : 'default'}>
<SvgIcon component={Lark} inheritViewBox="0 0 24 24" /> {inputs.lark_id || '未绑定'} <SvgIcon component={Lark} inheritViewBox="0 0 24 24" /> {inputs.lark_id || '未绑定'}
</Label> </Label>
<Label variant="ghost" color={inputs.oidc_id ? 'primary' : 'default'}>
<SvgIcon component={OIDC} inheritViewBox="0 0 24 24" /> {getOidcId() || '未绑定'}
</Label>
</Stack> </Stack>
<SubCard title="个人信息"> <SubCard title="个人信息">
<Grid container spacing={2}> <Grid container spacing={2}>
@ -216,6 +229,13 @@ export default function Profile() {
</Button> </Button>
</Grid> </Grid>
)} )}
{status.oidc && !inputs.oidc_id && (
<Grid xs={12} md={4}>
<Button variant="contained" onClick={() => onOidcClicked(status.oidc_authorization_endpoint,status.oidc_app_id,true)}>
绑定 OIDC 账号
</Button>
</Grid>
)}
<Grid xs={12} md={4}> <Grid xs={12} md={4}>
<Button <Button
variant="contained" variant="contained"

View File

@ -33,12 +33,12 @@ const SystemSetting = () => {
GitHubClientSecret: '', GitHubClientSecret: '',
LarkClientId: '', LarkClientId: '',
LarkClientSecret: '', LarkClientSecret: '',
OAuth2Enabled: '', OidcEnabled: '',
OAuth2AppId: '', OidcAppId: '',
OAuth2AppSecret: '', OidcAppSecret: '',
OAuth2AuthorizationEndpoint: '', OidcAuthorizationEndpoint: '',
OAuth2TokenEndpoint: '', OidcTokenEndpoint: '',
OAuth2UserinfoEndpoint: '', OidcUserinfoEndpoint: '',
Notice: '', Notice: '',
SMTPServer: '', SMTPServer: '',
SMTPPort: '', SMTPPort: '',
@ -100,7 +100,7 @@ const SystemSetting = () => {
case 'TurnstileCheckEnabled': case 'TurnstileCheckEnabled':
case 'EmailDomainRestrictionEnabled': case 'EmailDomainRestrictionEnabled':
case 'RegisterEnabled': case 'RegisterEnabled':
case 'OAuth2Enabled': case 'OidcEnabled':
value = inputs[key] === 'true' ? 'false' : 'true'; value = inputs[key] === 'true' ? 'false' : 'true';
break; break;
default: default:
@ -150,11 +150,11 @@ const SystemSetting = () => {
name === 'MessagePusherToken' || name === 'MessagePusherToken' ||
name === 'LarkClientId' || name === 'LarkClientId' ||
name === 'LarkClientSecret' || name === 'LarkClientSecret' ||
name === 'OAuth2AppId' || name === 'OidcAppId' ||
name === 'OAuth2AppSecret' || name === 'OidcAppSecret' ||
name === 'OAuth2AuthorizationEndpoint' || name === 'OidcAuthorizationEndpoint' ||
name === 'OAuth2TokenEndpoint' || name === 'OidcTokenEndpoint' ||
name === 'OAuth2UserinfoEndpoint' name === 'OidcUserinfoEndpoint'
) )
{ {
setInputs((inputs) => ({ ...inputs, [name]: value })); setInputs((inputs) => ({ ...inputs, [name]: value }));
@ -238,29 +238,29 @@ const SystemSetting = () => {
} }
}; };
const submitOAuth2 = async () => { const submitOidc = async () => {
const OAuth2Config = { const OidcConfig = {
OAuth2AppId: inputs.OAuth2AppId, OidcAppId: inputs.OidcAppId,
OAuth2AppSecret: inputs.OAuth2AppSecret, OidcAppSecret: inputs.OidcAppSecret,
OAuth2AuthorizationEndpoint: inputs.OAuth2AuthorizationEndpoint, OidcAuthorizationEndpoint: inputs.OidcAuthorizationEndpoint,
OAuth2TokenEndpoint: inputs.OAuth2TokenEndpoint, OidcTokenEndpoint: inputs.OidcTokenEndpoint,
OAuth2UserinfoEndpoint: inputs.OAuth2UserinfoEndpoint OidcUserinfoEndpoint: inputs.OidcUserinfoEndpoint
}; };
console.log(OAuth2Config); console.log(OidcConfig);
if (originInputs['OAuth2AppId'] !== inputs.OAuth2AppId) { if (originInputs['OidcAppId'] !== inputs.OidcAppId) {
await updateOption('OAuth2AppId', inputs.OAuth2AppId); await updateOption('OidcAppId', inputs.OidcAppId);
} }
if (originInputs['OAuth2AppSecret'] !== inputs.OAuth2AppSecret && inputs.OAuth2AppSecret !== '') { if (originInputs['OidcAppSecret'] !== inputs.OidcAppSecret && inputs.OidcAppSecret !== '') {
await updateOption('OAuth2AppSecret', inputs.OAuth2AppSecret); await updateOption('OidcAppSecret', inputs.OidcAppSecret);
} }
if (originInputs['OAuth2AuthorizationEndpoint'] !== inputs.OAuth2AuthorizationEndpoint) { if (originInputs['OidcAuthorizationEndpoint'] !== inputs.OidcAuthorizationEndpoint) {
await updateOption('OAuth2AuthorizationEndpoint', inputs.OAuth2AuthorizationEndpoint); await updateOption('OidcAuthorizationEndpoint', inputs.OidcAuthorizationEndpoint);
} }
if (originInputs['OAuth2TokenEndpoint'] !== inputs.OAuth2TokenEndpoint) { if (originInputs['OidcTokenEndpoint'] !== inputs.OidcTokenEndpoint) {
await updateOption('OAuth2TokenEndpoint', inputs.OAuth2TokenEndpoint); await updateOption('OidcTokenEndpoint', inputs.OidcTokenEndpoint);
} }
if (originInputs['OAuth2UserinfoEndpoint'] !== inputs.OAuth2UserinfoEndpoint) { if (originInputs['OidcUserinfoEndpoint'] !== inputs.OidcUserinfoEndpoint) {
await updateOption('OAuth2UserinfoEndpoint', inputs.OAuth2UserinfoEndpoint); await updateOption('OidcUserinfoEndpoint', inputs.OidcUserinfoEndpoint);
} }
}; };
@ -332,8 +332,8 @@ const SystemSetting = () => {
</Grid> </Grid>
<Grid xs={12} md={3}> <Grid xs={12} md={3}>
<FormControlLabel <FormControlLabel
label="允许通过 OAuth 2.0 登录 & 注册" label="允许通过 Oidc 登录 & 注册"
control={<Checkbox checked={inputs.OAuth2Enabled === 'true'} onChange={handleInputChange} name="OAuth2Enabled" />} control={<Checkbox checked={inputs.OidcEnabled === 'true'} onChange={handleInputChange} name="OidcEnabled" />}
/> />
</Grid> </Grid>
<Grid xs={12} md={3}> <Grid xs={12} md={3}>
@ -663,10 +663,10 @@ const SystemSetting = () => {
</SubCard> </SubCard>
<SubCard <SubCard
title="配置第三方 OAuth 2.0" title="配置 OIDC"
subTitle={ subTitle={
<span> <span>
用以支持通过第三方 OAuth2 登录例如 OktaAuth0 或自建的兼容 OAuth2.0 协议的 IdP 用以支持通过 OIDC 登录例如 OktaAuth0 等兼容 OIDC 协议的 IdP
</span> </span>
} }
> >
@ -679,11 +679,11 @@ const SystemSetting = () => {
</Grid> </Grid>
<Grid xs={ 12 } md={ 6 }> <Grid xs={ 12 } md={ 6 }>
<FormControl fullWidth> <FormControl fullWidth>
<InputLabel htmlFor="OAuth2AppId">App ID</InputLabel> <InputLabel htmlFor="OidcAppId">App ID</InputLabel>
<OutlinedInput <OutlinedInput
id="OAuth2AppId" id="OidcAppId"
name="OAuth2AppId" name="OidcAppId"
value={ inputs.OAuth2AppId || '' } value={ inputs.OidcAppId || '' }
onChange={ handleInputChange } onChange={ handleInputChange }
label="App ID" label="App ID"
placeholder="输入 OAuth 2.0 的 App ID" placeholder="输入 OAuth 2.0 的 App ID"
@ -693,11 +693,11 @@ const SystemSetting = () => {
</Grid> </Grid>
<Grid xs={ 12 } md={ 6 }> <Grid xs={ 12 } md={ 6 }>
<FormControl fullWidth> <FormControl fullWidth>
<InputLabel htmlFor="OAuth2AppSecret">App Secret</InputLabel> <InputLabel htmlFor="OidcAppSecret">App Secret</InputLabel>
<OutlinedInput <OutlinedInput
id="OAuth2AppSecret" id="OidcAppSecret"
name="OAuth2AppSecret" name="OidcAppSecret"
value={ inputs.OAuth2AppSecret || '' } value={ inputs.OidcAppSecret || '' }
onChange={ handleInputChange } onChange={ handleInputChange }
label="App Secret" label="App Secret"
placeholder="敏感信息不会发送到前端显示" placeholder="敏感信息不会发送到前端显示"
@ -707,11 +707,11 @@ const SystemSetting = () => {
</Grid> </Grid>
<Grid xs={ 12 } md={ 6 }> <Grid xs={ 12 } md={ 6 }>
<FormControl fullWidth> <FormControl fullWidth>
<InputLabel htmlFor="OAuth2AuthorizationEndpoint">授权地址</InputLabel> <InputLabel htmlFor="OidcAuthorizationEndpoint">授权地址</InputLabel>
<OutlinedInput <OutlinedInput
id="OAuth2AuthorizationEndpoint" id="OidcAuthorizationEndpoint"
name="OAuth2AuthorizationEndpoint" name="OidcAuthorizationEndpoint"
value={ inputs.OAuth2AuthorizationEndpoint || '' } value={ inputs.OidcAuthorizationEndpoint || '' }
onChange={ handleInputChange } onChange={ handleInputChange }
label="授权地址" label="授权地址"
placeholder="输入 OAuth 2.0 的 授权地址" placeholder="输入 OAuth 2.0 的 授权地址"
@ -721,11 +721,11 @@ const SystemSetting = () => {
</Grid> </Grid>
<Grid xs={ 12 } md={ 6 }> <Grid xs={ 12 } md={ 6 }>
<FormControl fullWidth> <FormControl fullWidth>
<InputLabel htmlFor="OAuth2TokenEndpoint">认证地址</InputLabel> <InputLabel htmlFor="OidcTokenEndpoint">认证地址</InputLabel>
<OutlinedInput <OutlinedInput
id="OAuth2TokenEndpoint" id="OidcTokenEndpoint"
name="OAuth2TokenEndpoint" name="OidcTokenEndpoint"
value={ inputs.OAuth2TokenEndpoint || '' } value={ inputs.OidcTokenEndpoint || '' }
onChange={ handleInputChange } onChange={ handleInputChange }
label="认证地址" label="认证地址"
placeholder="输入 OAuth 2.0 的 认证地址" placeholder="输入 OAuth 2.0 的 认证地址"
@ -735,11 +735,11 @@ const SystemSetting = () => {
</Grid> </Grid>
<Grid xs={ 12 } md={ 6 }> <Grid xs={ 12 } md={ 6 }>
<FormControl fullWidth> <FormControl fullWidth>
<InputLabel htmlFor="OAuth2UserinfoEndpoint">用户地址</InputLabel> <InputLabel htmlFor="OidcUserinfoEndpoint">用户地址</InputLabel>
<OutlinedInput <OutlinedInput
id="OAuth2UserinfoEndpoint" id="OidcUserinfoEndpoint"
name="OAuth2UserinfoEndpoint" name="OidcUserinfoEndpoint"
value={ inputs.OAuth2UserinfoEndpoint || '' } value={ inputs.OidcUserinfoEndpoint || '' }
onChange={ handleInputChange } onChange={ handleInputChange }
label="认证地址" label="认证地址"
placeholder="输入 OAuth 2.0 的 认证地址" placeholder="输入 OAuth 2.0 的 认证地址"
@ -748,7 +748,7 @@ const SystemSetting = () => {
</FormControl> </FormControl>
</Grid> </Grid>
<Grid xs={ 12 }> <Grid xs={ 12 }>
<Button variant="contained" onClick={ submitOAuth2 }> <Button variant="contained" onClick={ submitOidc }>
保存第三方 OAuth 2.0 设置 保存第三方 OAuth 2.0 设置
</Button> </Button>
</Grid> </Grid>