mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-12 02:02:23 +00:00
let users change their password if mfa is enforce but no mfa is yet set
This commit is contained in:
@@ -32,7 +32,9 @@ export default async function Page(props: {
|
||||
sessionFactors?.factors?.user?.organizationId,
|
||||
);
|
||||
|
||||
const loginSettings = await getLoginSettings(organization);
|
||||
const loginSettings = await getLoginSettings(
|
||||
sessionFactors?.factors?.user?.organizationId,
|
||||
);
|
||||
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
@@ -68,6 +70,7 @@ export default async function Page(props: {
|
||||
authRequestId={authRequestId}
|
||||
organization={organization}
|
||||
passwordComplexitySettings={passwordComplexity}
|
||||
loginSettings={loginSettings}
|
||||
/>
|
||||
) : (
|
||||
<div className="py-4">
|
||||
|
||||
@@ -6,10 +6,11 @@ import {
|
||||
symbolValidator,
|
||||
upperCaseValidator,
|
||||
} from "@/helpers/validators";
|
||||
import { setMyPassword } from "@/lib/self";
|
||||
import { sendPassword } from "@/lib/server/password";
|
||||
import { checkSessionAndSetPassword } from "@/lib/zitadel";
|
||||
import { create } from "@zitadel/client";
|
||||
import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
||||
import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||
import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useRouter } from "next/navigation";
|
||||
@@ -31,6 +32,7 @@ type Inputs =
|
||||
|
||||
type Props = {
|
||||
passwordComplexitySettings: PasswordComplexitySettings;
|
||||
loginSettings?: LoginSettings;
|
||||
sessionId: string;
|
||||
loginName: string;
|
||||
authRequestId?: string;
|
||||
@@ -39,6 +41,7 @@ type Props = {
|
||||
|
||||
export function ChangePasswordForm({
|
||||
passwordComplexitySettings,
|
||||
loginSettings,
|
||||
sessionId,
|
||||
loginName,
|
||||
authRequestId,
|
||||
@@ -60,9 +63,11 @@ export function ChangePasswordForm({
|
||||
|
||||
async function submitChange(values: Inputs) {
|
||||
setLoading(true);
|
||||
const changeResponse = await setMyPassword({
|
||||
sessionId: sessionId,
|
||||
|
||||
const changeResponse = checkSessionAndSetPassword({
|
||||
sessionId,
|
||||
password: values.password,
|
||||
forceMfa: !!(loginSettings?.forceMfa || loginSettings?.forceMfaLocalOnly),
|
||||
})
|
||||
.catch(() => {
|
||||
setError("Could not change password");
|
||||
@@ -72,8 +77,12 @@ export function ChangePasswordForm({
|
||||
setLoading(false);
|
||||
});
|
||||
|
||||
if (changeResponse && "error" in changeResponse) {
|
||||
setError(changeResponse.error);
|
||||
if (changeResponse && "error" in changeResponse && changeResponse.error) {
|
||||
setError(
|
||||
typeof changeResponse.error === "string"
|
||||
? changeResponse.error
|
||||
: "Unknown error",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
"use server";
|
||||
|
||||
import {
|
||||
createSessionServiceClient,
|
||||
createUserServiceClient,
|
||||
} from "@zitadel/client/v2";
|
||||
import { createUserServiceClient } from "@zitadel/client/v2";
|
||||
import { createServerTransport } from "@zitadel/node";
|
||||
import { getSessionCookieById } from "./cookies";
|
||||
import { getSession } from "./zitadel";
|
||||
@@ -13,12 +10,6 @@ const transport = (token: string) =>
|
||||
baseUrl: process.env.ZITADEL_API_URL!,
|
||||
});
|
||||
|
||||
const sessionService = (sessionId: string) => {
|
||||
return getSessionCookieById({ sessionId }).then((session) => {
|
||||
return createSessionServiceClient(transport(session.token));
|
||||
});
|
||||
};
|
||||
|
||||
const myUserService = (sessionToken: string) => {
|
||||
return createUserServiceClient(transport(sessionToken));
|
||||
};
|
||||
@@ -41,7 +32,7 @@ export async function setMyPassword({
|
||||
return { error: "Could not load session" };
|
||||
}
|
||||
|
||||
const service = await myUserService(sessionCookie.token);
|
||||
const service = await myUserService(`${sessionCookie.token}`);
|
||||
|
||||
if (!session?.factors?.user?.id) {
|
||||
return { error: "No user id found in session" };
|
||||
@@ -56,6 +47,7 @@ export async function setMyPassword({
|
||||
{},
|
||||
)
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
if (error.code === 7) {
|
||||
return { error: "Session is not valid." };
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { RequestChallenges } from "@zitadel/proto/zitadel/session/v2/challenge_p
|
||||
import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
||||
import { IDPInformation } from "@zitadel/proto/zitadel/user/v2/idp_pb";
|
||||
import {
|
||||
AuthenticationMethodType,
|
||||
RetrieveIdentityProviderIntentRequest,
|
||||
SetPasswordRequestSchema,
|
||||
VerifyPasskeyRegistrationRequest,
|
||||
@@ -38,6 +39,7 @@ import {
|
||||
UserState,
|
||||
} from "@zitadel/proto/zitadel/user/v2/user_pb";
|
||||
import { unstable_cacheLife as cacheLife } from "next/cache";
|
||||
import { getSessionCookieById } from "./cookies";
|
||||
import { PROVIDER_MAPPING } from "./idp";
|
||||
|
||||
const transport = createServerTransport(
|
||||
@@ -582,6 +584,94 @@ export async function setPassword(
|
||||
});
|
||||
}
|
||||
|
||||
type CheckSessionAndSetPasswordCommand = {
|
||||
sessionId: string;
|
||||
password: string;
|
||||
forceMfa: boolean;
|
||||
};
|
||||
|
||||
export async function checkSessionAndSetPassword({
|
||||
sessionId,
|
||||
password,
|
||||
forceMfa,
|
||||
}: CheckSessionAndSetPasswordCommand) {
|
||||
const sessionCookie = await getSessionCookieById({ sessionId });
|
||||
|
||||
const { session } = await getSession({
|
||||
sessionId: sessionCookie.id,
|
||||
sessionToken: sessionCookie.token,
|
||||
});
|
||||
|
||||
if (!session || !session.factors?.user?.id) {
|
||||
return { error: "Could not load session" };
|
||||
}
|
||||
|
||||
const payload = create(SetPasswordRequestSchema, {
|
||||
userId: session.factors.user.id,
|
||||
newPassword: {
|
||||
password,
|
||||
},
|
||||
});
|
||||
|
||||
// check if the user has no password set in order to set a password
|
||||
const authmethods = await listAuthenticationMethodTypes(
|
||||
session.factors.user.id,
|
||||
);
|
||||
|
||||
if (!authmethods) {
|
||||
return { error: "Could not load auth methods" };
|
||||
}
|
||||
|
||||
const requiredAuthMethodsForForceMFA = [
|
||||
AuthenticationMethodType.OTP_EMAIL,
|
||||
AuthenticationMethodType.OTP_SMS,
|
||||
AuthenticationMethodType.TOTP,
|
||||
AuthenticationMethodType.U2F,
|
||||
];
|
||||
|
||||
const hasNoMFAMethods = requiredAuthMethodsForForceMFA.every(
|
||||
(method) => !authmethods.authMethodTypes.includes(method),
|
||||
);
|
||||
|
||||
// if the user has no MFA but MFA is enforced, we can set a password otherwise we use the token of the user
|
||||
if (forceMfa && hasNoMFAMethods) {
|
||||
return userService.setPassword(payload, {}).catch((error) => {
|
||||
// throw error if failed precondition (ex. User is not yet initialized)
|
||||
if (error.code === 9 && error.message) {
|
||||
return { error: "Failed precondition" };
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const myUserService = (sessionToken: string) => {
|
||||
return createUserServiceClient(
|
||||
createServerTransport(sessionToken, {
|
||||
baseUrl: process.env.ZITADEL_API_URL!,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const selfService = await myUserService(`${sessionCookie.token}`);
|
||||
|
||||
return selfService
|
||||
.setPassword(
|
||||
{
|
||||
userId: session.factors.user.id,
|
||||
newPassword: { password, changeRequired: false },
|
||||
},
|
||||
{},
|
||||
)
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
if (error.code === 7) {
|
||||
return { error: "Session is not valid." };
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param server
|
||||
|
||||
Reference in New Issue
Block a user