src/proxy.ts

function·app·3.4 KB · 85 lignes· Voir l'itinéraire

Annotation

Ce fichier `proxy.ts` implémente un middleware Next.js qui gère le routage et l'authentification basés sur les sous-domaines et les chemins d'URL. Il extrait le sous-domaine pour identifier le tenant, puis applique des règles de redirection et de vérification de session pour les routes `/admin` et `/superadmin`. La fonction `proxy` est le point d'entrée principal, et `config` définit les chemins sur lesquels ce middleware doit s'exécuter. Ce middleware est branché dans le fichier `middleware.ts` à la racine du projet Next.js.

2 exports

proxyconfig

Code source· typescript

import { NextRequest, NextResponse } from "next/server";
import { jwtVerify } from "jose";

const NEXTAUTH_SECRET = process.env.NEXTAUTH_SECRET;
if (!NEXTAUTH_SECRET) {
  throw new Error("NEXTAUTH_SECRET is required");
}
const secret = new TextEncoder().encode(NEXTAUTH_SECRET);

export async function proxy(request: NextRequest) {
  const hostname = request.headers.get("host") || "";
  const url = request.nextUrl.clone();
  const baseDomain = process.env.DOMAIN || "localhost";

  // Extraire le sous-domaine
  let subdomain: string | null = null;
  if (hostname.endsWith("." + baseDomain)) {
    const sub = hostname.split(".")[0];
    if (sub !== "staging" && sub !== "www") subdomain = sub;
  }
  if (!subdomain) subdomain = url.searchParams.get("tenant");

  // Admin accessible uniquement depuis wari.pro (sans sous-domaine)
  if (url.pathname.startsWith("/admin") && subdomain) {
    return NextResponse.redirect(new URL("https://" + (process.env.DOMAIN || "localhost") + "/admin/login", request.url));
  }

  // Routes admin publiques (pas d'auth requise)
  const isPublicAdmin =
    url.pathname.startsWith("/admin/login") ||
    url.pathname.startsWith("/admin/setup") ||
    url.pathname.startsWith("/admin/reset-mdp");

  // Vérification session admin tenant
  if (url.pathname.startsWith("/admin") && !isPublicAdmin) {
    const token = request.cookies.get("superapp_session")?.value;
    if (!token) return NextResponse.redirect(new URL("/admin/login", request.url));
    try {
      const { payload } = await jwtVerify(token, secret);
      const session = payload as { role: string; tenantId: string | null };
      if (session.role !== "TENANT_ADMIN" || !session.tenantId) {
        return NextResponse.redirect(new URL("/admin/login", request.url));
      }
      const requestHeaders = new Headers(request.headers);
      if (subdomain) requestHeaders.set("x-tenant-subdomain", subdomain);
      requestHeaders.set("x-session-tenant-id", session.tenantId);
      requestHeaders.set("x-pathname", url.pathname);
      return NextResponse.next({ request: { headers: requestHeaders } });
    } catch {
      return NextResponse.redirect(new URL("/admin/login", request.url));
    }
  }

  if (url.pathname.startsWith("/superadmin")) {
    // Inject x-pathname pour que le layout superadmin sache exclure le shell
    // sur /superadmin/login. Auth gérée par le layout server component.
    const requestHeaders = new Headers(request.headers);
    requestHeaders.set("x-pathname", url.pathname);
    return NextResponse.next({ request: { headers: requestHeaders } });
  }
  if (url.pathname.startsWith("/admin")) {
    // Pour /admin/login + /admin/reset-mdp : injecter x-pathname pour que le
    // layout admin sache exclure le shell.
    const requestHeaders = new Headers(request.headers);
    requestHeaders.set("x-pathname", url.pathname);
    return NextResponse.next({ request: { headers: requestHeaders } });
  }
  if (url.pathname.startsWith("/api")) return NextResponse.next();
  if (url.pathname.startsWith("/auth")) return NextResponse.next();

  if (subdomain) {
    const requestHeaders = new Headers(request.headers);
    requestHeaders.set("x-tenant-subdomain", subdomain);
    const preview = url.searchParams.get("preview");
    if (preview) requestHeaders.set("x-preview", "true");
    return NextResponse.next({ request: { headers: requestHeaders } });
  }

  return NextResponse.next();
}

export const config = {
  matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};