Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
path: root/apps/web/proxy.ts
blob: 3eb5e60972537b3b112ae3b2be40f75007de080c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
// SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
// SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1

import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
import {
  isLocale,
  localeCookieName,
  localeCookieOptions,
  requestHost,
} from "@/lib/localization";

const accountHosts = new Set(["account.progmasoft.com", "account.localhost"]);
const vigetHosts = new Set(["viget.progmasoft.com", "viget.localhost"]);

export function proxy(request: NextRequest) {
  const host = requestHost(
    request.headers.get("x-forwarded-host"),
    request.headers.get("host"),
  );
  const pathname = request.nextUrl.pathname;

  // Language selection is a user preference, not a second canonical URL.
  // Consume the query parameter once, persist it across Progmasoft hosts, and
  // redirect to the clean URL so crawlers never index preference variants.
  // The header control switches in place through a server action; this
  // parameter remains for links and for browsers that run no script.
  const requestedLocale = request.nextUrl.searchParams.get("lang") ?? undefined;
  if (isLocale(requestedLocale)) {
    const destination = request.nextUrl.clone();
    destination.searchParams.delete("lang");
    const forwardedProtocol = request.headers.get("x-forwarded-proto");
    const isProductionHost =
      host === "progmasoft.com" || host.endsWith(".progmasoft.com");
    destination.protocol = isProductionHost
      ? "https:"
      : request.nextUrl.protocol;
    const response = NextResponse.redirect(destination);
    response.cookies.set(
      localeCookieName,
      requestedLocale,
      localeCookieOptions(
        host,
        forwardedProtocol === "https" || request.nextUrl.protocol === "https:",
      ),
    );
    return response;
  }

  // The account root has its own landing page, but authentication and dashboard URLs remain stable and readable.
  if (accountHosts.has(host) && pathname === "/") {
    const destination = new URL("/account", request.url);

    // TLS terminates at Nginx. Next derives the public scheme from X-Forwarded-Proto,
    // so an unmodified absolute rewrite would attempt TLS against its HTTP-only
    // loopback listener and fail before the account page can render.
    destination.protocol = "http:";
    return NextResponse.rewrite(destination);
  }

  // ViGet is deployed by Progmaweb but keeps short public catalog URLs. The
  // internal /viget tree prevents those routes from colliding with the apex
  // organization page and the account surface.
  if (vigetHosts.has(host)) {
    if (pathname === "/") {
      return NextResponse.rewrite(internalRoute(request, "/viget"));
    }
    if (pathname === "/dslplugins" || pathname === "/dslplugins/") {
      return NextResponse.rewrite(internalRoute(request, "/viget/dslplugins"));
    }
    if (pathname === "/login" || pathname === "/login/") {
      return NextResponse.redirect(
        new URL("https://account.progmasoft.com/login"),
      );
    }
    if (pathname === "/register" || pathname === "/register/") {
      return NextResponse.redirect(
        new URL("https://account.progmasoft.com/register"),
      );
    }
    if (pathname === "/recover" || pathname === "/recover/") {
      return NextResponse.redirect(
        new URL("https://account.progmasoft.com/recover"),
      );
    }
  }

  if (
    !vigetHosts.has(host) &&
    (pathname === "/viget" || pathname.startsWith("/viget/"))
  ) {
    const publicPath = pathname.replace(/^\/viget/, "") || "/";
    return NextResponse.redirect(
      new URL(publicPath, "https://viget.progmasoft.com"),
    );
  }

  // Corporate hosts must not accidentally expose the account-only landing page as a public navigation destination.
  if (!accountHosts.has(host) && pathname === "/account") {
    return NextResponse.redirect(new URL("https://account.progmasoft.com/"));
  }

  return NextResponse.next();
}

function internalRoute(request: NextRequest, pathname: string): URL {
  const destination = new URL(pathname, request.url);
  destination.protocol = "http:";
  return destination;
}

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