mirror of
https://github.com/ourongxing/newsnow.git
synced 2025-01-19 03:09:14 +08:00
feat: optimize auth logic
This commit is contained in:
parent
99b06e51cb
commit
4a3c9f586a
5
server/api/login.ts
Normal file
5
server/api/login.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import process from "node:process"
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
return sendRedirect(event, `https://github.com/login/oauth/authorize?client_id=${process.env.G_CLIENT_ID}`)
|
||||||
|
})
|
@ -1,3 +1,4 @@
|
|||||||
|
import process from "node:process"
|
||||||
import { verifyPrimitiveMetadata } from "@shared/verify"
|
import { verifyPrimitiveMetadata } from "@shared/verify"
|
||||||
import { UserTable } from "#/database/user"
|
import { UserTable } from "#/database/user"
|
||||||
|
|
||||||
@ -7,6 +8,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
const db = useDatabase()
|
const db = useDatabase()
|
||||||
if (!db) throw new Error("Not found database")
|
if (!db) throw new Error("Not found database")
|
||||||
const userTable = new UserTable(db)
|
const userTable = new UserTable(db)
|
||||||
|
if (process.env.INIT_TABLE !== "false") await userTable.init()
|
||||||
if (event.method === "GET") {
|
if (event.method === "GET") {
|
||||||
const { data, updated } = await userTable.getData(id)
|
const { data, updated } = await userTable.getData(id)
|
||||||
return {
|
return {
|
||||||
|
@ -3,7 +3,6 @@ import { SignJWT } from "jose"
|
|||||||
import { UserTable } from "#/database/user"
|
import { UserTable } from "#/database/user"
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
if (["JWT_SECRET", "G_CLIENT_ID", "G_CLIENT_SECRET"].find(k => !process.env[k])) throw new Error("Missing environment variables")
|
|
||||||
const db = useDatabase()
|
const db = useDatabase()
|
||||||
const userTable = db ? new UserTable(db) : undefined
|
const userTable = db ? new UserTable(db) : undefined
|
||||||
if (!userTable) throw new Error("db is not defined")
|
if (!userTable) throw new Error("db is not defined")
|
||||||
|
@ -5,11 +5,12 @@ export default defineEventHandler(async (event) => {
|
|||||||
const url = getRequestURL(event)
|
const url = getRequestURL(event)
|
||||||
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("/api/me")) throw createError({ statusCode: 506, message: "Server not configured" })
|
if (!url.pathname.startsWith("/api/s"))
|
||||||
|
throw createError({ statusCode: 506, message: "Server not configured, disable login" })
|
||||||
} else {
|
} else {
|
||||||
if (/^\/api\/(?:me|s)\//.test(url.pathname)) {
|
if (["/api/s", "/api/me"].find(p => url.pathname.startsWith(p))) {
|
||||||
const token = getHeader(event, "Authorization")?.replace("Bearer ", "")?.trim()
|
const token = getHeader(event, "Authorization")?.replace("Bearer ", "")?.trim()
|
||||||
if (token && process.env.JWT_SECRET) {
|
if (token) {
|
||||||
try {
|
try {
|
||||||
const { payload } = await jwtVerify(token, 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) {
|
||||||
@ -19,9 +20,12 @@ export default defineEventHandler(async (event) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
if (url.pathname.startsWith("/api/me")) throw createError({ statusCode: 401, message: "JWT verification failed" })
|
if (url.pathname.startsWith("/api/me"))
|
||||||
logger.warn("JWT verification failed")
|
throw createError({ statusCode: 401, message: "JWT verification failed" })
|
||||||
|
else logger.warn("JWT verification failed")
|
||||||
}
|
}
|
||||||
|
} else if (url.pathname.startsWith("/api/me")) {
|
||||||
|
throw createError({ statusCode: 401, message: "JWT verification failed" })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ function ThemeToggle() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Menu() {
|
export function Menu() {
|
||||||
const { loggedIn, login, logout, enabledLogin, userInfo } = useLogin()
|
const { loggedIn, login, logout, userInfo } = useLogin()
|
||||||
const [shown, show] = useState(false)
|
const [shown, show] = useState(false)
|
||||||
const ref = useRef<HTMLElement>(null)
|
const ref = useRef<HTMLElement>(null)
|
||||||
const isHover = useHoverDirty(ref)
|
const isHover = useHoverDirty(ref)
|
||||||
@ -30,7 +30,7 @@ export function Menu() {
|
|||||||
<span ref={ref} className="relative">
|
<span ref={ref} className="relative">
|
||||||
<span className="flex items-center scale-90">
|
<span className="flex items-center scale-90">
|
||||||
{
|
{
|
||||||
enabledLogin && loggedIn && userInfo.avatar
|
loggedIn && userInfo.avatar
|
||||||
? (
|
? (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -62,18 +62,19 @@ export function Menu() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ol className="bg-base bg-op-70! backdrop-blur-md p-2 rounded-lg color-base text-base">
|
<ol className="bg-base bg-op-70! backdrop-blur-md p-2 rounded-lg color-base text-base">
|
||||||
{enabledLogin && !loggedIn && (
|
{loggedIn
|
||||||
<li onClick={login}>
|
? (
|
||||||
<span className="i-ph:sign-in-duotone inline-block" />
|
<li onClick={logout}>
|
||||||
<span>Github 账号登录</span>
|
<span className="i-ph:sign-out-duotone inline-block" />
|
||||||
</li>
|
<span>退出登录</span>
|
||||||
)}
|
</li>
|
||||||
{enabledLogin && loggedIn && (
|
)
|
||||||
<li onClick={logout}>
|
: (
|
||||||
<span className="i-ph:sign-out-duotone inline-block" />
|
<li onClick={login}>
|
||||||
<span>退出登录</span>
|
<span className="i-ph:sign-in-duotone inline-block" />
|
||||||
</li>
|
<span>Github 账号登录</span>
|
||||||
)}
|
</li>
|
||||||
|
)}
|
||||||
<ThemeToggle />
|
<ThemeToggle />
|
||||||
<li onClick={() => window.open(Homepage)}>
|
<li onClick={() => window.open(Homepage)}>
|
||||||
<span className="i-ph:github-logo-duotone inline-block" />
|
<span className="i-ph:github-logo-duotone inline-block" />
|
||||||
|
@ -12,13 +12,11 @@ const jwtAtom = atomWithStorage("jwt", "")
|
|||||||
export function useLogin() {
|
export function useLogin() {
|
||||||
const [userInfo] = useAtom(userAtom)
|
const [userInfo] = useAtom(userAtom)
|
||||||
const [jwt, setJwt] = useAtom(jwtAtom)
|
const [jwt, setJwt] = useAtom(jwtAtom)
|
||||||
const enabledLogin = __ENABLE_LOGIN__
|
|
||||||
const login = useCallback(() => {
|
const login = useCallback(() => {
|
||||||
window.location.href = `https://github.com/login/oauth/authorize?client_id=${__G_CLIENT_ID__}`
|
window.location.href = __LOGIN_URL__
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
enabledLogin,
|
|
||||||
loggedIn: !!jwt,
|
loggedIn: !!jwt,
|
||||||
userInfo,
|
userInfo,
|
||||||
logout: () => setJwt(""),
|
logout: () => setJwt(""),
|
||||||
|
@ -8,27 +8,21 @@ import { preprocessMetadata, primitiveMetadataAtom } from "~/atoms"
|
|||||||
import { safeParseString } from "~/utils"
|
import { safeParseString } from "~/utils"
|
||||||
|
|
||||||
export async function uploadMetadata(metadata: PrimitiveMetadata) {
|
export async function uploadMetadata(metadata: PrimitiveMetadata) {
|
||||||
if (!__ENABLE_LOGIN__) return
|
|
||||||
const jwt = safeParseString(localStorage.getItem("jwt"))
|
const jwt = safeParseString(localStorage.getItem("jwt"))
|
||||||
if (!jwt) return
|
if (!jwt) return
|
||||||
try {
|
await ofetch("/api/me/sync", {
|
||||||
await ofetch("/api/me/sync", {
|
method: "POST",
|
||||||
method: "POST",
|
headers: {
|
||||||
headers: {
|
Authorization: `Bearer ${jwt}`,
|
||||||
Authorization: `Bearer ${jwt}`,
|
},
|
||||||
},
|
body: {
|
||||||
body: {
|
data: metadata.data,
|
||||||
data: metadata.data,
|
updatedTime: metadata.updatedTime,
|
||||||
updatedTime: metadata.updatedTime,
|
},
|
||||||
},
|
})
|
||||||
})
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function downloadMetadata(): Promise<PrimitiveMetadata | undefined> {
|
export async function downloadMetadata(): Promise<PrimitiveMetadata | undefined> {
|
||||||
if (!__ENABLE_LOGIN__) return
|
|
||||||
const jwt = safeParseString(localStorage.getItem("jwt"))
|
const jwt = safeParseString(localStorage.getItem("jwt"))
|
||||||
if (!jwt) return
|
if (!jwt) return
|
||||||
const { data, updatedTime } = await ofetch("/api/me/sync", {
|
const { data, updatedTime } = await ofetch("/api/me/sync", {
|
||||||
@ -52,8 +46,25 @@ export function useSync() {
|
|||||||
const toaster = useToast()
|
const toaster = useToast()
|
||||||
|
|
||||||
useDebounce(async () => {
|
useDebounce(async () => {
|
||||||
|
const fn = async () => {
|
||||||
|
try {
|
||||||
|
await uploadMetadata(primitiveMetadata)
|
||||||
|
} catch (e: any) {
|
||||||
|
if (e.statusCode !== 506) {
|
||||||
|
toaster("身份校验失败,无法同步,请重新登录", {
|
||||||
|
type: "error",
|
||||||
|
action: {
|
||||||
|
label: "登录",
|
||||||
|
onClick: login,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
logout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (primitiveMetadata.action === "manual") {
|
if (primitiveMetadata.action === "manual") {
|
||||||
uploadMetadata(primitiveMetadata)
|
fn()
|
||||||
}
|
}
|
||||||
}, 10000, [primitiveMetadata])
|
}, 10000, [primitiveMetadata])
|
||||||
useMount(() => {
|
useMount(() => {
|
||||||
@ -64,8 +75,8 @@ export function useSync() {
|
|||||||
setPrimitiveMetadata(preprocessMetadata(metadata))
|
setPrimitiveMetadata(preprocessMetadata(metadata))
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e.statusCode === 401) {
|
if (e.statusCode !== 506) {
|
||||||
toaster("身份校验失败,请重新登录", {
|
toaster("身份校验失败,无法同步,请重新登录", {
|
||||||
type: "error",
|
type: "error",
|
||||||
action: {
|
action: {
|
||||||
label: "登录",
|
label: "登录",
|
||||||
|
@ -17,11 +17,13 @@ import { Route as CColumnImport } from './routes/c.$column'
|
|||||||
// Create/Update Routes
|
// Create/Update Routes
|
||||||
|
|
||||||
const IndexRoute = IndexImport.update({
|
const IndexRoute = IndexImport.update({
|
||||||
|
id: '/',
|
||||||
path: '/',
|
path: '/',
|
||||||
getParentRoute: () => rootRoute,
|
getParentRoute: () => rootRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
const CColumnRoute = CColumnImport.update({
|
const CColumnRoute = CColumnImport.update({
|
||||||
|
id: '/c/$column',
|
||||||
path: '/c/$column',
|
path: '/c/$column',
|
||||||
getParentRoute: () => rootRoute,
|
getParentRoute: () => rootRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
3
src/vite-env.d.ts
vendored
3
src/vite-env.d.ts
vendored
@ -2,5 +2,4 @@
|
|||||||
/// <reference types="vite-plugin-pwa/react" />
|
/// <reference types="vite-plugin-pwa/react" />
|
||||||
/// <reference types="vite-plugin-pwa/info" />
|
/// <reference types="vite-plugin-pwa/info" />
|
||||||
/// <reference lib="webworker" />
|
/// <reference lib="webworker" />
|
||||||
declare const __G_CLIENT_ID__: string
|
declare const __LOGIN_URL__: string
|
||||||
declare const __ENABLE_LOGIN__: boolean
|
|
||||||
|
@ -97,8 +97,7 @@ if (process.env.VERCEL) {
|
|||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
define: {
|
define: {
|
||||||
__G_CLIENT_ID__: `"${process.env.G_CLIENT_ID}"`,
|
__LOGIN_URL__: process.env.G_CLIENT_ID ? `"https://github.com/login/oauth/authorize?client_id=${process.env.G_CLIENT_ID}"` : `"/api/login"`,
|
||||||
__ENABLE_LOGIN__: ["JWT_SECRET", "G_CLIENT_ID", "G_CLIENT_SECRET"].every(k => process.env[k]),
|
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
tsconfigPath(),
|
tsconfigPath(),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user