mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-13 11:32:12 +00:00
always create session from /verify page, cleanup idp session, theme wrapper
This commit is contained in:
@@ -1,9 +1,8 @@
|
||||
import { Alert, AlertType } from "@/components/alert";
|
||||
import { BackButton } from "@/components/back-button";
|
||||
import { Button, ButtonVariants } from "@/components/button";
|
||||
import { Alert } from "@/components/alert";
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { UserAvatar } from "@/components/user-avatar";
|
||||
import { VerifyForm } from "@/components/verify-form";
|
||||
import { VerifyRedirectButton } from "@/components/verify-redirect-button";
|
||||
import {
|
||||
getBrandingSettings,
|
||||
getUserByID,
|
||||
@@ -12,7 +11,6 @@ import {
|
||||
import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb";
|
||||
import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import Link from "next/link";
|
||||
|
||||
export default async function Page({ searchParams }: { searchParams: any }) {
|
||||
const locale = getLocale();
|
||||
@@ -85,26 +83,13 @@ export default async function Page({ searchParams }: { searchParams: any }) {
|
||||
showDropdown={false}
|
||||
/>
|
||||
)}
|
||||
{human?.email?.isVerified ? (
|
||||
<>
|
||||
<Alert type={AlertType.INFO}>{t("success")}</Alert>
|
||||
|
||||
<div className="mt-8 flex w-full flex-row items-center">
|
||||
<BackButton />
|
||||
<span className="flex-grow"></span>
|
||||
{authMethods?.length === 0 && (
|
||||
<Link href={`/authenticator/set?+${params}`}>
|
||||
<Button
|
||||
type="submit"
|
||||
className="self-end"
|
||||
variant={ButtonVariants.Primary}
|
||||
>
|
||||
{t("setupAuthenticator")}
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
{human?.email?.isVerified ? (
|
||||
<VerifyRedirectButton
|
||||
userId={userId}
|
||||
authRequestId={authRequestId}
|
||||
authMethods={authMethods}
|
||||
/>
|
||||
) : (
|
||||
// check if auth methods are set
|
||||
<VerifyForm
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { createNewSession } from "@/lib/server/session";
|
||||
import { createNewSessionForIdp } from "@/lib/server/session";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Alert } from "./alert";
|
||||
@@ -27,7 +27,7 @@ export function IdpSignin({
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
createNewSession({
|
||||
createNewSessionForIdp({
|
||||
userId,
|
||||
idpIntent: {
|
||||
idpIntentId,
|
||||
|
||||
@@ -12,9 +12,7 @@ type Props = {
|
||||
export const ThemeWrapper = ({ children, branding }: Props) => {
|
||||
useEffect(() => {
|
||||
setTheme(document, branding);
|
||||
}, []);
|
||||
}, [branding]);
|
||||
|
||||
const defaultClasses = "";
|
||||
|
||||
return <div className={defaultClasses}>{children}</div>;
|
||||
return <div>{children}</div>;
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Alert } from "@/components/alert";
|
||||
import { resendVerification, sendVerification } from "@/lib/server/email";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { Button, ButtonVariants } from "./button";
|
||||
import { TextInput } from "./input";
|
||||
@@ -37,12 +37,6 @@ export function VerifyForm({ userId, code, isInvite, params }: Props) {
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
if (code) {
|
||||
submitCodeAndContinue({ code });
|
||||
}
|
||||
}, []);
|
||||
|
||||
async function resendCode() {
|
||||
setError("");
|
||||
setLoading(true);
|
||||
@@ -60,29 +54,32 @@ export function VerifyForm({ userId, code, isInvite, params }: Props) {
|
||||
return response;
|
||||
}
|
||||
|
||||
async function submitCodeAndContinue(value: Inputs): Promise<boolean | void> {
|
||||
setLoading(true);
|
||||
const fcn = useCallback(
|
||||
async function submitCodeAndContinue(
|
||||
value: Inputs,
|
||||
): Promise<boolean | void> {
|
||||
setLoading(true);
|
||||
|
||||
await sendVerification({
|
||||
code: value.code,
|
||||
userId,
|
||||
isInvite: isInvite,
|
||||
}).catch((error) => {
|
||||
setError("Could not verify user");
|
||||
setLoading(false);
|
||||
return;
|
||||
});
|
||||
|
||||
const verifyResponse = await sendVerification({
|
||||
code: value.code,
|
||||
userId,
|
||||
isInvite: isInvite,
|
||||
}).catch(() => {
|
||||
setError("Could not verify user");
|
||||
setLoading(false);
|
||||
return;
|
||||
});
|
||||
},
|
||||
[isInvite, userId],
|
||||
);
|
||||
|
||||
setLoading(false);
|
||||
|
||||
if (!verifyResponse) {
|
||||
setError("Could not verify user");
|
||||
return;
|
||||
} else {
|
||||
setError("");
|
||||
return router.push("/authenticator/set?" + params);
|
||||
useEffect(() => {
|
||||
if (code) {
|
||||
fcn({ code });
|
||||
}
|
||||
}
|
||||
}, [code, fcn]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -116,7 +113,7 @@ export function VerifyForm({ userId, code, isInvite, params }: Props) {
|
||||
className="self-end"
|
||||
variant={ButtonVariants.Primary}
|
||||
disabled={loading || !formState.isValid}
|
||||
onClick={handleSubmit(submitCodeAndContinue)}
|
||||
onClick={handleSubmit(fcn)}
|
||||
>
|
||||
{loading && <Spinner className="h-5 w-5 mr-2" />}
|
||||
{t("verify.submit")}
|
||||
|
||||
68
apps/login/src/components/verify-redirect-button.tsx
Normal file
68
apps/login/src/components/verify-redirect-button.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
"use client";
|
||||
|
||||
import { sendVerificationRedirectWithoutCheck } from "@/lib/server/email";
|
||||
import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||
import { useTranslations } from "next-intl";
|
||||
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,
|
||||
authRequestId,
|
||||
authMethods,
|
||||
}: {
|
||||
userId: string;
|
||||
authRequestId: string;
|
||||
authMethods: AuthenticationMethodType[] | null;
|
||||
}) {
|
||||
const t = useTranslations("verify");
|
||||
const [error, setError] = useState<string>("");
|
||||
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
async function submitAndContinue(): Promise<boolean | void> {
|
||||
setLoading(true);
|
||||
|
||||
await sendVerificationRedirectWithoutCheck({
|
||||
userId,
|
||||
authRequestId,
|
||||
}).catch((error) => {
|
||||
setError("Could not verify user");
|
||||
setLoading(false);
|
||||
return;
|
||||
});
|
||||
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -76,12 +76,6 @@ export async function sendVerification(command: VerifyUserByEmailCommand) {
|
||||
}
|
||||
return redirect("/authenticator/set?" + params);
|
||||
}
|
||||
|
||||
// return {
|
||||
// authMethodTypes: authMethodResponse.authMethodTypes,
|
||||
// sessionId: session.id,
|
||||
// factors: session.factors,
|
||||
// };
|
||||
}
|
||||
|
||||
type resendVerifyEmailCommand = {
|
||||
@@ -94,3 +88,52 @@ export async function resendVerification(command: resendVerifyEmailCommand) {
|
||||
? resendEmailCode(command.userId)
|
||||
: resendInviteCode(command.userId);
|
||||
}
|
||||
|
||||
export async function sendVerificationRedirectWithoutCheck(command: {
|
||||
userId: string;
|
||||
authRequestId?: string;
|
||||
}) {
|
||||
const userResponse = await getUserByID(command.userId);
|
||||
|
||||
if (!userResponse || !userResponse.user) {
|
||||
return { error: "Could not load user" };
|
||||
}
|
||||
|
||||
const checks = create(ChecksSchema, {
|
||||
user: {
|
||||
search: {
|
||||
case: "loginName",
|
||||
value: userResponse.user.preferredLoginName,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const session = await createSessionAndUpdateCookie(
|
||||
checks,
|
||||
undefined,
|
||||
command.authRequestId,
|
||||
);
|
||||
|
||||
const authMethodResponse = await listAuthenticationMethodTypes(
|
||||
command.userId,
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
"use server";
|
||||
|
||||
import {
|
||||
createSessionAndUpdateCookie,
|
||||
createSessionForIdpAndUpdateCookie,
|
||||
setSessionAndUpdateCookie,
|
||||
} from "@/lib/server/cookie";
|
||||
import { deleteSession, listAuthenticationMethodTypes } from "@/lib/zitadel";
|
||||
import { create } from "@zitadel/client";
|
||||
import { RequestChallenges } from "@zitadel/proto/zitadel/session/v2/challenge_pb";
|
||||
import {
|
||||
Checks,
|
||||
ChecksSchema,
|
||||
} from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
||||
import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
||||
import { headers } from "next/headers";
|
||||
import {
|
||||
getMostRecentSessionCookie,
|
||||
@@ -31,26 +26,13 @@ type CreateNewSessionCommand = {
|
||||
authRequestId?: string;
|
||||
};
|
||||
|
||||
export async function createNewSession(options: CreateNewSessionCommand) {
|
||||
const { userId, idpIntent, loginName, password, authRequestId } = options;
|
||||
export async function createNewSessionForIdp(options: CreateNewSessionCommand) {
|
||||
const { userId, idpIntent, authRequestId } = options;
|
||||
|
||||
if (userId && idpIntent) {
|
||||
return createSessionForIdpAndUpdateCookie(userId, idpIntent, authRequestId);
|
||||
} else if (loginName) {
|
||||
const checks = create(
|
||||
ChecksSchema,
|
||||
password
|
||||
? {
|
||||
user: { search: { case: "loginName", value: loginName } },
|
||||
password: { password },
|
||||
}
|
||||
: { user: { search: { case: "loginName", value: loginName } } },
|
||||
);
|
||||
|
||||
return createSessionAndUpdateCookie(checks, undefined, authRequestId);
|
||||
} else {
|
||||
if (!userId || !idpIntent) {
|
||||
throw new Error("No userId or loginName provided");
|
||||
}
|
||||
return createSessionForIdpAndUpdateCookie(userId, idpIntent, authRequestId);
|
||||
}
|
||||
|
||||
export type UpdateSessionCommand = {
|
||||
|
||||
Reference in New Issue
Block a user