let users change their password if mfa is enforce but no mfa is yet set

This commit is contained in:
Max Peintner
2024-12-12 16:37:58 +01:00
parent 1a26192a0d
commit b68ea32748
4 changed files with 111 additions and 17 deletions

View File

@@ -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">

View File

@@ -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;
}

View File

@@ -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." };
}

View File

@@ -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