mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 08:27:32 +00:00
custom header in middleware
This commit is contained in:
@@ -1,4 +1,6 @@
|
|||||||
import { LANGS, LANGUAGE_COOKIE_NAME, LANGUAGE_HEADER_NAME } from "@/lib/i18n";
|
import { LANGS, LANGUAGE_COOKIE_NAME, LANGUAGE_HEADER_NAME } from "@/lib/i18n";
|
||||||
|
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||||
|
import { getHostedLoginTranslation } from "@/lib/zitadel";
|
||||||
import deepmerge from "deepmerge";
|
import deepmerge from "deepmerge";
|
||||||
import { getRequestConfig } from "next-intl/server";
|
import { getRequestConfig } from "next-intl/server";
|
||||||
import { cookies, headers } from "next/headers";
|
import { cookies, headers } from "next/headers";
|
||||||
@@ -9,6 +11,18 @@ export default getRequestConfig(async () => {
|
|||||||
|
|
||||||
let locale: string = fallback;
|
let locale: string = fallback;
|
||||||
|
|
||||||
|
const _headers = await headers();
|
||||||
|
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
||||||
|
|
||||||
|
const i18nOrganization = _headers.get("x-zitadel-i18n-organization") || ""; // You may need to set this header in middleware
|
||||||
|
|
||||||
|
const translations = await getHostedLoginTranslation({
|
||||||
|
serviceUrl,
|
||||||
|
organization: i18nOrganization,
|
||||||
|
});
|
||||||
|
|
||||||
|
translations.
|
||||||
|
|
||||||
const languageHeader = await (await headers()).get(LANGUAGE_HEADER_NAME);
|
const languageHeader = await (await headers()).get(LANGUAGE_HEADER_NAME);
|
||||||
if (languageHeader) {
|
if (languageHeader) {
|
||||||
const headerLocale = languageHeader.split(",")[0].split("-")[0]; // Extract the language code
|
const headerLocale = languageHeader.split(",")[0].split("-")[0]; // Extract the language code
|
||||||
|
@@ -21,6 +21,7 @@ import {
|
|||||||
SessionService,
|
SessionService,
|
||||||
} from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
} from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
||||||
import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||||
|
import { TranslationLevelType } from "@zitadel/proto/zitadel/settings/v2/settings_pb";
|
||||||
import { SettingsService } from "@zitadel/proto/zitadel/settings/v2/settings_service_pb";
|
import { SettingsService } from "@zitadel/proto/zitadel/settings/v2/settings_service_pb";
|
||||||
import { SendEmailVerificationCodeSchema } from "@zitadel/proto/zitadel/user/v2/email_pb";
|
import { SendEmailVerificationCodeSchema } from "@zitadel/proto/zitadel/user/v2/email_pb";
|
||||||
import type { RedirectURLsJson } from "@zitadel/proto/zitadel/user/v2/idp_pb";
|
import type { RedirectURLsJson } from "@zitadel/proto/zitadel/user/v2/idp_pb";
|
||||||
@@ -45,6 +46,7 @@ import {
|
|||||||
VerifyPasskeyRegistrationRequest,
|
VerifyPasskeyRegistrationRequest,
|
||||||
VerifyU2FRegistrationRequest,
|
VerifyU2FRegistrationRequest,
|
||||||
} from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
} from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||||
|
import { getLocale } from "next-intl/server";
|
||||||
import { unstable_cacheLife as cacheLife } from "next/cache";
|
import { unstable_cacheLife as cacheLife } from "next/cache";
|
||||||
import { getUserAgent } from "./fingerprint";
|
import { getUserAgent } from "./fingerprint";
|
||||||
import { createServiceForHost } from "./service";
|
import { createServiceForHost } from "./service";
|
||||||
@@ -58,6 +60,31 @@ async function cacheWrapper<T>(callback: Promise<T>) {
|
|||||||
return callback;
|
return callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getHostedLoginTranslation({
|
||||||
|
serviceUrl,
|
||||||
|
organization,
|
||||||
|
}: {
|
||||||
|
serviceUrl: string;
|
||||||
|
organization?: string;
|
||||||
|
}) {
|
||||||
|
const locale = await getLocale();
|
||||||
|
const settingsService: Client<typeof SettingsService> =
|
||||||
|
await createServiceForHost(SettingsService, serviceUrl);
|
||||||
|
|
||||||
|
const callback = settingsService
|
||||||
|
.getHostedLoginTranslation(
|
||||||
|
{
|
||||||
|
level: TranslationLevelType.INSTANCE,
|
||||||
|
levelId: organization,
|
||||||
|
locale: locale,
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
.then((resp) => (resp.translations ? resp.translations : undefined));
|
||||||
|
|
||||||
|
return useCache ? cacheWrapper(callback) : callback;
|
||||||
|
}
|
||||||
|
|
||||||
export async function getBrandingSettings({
|
export async function getBrandingSettings({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
organization,
|
organization,
|
||||||
|
@@ -10,21 +10,52 @@ export const config = {
|
|||||||
"/oidc/:path*",
|
"/oidc/:path*",
|
||||||
"/idps/callback/:path*",
|
"/idps/callback/:path*",
|
||||||
"/saml/:path*",
|
"/saml/:path*",
|
||||||
|
// Add "/*" to match all routes for translation header injection
|
||||||
|
"/*",
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function middleware(request: NextRequest) {
|
export async function middleware(request: NextRequest) {
|
||||||
|
// Add the original URL as a header to all requests
|
||||||
|
const requestHeaders = new Headers(request.headers);
|
||||||
|
|
||||||
|
// Extract "organization" search param from the URL and set it as a header if available
|
||||||
|
const organization = request.nextUrl.searchParams.get("organization");
|
||||||
|
if (organization) {
|
||||||
|
requestHeaders.set("x-zitadel-i18n-organization", organization);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only run the rest of the logic for the original matcher paths
|
||||||
|
const matchedPaths = [
|
||||||
|
"/.well-known/",
|
||||||
|
"/oauth/",
|
||||||
|
"/oidc/",
|
||||||
|
"/idps/callback/",
|
||||||
|
"/saml/",
|
||||||
|
];
|
||||||
|
|
||||||
|
const isMatched = matchedPaths.some((prefix) =>
|
||||||
|
request.nextUrl.pathname.startsWith(prefix),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isMatched) {
|
||||||
|
// For all other routes, just add the header and continue
|
||||||
|
return NextResponse.next({
|
||||||
|
request: { headers: requestHeaders },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// escape proxy if the environment is setup for multitenancy
|
// escape proxy if the environment is setup for multitenancy
|
||||||
if (!process.env.ZITADEL_API_URL || !process.env.ZITADEL_SERVICE_USER_TOKEN) {
|
if (!process.env.ZITADEL_API_URL || !process.env.ZITADEL_SERVICE_USER_TOKEN) {
|
||||||
return NextResponse.next();
|
return NextResponse.next({
|
||||||
|
request: { headers: requestHeaders },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const _headers = await headers();
|
const _headers = await headers();
|
||||||
|
|
||||||
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
||||||
|
|
||||||
// Call the /security route handler
|
// Call the /security route handler
|
||||||
// TODO check this on cloud run deployment
|
|
||||||
const securityResponse = await fetch(`${request.nextUrl.origin}/security`);
|
const securityResponse = await fetch(`${request.nextUrl.origin}/security`);
|
||||||
|
|
||||||
if (!securityResponse.ok) {
|
if (!securityResponse.ok) {
|
||||||
@@ -32,7 +63,9 @@ export async function middleware(request: NextRequest) {
|
|||||||
"Failed to fetch security settings:",
|
"Failed to fetch security settings:",
|
||||||
securityResponse.statusText,
|
securityResponse.statusText,
|
||||||
);
|
);
|
||||||
return NextResponse.next(); // Fallback if the request fails
|
return NextResponse.next({
|
||||||
|
request: { headers: requestHeaders },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { settings: securitySettings } = await securityResponse.json();
|
const { settings: securitySettings } = await securityResponse.json();
|
||||||
@@ -41,13 +74,8 @@ export async function middleware(request: NextRequest) {
|
|||||||
.replace("https://", "")
|
.replace("https://", "")
|
||||||
.replace("http://", "");
|
.replace("http://", "");
|
||||||
|
|
||||||
const requestHeaders = new Headers(request.headers);
|
// Add additional headers as before
|
||||||
|
|
||||||
// this is a workaround for the next.js server not forwarding the host header
|
|
||||||
// requestHeaders.set("x-zitadel-forwarded", `host="${request.nextUrl.host}"`);
|
|
||||||
requestHeaders.set("x-zitadel-public-host", `${request.nextUrl.host}`);
|
requestHeaders.set("x-zitadel-public-host", `${request.nextUrl.host}`);
|
||||||
|
|
||||||
// this is a workaround for the next.js server not forwarding the host header
|
|
||||||
requestHeaders.set("x-zitadel-instance-host", instanceHost);
|
requestHeaders.set("x-zitadel-instance-host", instanceHost);
|
||||||
|
|
||||||
const responseHeaders = new Headers();
|
const responseHeaders = new Headers();
|
||||||
@@ -55,7 +83,6 @@ export async function middleware(request: NextRequest) {
|
|||||||
responseHeaders.set("Access-Control-Allow-Headers", "*");
|
responseHeaders.set("Access-Control-Allow-Headers", "*");
|
||||||
|
|
||||||
if (securitySettings?.embeddedIframe?.enabled) {
|
if (securitySettings?.embeddedIframe?.enabled) {
|
||||||
securitySettings.embeddedIframe.allowedOrigins;
|
|
||||||
responseHeaders.set(
|
responseHeaders.set(
|
||||||
"Content-Security-Policy",
|
"Content-Security-Policy",
|
||||||
`${DEFAULT_CSP} frame-ancestors ${securitySettings.embeddedIframe.allowedOrigins.join(" ")};`,
|
`${DEFAULT_CSP} frame-ancestors ${securitySettings.embeddedIframe.allowedOrigins.join(" ")};`,
|
||||||
|
@@ -14,7 +14,7 @@
|
|||||||
],
|
],
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"generate": "buf generate https://github.com/zitadel/zitadel.git --path ./proto/zitadel",
|
"generate": "buf generate https://github.com/zitadel/zitadel.git#branch=feat/9850-hosted-login-translation-api --path ./proto/zitadel",
|
||||||
"clean": "rm -rf zitadel .turbo node_modules google protoc-gen-openapiv2 validate"
|
"clean": "rm -rf zitadel .turbo node_modules google protoc-gen-openapiv2 validate"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
Reference in New Issue
Block a user