refactor: centralize Logto config and improve auth flow
- Centralized Logto domain and API base URLs in `common.ts` to avoid duplication. - Replaced deprecated `Modal.useModal` with `App.useApp().modal` for consistent Ant Design usage. - Refactored `useAutoLogin` hook to handle token expiration checks and trigger re-authentication. - Updated `UserCenter` and `FavPlayersPage` to use the new `autoSignIn` flow. - Removed the `useAuthHeaders` hook as logic was consolidated into `useAutoLogin`. - Added `AUTH_CALLBACK_URL` and `USER_CENTER_URL` constants for cleaner routing.
This commit is contained in:
parent
de05ca2ecf
commit
f188b4eac4
@ -7,17 +7,18 @@
|
||||
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { ConfigProvider, Empty, theme } from "antd";
|
||||
import { App, ConfigProvider, Empty, theme } from "antd";
|
||||
import zhCN from 'antd/locale/zh_CN';
|
||||
import { RouterProvider } from "react-router";
|
||||
import { LogtoProvider, type LogtoConfig } from '@logto/react';
|
||||
import { route } from "./routes";
|
||||
import { LOGTO_RESOURCE } from "./utils/constants";
|
||||
import { LOGTO_DOMAIN } from "./utils/common";
|
||||
|
||||
const elem = document.getElementById("root")!;
|
||||
|
||||
const config: LogtoConfig = {
|
||||
endpoint: 'https://logto.ksr.la/',
|
||||
endpoint: LOGTO_DOMAIN,
|
||||
appId: 'iq0oceaeqazpcned7hzil',
|
||||
resources: [LOGTO_RESOURCE],
|
||||
};
|
||||
@ -28,9 +29,11 @@ const app = (
|
||||
locale={zhCN}
|
||||
renderEmpty={() => <Empty description={'暂无数据'} />}
|
||||
>
|
||||
<LogtoProvider config={config}>
|
||||
<RouterProvider router={route} />
|
||||
</LogtoProvider>
|
||||
<App>
|
||||
<LogtoProvider config={config}>
|
||||
<RouterProvider router={route} />
|
||||
</LogtoProvider>
|
||||
</App>
|
||||
</ConfigProvider>
|
||||
</StrictMode>
|
||||
);
|
||||
|
||||
@ -1,17 +1,25 @@
|
||||
import { useLogto } from "@logto/react"
|
||||
import { useEffect, useState } from "react";
|
||||
import { LOGTO_RESOURCE } from "../utils/constants";
|
||||
import { App, Button } from "antd";
|
||||
import useAutoLogin from "./useAutoLogin";
|
||||
|
||||
export const useAuthHeaders = (): HeadersInit => {
|
||||
const { isAuthenticated, getAccessToken } = useLogto();
|
||||
const { login } = useAutoLogin();
|
||||
const [headers, setHeaders] = useState<HeadersInit>({});
|
||||
const { autoSignIn } = useAutoLogin();
|
||||
const app = App.useApp();
|
||||
useEffect(() => {
|
||||
if (isAuthenticated) {
|
||||
getAccessToken(LOGTO_RESOURCE).then(token => {
|
||||
if (!token) {
|
||||
login();
|
||||
app.notification.warning({
|
||||
key: 'use-auth-headers-login-expired',
|
||||
title: '登陆已过期',
|
||||
actions: [
|
||||
<Button onClick={() => autoSignIn() }>重新登陆</Button>
|
||||
]
|
||||
})
|
||||
return;
|
||||
}
|
||||
setHeaders({ Authorization: `Bearer ${token}` })
|
||||
@ -1,15 +1,24 @@
|
||||
import { useCallback } from "react"
|
||||
import { useNavigate } from "react-router";
|
||||
import { useLogto } from "@logto/react";
|
||||
import { useCallback, useEffect, useState } from "react"
|
||||
import { AUTH_CALLBACK_URL } from "../utils/front";
|
||||
|
||||
|
||||
const useAutoLogin = () => {
|
||||
const navigate = useNavigate();
|
||||
const login = useCallback((redirect = window.location.pathname) => {
|
||||
if (redirect) {
|
||||
sessionStorage.setItem('redirect', redirect);
|
||||
}
|
||||
navigate('/user-center?autoSignIn=true');
|
||||
const { isAuthenticated, getAccessToken } = useLogto();
|
||||
const [isAuthExpired, setIsAuthExpired] = useState(false);
|
||||
const { signIn } = useLogto();
|
||||
const autoSignIn = useCallback((redirect?: string) => {
|
||||
sessionStorage.setItem('redirect', redirect ?? window.location.pathname);
|
||||
signIn(AUTH_CALLBACK_URL);
|
||||
}, []);
|
||||
return { login };
|
||||
useEffect(() => {
|
||||
if (isAuthenticated) {
|
||||
getAccessToken().then(e => {
|
||||
if (!e) setIsAuthExpired(true); // Assuming
|
||||
});
|
||||
}
|
||||
}, [isAuthenticated]);
|
||||
return { autoSignIn, isAuthExpired };
|
||||
}
|
||||
|
||||
export default useAutoLogin;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Avatar, Button, Card, Divider, Flex, Image, message as AntdMessage, Popconfirm, Radio, Segmented, Spin, Typography } from "antd";
|
||||
import { Avatar, Button, Card, Divider, Flex, Image, message as AntdMessage, Popconfirm, Radio, Segmented, Spin, Typography, App } from "antd";
|
||||
import { useFavPlayerStore } from "../store/useFavPlayerStore";
|
||||
import { useNavigate } from "react-router";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
@ -20,13 +20,12 @@ enum ShowType {
|
||||
}
|
||||
|
||||
export function FavePlayersPage() {
|
||||
const [message, contextHolder] = AntdMessage.useMessage();
|
||||
const { favMap, unFav } = useFavPlayerStore(state => state);
|
||||
const [sortType, setSortType] = useState<SortType>(SortType.DEFAULT);
|
||||
const [showType, setShowType] = useState(ShowType.LOCAL);
|
||||
const [claims, setClaims] = useState<IdTokenClaims>();
|
||||
const headers = useAuthHeaders();
|
||||
const { login } = useAutoLogin();
|
||||
const { autoSignIn } = useAutoLogin();
|
||||
const localList = Object.values(favMap);
|
||||
const favListRequest = useRequest<XCXProfile[], [string]>(async (aud: string) => {
|
||||
const res = await fetch(`/api/fav`, { headers });
|
||||
@ -49,6 +48,7 @@ export function FavePlayersPage() {
|
||||
}, [favMap, sortType, showType, localList, favListRequest]);
|
||||
const navigate = useNavigate();
|
||||
const { isAuthenticated, getIdTokenClaims } = useLogto();
|
||||
const message = App.useApp().message;
|
||||
useEffect(() => {
|
||||
if (!isAuthenticated) return;
|
||||
const id = setTimeout(async () => {
|
||||
@ -75,7 +75,6 @@ export function FavePlayersPage() {
|
||||
}, []);
|
||||
return (
|
||||
<div className="app">
|
||||
{contextHolder}
|
||||
<Flex vertical gap={48}>
|
||||
<Typography.Title>收藏的球员</Typography.Title>
|
||||
<Flex justify="center" align="center" gap={12} style={{ display: isAuthenticated ? '' : 'none' }}>
|
||||
@ -143,7 +142,7 @@ export function FavePlayersPage() {
|
||||
) }
|
||||
{ (showType === ShowType.LOCAL && !isAuthenticated && list.length > 0) && (
|
||||
<Button
|
||||
onClick={() => login(window.location.pathname)}
|
||||
onClick={() => autoSignIn()}
|
||||
icon={<LoginOutlined />}
|
||||
>登陆后可同步收藏球员</Button>
|
||||
) }
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
import { EditOutlined, FileTextOutlined, HomeOutlined, KeyOutlined, LockOutlined, LoginOutlined, LogoutOutlined, MailOutlined, MobileOutlined } from "@ant-design/icons";
|
||||
import { EditOutlined, FileTextOutlined, HomeOutlined, KeyOutlined, LockOutlined, LoginOutlined, LogoutOutlined, MailOutlined, MobileOutlined, ReloadOutlined } from "@ant-design/icons";
|
||||
import { useLogto, type IdTokenClaims } from "@logto/react";
|
||||
import { App, Avatar, Button, Divider, Flex, Modal, Typography } from "antd";
|
||||
import { App, Avatar, Button, Divider, Flex, Typography } from "antd";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useLocation, useNavigate } from "react-router";
|
||||
import { AUTH_CALLBACK_URL, USER_CENTER_URL } from "../utils/front";
|
||||
import useAutoLogin from "../hooks/useAutoLogin";
|
||||
import { LOGTO_DOMAIN } from "../utils/common";
|
||||
|
||||
enum modifyRoutes {
|
||||
username = '/account/username',
|
||||
@ -13,17 +16,16 @@ enum modifyRoutes {
|
||||
backup_code = '/account/backup-codes/generate',
|
||||
}
|
||||
|
||||
const redirect = encodeURIComponent(`${window.location.origin}/user-center`);
|
||||
const logto = 'https://logto.ksr.la';
|
||||
const redirect = encodeURIComponent(USER_CENTER_URL);
|
||||
|
||||
export const UserCenter = () => {
|
||||
const { signIn, isAuthenticated, signOut, getIdTokenClaims } = useLogto();
|
||||
const { isAuthExpired, autoSignIn } = useAutoLogin();
|
||||
const [user, setUser] = useState<IdTokenClaims>();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const handleSignIn = useCallback(() => {
|
||||
const redirect = `${window.location.origin}/auth/callback`;
|
||||
signIn(redirect);
|
||||
signIn(AUTH_CALLBACK_URL);
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
if (isAuthenticated) return;
|
||||
@ -37,9 +39,9 @@ export const UserCenter = () => {
|
||||
getIdTokenClaims().then(claims => setUser(claims));
|
||||
}, [isAuthenticated]);
|
||||
const handleModifyInfo = useCallback((url: string) => {
|
||||
window.location.href = `${logto}${url}?redirect=${redirect}`;
|
||||
window.location.href = `${LOGTO_DOMAIN}${url}?redirect=${redirect}`;
|
||||
}, []);
|
||||
const [modal, contextHolder] = Modal.useModal();
|
||||
const app = App.useApp();
|
||||
if (!isAuthenticated) {
|
||||
return (
|
||||
<div className="app">
|
||||
@ -57,7 +59,6 @@ export const UserCenter = () => {
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{contextHolder}
|
||||
<Flex className="app" gap={12} vertical align="center" style={{ maxWidth: 600 }}>
|
||||
<Avatar size={128} src={user?.picture ?? user?.name} />
|
||||
<Flex>
|
||||
@ -73,24 +74,35 @@ export const UserCenter = () => {
|
||||
<Divider />
|
||||
<Button block type="primary" onClick={() => navigate('/')} icon={<HomeOutlined />}>回到首页</Button>
|
||||
<Divider />
|
||||
<Button
|
||||
block
|
||||
danger
|
||||
icon={<LoginOutlined />}
|
||||
onClick={() => modal.confirm({
|
||||
maskClosable: true,
|
||||
title: '确认登出?',
|
||||
cancelText: '保持登录',
|
||||
okText: '确认登出',
|
||||
okButtonProps: {
|
||||
icon: <LogoutOutlined />,
|
||||
danger: true,
|
||||
},
|
||||
onOk: () => signOut(`${window.location.origin}/user-center`),
|
||||
})}
|
||||
>
|
||||
登出
|
||||
</Button>
|
||||
<Flex align="center" justify="center" style={{ width: '100%' }} gap={12}>
|
||||
{isAuthExpired && (
|
||||
<Flex flex={1}>
|
||||
<Button block icon={<ReloadOutlined />} onClick={() => autoSignIn()}>
|
||||
重新登陆
|
||||
</Button>
|
||||
</Flex>
|
||||
)}
|
||||
<Flex flex={1}>
|
||||
<Button
|
||||
block
|
||||
danger
|
||||
icon={<LoginOutlined />}
|
||||
onClick={() => app.modal.confirm({
|
||||
maskClosable: true,
|
||||
title: '确认登出?',
|
||||
cancelText: '保持登录',
|
||||
okText: '确认登出',
|
||||
okButtonProps: {
|
||||
icon: <LogoutOutlined />,
|
||||
danger: true,
|
||||
},
|
||||
onOk: () => signOut(USER_CENTER_URL),
|
||||
})}
|
||||
>
|
||||
登出
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import type { GamesData, XCXFindUserResp, XCXMember, XCXProfile, XCXTag } from "../types";
|
||||
import { BASE_URL } from "../utils/common";
|
||||
import { KAIQIU_BASE_URL } from "../utils/common";
|
||||
import { redis } from "../utils/server";
|
||||
|
||||
const XCX_BASE_URL = `${BASE_URL}/xcx/public/index.php`;
|
||||
const XCX_BASE_URL = `${KAIQIU_BASE_URL}/xcx/public/index.php`;
|
||||
|
||||
export function createXCXHeader(token: string) {
|
||||
const xcxDefaultHeaders = {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { chunk } from "lodash";
|
||||
|
||||
export const BASE_URL = `https://kaiqiuwang.cc`;
|
||||
export const KAIQIU_BASE_URL = `https://kaiqiuwang.cc`;
|
||||
export const LOGTO_DOMAIN = 'https://logto.ksr.la';
|
||||
|
||||
export function sneckGroup(size: number, groupLen: number) {
|
||||
const indexArray = new Array<number>(size).fill(0).map((_, i) => i);
|
||||
|
||||
@ -90,4 +90,8 @@ export const openWebMapRaw = (type: MapType, location: MapLocation): void => {
|
||||
if (url) {
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
export const AUTH_CALLBACK_URL = `${window.location.origin}/auth/callback`;
|
||||
export const USER_CENTER_URL = `${window.location.origin}/user-center`;
|
||||
@ -1,7 +1,7 @@
|
||||
import type { Player } from "../types";
|
||||
import * as cheerio from "cheerio";
|
||||
import { XCXAPI } from "../services/xcxApi";
|
||||
import { BASE_URL } from "./common";
|
||||
import { KAIQIU_BASE_URL, LOGTO_DOMAIN } from "./common";
|
||||
import { RedisClient } from "bun";
|
||||
import { createRemoteJWKSet, jwtVerify } from 'jose';
|
||||
import { LOGTO_RESOURCE } from "./constants";
|
||||
@ -48,7 +48,7 @@ export const htmlRequestHeaders = {
|
||||
* @returns HTML
|
||||
*/
|
||||
export async function fetchEventContentHTML(matchId: string) {
|
||||
const url = `${BASE_URL}/home/space.php?do=event&id=${matchId}&view=member&status=2`;
|
||||
const url = `${KAIQIU_BASE_URL}/home/space.php?do=event&id=${matchId}&view=member&status=2`;
|
||||
const resp = await fetch(url, { headers: htmlRequestHeaders });
|
||||
return resp.text() ?? ''
|
||||
}
|
||||
@ -97,7 +97,7 @@ export const extractBearerTokenFromHeaders = (authorization: string | null) => {
|
||||
return authorization.slice(7); // The length of 'Bearer ' is 7
|
||||
};
|
||||
|
||||
const jwks = createRemoteJWKSet(new URL('https://logto.ksr.la/oidc/jwks'));
|
||||
const jwks = createRemoteJWKSet(new URL(`${LOGTO_DOMAIN}/oidc/jwks`));
|
||||
|
||||
export const verifyLogtoToken = async (headers: Headers) => {
|
||||
const auth = headers.get('Authorization');
|
||||
@ -110,7 +110,7 @@ export const verifyLogtoToken = async (headers: Headers) => {
|
||||
jwks,
|
||||
{
|
||||
// Expected issuer of the token, issued by the Logto server
|
||||
issuer: 'https://logto.ksr.la/oidc',
|
||||
issuer: `${LOGTO_DOMAIN}/oidc`,
|
||||
// Expected audience token, the resource indicator of the current API
|
||||
audience: LOGTO_RESOURCE,
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user