From 1e6b9edd52811f1a70b4d3c8940b972c19384e7e Mon Sep 17 00:00:00 2001 From: nakaterm <104970808+nakaterm@users.noreply.github.com> Date: Mon, 1 Dec 2025 05:18:13 +0900 Subject: [PATCH] =?UTF-8?q?Cookie=20=E3=81=AE=20path=20=E3=82=92=E6=8C=87?= =?UTF-8?q?=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/pages/Home.tsx | 30 ++++++++++++++++ client/src/pages/Project.tsx | 20 +++++++++++ client/src/pages/eventId/Submission.tsx | 20 +++++++++++ server/src/main.ts | 1 + server/src/middleware/browserId.ts | 47 +++++++++++++------------ 5 files changed, 95 insertions(+), 23 deletions(-) diff --git a/client/src/pages/Home.tsx b/client/src/pages/Home.tsx index 9c3c7b7..258b0f9 100644 --- a/client/src/pages/Home.tsx +++ b/client/src/pages/Home.tsx @@ -13,6 +13,10 @@ const client = hc(API_ENDPOINT); export default function HomePage() { const [involvedProjects, setInvolvedProjects] = useState(null); const [loading, setLoading] = useState(true); + const [toast, setToast] = useState<{ + message: string; + variant: "success" | "error"; + } | null>(null); useEffect(() => { const fetchInvolvedProjects = async () => { @@ -24,10 +28,29 @@ export default function HomePage() { const parsedData = data.map((p) => briefProjectReviver(p)); setInvolvedProjects(parsedData); } else { + let errorMessage = "イベントの取得に失敗しました。"; + try { + const data = await res.json(); + if (data && typeof data.message === "string" && data.message.trim()) { + errorMessage = data.message.trim(); + } + } catch (_) { + // レスポンスがJSONでない場合は無視 + } + setToast({ + message: errorMessage, + variant: "error", + }); + setTimeout(() => setToast(null), 5000); setInvolvedProjects(null); } } catch (error) { console.error("Error fetching involved projects:", error); + setToast({ + message: "ネットワークエラーが発生しました。", + variant: "error", + }); + setTimeout(() => setToast(null), 5000); setInvolvedProjects(null); } finally { setLoading(false); @@ -52,6 +75,13 @@ export default function HomePage() { )} + {toast && ( +
+
+ {toast.message} +
+
+ )} ); } diff --git a/client/src/pages/Project.tsx b/client/src/pages/Project.tsx index 722ccf2..5b6be0d 100644 --- a/client/src/pages/Project.tsx +++ b/client/src/pages/Project.tsx @@ -53,9 +53,29 @@ export default function ProjectPage() { const data = await res.json(); const parsedData = projectReviver(data); setProject(parsedData); + } else { + let errorMessage = "プロジェクトの取得に失敗しました。"; + try { + const data = await res.json(); + if (data && typeof data.message === "string" && data.message.trim()) { + errorMessage = data.message.trim(); + } + } catch (_) { + // レスポンスがJSONでない場合は無視 + } + setToast({ + message: errorMessage, + variant: "error", + }); + setTimeout(() => setToast(null), 5000); } } catch (error) { console.error(error); + setToast({ + message: "ネットワークエラーが発生しました。", + variant: "error", + }); + setTimeout(() => setToast(null), 5000); } finally { setProjectLoading(false); } diff --git a/client/src/pages/eventId/Submission.tsx b/client/src/pages/eventId/Submission.tsx index e199edf..628473b 100644 --- a/client/src/pages/eventId/Submission.tsx +++ b/client/src/pages/eventId/Submission.tsx @@ -59,9 +59,29 @@ export default function SubmissionPage() { const data = await res.json(); const parsedData = projectReviver(data); setProject(parsedData); + } else { + let errorMessage = "プロジェクトの取得に失敗しました。"; + try { + const data = await res.json(); + if (data && typeof data.message === "string" && data.message.trim()) { + errorMessage = data.message.trim(); + } + } catch (_) { + // レスポンスがJSONでない場合は無視 + } + setToast({ + message: errorMessage, + variant: "error", + }); + setTimeout(() => setToast(null), 5000); } } catch (error) { console.error("Error fetching project:", error); + setToast({ + message: "ネットワークエラーが発生しました。", + variant: "error", + }); + setTimeout(() => setToast(null), 5000); } finally { setProjectLoading(false); } diff --git a/server/src/main.ts b/server/src/main.ts index 2898e46..480fc95 100644 --- a/server/src/main.ts +++ b/server/src/main.ts @@ -44,6 +44,7 @@ serve( const isProduction = process.env.NODE_ENV === "prod"; export const cookieOptions = { + path: "/", domain: process.env.DOMAIN, httpOnly: true, secure: isProduction, diff --git a/server/src/middleware/browserId.ts b/server/src/middleware/browserId.ts index cdde0c0..b166b79 100644 --- a/server/src/middleware/browserId.ts +++ b/server/src/middleware/browserId.ts @@ -38,35 +38,36 @@ export const browserIdMiddleware: MiddlewareHandler = async (c: Context, next) = return c.json({ message: "サーバー設定エラー" }, 500); } - let browserId: string | undefined; - let needsReissue = false; - // 新形式 (Hono) を試す - browserId = (await getSignedCookie(c, cookieSecret, COOKIE_NAME)) || undefined; - - if (!browserId) { - const rawCookie = getCookie(c, COOKIE_NAME); - - if (rawCookie?.startsWith("s:")) { - const legacy = unsignExpressCookie(rawCookie, cookieSecret); - if (legacy) { - browserId = legacy; - needsReissue = true; - } - } + const browserIdHono = (await getSignedCookie(c, cookieSecret, COOKIE_NAME)) || undefined; + if (browserIdHono) { + c.set("browserId", browserIdHono); + return next(); } - if (browserId && needsReissue) { + // "browserId" という Cookie が存在しない場合は新規発行 + const rawCookie = getCookie(c, COOKIE_NAME); + if (!rawCookie) { + const browserId = crypto.randomUUID(); await setSignedCookie(c, COOKIE_NAME, browserId, cookieSecret, cookieOptions); + c.set("browserId", browserId); + return next(); } - if (!browserId) { - browserId = crypto.randomUUID(); - await setSignedCookie(c, COOKIE_NAME, browserId, cookieSecret, cookieOptions); + // 旧形式(Express)を試す + const browserIdExpress = unsignExpressCookie(rawCookie, cookieSecret); + if (browserIdExpress) { + // 旧形式が有効な場合は新形式で再発行 + await setSignedCookie(c, COOKIE_NAME, browserIdExpress, cookieSecret, cookieOptions); + c.set("browserId", browserIdExpress); + return next(); } - // コンテキストに保存(後続のハンドラで c.get('browserId') で取得可能) - c.set("browserId", browserId); - - await next(); + // ここまで来たら Cookie が不正 + return c.json( + { + message: "ブラウザのCookie設定に問題があります。", + }, + 400, + ); };