mirror of
https://github.com/ourongxing/newsnow.git
synced 2025-01-31 10:58:04 +08:00
fix: user verify
This commit is contained in:
parent
7030f3dec0
commit
9eeee30e8c
@ -63,8 +63,8 @@ export default defineEventHandler(async (event) => {
|
|||||||
|
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
login: "github",
|
login: "github",
|
||||||
user_jwt: jwtToken,
|
jwt: jwtToken,
|
||||||
user_info: JSON.stringify({
|
user: JSON.stringify({
|
||||||
avatar: userInfo.avatar_url,
|
avatar: userInfo.avatar_url,
|
||||||
name: userInfo.name,
|
name: userInfo.name,
|
||||||
}),
|
}),
|
||||||
|
@ -6,13 +6,13 @@ export default defineEventHandler(async (event) => {
|
|||||||
console.log(url.pathname)
|
console.log(url.pathname)
|
||||||
if (["JWT_SECRET", "G_CLIENT_ID", "G_CLIENT_SECRET"].find(k => !process.env[k])) {
|
if (["JWT_SECRET", "G_CLIENT_ID", "G_CLIENT_SECRET"].find(k => !process.env[k])) {
|
||||||
event.context.disabledLogin = true
|
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 {
|
} else {
|
||||||
if (/^\/api\/(?:me|s)\//.test(url.pathname)) {
|
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) {
|
if (token && process.env.JWT_SECRET) {
|
||||||
try {
|
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) {
|
if (payload?.id) {
|
||||||
event.context.user = {
|
event.context.user = {
|
||||||
id: payload.id,
|
id: payload.id,
|
||||||
@ -20,7 +20,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} 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")
|
logger.warn("JWT verification failed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import { ofetch } from "ofetch"
|
|||||||
import { OverlayScrollbar } from "../common/overlay-scrollbar"
|
import { OverlayScrollbar } from "../common/overlay-scrollbar"
|
||||||
import { focusSourcesAtom, refetchSourcesAtom } from "~/atoms"
|
import { focusSourcesAtom, refetchSourcesAtom } from "~/atoms"
|
||||||
import { useRelativeTime } from "~/hooks/useRelativeTime"
|
import { useRelativeTime } from "~/hooks/useRelativeTime"
|
||||||
|
import { safeParseString } from "~/utils"
|
||||||
|
|
||||||
export interface ItemsProps extends React.HTMLAttributes<HTMLDivElement> {
|
export interface ItemsProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||||
id: SourceID
|
id: SourceID
|
||||||
@ -71,7 +72,7 @@ function NewsCard({ id, inView, handleListeners }: NewsCardProps) {
|
|||||||
const response: SourceResponse = await ofetch(url, {
|
const response: SourceResponse = await ofetch(url, {
|
||||||
timeout: 10000,
|
timeout: 10000,
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${localStorage.getItem("user_jwt")}`,
|
Authorization: `Bearer ${safeParseString(localStorage.getItem("jwt"))}`,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return response
|
return response
|
||||||
|
27
src/hooks/useLogin.ts
Normal file
27
src/hooks/useLogin.ts
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,15 @@
|
|||||||
import type { PrimitiveMetadata } from "@shared/types"
|
import type { PrimitiveMetadata } from "@shared/types"
|
||||||
import { useAtom } from "jotai"
|
import { useAtom } from "jotai"
|
||||||
import { ofetch } from "ofetch"
|
import { ofetch } from "ofetch"
|
||||||
import { useEffect } from "react"
|
import { useDebounce, useMount } from "react-use"
|
||||||
import { useDebounce } from "react-use"
|
import { toast } from "sonner"
|
||||||
|
import { useLogin } from "./useLogin"
|
||||||
import { preprocessMetadata, primitiveMetadataAtom } from "~/atoms"
|
import { preprocessMetadata, primitiveMetadataAtom } from "~/atoms"
|
||||||
|
import { safeParseString } from "~/utils"
|
||||||
|
|
||||||
export async function uploadMetadata(metadata: PrimitiveMetadata) {
|
export async function uploadMetadata(metadata: PrimitiveMetadata) {
|
||||||
if (!__ENABLE_LOGIN__) return
|
if (!__ENABLE_LOGIN__) return
|
||||||
const jwt = localStorage.getItem("user_jwt")
|
const jwt = localStorage.getItem("jwt")
|
||||||
if (!jwt) return
|
if (!jwt) return
|
||||||
try {
|
try {
|
||||||
await ofetch("/api/me/sync", {
|
await ofetch("/api/me/sync", {
|
||||||
@ -27,42 +29,51 @@ export async function uploadMetadata(metadata: PrimitiveMetadata) {
|
|||||||
|
|
||||||
export async function downloadMetadata(): Promise<PrimitiveMetadata | undefined> {
|
export async function downloadMetadata(): Promise<PrimitiveMetadata | undefined> {
|
||||||
if (!__ENABLE_LOGIN__) return
|
if (!__ENABLE_LOGIN__) return
|
||||||
const jwt = localStorage.getItem("user_jwt")
|
const jwt = safeParseString(localStorage.getItem("jwt"))
|
||||||
if (!jwt) return
|
if (!jwt) return
|
||||||
try {
|
const { data, updatedTime } = await ofetch("/api/me/sync", {
|
||||||
const { data, updatedTime } = await ofetch("/api/me/sync", {
|
headers: {
|
||||||
headers: {
|
Authorization: `Bearer ${jwt}`,
|
||||||
Authorization: `Bearer ${jwt}`,
|
},
|
||||||
},
|
}) as PrimitiveMetadata
|
||||||
}) as PrimitiveMetadata
|
// 不用同步 action 字段
|
||||||
// 不用同步 action 字段
|
if (data) {
|
||||||
if (data) {
|
return {
|
||||||
return {
|
action: "sync",
|
||||||
action: "sync",
|
data,
|
||||||
data,
|
updatedTime,
|
||||||
updatedTime,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useSync() {
|
export function useSync() {
|
||||||
const [primitiveMetadata, setPrimitiveMetadata] = useAtom(primitiveMetadataAtom)
|
const [primitiveMetadata, setPrimitiveMetadata] = useAtom(primitiveMetadataAtom)
|
||||||
|
const { logout, login } = useLogin()
|
||||||
|
|
||||||
useDebounce(async () => {
|
useDebounce(async () => {
|
||||||
if (primitiveMetadata.action === "manual") {
|
if (primitiveMetadata.action === "manual") {
|
||||||
uploadMetadata(primitiveMetadata)
|
uploadMetadata(primitiveMetadata)
|
||||||
}
|
}
|
||||||
}, 10000, [primitiveMetadata])
|
}, 10000, [primitiveMetadata])
|
||||||
useEffect(() => {
|
useMount(() => {
|
||||||
const fn = async () => {
|
const fn = async () => {
|
||||||
const metadata = await downloadMetadata()
|
try {
|
||||||
if (metadata) {
|
const metadata = await downloadMetadata()
|
||||||
setPrimitiveMetadata(preprocessMetadata(metadata))
|
if (metadata) {
|
||||||
|
setPrimitiveMetadata(preprocessMetadata(metadata))
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
if (e.statusCode === 401) {
|
||||||
|
toast.error("身份校验失败,请重新登录", {
|
||||||
|
action: {
|
||||||
|
label: "登录",
|
||||||
|
onClick: login,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
logout()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn()
|
fn()
|
||||||
}, [setPrimitiveMetadata])
|
})
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import { useOnReload } from "~/hooks/useOnReload"
|
|||||||
import { GlobalOverlayScrollbar } from "~/components/common/overlay-scrollbar"
|
import { GlobalOverlayScrollbar } from "~/components/common/overlay-scrollbar"
|
||||||
import { useSync } from "~/hooks/useSync"
|
import { useSync } from "~/hooks/useSync"
|
||||||
import { Footer } from "~/components/footer"
|
import { Footer } from "~/components/footer"
|
||||||
|
import { Toast } from "~/components/common/toast"
|
||||||
|
|
||||||
export const Route = createRootRouteWithContext<{
|
export const Route = createRootRouteWithContext<{
|
||||||
queryClient: QueryClient
|
queryClient: QueryClient
|
||||||
@ -18,8 +19,9 @@ export const Route = createRootRouteWithContext<{
|
|||||||
notFoundComponent: NotFoundComponent,
|
notFoundComponent: NotFoundComponent,
|
||||||
beforeLoad: () => {
|
beforeLoad: () => {
|
||||||
const query = new URLSearchParams(window.location.search)
|
const query = new URLSearchParams(window.location.search)
|
||||||
if (query.has("login")) {
|
if (query.has("login") && query.has("user") && query.has("jwt")) {
|
||||||
[...query.entries()].forEach(key => localStorage.setItem(key[0], key[1]))
|
localStorage.setItem("user", query.get("user")!)
|
||||||
|
localStorage.setItem("jwt", JSON.stringify(query.get("jwt")!))
|
||||||
window.history.replaceState({}, document.title, window.location.pathname)
|
window.history.replaceState({}, document.title, window.location.pathname)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -46,15 +48,15 @@ function RootComponent() {
|
|||||||
<header className={clsx([
|
<header className={clsx([
|
||||||
"flex justify-between items-center py-4 px-5",
|
"flex justify-between items-center py-4 px-5",
|
||||||
"lg:(py-6)",
|
"lg:(py-6)",
|
||||||
"sticky top-0 z-100 backdrop-blur-md",
|
"sticky top-0 z-10 backdrop-blur-md",
|
||||||
])}
|
])}
|
||||||
>
|
>
|
||||||
<Header />
|
<Header />
|
||||||
</header>
|
</header>
|
||||||
<main className={clsx([
|
<main className={clsx([
|
||||||
"min-h-[calc(100vh-170px)] transition-margin",
|
"min-h-[calc(100vh-170px)]",
|
||||||
"md:(min-h-[calc(100vh-110px)] mt--14)",
|
"md:(min-h-[calc(100vh-110px)])",
|
||||||
"lg:(min-h-[calc(100vh-124px)] mt--16)",
|
"lg:(min-h-[calc(100vh-124px)])",
|
||||||
])}
|
])}
|
||||||
>
|
>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
@ -63,6 +65,7 @@ function RootComponent() {
|
|||||||
<Footer />
|
<Footer />
|
||||||
</footer>
|
</footer>
|
||||||
</GlobalOverlayScrollbar>
|
</GlobalOverlayScrollbar>
|
||||||
|
<Toast />
|
||||||
{import.meta.env.DEV && (
|
{import.meta.env.DEV && (
|
||||||
<>
|
<>
|
||||||
<ReactQueryDevtools buttonPosition="bottom-left" />
|
<ReactQueryDevtools buttonPosition="bottom-left" />
|
||||||
|
7
src/utils/index.ts
Normal file
7
src/utils/index.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export function safeParseString(str: any) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(str)
|
||||||
|
} catch {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user