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