Merge branch 'qa' into ldap

This commit is contained in:
Max Peintner
2025-05-27 09:14:36 +02:00
5 changed files with 21 additions and 302 deletions

View File

@@ -134,7 +134,6 @@ export default async function Page(props: {
{!(loginName || sessionId) && <Alert>{tError("unknownContext")}</Alert>}
{/* this happens if you register a user and open up the email verification link on a different device than the device where the registration was made. */}
{!valid && <Alert>{tError("sessionExpired")}</Alert>}
{isSessionValid(sessionWithData).valid &&

View File

@@ -45,7 +45,6 @@ export default async function Page(props: { searchParams: Promise<any> }) {
if (invite === "true") {
await sendInviteEmailCode({
serviceUrl,
userId,
urlTemplate:
`${host.includes("localhost") ? "http://" : "https://"}${host}${basePath}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}&invite=true` +
@@ -56,7 +55,6 @@ export default async function Page(props: { searchParams: Promise<any> }) {
});
} else {
await sendEmailCode({
serviceUrl,
userId,
urlTemplate:
`${host.includes("localhost") ? "http://" : "https://"}${host}${basePath}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}` +

View File

@@ -1,102 +0,0 @@
"use client";
import {
sendVerificationRedirectWithoutCheck,
SendVerificationRedirectWithoutCheckCommand,
} from "@/lib/server/verify";
import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
import { useTranslations } from "next-intl";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { Alert, AlertType } from "./alert";
import { BackButton } from "./back-button";
import { Button, ButtonVariants } from "./button";
import { Spinner } from "./spinner";
export function VerifyRedirectButton({
userId,
loginName,
requestId,
authMethods,
organization,
}: {
userId?: string;
loginName?: string;
requestId: string;
authMethods: AuthenticationMethodType[] | null;
organization?: string;
}) {
const t = useTranslations("verify");
const [error, setError] = useState<string>("");
const [loading, setLoading] = useState<boolean>(false);
const router = useRouter();
async function submitAndContinue(): Promise<boolean | void> {
setLoading(true);
let command = {
organization,
requestId,
} as SendVerificationRedirectWithoutCheckCommand;
if (userId) {
command = {
...command,
userId,
} as SendVerificationRedirectWithoutCheckCommand;
} else if (loginName) {
command = {
...command,
loginName,
} as SendVerificationRedirectWithoutCheckCommand;
}
const response = await sendVerificationRedirectWithoutCheck(command)
.catch(() => {
setError("Could not verify");
return;
})
.finally(() => {
setLoading(false);
});
if (response && "error" in response && response.error) {
setError(response.error);
return;
}
if (response && "redirect" in response && response.redirect) {
router.push(response.redirect);
return true;
}
}
return (
<>
<Alert type={AlertType.INFO}>{t("success")}</Alert>
{error && (
<div className="py-4">
<Alert>{error}</Alert>
</div>
)}
<div className="mt-8 flex w-full flex-row items-center">
<BackButton />
<span className="flex-grow"></span>
{authMethods?.length === 0 && (
<Button
onClick={() => submitAndContinue()}
type="submit"
className="self-end"
variant={ButtonVariants.Primary}
>
{loading && <Spinner className="h-5 w-5 mr-2" />}
{t("setupAuthenticator")}
</Button>
)}
</div>
</>
);
}

View File

@@ -6,7 +6,6 @@ import {
getSession,
getUserByID,
listAuthenticationMethodTypes,
resendInviteCode,
verifyEmail,
verifyInviteCode,
verifyTOTPRegistration,
@@ -17,7 +16,6 @@ import crypto from "crypto";
import { create } from "@zitadel/client";
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
import { User } from "@zitadel/proto/zitadel/user/v2/user_pb";
import { cookies, headers } from "next/headers";
import { getNextUrl } from "../client";
import { getSessionCookieByLoginName } from "../cookies";
@@ -282,16 +280,19 @@ export async function resendVerification(command: resendVerifyEmailCommand) {
const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? "";
return command.isInvite
? resendInviteCode({
? createInviteCode({
serviceUrl,
userId: command.userId,
urlTemplate:
`${host.includes("localhost") ? "http://" : "https://"}${host}${basePath}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}&invite=true` +
(command.requestId ? `&requestId=${command.requestId}` : ""),
}).catch((error) => {
if (error.code === 9) {
return { error: "User is already verified!" };
}
return { error: "Could not resend invite" };
})
: sendEmailCode({
: zitadelSendEmailCode({
userId: command.userId,
serviceUrl,
urlTemplate:
@@ -300,191 +301,29 @@ export async function resendVerification(command: resendVerifyEmailCommand) {
});
}
type sendEmailCommand = {
serviceUrl: string;
type SendEmailCommand = {
userId: string;
urlTemplate: string;
};
export async function sendEmailCode(command: sendEmailCommand) {
return zitadelSendEmailCode({
serviceUrl: command.serviceUrl,
userId: command.userId,
urlTemplate: command.urlTemplate,
});
}
export async function sendInviteEmailCode(command: sendEmailCommand) {
// TODO: change this to sendInvite
return createInviteCode({
serviceUrl: command.serviceUrl,
userId: command.userId,
urlTemplate: command.urlTemplate,
});
}
export type SendVerificationRedirectWithoutCheckCommand = {
organization?: string;
requestId?: string;
} & (
| { userId: string; loginName?: never }
| { userId?: never; loginName: string }
);
export async function sendVerificationRedirectWithoutCheck(
command: SendVerificationRedirectWithoutCheckCommand,
) {
export async function sendEmailCode(command: SendEmailCommand) {
const _headers = await headers();
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
if (!("loginName" in command || "userId" in command)) {
return { error: "No userId, nor loginname provided" };
}
let session: Session | undefined;
let user: User | undefined;
if ("loginName" in command) {
const sessionCookie = await getSessionCookieByLoginName({
loginName: command.loginName,
organization: command.organization,
}).catch((error) => {
console.warn("Ignored error:", error);
});
if (!sessionCookie) {
return { error: "Could not load session cookie" };
}
session = await getSession({
serviceUrl,
sessionId: sessionCookie.id,
sessionToken: sessionCookie.token,
}).then((response) => {
if (response?.session) {
return response.session;
}
});
if (!session?.factors?.user?.id) {
return { error: "Could not create session for user" };
}
const userResponse = await getUserByID({
serviceUrl,
userId: session?.factors?.user?.id,
});
if (!userResponse?.user) {
return { error: "Could not load user" };
}
user = userResponse.user;
} else if ("userId" in command) {
const userResponse = await getUserByID({
serviceUrl,
userId: command.userId,
});
if (!userResponse?.user) {
return { error: "Could not load user" };
}
user = userResponse.user;
const checks = create(ChecksSchema, {
user: {
search: {
case: "loginName",
value: userResponse.user.preferredLoginName,
},
},
});
session = await createSessionAndUpdateCookie({
checks,
requestId: command.requestId,
});
}
if (!session?.factors?.user?.id) {
return { error: "Could not create session for user" };
}
if (!session?.factors?.user?.id) {
return { error: "Could not create session for user" };
}
if (!user) {
return { error: "Could not load user" };
}
const authMethodResponse = await listAuthenticationMethodTypes({
return zitadelSendEmailCode({
serviceUrl,
userId: user.userId,
userId: command.userId,
urlTemplate: command.urlTemplate,
});
}
export async function sendInviteEmailCode(command: SendEmailCommand) {
const _headers = await headers();
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
return createInviteCode({
serviceUrl,
userId: command.userId,
urlTemplate: command.urlTemplate,
});
if (!authMethodResponse || !authMethodResponse.authMethodTypes) {
return { error: "Could not load possible authenticators" };
}
// if no authmethods are found on the user, redirect to set one up
if (
authMethodResponse &&
authMethodResponse.authMethodTypes &&
authMethodResponse.authMethodTypes.length == 0
) {
const params = new URLSearchParams({
sessionId: session.id,
});
if (session.factors?.user?.loginName) {
params.set("loginName", session.factors?.user?.loginName);
}
return { redirect: `/authenticator/set?${params}` };
}
const loginSettings = await getLoginSettings({
serviceUrl,
organization: user.details?.resourceOwner,
});
// redirect to mfa factor if user has one, or redirect to set one up
const mfaFactorCheck = await checkMFAFactors(
serviceUrl,
session,
loginSettings,
authMethodResponse.authMethodTypes,
command.organization,
command.requestId,
);
if (mfaFactorCheck?.redirect) {
return mfaFactorCheck;
}
// login user if no additional steps are required
if (command.requestId && session.id) {
const nextUrl = await getNextUrl(
{
sessionId: session.id,
requestId: command.requestId,
organization:
command.organization ?? session.factors?.user?.organizationId,
},
loginSettings?.defaultRedirectUri,
);
return { redirect: nextUrl };
}
const url = await getNextUrl(
{
loginName: session.factors.user.loginName,
organization: session.factors?.user?.organizationId,
},
loginSettings?.defaultRedirectUri,
);
return { redirect: url };
}

View File

@@ -502,21 +502,6 @@ export async function verifyInviteCode({
return userService.verifyInviteCode({ userId, verificationCode }, {});
}
export async function resendInviteCode({
serviceUrl,
userId,
}: {
serviceUrl: string;
userId: string;
}) {
const userService: Client<typeof UserService> = await createServiceForHost(
UserService,
serviceUrl,
);
return userService.resendInviteCode({ userId }, {});
}
export async function sendEmailCode({
serviceUrl,
userId,