mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-14 09:55:12 +00:00
<!-- Please inform yourself about the contribution guidelines on submitting a PR here: https://github.com/zitadel/zitadel/blob/main/CONTRIBUTING.md#submit-a-pull-request-pr. Take note of how PR/commit titles should be written and replace the template texts in the sections below. Don't remove any of the sections. It is important that the commit history clearly shows what is changed and why. Important: By submitting a contribution you agree to the terms from our Licensing Policy as described here: https://github.com/zitadel/zitadel/blob/main/LICENSING.md#community-contributions. --> # Which Problems Are Solved When deploying the login application behind proxies or using Vercel rewrites (e.g., `zitadel.com/login` → `login-zitadel-qa.vercel.app`), the application was using the internal rewritten host instead of the original user-facing host. This caused several issues: 1. **Broken Password Reset Emails**: Email links contained internal hosts like `login-zitadel-qa.vercel.app` instead of `zitadel.com` 2. **Inconsistent User Experience**: Users would see different domains in various parts of the flow 3. **Security Concerns**: Internal infrastructure details were exposed to end users 4. **Scattered Logic**: Host detection logic was duplicated across multiple files with inconsistent error handling # How the Problems Are Solved Created comprehensive host detection utilities in `/lib/server/host.ts` and `/lib/client/host.ts`: **Server-side utilities:** - `getOriginalHost()` - Returns the original user-facing host - `getOriginalHostWithProtocol()` - Returns host with proper protocol (http/https)
95 lines
2.6 KiB
TypeScript
95 lines
2.6 KiB
TypeScript
"use server";
|
|
|
|
import { getSession, registerU2F, verifyU2FRegistration } from "@/lib/zitadel";
|
|
import { create } from "@zitadel/client";
|
|
import { VerifyU2FRegistrationRequestSchema } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
|
import { headers } from "next/headers";
|
|
import { userAgent } from "next/server";
|
|
import { getSessionCookieById } from "../cookies";
|
|
import { getServiceUrlFromHeaders } from "../service-url";
|
|
import { getOriginalHost } from "./host";
|
|
|
|
type RegisterU2FCommand = {
|
|
sessionId: string;
|
|
};
|
|
|
|
type VerifyU2FCommand = {
|
|
u2fId: string;
|
|
passkeyName?: string;
|
|
publicKeyCredential: any;
|
|
sessionId: string;
|
|
};
|
|
|
|
export async function addU2F(command: RegisterU2FCommand) {
|
|
const _headers = await headers();
|
|
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
|
const host = await getOriginalHost();
|
|
|
|
const sessionCookie = await getSessionCookieById({
|
|
sessionId: command.sessionId,
|
|
});
|
|
|
|
if (!sessionCookie) {
|
|
return { error: "Could not get session" };
|
|
}
|
|
|
|
const session = await getSession({
|
|
serviceUrl,
|
|
sessionId: sessionCookie.id,
|
|
sessionToken: sessionCookie.token,
|
|
});
|
|
|
|
const [hostname] = host.split(":");
|
|
|
|
if (!hostname) {
|
|
throw new Error("Could not get hostname");
|
|
}
|
|
|
|
const userId = session?.session?.factors?.user?.id;
|
|
|
|
if (!session || !userId) {
|
|
return { error: "Could not get session" };
|
|
}
|
|
|
|
return registerU2F({ serviceUrl, userId, domain: hostname });
|
|
}
|
|
|
|
export async function verifyU2F(command: VerifyU2FCommand) {
|
|
const _headers = await headers();
|
|
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
|
let passkeyName = command.passkeyName;
|
|
if (!passkeyName) {
|
|
const headersList = await headers();
|
|
const userAgentStructure = { headers: headersList };
|
|
const { browser, device, os } = userAgent(userAgentStructure);
|
|
|
|
passkeyName = `${device.vendor ?? ""} ${device.model ?? ""}${
|
|
device.vendor || device.model ? ", " : ""
|
|
}${os.name}${os.name ? ", " : ""}${browser.name}`;
|
|
}
|
|
const sessionCookie = await getSessionCookieById({
|
|
sessionId: command.sessionId,
|
|
});
|
|
|
|
const session = await getSession({
|
|
serviceUrl,
|
|
sessionId: sessionCookie.id,
|
|
sessionToken: sessionCookie.token,
|
|
});
|
|
|
|
const userId = session?.session?.factors?.user?.id;
|
|
|
|
if (!userId) {
|
|
return { error: "Could not get session" };
|
|
}
|
|
|
|
const request = create(VerifyU2FRegistrationRequestSchema, {
|
|
u2fId: command.u2fId,
|
|
publicKeyCredential: command.publicKeyCredential,
|
|
tokenName: passkeyName,
|
|
userId,
|
|
});
|
|
|
|
return verifyU2FRegistration({ serviceUrl, request });
|
|
}
|