feat: optimize auth logic

This commit is contained in:
Ou 2024-10-24 20:50:30 +08:00
parent 99b06e51cb
commit 4a3c9f586a
10 changed files with 66 additions and 46 deletions

5
server/api/login.ts Normal file
View 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}`)
})

View File

@ -1,3 +1,4 @@
import process from "node:process"
import { verifyPrimitiveMetadata } from "@shared/verify"
import { UserTable } from "#/database/user"
@ -7,6 +8,7 @@ export default defineEventHandler(async (event) => {
const db = useDatabase()
if (!db) throw new Error("Not found database")
const userTable = new UserTable(db)
if (process.env.INIT_TABLE !== "false") await userTable.init()
if (event.method === "GET") {
const { data, updated } = await userTable.getData(id)
return {

View File

@ -3,7 +3,6 @@ import { SignJWT } from "jose"
import { UserTable } from "#/database/user"
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 userTable = db ? new UserTable(db) : undefined
if (!userTable) throw new Error("db is not defined")

View File

@ -5,11 +5,12 @@ export default defineEventHandler(async (event) => {
const url = getRequestURL(event)
if (["JWT_SECRET", "G_CLIENT_ID", "G_CLIENT_SECRET"].find(k => !process.env[k])) {
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 {
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()
if (token && process.env.JWT_SECRET) {
if (token) {
try {
const { payload } = await jwtVerify(token, new TextEncoder().encode(process.env.JWT_SECRET)) as { payload?: { id: string, type: string } }
if (payload?.id) {
@ -19,9 +20,12 @@ export default defineEventHandler(async (event) => {
}
}
} catch {
if (url.pathname.startsWith("/api/me")) throw createError({ statusCode: 401, message: "JWT verification failed" })
logger.warn("JWT verification failed")
if (url.pathname.startsWith("/api/me"))
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" })
}
}
}

View File

@ -19,7 +19,7 @@ function ThemeToggle() {
}
export function Menu() {
const { loggedIn, login, logout, enabledLogin, userInfo } = useLogin()
const { loggedIn, login, logout, userInfo } = useLogin()
const [shown, show] = useState(false)
const ref = useRef<HTMLElement>(null)
const isHover = useHoverDirty(ref)
@ -30,7 +30,7 @@ export function Menu() {
<span ref={ref} className="relative">
<span className="flex items-center scale-90">
{
enabledLogin && loggedIn && userInfo.avatar
loggedIn && userInfo.avatar
? (
<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">
{enabledLogin && !loggedIn && (
<li onClick={login}>
<span className="i-ph:sign-in-duotone inline-block" />
<span>Github </span>
</li>
)}
{enabledLogin && loggedIn && (
<li onClick={logout}>
<span className="i-ph:sign-out-duotone inline-block" />
<span>退</span>
</li>
)}
{loggedIn
? (
<li onClick={logout}>
<span className="i-ph:sign-out-duotone inline-block" />
<span>退</span>
</li>
)
: (
<li onClick={login}>
<span className="i-ph:sign-in-duotone inline-block" />
<span>Github </span>
</li>
)}
<ThemeToggle />
<li onClick={() => window.open(Homepage)}>
<span className="i-ph:github-logo-duotone inline-block" />

View File

@ -12,13 +12,11 @@ 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__}`
window.location.href = __LOGIN_URL__
}, [])
return {
enabledLogin,
loggedIn: !!jwt,
userInfo,
logout: () => setJwt(""),

View File

@ -8,27 +8,21 @@ import { preprocessMetadata, primitiveMetadataAtom } from "~/atoms"
import { safeParseString } from "~/utils"
export async function uploadMetadata(metadata: PrimitiveMetadata) {
if (!__ENABLE_LOGIN__) return
const jwt = safeParseString(localStorage.getItem("jwt"))
if (!jwt) return
try {
await ofetch("/api/me/sync", {
method: "POST",
headers: {
Authorization: `Bearer ${jwt}`,
},
body: {
data: metadata.data,
updatedTime: metadata.updatedTime,
},
})
} catch (e) {
console.error(e)
}
await ofetch("/api/me/sync", {
method: "POST",
headers: {
Authorization: `Bearer ${jwt}`,
},
body: {
data: metadata.data,
updatedTime: metadata.updatedTime,
},
})
}
export async function downloadMetadata(): Promise<PrimitiveMetadata | undefined> {
if (!__ENABLE_LOGIN__) return
const jwt = safeParseString(localStorage.getItem("jwt"))
if (!jwt) return
const { data, updatedTime } = await ofetch("/api/me/sync", {
@ -52,8 +46,25 @@ export function useSync() {
const toaster = useToast()
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") {
uploadMetadata(primitiveMetadata)
fn()
}
}, 10000, [primitiveMetadata])
useMount(() => {
@ -64,8 +75,8 @@ export function useSync() {
setPrimitiveMetadata(preprocessMetadata(metadata))
}
} catch (e: any) {
if (e.statusCode === 401) {
toaster("身份校验失败,请重新登录", {
if (e.statusCode !== 506) {
toaster("身份校验失败,无法同步,请重新登录", {
type: "error",
action: {
label: "登录",

View File

@ -17,11 +17,13 @@ import { Route as CColumnImport } from './routes/c.$column'
// Create/Update Routes
const IndexRoute = IndexImport.update({
id: '/',
path: '/',
getParentRoute: () => rootRoute,
} as any)
const CColumnRoute = CColumnImport.update({
id: '/c/$column',
path: '/c/$column',
getParentRoute: () => rootRoute,
} as any)

3
src/vite-env.d.ts vendored
View File

@ -2,5 +2,4 @@
/// <reference types="vite-plugin-pwa/react" />
/// <reference types="vite-plugin-pwa/info" />
/// <reference lib="webworker" />
declare const __G_CLIENT_ID__: string
declare const __ENABLE_LOGIN__: boolean
declare const __LOGIN_URL__: string

View File

@ -97,8 +97,7 @@ if (process.env.VERCEL) {
export default defineConfig({
define: {
__G_CLIENT_ID__: `"${process.env.G_CLIENT_ID}"`,
__ENABLE_LOGIN__: ["JWT_SECRET", "G_CLIENT_ID", "G_CLIENT_SECRET"].every(k => process.env[k]),
__LOGIN_URL__: process.env.G_CLIENT_ID ? `"https://github.com/login/oauth/authorize?client_id=${process.env.G_CLIENT_ID}"` : `"/api/login"`,
},
plugins: [
tsconfigPath(),