fix: user verify

This commit is contained in:
Ou 2024-10-19 13:14:31 +08:00
parent 7030f3dec0
commit 9eeee30e8c
7 changed files with 86 additions and 37 deletions

View File

@ -63,8 +63,8 @@ export default defineEventHandler(async (event) => {
const params = new URLSearchParams({
login: "github",
user_jwt: jwtToken,
user_info: JSON.stringify({
jwt: jwtToken,
user: JSON.stringify({
avatar: userInfo.avatar_url,
name: userInfo.name,
}),

View File

@ -6,13 +6,13 @@ export default defineEventHandler(async (event) => {
console.log(url.pathname)
if (["JWT_SECRET", "G_CLIENT_ID", "G_CLIENT_SECRET"].find(k => !process.env[k])) {
event.context.disabledLogin = true
if (url.pathname.startsWith("/me")) throw createError({ statusCode: 506, message: "Server not configured" })
if (url.pathname.startsWith("/api/me")) throw createError({ statusCode: 506, message: "Server not configured" })
} else {
if (/^\/api\/(?:me|s)\//.test(url.pathname)) {
const token = getHeader(event, "Authorization")
const token = getHeader(event, "Authorization")?.replace("Bearer ", "")?.trim()
if (token && process.env.JWT_SECRET) {
try {
const { payload } = await jwtVerify(token.replace("Bearer ", ""), new TextEncoder().encode(process.env.JWT_SECRET)) as { payload?: { id: string, type: string } }
const { payload } = await jwtVerify(token, new TextEncoder().encode(process.env.JWT_SECRET)) as { payload?: { id: string, type: string } }
if (payload?.id) {
event.context.user = {
id: payload.id,
@ -20,7 +20,7 @@ export default defineEventHandler(async (event) => {
}
}
} catch {
if (url.pathname.startsWith("/me")) throw createError({ statusCode: 401, message: "JWT verification failed" })
if (url.pathname.startsWith("/api/me")) throw createError({ statusCode: 401, message: "JWT verification failed" })
logger.warn("JWT verification failed")
}
}

View File

@ -11,6 +11,7 @@ import { ofetch } from "ofetch"
import { OverlayScrollbar } from "../common/overlay-scrollbar"
import { focusSourcesAtom, refetchSourcesAtom } from "~/atoms"
import { useRelativeTime } from "~/hooks/useRelativeTime"
import { safeParseString } from "~/utils"
export interface ItemsProps extends React.HTMLAttributes<HTMLDivElement> {
id: SourceID
@ -71,7 +72,7 @@ function NewsCard({ id, inView, handleListeners }: NewsCardProps) {
const response: SourceResponse = await ofetch(url, {
timeout: 10000,
headers: {
Authorization: `Bearer ${localStorage.getItem("user_jwt")}`,
Authorization: `Bearer ${safeParseString(localStorage.getItem("jwt"))}`,
},
})
return response

27
src/hooks/useLogin.ts Normal file
View File

@ -0,0 +1,27 @@
import { useAtom } from "jotai"
import { atomWithStorage } from "jotai/utils"
import { useCallback } from "react"
const userAtom = atomWithStorage<{
name?: string
avatar?: string
}>("user", {})
const jwtAtom = atomWithStorage("jwt", "")
export function useLogin() {
const [userInfo] = useAtom(userAtom)
const [jwt, setJwt] = useAtom(jwtAtom)
const enabledLogin = __ENABLE_LOGIN__
const login = useCallback(() => {
window.location.href = `https://github.com/login/oauth/authorize?client_id=${__G_CLIENT_ID__}`
}, [])
return {
enabledLogin,
loggedIn: !!jwt,
userInfo,
logout: () => setJwt(""),
login,
}
}

View File

@ -1,13 +1,15 @@
import type { PrimitiveMetadata } from "@shared/types"
import { useAtom } from "jotai"
import { ofetch } from "ofetch"
import { useEffect } from "react"
import { useDebounce } from "react-use"
import { useDebounce, useMount } from "react-use"
import { toast } from "sonner"
import { useLogin } from "./useLogin"
import { preprocessMetadata, primitiveMetadataAtom } from "~/atoms"
import { safeParseString } from "~/utils"
export async function uploadMetadata(metadata: PrimitiveMetadata) {
if (!__ENABLE_LOGIN__) return
const jwt = localStorage.getItem("user_jwt")
const jwt = localStorage.getItem("jwt")
if (!jwt) return
try {
await ofetch("/api/me/sync", {
@ -27,42 +29,51 @@ export async function uploadMetadata(metadata: PrimitiveMetadata) {
export async function downloadMetadata(): Promise<PrimitiveMetadata | undefined> {
if (!__ENABLE_LOGIN__) return
const jwt = localStorage.getItem("user_jwt")
const jwt = safeParseString(localStorage.getItem("jwt"))
if (!jwt) return
try {
const { data, updatedTime } = await ofetch("/api/me/sync", {
headers: {
Authorization: `Bearer ${jwt}`,
},
}) as PrimitiveMetadata
// 不用同步 action 字段
if (data) {
return {
action: "sync",
data,
updatedTime,
}
const { data, updatedTime } = await ofetch("/api/me/sync", {
headers: {
Authorization: `Bearer ${jwt}`,
},
}) as PrimitiveMetadata
// 不用同步 action 字段
if (data) {
return {
action: "sync",
data,
updatedTime,
}
} catch (e) {
console.error(e)
}
}
export function useSync() {
const [primitiveMetadata, setPrimitiveMetadata] = useAtom(primitiveMetadataAtom)
const { logout, login } = useLogin()
useDebounce(async () => {
if (primitiveMetadata.action === "manual") {
uploadMetadata(primitiveMetadata)
}
}, 10000, [primitiveMetadata])
useEffect(() => {
useMount(() => {
const fn = async () => {
const metadata = await downloadMetadata()
if (metadata) {
setPrimitiveMetadata(preprocessMetadata(metadata))
try {
const metadata = await downloadMetadata()
if (metadata) {
setPrimitiveMetadata(preprocessMetadata(metadata))
}
} catch (e: any) {
if (e.statusCode === 401) {
toast.error("身份校验失败,请重新登录", {
action: {
label: "登录",
onClick: login,
},
})
logout()
}
}
}
fn()
}, [setPrimitiveMetadata])
})
}

View File

@ -10,6 +10,7 @@ import { useOnReload } from "~/hooks/useOnReload"
import { GlobalOverlayScrollbar } from "~/components/common/overlay-scrollbar"
import { useSync } from "~/hooks/useSync"
import { Footer } from "~/components/footer"
import { Toast } from "~/components/common/toast"
export const Route = createRootRouteWithContext<{
queryClient: QueryClient
@ -18,8 +19,9 @@ export const Route = createRootRouteWithContext<{
notFoundComponent: NotFoundComponent,
beforeLoad: () => {
const query = new URLSearchParams(window.location.search)
if (query.has("login")) {
[...query.entries()].forEach(key => localStorage.setItem(key[0], key[1]))
if (query.has("login") && query.has("user") && query.has("jwt")) {
localStorage.setItem("user", query.get("user")!)
localStorage.setItem("jwt", JSON.stringify(query.get("jwt")!))
window.history.replaceState({}, document.title, window.location.pathname)
}
},
@ -46,15 +48,15 @@ function RootComponent() {
<header className={clsx([
"flex justify-between items-center py-4 px-5",
"lg:(py-6)",
"sticky top-0 z-100 backdrop-blur-md",
"sticky top-0 z-10 backdrop-blur-md",
])}
>
<Header />
</header>
<main className={clsx([
"min-h-[calc(100vh-170px)] transition-margin",
"md:(min-h-[calc(100vh-110px)] mt--14)",
"lg:(min-h-[calc(100vh-124px)] mt--16)",
"min-h-[calc(100vh-170px)]",
"md:(min-h-[calc(100vh-110px)])",
"lg:(min-h-[calc(100vh-124px)])",
])}
>
<Outlet />
@ -63,6 +65,7 @@ function RootComponent() {
<Footer />
</footer>
</GlobalOverlayScrollbar>
<Toast />
{import.meta.env.DEV && (
<>
<ReactQueryDevtools buttonPosition="bottom-left" />

7
src/utils/index.ts Normal file
View File

@ -0,0 +1,7 @@
export function safeParseString(str: any) {
try {
return JSON.parse(str)
} catch {
return ""
}
}