From 3f12e9518079132bfbd600298a474c9b76496d6d Mon Sep 17 00:00:00 2001 From: npmrun <1549469775@qq.com> Date: Sun, 17 May 2026 15:15:29 +0800 Subject: [PATCH] feat(auth): enhance authentication flow with user context and error handling --- app/composables/useAuth.ts | 12 +++++++++--- app/middleware/auth.global.ts | 18 +++++++++--------- app/pages/index/index.vue | 4 +++- app/plugins/auth.ts | 4 ++++ packages/drizzle-pkg/db.sqlite | Bin 32768 -> 32768 bytes server/api/auth/captcha.post.ts | 2 +- server/api/auth/login.post.ts | 2 +- server/api/auth/logout.post.ts | 2 +- server/api/auth/me.get.ts | 15 ++------------- server/api/auth/register.post.ts | 2 +- server/api/file/upload.post.ts | 4 ++-- server/api/hello.get.ts | 5 ++--- server/api/pic/random.get.ts | 6 +----- server/types/index.d.ts | 9 +++++++++ server/utils/context.ts | 18 ++++++++++++++++++ server/utils/handler.ts | 27 ++++++++++++++++++++++++--- 16 files changed, 87 insertions(+), 43 deletions(-) create mode 100644 app/plugins/auth.ts create mode 100644 server/utils/context.ts diff --git a/app/composables/useAuth.ts b/app/composables/useAuth.ts index 7046bf8..553e7a5 100644 --- a/app/composables/useAuth.ts +++ b/app/composables/useAuth.ts @@ -24,9 +24,15 @@ export function useAuth() { const loading = ref(false); async function loadUser() { - const res = await $fetch>("/api/auth/me"); - if (res.code === 0 && res.data?.user) { - user.value = res.data.user; + try { + const res = await $fetch>("/api/auth/me", { + headers: process.server ? useRequestHeaders(["cookie"]) : {}, + }); + if (res.code === 0 && res.data?.user) { + user.value = res.data.user; + } + } catch { + // 未登录时静默处理 } } diff --git a/app/middleware/auth.global.ts b/app/middleware/auth.global.ts index 5b078c3..cce41f9 100644 --- a/app/middleware/auth.global.ts +++ b/app/middleware/auth.global.ts @@ -1,14 +1,14 @@ -const PUBLIC_PATHS = ["/", "/index", "/login", "/register"]; +const PUBLIC_PATHS = ["/", "/index"]; +const AUTH_PATHS = ["/login", "/register"]; -export default defineNuxtRouteMiddleware(async (to) => { - if (PUBLIC_PATHS.includes(to.path)) return; +export default defineNuxtRouteMiddleware((to) => { + const { isLoggedIn } = useAuth(); - try { - const res = await $fetch<{ code: number }>("/api/auth/me"); - if (res.code !== 0) { - return navigateTo("/login?redirect=" + encodeURIComponent(to.fullPath)); - } - } catch { + if (isLoggedIn.value && AUTH_PATHS.includes(to.path)) { + return navigateTo("/"); + } + + if (!AUTH_PATHS.includes(to.path) && !PUBLIC_PATHS.includes(to.path) && !isLoggedIn.value) { return navigateTo("/login?redirect=" + encodeURIComponent(to.fullPath)); } }); diff --git a/app/pages/index/index.vue b/app/pages/index/index.vue index ff2ebaf..539f64f 100644 --- a/app/pages/index/index.vue +++ b/app/pages/index/index.vue @@ -1,10 +1,12 @@ \ No newline at end of file diff --git a/app/plugins/auth.ts b/app/plugins/auth.ts new file mode 100644 index 0000000..3262f65 --- /dev/null +++ b/app/plugins/auth.ts @@ -0,0 +1,4 @@ +export default defineNuxtPlugin(async () => { + const { loadUser } = useAuth() + await loadUser() +}) diff --git a/packages/drizzle-pkg/db.sqlite b/packages/drizzle-pkg/db.sqlite index 26d69111039f7d153422bca0a9b0c559324b51e2..3ef16506867046b1c6076c4b86de3b3522a71d4c 100644 GIT binary patch delta 1098 zcmbu-yK7WI6u{xEdXu~FMzL^{>}o_%2=ji}E(t;i#0YvxW9H055iGT^@a`6(osU2e z3oA=emPGsmBHCz?G7z+|wh6&foLyN6Vnj@Hieb*jF#I~5L!Hi{M<)u?j~ng6bZ_~^ z)x)i1b7gEoGw^yrt-d!y%)&0hC@l4q@Sq+30fqzQ0jz0uU0KqUe0CD$MoC@(2U zZoROIqHnf#;_S^H%_fJB_EvfdjtC@! zXG{~TH3kAUE+YjXhyoU`VCx+>#!!xBKqT%E&m5BZ(%j{CMAsiAalYO?olOj1Euur? zz|LtGe}O0xgdqqhmGQ(#4~kRfrDB4BpiBizoOFV59lw%a%;rg7HY0ucQ!^VM%7N$* zg2h^fTq~rxjekmomfj0x04(AlaJ&J639fL6HW(5YgyH?G?Y5fj(^oe7^v2h$F{HtB zsuV?Fw0GJX0tz{$kqTHS1Xxf`rHKx5mJkt|AsC9CmCjHQz+q7k)~CeH*w*1lak zp3b(?FjAYUKCR!ZoG&fZ*3*}z?b?s>t7@gF>KE#7OQrg$^2yqb`jgtj>SFnN^>Z4E z@5;ON?b=r5eQ7n_sdS2Wd)-Ew&m8Gi3kPkjHBkQ2)xKHfd%J25n1ePnIZ*yFmwew| eCI-x&HvTJ(@qw~`250` { +export default defineWrappedResponseHandler({ auth: 'public' }, async () => { const result = await createCaptcha(); return R.success(result); }); diff --git a/server/api/auth/login.post.ts b/server/api/auth/login.post.ts index 3d380e7..1119414 100644 --- a/server/api/auth/login.post.ts +++ b/server/api/auth/login.post.ts @@ -8,7 +8,7 @@ const loginSchema = z.object({ captchaCode: z.string().min(1), }); -export default defineWrappedResponseHandler(async (event) => { +export default defineWrappedResponseHandler({ auth: 'public' }, async (event) => { const body = await readBody(event); const parsed = loginSchema.safeParse(body); diff --git a/server/api/auth/logout.post.ts b/server/api/auth/logout.post.ts index 2d3137e..c93107e 100644 --- a/server/api/auth/logout.post.ts +++ b/server/api/auth/logout.post.ts @@ -1,4 +1,4 @@ -export default defineWrappedResponseHandler(async (event) => { +export default defineWrappedResponseHandler({ auth: 'public' }, async (event) => { deleteCookie(event, "token", { path: "/" }); return R.success(null); }); diff --git a/server/api/auth/me.get.ts b/server/api/auth/me.get.ts index aaedfcd..e4e91d0 100644 --- a/server/api/auth/me.get.ts +++ b/server/api/auth/me.get.ts @@ -1,16 +1,5 @@ -import { getUserFromEvent } from "#server/utils/jwt"; -import { getCurrentUser } from "#server/service/auth"; +import { getContextUser } from "#server/utils/context"; export default defineWrappedResponseHandler(async (event) => { - const payload = getUserFromEvent(event); - if (!payload) { - return R.error("未登录", null); - } - - const user = await getCurrentUser(payload); - if (!user) { - return R.error("用户不存在", null); - } - - return R.success({ user }); + return R.success({ user: getContextUser(event) }); }); diff --git a/server/api/auth/register.post.ts b/server/api/auth/register.post.ts index c4a97bb..bf54d43 100644 --- a/server/api/auth/register.post.ts +++ b/server/api/auth/register.post.ts @@ -8,7 +8,7 @@ const registerSchema = z.object({ captchaCode: z.string().min(1), }); -export default defineWrappedResponseHandler(async (event) => { +export default defineWrappedResponseHandler({ auth: 'public' }, async (event) => { const body = await readBody(event); const parsed = registerSchema.safeParse(body); diff --git a/server/api/file/upload.post.ts b/server/api/file/upload.post.ts index 24bb12a..5011408 100644 --- a/server/api/file/upload.post.ts +++ b/server/api/file/upload.post.ts @@ -12,7 +12,7 @@ interface IFile { size: number; } -export default defineWrappedResponseHandler(async (event) => { +export default defineWrappedResponseHandler({ auth: 'optional' }, async (event) => { try { // 存储目录 const uploadDir = path.join(process.cwd(), 'public/assets'); @@ -83,4 +83,4 @@ export default defineWrappedResponseHandler(async (event) => { statusMessage: err.message || '上传失败', }); } -}); \ No newline at end of file +}); diff --git a/server/api/hello.get.ts b/server/api/hello.get.ts index df2bb4a..4baaac4 100644 --- a/server/api/hello.get.ts +++ b/server/api/hello.get.ts @@ -1,11 +1,10 @@ import { getUsers } from "../service/auth" import { compare, hash } from "bcryptjs"; -export default defineWrappedResponseHandler(async (event) => { - compare +export default defineWrappedResponseHandler({ auth: 'public' }, async (event) => { const users = await getUsers() return R.success({ hello: "aa", users: users }) -}) \ No newline at end of file +}) diff --git a/server/api/pic/random.get.ts b/server/api/pic/random.get.ts index d3d406b..b4277cf 100644 --- a/server/api/pic/random.get.ts +++ b/server/api/pic/random.get.ts @@ -19,15 +19,11 @@ const handler = eventHandler(async (event: H3Event) => { return "error" } if (Reflect.has(query, "miaomc")) { - // return await $fetch("https://api.miaomc.cn/image/get", { method: "get", mode: "cors" }) event.node.res.statusCode = 302; event.node.res.setHeader("location", "https://api.miaomc.cn/image/get"); return; } if (Reflect.has(query, "r10086")) { - // return `选择 - // - // `; return await sendRedirect( event, encodeURI("https://api.r10086.com/樱道随机图片api接口.php?图片系列=动漫综合1"), @@ -37,7 +33,7 @@ const handler = eventHandler(async (event: H3Event) => { if (Reflect.has(query, "favicon")) { const avatarPath = resolve("public", "favicon.ico") event.node.res.setHeader("Content-Type", "image/jpeg"); - return fs.readFile(avatarPath) //fs.createReadStream(avatarPath); + return fs.readFile(avatarPath) } return `选择 diff --git a/server/types/index.d.ts b/server/types/index.d.ts index e69de29..08a902a 100644 --- a/server/types/index.d.ts +++ b/server/types/index.d.ts @@ -0,0 +1,9 @@ +import type { getCurrentUser } from "#server/service/auth"; + +type CurrentUser = Exclude>, null>; + +declare module "h3" { + interface H3EventContext { + user?: CurrentUser; + } +} diff --git a/server/utils/context.ts b/server/utils/context.ts new file mode 100644 index 0000000..7e59c99 --- /dev/null +++ b/server/utils/context.ts @@ -0,0 +1,18 @@ +import type { H3Event } from "h3"; +import type { getCurrentUser } from "#server/service/auth"; + +type CurrentUser = Exclude>, null>; + +/** + * 设置当前登录用户到 event.context + */ +export function setContextUser(event: H3Event, user: CurrentUser): void { + (event.context as Record).user = user; +} + +/** + * 从 event.context 获取当前登录用户 + */ +export function getContextUser(event: H3Event): CurrentUser | undefined { + return (event.context as Record).user as CurrentUser | undefined; +} diff --git a/server/utils/handler.ts b/server/utils/handler.ts index da2d78a..63e5c92 100644 --- a/server/utils/handler.ts +++ b/server/utils/handler.ts @@ -1,11 +1,14 @@ import log4js from "logger"; +import { getUserFromEvent } from "#server/utils/jwt"; +import { getCurrentUser } from "#server/service/auth"; +import { setContextUser } from "#server/utils/context"; interface IConfig { - + auth?: 'required' | 'public' | 'optional'; } const defaultConfig: IConfig = { - + auth: 'required', } const logger = log4js.getLogger("ERROR"); @@ -21,7 +24,25 @@ export const defineWrappedResponseHandler = ( const config = Object.assign({ ...defaultConfig }, typeof handlerOrConfig === 'object' ? handlerOrConfig : {}); return defineEventHandler(async (event) => { + // ---- auth guard ---- + if (config.auth !== 'public') { + const payload = getUserFromEvent(event); + if (config.auth === 'required' && !payload) { + return R.error("未登录", null); + } + if (payload) { + const user = await getCurrentUser(payload); + if (config.auth === 'required' && !user) { + return R.error("用户不存在", null); + } + if (user) { + setContextUser(event, user); + } + } + } + // ---- end auth guard ---- + const response = await handler(event) return response }) -} \ No newline at end of file +}