From 721110d5dd77910f300651d9c8948f97457cdd5c Mon Sep 17 00:00:00 2001 From: kokopi Date: Tue, 10 Mar 2026 00:38:50 +0900 Subject: [PATCH] update:deploy --- backend/src/index.ts | 70 ++++++++++++++++------------ backend/src/middleware/cloudflare.ts | 21 +++++++++ 2 files changed, 62 insertions(+), 29 deletions(-) create mode 100644 backend/src/middleware/cloudflare.ts diff --git a/backend/src/index.ts b/backend/src/index.ts index 0fa93b8..428bbf9 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,49 +1,61 @@ -import Fastify from 'fastify' -import cors from '@fastify/cors' -import cookie from '@fastify/cookie' -import session from '@fastify/session' -import csrf from '@fastify/csrf-protection' +import Fastify from "fastify"; +import cors from "@fastify/cors"; +import cookie from "@fastify/cookie"; +import session from "@fastify/session"; +import csrf from "@fastify/csrf-protection"; -import { authMiddleware } from './middleware/auth.js' -import { storageMiddleware } from './middleware/storage.js' -import { ticketsRouter } from './routes/tickets.js' -import { authRouter } from './routes/auth.js' -import { SqliteSessionStore } from './db/sessionStore.js' +import { authMiddleware } from "./middleware/auth.js"; +import { storageMiddleware } from "./middleware/storage.js"; +import { ticketsRouter } from "./routes/tickets.js"; +import { authRouter } from "./routes/auth.js"; +import { SqliteSessionStore } from "./db/sessionStore.js"; +import { cloudflareMiddleware } from "./middleware/cloudflare.js"; -const isProd = process.env.NODE_ENV === 'production' +const isProd = process.env.NODE_ENV === "production"; -const app = Fastify({ logger: true }) +const sessionSecret = process.env.SESSION_SECRET; +if (!sessionSecret) throw new Error("SESSION_SECRET env var is required"); + +const app = Fastify({ + // In prod: warn-level only to reduce noise; in dev: full pretty logging + logger: isProd ? { level: "warn" } : true, + // Trust the nginx reverse proxy so secure cookies and req.ip work correctly + trustProxy: isProd, +}); await app.register(cors, { - methods: ['GET', 'POST', 'PATCH', 'DELETE', 'OPTIONS'], - origin: process.env.FRONTEND_URL ?? 'http://localhost:5173', + methods: ["GET", "POST", "PATCH", "DELETE", "OPTIONS"], + origin: process.env.FRONTEND_URL ?? "http://localhost:5173", credentials: true, -}) +}); -await app.register(cookie) +await app.register(cookie); await app.register(session, { - secret: process.env.SESSION_SECRET!, - store: new SqliteSessionStore(), // ← persistent SQLite store + secret: sessionSecret, + store: new SqliteSessionStore(), // ← persistent SQLite store cookie: { httpOnly: true, - secure: isProd, // HTTPS-only in production - sameSite: isProd ? 'strict' : 'lax', // strict in prod, lax in dev - maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days in ms + secure: isProd, // HTTPS-only in production + sameSite: isProd ? "strict" : "lax", // strict in prod, lax in dev + maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days in ms }, saveUninitialized: false, -}) +}); if (isProd) { await app.register(csrf, { - sessionPlugin: '@fastify/session', - }) + sessionPlugin: "@fastify/session", + }); } -await app.register(authMiddleware) -await app.register(storageMiddleware) +await app.register(authMiddleware); +await app.register(storageMiddleware); +if (isProd) { + await app.register(cloudflareMiddleware); +} -await app.register(authRouter, { prefix: '/api/auth' }) -await app.register(ticketsRouter, { prefix: '/api/tickets' }) +await app.register(authRouter, { prefix: "/api/auth" }); +await app.register(ticketsRouter, { prefix: "/api/tickets" }); -await app.listen({ port: 4500, host: 'localhost' }) +await app.listen({ port: 4500, host: process.env.HOST ?? "localhost" }); diff --git a/backend/src/middleware/cloudflare.ts b/backend/src/middleware/cloudflare.ts new file mode 100644 index 0000000..a7784c3 --- /dev/null +++ b/backend/src/middleware/cloudflare.ts @@ -0,0 +1,21 @@ +import fp from "fastify-plugin"; +import type { FastifyPluginAsync } from "fastify"; + +declare module "fastify" { + interface FastifyRequest { + // Real client IP resolved from CF-Connecting-IP in prod, req.ip in dev + clientIp: string; + } +} + +export const cloudflareMiddleware: FastifyPluginAsync = fp(async (app) => { + app.decorateRequest("clientIp", ""); + + app.addHook("onRequest", async (req) => { + // CF-Connecting-IP is set by Cloudflare and cannot be spoofed by the client. + // X-Forwarded-For is not used here because it can be injected by anyone + // sending a request directly to the origin, bypassing Cloudflare. + const cfIp = req.headers["cf-connecting-ip"]; + req.clientIp = (Array.isArray(cfIp) ? cfIp[0] : cfIp) ?? req.ip; + }); +});