mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-12 10:36:44 +00:00
self service change password
This commit is contained in:
@@ -26,6 +26,11 @@
|
||||
"codeSent": "Ein Code wurde an Ihre E-Mail-Adresse gesendet.",
|
||||
"resend": "Erneut senden",
|
||||
"submit": "Weiter"
|
||||
},
|
||||
"change": {
|
||||
"title": "Passwort ändern",
|
||||
"description": "Legen Sie das Passwort für Ihr Konto fest",
|
||||
"submit": "Weiter"
|
||||
}
|
||||
},
|
||||
"idp": {
|
||||
|
||||
@@ -26,6 +26,11 @@
|
||||
"codeSent": "A code has been sent to your email address.",
|
||||
"resend": "Resend code",
|
||||
"submit": "Continue"
|
||||
},
|
||||
"change": {
|
||||
"title": "Change Password",
|
||||
"description": "Set the password for your account",
|
||||
"submit": "Continue"
|
||||
}
|
||||
},
|
||||
"idp": {
|
||||
|
||||
@@ -26,6 +26,11 @@
|
||||
"codeSent": "Se ha enviado un código a su correo electrónico.",
|
||||
"resend": "Reenviar código",
|
||||
"submit": "Continuar"
|
||||
},
|
||||
"change": {
|
||||
"title": "Cambiar Contraseña",
|
||||
"description": "Establece la contraseña para tu cuenta",
|
||||
"submit": "Continuar"
|
||||
}
|
||||
},
|
||||
"idp": {
|
||||
|
||||
@@ -26,6 +26,11 @@
|
||||
"codeSent": "Un codice è stato inviato al tuo indirizzo email.",
|
||||
"resend": "Invia di nuovo",
|
||||
"submit": "Continua"
|
||||
},
|
||||
"change": {
|
||||
"title": "Cambia Password",
|
||||
"description": "Imposta la password per il tuo account",
|
||||
"submit": "Continua"
|
||||
}
|
||||
},
|
||||
"idp": {
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
import { Alert } from "@/components/alert";
|
||||
import { ChangePasswordForm } from "@/components/change-password-form";
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { UserAvatar } from "@/components/user-avatar";
|
||||
import { getSessionCookieById } from "@/lib/cookies";
|
||||
import {
|
||||
getBrandingSettings,
|
||||
getPasswordComplexitySettings,
|
||||
getSession,
|
||||
} from "@/lib/zitadel";
|
||||
|
||||
export default async function Page({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Record<string | number | symbol, string | undefined>;
|
||||
}) {
|
||||
const { sessionId } = searchParams;
|
||||
|
||||
if (!sessionId) {
|
||||
return (
|
||||
<div>
|
||||
<h1>Session ID not found</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const sessionCookie = await getSessionCookieById({
|
||||
sessionId,
|
||||
});
|
||||
|
||||
const { session } = await getSession({
|
||||
sessionId: sessionCookie.id,
|
||||
sessionToken: sessionCookie.token,
|
||||
});
|
||||
|
||||
const passwordComplexitySettings = await getPasswordComplexitySettings(
|
||||
session?.factors?.user?.organizationId,
|
||||
);
|
||||
|
||||
const branding = await getBrandingSettings(
|
||||
session?.factors?.user?.organizationId,
|
||||
);
|
||||
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>Set Password</h1>
|
||||
<p className="ztdl-p">Set the password for your account</p>
|
||||
|
||||
{!session && (
|
||||
<div className="py-4">
|
||||
<Alert>
|
||||
Could not get the context of the user. Make sure to enter the
|
||||
username first or provide a loginName as searchParam.
|
||||
</Alert>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{session && (
|
||||
<UserAvatar
|
||||
loginName={session.factors?.user?.loginName}
|
||||
displayName={session.factors?.user?.displayName}
|
||||
showDropdown
|
||||
searchParams={searchParams}
|
||||
></UserAvatar>
|
||||
)}
|
||||
|
||||
{passwordComplexitySettings && session?.factors?.user?.id && (
|
||||
<ChangePasswordForm
|
||||
passwordComplexitySettings={passwordComplexitySettings}
|
||||
userId={session.factors.user.id}
|
||||
sessionId={sessionId}
|
||||
></ChangePasswordForm>
|
||||
)}
|
||||
</div>
|
||||
</DynamicTheme>
|
||||
);
|
||||
}
|
||||
80
apps/login/src/app/(login)/password/change/page.tsx
Normal file
80
apps/login/src/app/(login)/password/change/page.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import { Alert } from "@/components/alert";
|
||||
import { ChangePasswordForm } from "@/components/change-password-form";
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { UserAvatar } from "@/components/user-avatar";
|
||||
import { loadMostRecentSession } from "@/lib/session";
|
||||
import {
|
||||
getBrandingSettings,
|
||||
getLoginSettings,
|
||||
getPasswordComplexitySettings,
|
||||
} from "@/lib/zitadel";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
|
||||
export default async function Page({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Record<string | number | symbol, string | undefined>;
|
||||
}) {
|
||||
const locale = getLocale();
|
||||
const t = await getTranslations({ locale, namespace: "password" });
|
||||
|
||||
const { loginName, organization, authRequestId, code } = searchParams;
|
||||
|
||||
// also allow no session to be found (ignoreUnkownUsername)
|
||||
const sessionFactors = await loadMostRecentSession({
|
||||
loginName,
|
||||
organization,
|
||||
});
|
||||
|
||||
const branding = await getBrandingSettings(organization);
|
||||
|
||||
const passwordComplexity = await getPasswordComplexitySettings(
|
||||
sessionFactors?.factors?.user?.organizationId,
|
||||
);
|
||||
|
||||
const loginSettings = await getLoginSettings(organization);
|
||||
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>
|
||||
{sessionFactors?.factors?.user?.displayName ?? t("change.title")}
|
||||
</h1>
|
||||
<p className="ztdl-p mb-6 block">{t("change.description")}</p>
|
||||
|
||||
{/* show error only if usernames should be shown to be unknown */}
|
||||
{(!sessionFactors || !loginName) &&
|
||||
!loginSettings?.ignoreUnknownUsernames && (
|
||||
<div className="py-4">
|
||||
<Alert>{t("error:unknownContext")}</Alert>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{sessionFactors && (
|
||||
<UserAvatar
|
||||
loginName={loginName ?? sessionFactors.factors?.user?.loginName}
|
||||
displayName={sessionFactors.factors?.user?.displayName}
|
||||
showDropdown
|
||||
searchParams={searchParams}
|
||||
></UserAvatar>
|
||||
)}
|
||||
|
||||
{passwordComplexity &&
|
||||
loginName &&
|
||||
sessionFactors?.factors?.user?.id ? (
|
||||
<ChangePasswordForm
|
||||
sessionId={sessionFactors.id}
|
||||
loginName={loginName}
|
||||
authRequestId={authRequestId}
|
||||
organization={organization}
|
||||
passwordComplexitySettings={passwordComplexity}
|
||||
/>
|
||||
) : (
|
||||
<div className="py-4">
|
||||
<Alert>{t("error:failedLoading")}</Alert>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</DynamicTheme>
|
||||
);
|
||||
}
|
||||
@@ -6,8 +6,9 @@ import {
|
||||
symbolValidator,
|
||||
upperCaseValidator,
|
||||
} from "@/helpers/validators";
|
||||
import { setPassword } from "@/lib/self";
|
||||
import { setMyPassword } from "@/lib/self";
|
||||
import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { FieldValues, useForm } from "react-hook-form";
|
||||
@@ -27,15 +28,21 @@ type Inputs =
|
||||
|
||||
type Props = {
|
||||
passwordComplexitySettings: PasswordComplexitySettings;
|
||||
userId: string;
|
||||
sessionId: string;
|
||||
loginName: string;
|
||||
authRequestId?: string;
|
||||
organization?: string;
|
||||
};
|
||||
|
||||
export function ChangePasswordForm({
|
||||
passwordComplexitySettings,
|
||||
userId,
|
||||
sessionId,
|
||||
loginName,
|
||||
authRequestId,
|
||||
organization,
|
||||
}: Props) {
|
||||
const t = useTranslations("password");
|
||||
|
||||
const { register, handleSubmit, watch, formState } = useForm<Inputs>({
|
||||
mode: "onBlur",
|
||||
defaultValues: {
|
||||
@@ -51,9 +58,8 @@ export function ChangePasswordForm({
|
||||
|
||||
async function submitChange(values: Inputs) {
|
||||
setLoading(true);
|
||||
const response = await setPassword({
|
||||
const response = await setMyPassword({
|
||||
sessionId: sessionId,
|
||||
userId: userId,
|
||||
password: values.password,
|
||||
}).catch(() => {
|
||||
setError("Could not change password");
|
||||
@@ -61,12 +67,36 @@ export function ChangePasswordForm({
|
||||
|
||||
setLoading(false);
|
||||
|
||||
if (response && "error" in response) {
|
||||
setError(response.error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
setError("Could not change password");
|
||||
return;
|
||||
}
|
||||
|
||||
return response;
|
||||
const params = new URLSearchParams({});
|
||||
|
||||
if (loginName) {
|
||||
params.append("loginName", loginName);
|
||||
}
|
||||
if (organization) {
|
||||
params.append("organization", organization);
|
||||
}
|
||||
|
||||
if (authRequestId && sessionId) {
|
||||
if (authRequestId) {
|
||||
params.append("authRequest", authRequestId);
|
||||
}
|
||||
return router.push(`/login?` + params);
|
||||
} else {
|
||||
if (authRequestId) {
|
||||
params.append("authRequestId", authRequestId);
|
||||
}
|
||||
return router.push(`/signedin?` + params);
|
||||
}
|
||||
}
|
||||
|
||||
const { errors } = formState;
|
||||
@@ -99,9 +129,9 @@ export function ChangePasswordForm({
|
||||
autoComplete="new-password"
|
||||
required
|
||||
{...register("password", {
|
||||
required: "You have to provide a password!",
|
||||
required: "You have to provide a new password!",
|
||||
})}
|
||||
label="Password"
|
||||
label="New Password"
|
||||
error={errors.password?.message as string}
|
||||
/>
|
||||
</div>
|
||||
@@ -143,7 +173,7 @@ export function ChangePasswordForm({
|
||||
onClick={handleSubmit(submitChange)}
|
||||
>
|
||||
{loading && <Spinner className="h-5 w-5 mr-2" />}
|
||||
continue
|
||||
{t("change.submit")}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -61,15 +61,17 @@ export function PasswordForm({
|
||||
}),
|
||||
authRequestId,
|
||||
}).catch(() => {
|
||||
setLoading(false);
|
||||
setError("Could not verify password");
|
||||
return;
|
||||
});
|
||||
|
||||
setLoading(false);
|
||||
|
||||
if (response && "error" in response && response.error) {
|
||||
setError(response.error);
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
} from "@zitadel/client/v2";
|
||||
import { createServerTransport } from "@zitadel/node";
|
||||
import { getSessionCookieById } from "./cookies";
|
||||
import { getSession } from "./zitadel";
|
||||
|
||||
const transport = (token: string) =>
|
||||
createServerTransport(token, {
|
||||
@@ -19,26 +20,46 @@ const sessionService = (sessionId: string) => {
|
||||
});
|
||||
};
|
||||
|
||||
const userService = (sessionId: string) => {
|
||||
return getSessionCookieById({ sessionId }).then((session) => {
|
||||
return createUserServiceClient(transport(session.token));
|
||||
});
|
||||
const myUserService = (sessionToken: string) => {
|
||||
return createUserServiceClient(transport(sessionToken));
|
||||
};
|
||||
|
||||
export async function setPassword({
|
||||
export async function setMyPassword({
|
||||
sessionId,
|
||||
userId,
|
||||
password,
|
||||
}: {
|
||||
sessionId: string;
|
||||
userId: string;
|
||||
password: string;
|
||||
}) {
|
||||
return (await userService(sessionId)).setPassword(
|
||||
{
|
||||
userId,
|
||||
newPassword: { password, changeRequired: false },
|
||||
},
|
||||
{},
|
||||
);
|
||||
const sessionCookie = await getSessionCookieById({ sessionId });
|
||||
|
||||
const { session } = await getSession({
|
||||
sessionId: sessionCookie.id,
|
||||
sessionToken: sessionCookie.token,
|
||||
});
|
||||
|
||||
if (!session) {
|
||||
return { error: "Could not load session" };
|
||||
}
|
||||
|
||||
const service = await myUserService(sessionCookie.token);
|
||||
|
||||
if (!session?.factors?.user?.id) {
|
||||
return { error: "No user id found in session" };
|
||||
}
|
||||
|
||||
return service
|
||||
.setPassword(
|
||||
{
|
||||
userId: session.factors.user.id,
|
||||
newPassword: { password, changeRequired: false },
|
||||
},
|
||||
{},
|
||||
)
|
||||
.catch((error) => {
|
||||
if (error.code === 7) {
|
||||
return { error: "Session is not valid." };
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user