mirror of
https://github.com/linux-do/new-api.git
synced 2025-11-11 00:23:42 +08:00
Merge pull request #562 from seefs001/main
feat: integrate Linux DO OAuth authentication
This commit is contained in:
@@ -26,6 +26,7 @@ import Midjourney from './pages/Midjourney';
|
||||
import Pricing from './pages/Pricing/index.js';
|
||||
import Task from "./pages/Task/index.js";
|
||||
import Playground from './components/Playground.js';
|
||||
import LinuxDoOAuth from './components/LinuxDoOAuth.js';
|
||||
|
||||
const Home = lazy(() => import('./pages/Home'));
|
||||
const Detail = lazy(() => import('./pages/Detail'));
|
||||
@@ -181,6 +182,14 @@ function App() {
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/oauth/linuxdo'
|
||||
element={
|
||||
<Suspense fallback={<Loading></Loading>}>
|
||||
<LinuxDoOAuth />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/setting'
|
||||
element={
|
||||
|
||||
61
web/src/components/LinuxDoOAuth.js
Normal file
61
web/src/components/LinuxDoOAuth.js
Normal file
@@ -0,0 +1,61 @@
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { Dimmer, Loader, Segment } from 'semantic-ui-react';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { API, showError, showSuccess, updateAPI } from '../helpers';
|
||||
import { UserContext } from '../context/User';
|
||||
import { setUserData } from '../helpers/data.js';
|
||||
|
||||
const LinuxDoOAuth = () => {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const [userState, userDispatch] = useContext(UserContext);
|
||||
const [prompt, setPrompt] = useState('处理中...');
|
||||
const [processing, setProcessing] = useState(true);
|
||||
|
||||
let navigate = useNavigate();
|
||||
|
||||
const sendCode = async (code, state, count) => {
|
||||
const res = await API.get(`/api/oauth/linuxdo?code=${code}&state=${state}`);
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
if (message === 'bind') {
|
||||
showSuccess('绑定成功!');
|
||||
navigate('/setting');
|
||||
} else {
|
||||
userDispatch({ type: 'login', payload: data });
|
||||
localStorage.setItem('user', JSON.stringify(data));
|
||||
setUserData(data);
|
||||
updateAPI()
|
||||
showSuccess('登录成功!');
|
||||
navigate('/token');
|
||||
}
|
||||
} else {
|
||||
showError(message);
|
||||
if (count === 0) {
|
||||
setPrompt(`操作失败,重定向至登录界面中...`);
|
||||
navigate('/setting'); // in case this is failed to bind GitHub
|
||||
return;
|
||||
}
|
||||
count++;
|
||||
setPrompt(`出现错误,第 ${count} 次重试中...`);
|
||||
await new Promise((resolve) => setTimeout(resolve, count * 2000));
|
||||
await sendCode(code, state, count);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let code = searchParams.get('code');
|
||||
let state = searchParams.get('state');
|
||||
sendCode(code, state, 0).then();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Segment style={{ minHeight: '300px' }}>
|
||||
<Dimmer active inverted>
|
||||
<Loader size='large'>{prompt}</Loader>
|
||||
</Dimmer>
|
||||
</Segment>
|
||||
);
|
||||
};
|
||||
|
||||
export default LinuxDoOAuth;
|
||||
@@ -1,8 +1,15 @@
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { UserContext } from '../context/User';
|
||||
import { API, getLogo, showError, showInfo, showSuccess, updateAPI } from '../helpers';
|
||||
import { onGitHubOAuthClicked } from './utils';
|
||||
import {
|
||||
API,
|
||||
getLogo,
|
||||
showError,
|
||||
showInfo,
|
||||
showSuccess,
|
||||
updateAPI,
|
||||
} from '../helpers';
|
||||
import { onGitHubOAuthClicked, onLinuxDOOAuthClicked } from './utils';
|
||||
import Turnstile from 'react-turnstile';
|
||||
import {
|
||||
Button,
|
||||
@@ -17,7 +24,7 @@ import Title from '@douyinfe/semi-ui/lib/es/typography/title';
|
||||
import Text from '@douyinfe/semi-ui/lib/es/typography/text';
|
||||
import TelegramLoginButton from 'react-telegram-login';
|
||||
|
||||
import { IconGithubLogo } from '@douyinfe/semi-icons';
|
||||
import { IconGithubLogo, IconAlarm } from '@douyinfe/semi-icons';
|
||||
import WeChatIcon from './WeChatIcon';
|
||||
import { setUserData } from '../helpers/data.js';
|
||||
|
||||
@@ -72,7 +79,7 @@ const LoginForm = () => {
|
||||
userDispatch({ type: 'login', payload: data });
|
||||
localStorage.setItem('user', JSON.stringify(data));
|
||||
setUserData(data);
|
||||
updateAPI()
|
||||
updateAPI();
|
||||
navigate('/');
|
||||
showSuccess('登录成功!');
|
||||
setShowWeChatLoginModal(false);
|
||||
@@ -103,7 +110,7 @@ const LoginForm = () => {
|
||||
if (success) {
|
||||
userDispatch({ type: 'login', payload: data });
|
||||
setUserData(data);
|
||||
updateAPI()
|
||||
updateAPI();
|
||||
showSuccess('登录成功!');
|
||||
if (username === 'root' && password === '123456') {
|
||||
Modal.error({
|
||||
@@ -146,7 +153,7 @@ const LoginForm = () => {
|
||||
localStorage.setItem('user', JSON.stringify(data));
|
||||
showSuccess('登录成功!');
|
||||
setUserData(data);
|
||||
updateAPI()
|
||||
updateAPI();
|
||||
navigate('/');
|
||||
} else {
|
||||
showError(message);
|
||||
@@ -214,7 +221,8 @@ const LoginForm = () => {
|
||||
</div>
|
||||
{status.github_oauth ||
|
||||
status.wechat_login ||
|
||||
status.telegram_oauth ? (
|
||||
status.telegram_oauth ||
|
||||
status.linuxdo_oauth ? (
|
||||
<>
|
||||
<Divider margin='12px' align='center'>
|
||||
第三方登录
|
||||
@@ -237,6 +245,17 @@ const LoginForm = () => {
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
{status.linuxdo_oauth ? (
|
||||
<Button
|
||||
type='primary'
|
||||
icon={<IconAlarm />}
|
||||
onClick={() =>
|
||||
onLinuxDOOAuthClicked(status.linuxdo_client_id)
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
{status.wechat_login ? (
|
||||
<Button
|
||||
type='primary'
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
} from '../helpers';
|
||||
import Turnstile from 'react-turnstile';
|
||||
import { UserContext } from '../context/User';
|
||||
import { onGitHubOAuthClicked } from './utils';
|
||||
import { onGitHubOAuthClicked, onLinuxDOOAuthClicked } from './utils';
|
||||
import {
|
||||
Avatar,
|
||||
Banner,
|
||||
@@ -519,7 +519,6 @@ const PersonalSetting = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>Telegram</Typography.Text>
|
||||
<div
|
||||
@@ -551,7 +550,36 @@ const PersonalSetting = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>LinuxDO</Typography.Text>
|
||||
<div
|
||||
style={{ display: 'flex', justifyContent: 'space-between' }}
|
||||
>
|
||||
<div>
|
||||
<Input
|
||||
value={
|
||||
userState.user && userState.user.linux_do_id !== ''
|
||||
? userState.user.linux_do_id
|
||||
: '未绑定'
|
||||
}
|
||||
readonly={true}
|
||||
></Input>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
onClick={() => {
|
||||
onLinuxDOOAuthClicked(status.linuxdo_client_id);
|
||||
}}
|
||||
disabled={
|
||||
(userState.user && userState.user.linux_do_id !== '') ||
|
||||
!status.linuxdo_oauth
|
||||
}
|
||||
>
|
||||
{status.linuxdo_oauth ? '绑定' : '未启用'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Space>
|
||||
<Button onClick={generateAccessToken}>
|
||||
|
||||
@@ -53,6 +53,9 @@ const SystemSetting = () => {
|
||||
TelegramOAuthEnabled: '',
|
||||
TelegramBotToken: '',
|
||||
TelegramBotName: '',
|
||||
LinuxDOOAuthEnabled: '',
|
||||
LinuxDOClientId: '',
|
||||
LinuxDOClientSecret: '',
|
||||
});
|
||||
const [originInputs, setOriginInputs] = useState({});
|
||||
let [loading, setLoading] = useState(false);
|
||||
@@ -103,6 +106,7 @@ const SystemSetting = () => {
|
||||
case 'PasswordRegisterEnabled':
|
||||
case 'EmailVerificationEnabled':
|
||||
case 'GitHubOAuthEnabled':
|
||||
case 'LinuxDOOAuthEnabled':
|
||||
case 'WeChatAuthEnabled':
|
||||
case 'TelegramOAuthEnabled':
|
||||
case 'TurnstileCheckEnabled':
|
||||
@@ -163,7 +167,9 @@ const SystemSetting = () => {
|
||||
name === 'EmailDomainWhitelist' ||
|
||||
name === 'TopupGroupRatio' ||
|
||||
name === 'TelegramBotToken' ||
|
||||
name === 'TelegramBotName'
|
||||
name === 'TelegramBotName' ||
|
||||
name === 'LinuxDOClientId' ||
|
||||
name === 'LinuxDOClientSecret'
|
||||
) {
|
||||
setInputs((inputs) => ({ ...inputs, [name]: value }));
|
||||
} else {
|
||||
@@ -182,7 +188,7 @@ const SystemSetting = () => {
|
||||
if (inputs.WorkerValidKey !== '') {
|
||||
await updateOption('WorkerValidKey', inputs.WorkerValidKey);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const submitPayAddress = async () => {
|
||||
if (inputs.ServerAddress === '') {
|
||||
@@ -320,6 +326,18 @@ const SystemSetting = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const submitLinuxDOOAuth = async () => {
|
||||
if (originInputs['LinuxDOClientId'] !== inputs.LinuxDOClientId) {
|
||||
await updateOption('LinuxDOClientId', inputs.LinuxDOClientId);
|
||||
}
|
||||
if (
|
||||
originInputs['LinuxDOClientSecret'] !== inputs.LinuxDOClientSecret &&
|
||||
inputs.LinuxDOClientSecret !== ''
|
||||
) {
|
||||
await updateOption('LinuxDOClientSecret', inputs.LinuxDOClientSecret);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid columns={1}>
|
||||
<Grid.Column>
|
||||
@@ -340,7 +358,15 @@ const SystemSetting = () => {
|
||||
更新服务器地址
|
||||
</Form.Button>
|
||||
<Header as='h3' inverted={isDark}>
|
||||
代理设置(支持 <a href='https://github.com/Calcium-Ion/new-api-worker' target='_blank' rel='noreferrer'>new-api-worker</a>)
|
||||
代理设置(支持{' '}
|
||||
<a
|
||||
href='https://github.com/Calcium-Ion/new-api-worker'
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
>
|
||||
new-api-worker
|
||||
</a>
|
||||
)
|
||||
</Header>
|
||||
<Form.Group widths='equal'>
|
||||
<Form.Input
|
||||
@@ -358,9 +384,7 @@ const SystemSetting = () => {
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Button onClick={submitWorker}>
|
||||
更新Worker设置
|
||||
</Form.Button>
|
||||
<Form.Button onClick={submitWorker}>更新Worker设置</Form.Button>
|
||||
<Divider />
|
||||
<Header as='h3' inverted={isDark}>
|
||||
支付设置(当前仅支持易支付接口,默认使用上方服务器地址作为回调地址!)
|
||||
@@ -483,6 +507,12 @@ const SystemSetting = () => {
|
||||
name='GitHubOAuthEnabled'
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<Form.Checkbox
|
||||
checked={inputs.LinuxDOOAuthEnabled === 'true'}
|
||||
label='允许通过 LinuxDO 账户登录 & 注册'
|
||||
name='LinuxDOOAuthEnabled'
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<Form.Checkbox
|
||||
checked={inputs.WeChatAuthEnabled === 'true'}
|
||||
label='允许通过微信登录 & 注册'
|
||||
@@ -781,6 +811,48 @@ const SystemSetting = () => {
|
||||
<Form.Button onClick={submitTurnstile}>
|
||||
保存 Turnstile 设置
|
||||
</Form.Button>
|
||||
<Divider />
|
||||
<Header as='h3' inverted={isDark}>
|
||||
配置 LinuxDO OAuth App
|
||||
<Header.Subheader>
|
||||
用以支持通过 LinuxDO 进行登录注册,
|
||||
<a
|
||||
href='https://connect.linux.do/'
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
>
|
||||
点击此处
|
||||
</a>
|
||||
管理你的 LinuxDO OAuth App
|
||||
</Header.Subheader>
|
||||
</Header>
|
||||
<Message>
|
||||
Homepage URL 填 <code>{inputs.ServerAddress}</code>
|
||||
,Authorization callback URL 填{' '}
|
||||
<code>{`${inputs.ServerAddress}/oauth/linuxdo`}</code>
|
||||
</Message>
|
||||
<Form.Group widths={3}>
|
||||
<Form.Input
|
||||
label='LinuxDO Client ID'
|
||||
name='LinuxDOClientId'
|
||||
onChange={handleInputChange}
|
||||
autoComplete='new-password'
|
||||
value={inputs.LinuxDOClientId}
|
||||
placeholder='输入你注册的 LinuxDO OAuth APP 的 ID'
|
||||
/>
|
||||
<Form.Input
|
||||
label='LinuxDO Client Secret'
|
||||
name='LinuxDOClientSecret'
|
||||
onChange={handleInputChange}
|
||||
type='password'
|
||||
autoComplete='new-password'
|
||||
value={inputs.LinuxDOClientSecret}
|
||||
placeholder='敏感信息不会发送到前端显示'
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Button onClick={submitLinuxDOOAuth}>
|
||||
保存 LinuxDO OAuth 设置
|
||||
</Form.Button>
|
||||
</Form>
|
||||
</Grid.Column>
|
||||
</Grid>
|
||||
|
||||
@@ -19,6 +19,14 @@ export async function onGitHubOAuthClicked(github_client_id) {
|
||||
);
|
||||
}
|
||||
|
||||
export async function onLinuxDOOAuthClicked(linuxdo_client_id) {
|
||||
const state = await getOAuthState();
|
||||
if (!state) return;
|
||||
window.open(
|
||||
`https://connect.linux.do/oauth2/authorize?response_type=code&client_id=${linuxdo_client_id}&state=${state}`,
|
||||
);
|
||||
}
|
||||
|
||||
let channelModels = undefined;
|
||||
export async function loadChannelModels() {
|
||||
const res = await API.get('/api/models');
|
||||
|
||||
@@ -150,6 +150,12 @@ const Home = () => {
|
||||
? '已启用'
|
||||
: '未启用'}
|
||||
</p>
|
||||
<p>
|
||||
Linux DO 身份验证:
|
||||
{statusState?.status?.linuxdo_oauth === true
|
||||
? '已启用'
|
||||
: '未启用'}
|
||||
</p>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
Reference in New Issue
Block a user