From fc310c06162b713a4725f8294ec9ba56cfeda56d Mon Sep 17 00:00:00 2001 From: Erik Silva Date: Tue, 9 Dec 2025 02:24:56 -0300 Subject: [PATCH] fix: add Next.js API route handler to proxy with correct host headers --- backend/internal/api/middleware/tenant.go | 17 +++- .../app/api/[...path]/route.ts | 77 +++++++++++++++++++ front-end-dash.aggios.app/next.config.ts | 19 +++++ 3 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 front-end-dash.aggios.app/app/api/[...path]/route.ts diff --git a/backend/internal/api/middleware/tenant.go b/backend/internal/api/middleware/tenant.go index bd29737..f7082d3 100644 --- a/backend/internal/api/middleware/tenant.go +++ b/backend/internal/api/middleware/tenant.go @@ -2,6 +2,7 @@ package middleware import ( "context" + "log" "net/http" "strings" @@ -17,7 +18,16 @@ const SubdomainKey tenantContextKey = "subdomain" func TenantDetector(tenantRepo *repository.TenantRepository) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - host := r.Host + // Get host from X-Forwarded-Host header (set by Next.js proxy) or Host header + host := r.Header.Get("X-Forwarded-Host") + if host == "" { + host = r.Header.Get("X-Original-Host") + } + if host == "" { + host = r.Host + } + + log.Printf("TenantDetector: host = %s (from headers), path = %s", host, r.RequestURI) // Extract subdomain // Examples: @@ -39,6 +49,8 @@ func TenantDetector(tenantRepo *repository.TenantRepository) func(http.Handler) } } + log.Printf("TenantDetector: extracted subdomain = %s", subdomain) + // Add subdomain to context ctx := context.WithValue(r.Context(), SubdomainKey, subdomain) @@ -46,7 +58,10 @@ func TenantDetector(tenantRepo *repository.TenantRepository) func(http.Handler) if subdomain != "" && subdomain != "dash" && subdomain != "api" && subdomain != "localhost" { tenant, err := tenantRepo.FindBySubdomain(subdomain) if err == nil && tenant != nil { + log.Printf("TenantDetector: found tenant %s for subdomain %s", tenant.ID.String(), subdomain) ctx = context.WithValue(ctx, TenantIDKey, tenant.ID.String()) + } else { + log.Printf("TenantDetector: tenant not found for subdomain %s (err=%v)", subdomain, err) } } diff --git a/front-end-dash.aggios.app/app/api/[...path]/route.ts b/front-end-dash.aggios.app/app/api/[...path]/route.ts new file mode 100644 index 0000000..03c50c8 --- /dev/null +++ b/front-end-dash.aggios.app/app/api/[...path]/route.ts @@ -0,0 +1,77 @@ +import { NextRequest, NextResponse } from "next/server"; + +export async function GET(req: NextRequest, { params }: { params: { path: string[] } }) { + const path = params.path?.join("/") || ""; + const token = req.headers.get("authorization"); + const host = req.headers.get("host"); + + try { + const response = await fetch(`http://backend:8080/api/${path}${req.nextUrl.search}`, { + method: "GET", + headers: { + "Authorization": token || "", + "Content-Type": "application/json", + "X-Forwarded-Host": host || "", + "X-Original-Host": host || "", + }, + }); + + const data = await response.json(); + return NextResponse.json(data, { status: response.status }); + } catch (error) { + console.error("API proxy error:", error); + return NextResponse.json({ error: "Internal Server Error" }, { status: 500 }); + } +} + +export async function PUT(req: NextRequest, { params }: { params: { path: string[] } }) { + const path = params.path?.join("/") || ""; + const token = req.headers.get("authorization"); + const host = req.headers.get("host"); + const body = await req.json(); + + try { + const response = await fetch(`http://backend:8080/api/${path}${req.nextUrl.search}`, { + method: "PUT", + headers: { + "Authorization": token || "", + "Content-Type": "application/json", + "X-Forwarded-Host": host || "", + "X-Original-Host": host || "", + }, + body: JSON.stringify(body), + }); + + const data = await response.json(); + return NextResponse.json(data, { status: response.status }); + } catch (error) { + console.error("API proxy error:", error); + return NextResponse.json({ error: "Internal Server Error" }, { status: 500 }); + } +} + +export async function POST(req: NextRequest, { params }: { params: { path: string[] } }) { + const path = params.path?.join("/") || ""; + const token = req.headers.get("authorization"); + const host = req.headers.get("host"); + const body = await req.json(); + + try { + const response = await fetch(`http://backend:8080/api/${path}${req.nextUrl.search}`, { + method: "POST", + headers: { + "Authorization": token || "", + "Content-Type": "application/json", + "X-Forwarded-Host": host || "", + "X-Original-Host": host || "", + }, + body: JSON.stringify(body), + }); + + const data = await response.json(); + return NextResponse.json(data, { status: response.status }); + } catch (error) { + console.error("API proxy error:", error); + return NextResponse.json({ error: "Internal Server Error" }, { status: 500 }); + } +} diff --git a/front-end-dash.aggios.app/next.config.ts b/front-end-dash.aggios.app/next.config.ts index 84b36f2..597f089 100644 --- a/front-end-dash.aggios.app/next.config.ts +++ b/front-end-dash.aggios.app/next.config.ts @@ -10,10 +10,29 @@ const nextConfig: NextConfig = { { source: "/api/:path*", destination: "http://backend:8080/api/:path*", + has: [ + { + type: "header", + key: "X-Forwarded-Host", + }, + ], }, ], }; }, + headers: async () => { + return [ + { + source: "/api/:path*", + headers: [ + { + key: "X-Forwarded-For", + value: "127.0.0.1", + }, + ], + }, + ]; + }, }; export default nextConfig;