initial param for password set page, fix cookie overflow

This commit is contained in:
peintnermax
2024-10-22 14:01:11 +02:00
parent 8a88e939e7
commit c626dd53e8
10 changed files with 104 additions and 69 deletions

View File

@@ -19,7 +19,7 @@ export default async function Page({
const t = await getTranslations({ locale, namespace: "password" }); const t = await getTranslations({ locale, namespace: "password" });
const tError = await getTranslations({ locale, namespace: "error" }); const tError = await getTranslations({ locale, namespace: "error" });
const { loginName, organization, authRequestId, code } = searchParams; const { loginName, organization, authRequestId } = searchParams;
// also allow no session to be found (ignoreUnkownUsername) // also allow no session to be found (ignoreUnkownUsername)
const sessionFactors = await loadMostRecentSession({ const sessionFactors = await loadMostRecentSession({

View File

@@ -22,7 +22,8 @@ export default async function Page({
const t = await getTranslations({ locale, namespace: "password" }); const t = await getTranslations({ locale, namespace: "password" });
const tError = await getTranslations({ locale, namespace: "error" }); const tError = await getTranslations({ locale, namespace: "error" });
const { userId, loginName, organization, authRequestId, code } = searchParams; const { userId, loginName, organization, authRequestId, code, initial } =
searchParams;
// also allow no session to be found (ignoreUnkownUsername) // also allow no session to be found (ignoreUnkownUsername)
let session: Session | undefined; let session: Session | undefined;
@@ -81,7 +82,7 @@ export default async function Page({
></UserAvatar> ></UserAvatar>
) : null} ) : null}
<Alert type={AlertType.INFO}>{t("set.codeSent")}</Alert> {!initial && <Alert type={AlertType.INFO}>{t("set.codeSent")}</Alert>}
{passwordComplexity && {passwordComplexity &&
(loginName ?? user?.preferredLoginName) && (loginName ?? user?.preferredLoginName) &&
@@ -93,6 +94,7 @@ export default async function Page({
authRequestId={authRequestId} authRequestId={authRequestId}
organization={organization} organization={organization}
passwordComplexitySettings={passwordComplexity} passwordComplexitySettings={passwordComplexity}
codeRequired={!(initial === "true")}
/> />
) : ( ) : (
<div className="py-4"> <div className="py-4">

View File

@@ -104,13 +104,13 @@ export const U2F = (alreadyAdded: boolean, link: string) => {
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-width="1.5" strokeWidth="1.5"
stroke="currentColor" stroke="currentColor"
className="w-8 h-8 mr-4" className="w-8 h-8 mr-4"
> >
<path <path
stroke-linecap="round" strokeLinecap="round"
stroke-linejoin="round" strokeLinejoin="round"
d="M7.864 4.243A7.5 7.5 0 0119.5 10.5c0 2.92-.556 5.709-1.568 8.268M5.742 6.364A7.465 7.465 0 004.5 10.5a7.464 7.464 0 01-1.15 3.993m1.989 3.559A11.209 11.209 0 008.25 10.5a3.75 3.75 0 117.5 0c0 .527-.021 1.049-.064 1.565M12 10.5a14.94 14.94 0 01-3.6 9.75m6.633-4.596a18.666 18.666 0 01-2.485 5.33" d="M7.864 4.243A7.5 7.5 0 0119.5 10.5c0 2.92-.556 5.709-1.568 8.268M5.742 6.364A7.465 7.465 0 004.5 10.5a7.464 7.464 0 01-1.15 3.993m1.989 3.559A11.209 11.209 0 008.25 10.5a3.75 3.75 0 117.5 0c0 .527-.021 1.049-.064 1.565M12 10.5a14.94 14.94 0 01-3.6 9.75m6.633-4.596a18.666 18.666 0 01-2.485 5.33"
/> />
</svg> </svg>
@@ -143,8 +143,8 @@ export const EMAIL = (alreadyAdded: boolean, link: string) => {
stroke="currentColor" stroke="currentColor"
> >
<path <path
stroke-linecap="round" strokeLinecap="round"
stroke-linejoin="round" strokeLinejoin="round"
d="M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75" d="M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75"
/> />
</svg> </svg>

View File

@@ -12,7 +12,6 @@ import { create } from "@zitadel/client";
import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb"; import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { useRouter } from "next/navigation";
import { useState } from "react"; import { useState } from "react";
import { FieldValues, useForm } from "react-hook-form"; import { FieldValues, useForm } from "react-hook-form";
import { Alert } from "./alert"; import { Alert } from "./alert";
@@ -57,8 +56,6 @@ export function ChangePasswordForm({
const [loading, setLoading] = useState<boolean>(false); const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string>(""); const [error, setError] = useState<string>("");
const router = useRouter();
async function submitChange(values: Inputs) { async function submitChange(values: Inputs) {
setLoading(true); setLoading(true);
const changeResponse = await setMyPassword({ const changeResponse = await setMyPassword({

View File

@@ -35,6 +35,7 @@ type Props = {
userId: string; userId: string;
organization?: string; organization?: string;
authRequestId?: string; authRequestId?: string;
codeRequired: boolean;
}; };
export function SetPasswordForm({ export function SetPasswordForm({
@@ -44,6 +45,7 @@ export function SetPasswordForm({
loginName, loginName,
userId, userId,
code, code,
codeRequired,
}: Props) { }: Props) {
const t = useTranslations("password"); const t = useTranslations("password");
@@ -59,11 +61,17 @@ export function SetPasswordForm({
async function submitRegister(values: Inputs) { async function submitRegister(values: Inputs) {
setLoading(true); setLoading(true);
const changeResponse = await changePassword({ let payload: { userId: string; password: string; code?: string } = {
userId: userId, userId: userId,
password: values.password, password: values.password,
code: values.code, };
}).catch(() => {
// this is not required for initial password setup
if (codeRequired) {
payload = { ...payload, code: values.code };
}
const changeResponse = await changePassword(payload).catch(() => {
setError("Could not register user"); setError("Could not register user");
}); });
@@ -94,7 +102,8 @@ export function SetPasswordForm({
password: { password: values.password }, password: { password: values.password },
}), }),
authRequestId, authRequestId,
}).catch(() => { }).catch((error) => {
console.error("verifyerror", error);
setLoading(false); setLoading(false);
setError("Could not verify password"); setError("Could not verify password");
return; return;
@@ -109,23 +118,6 @@ export function SetPasswordForm({
) { ) {
setError(passwordResponse.error); setError(passwordResponse.error);
} }
// // skip verification for now as it is an app based flow
// // return router.push(`/verify?` + params);
// // check for mfa force to continue with mfa setup
// if (authRequestId && changeResponse.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; const { errors } = formState;
@@ -152,25 +144,28 @@ export function SetPasswordForm({
return ( return (
<form className="w-full"> <form className="w-full">
<div className="pt-4 grid grid-cols-1 gap-4 mb-4"> <div className="pt-4 grid grid-cols-1 gap-4 mb-4">
<div className="flex flex-row items-end"> {codeRequired && (
<div className="flex-1"> <div className="flex flex-row items-end">
<TextInput <div className="flex-1">
type="text" <TextInput
required type="text"
{...register("code", { required
required: "This field is required", {...register("code", {
})} required: "This field is required",
label="Code" })}
autoComplete="one-time-code" label="Code"
error={errors.code?.message as string} autoComplete="one-time-code"
/> error={errors.code?.message as string}
/>
</div>
<div className="ml-4 mb-1">
<Button variant={ButtonVariants.Secondary}>
{t("set.resend")}
</Button>
</div>
</div> </div>
<div className="ml-4 mb-1"> )}
<Button variant={ButtonVariants.Secondary}>
{t("set.resend")}
</Button>
</div>
</div>
<div className=""> <div className="">
<TextInput <TextInput
type="password" type="password"

View File

@@ -65,7 +65,13 @@ export function VerifyEmailForm({
const router = useRouter(); const router = useRouter();
const params = new URLSearchParams({}); const params = new URLSearchParams({
userId: userId,
});
if (isInvite) {
params.append("initial", "true");
}
if (loginName) { if (loginName) {
params.append("loginName", loginName); params.append("loginName", loginName);
@@ -121,7 +127,10 @@ export function VerifyEmailForm({
} }
// if auth methods fall trough, we complete to login // if auth methods fall trough, we complete to login
const params = new URLSearchParams({}); const params = new URLSearchParams({
userId: userId,
initial: "true", // defines that a code is not required and is therefore not shown in the UI
});
if (organization) { if (organization) {
params.set("organization", organization); params.set("organization", organization);

View File

@@ -3,6 +3,9 @@
import { cookies } from "next/headers"; import { cookies } from "next/headers";
import { LANGUAGE_COOKIE_NAME } from "./i18n"; import { LANGUAGE_COOKIE_NAME } from "./i18n";
// TODO: improve this to handle overflow
export const MAX_COOKIE_SIZE = 4096;
export type Cookie = { export type Cookie = {
id: string; id: string;
token: string; token: string;
@@ -56,7 +59,13 @@ export async function addSessionToCookie<T>(
if (index > -1) { if (index > -1) {
currentSessions[index] = session; currentSessions[index] = session;
} else { } else {
currentSessions = [...currentSessions, session]; const temp = [...currentSessions, session];
if (temp.length > MAX_COOKIE_SIZE) {
// TODO: improve cookie handling
// this replaces the first session (oldest) with the new one
currentSessions = [session].concat(currentSessions.slice(1));
}
} }
if (cleanup) { if (cleanup) {

View File

@@ -169,7 +169,6 @@ export const PROVIDER_MAPPING: {
} = { } = {
[IdentityProviderType.GOOGLE]: (idp: IDPInformation) => { [IdentityProviderType.GOOGLE]: (idp: IDPInformation) => {
const rawInfo = idp.rawInformation as OIDC_USER; const rawInfo = idp.rawInformation as OIDC_USER;
console.log(rawInfo);
return create(AddHumanUserRequestSchema, { return create(AddHumanUserRequestSchema, {
username: idp.userName, username: idp.userName,

View File

@@ -71,8 +71,6 @@ export async function sendPassword(command: UpdateSessionCommand) {
organizationId: command.organization, organizationId: command.organization,
}); });
console.log(users);
if (users.details?.totalResult == BigInt(1) && users.result[0].userId) { if (users.details?.totalResult == BigInt(1) && users.result[0].userId) {
user = users.result[0]; user = users.result[0];
@@ -89,7 +87,7 @@ export async function sendPassword(command: UpdateSessionCommand) {
} }
// this is a fake error message to hide that the user does not even exist // this is a fake error message to hide that the user does not even exist
return { error: "Could not verify password!" }; return { error: "Could not verify password" };
} else { } else {
session = await setSessionAndUpdateCookie( session = await setSessionAndUpdateCookie(
sessionCookie, sessionCookie,
@@ -274,7 +272,7 @@ export async function sendPassword(command: UpdateSessionCommand) {
} }
export async function changePassword(command: { export async function changePassword(command: {
code: string; code?: string;
userId: string; userId: string;
password: string; password: string;
}) { }) {

View File

@@ -13,6 +13,7 @@ import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
import { IDPInformation } from "@zitadel/proto/zitadel/user/v2/idp_pb"; import { IDPInformation } from "@zitadel/proto/zitadel/user/v2/idp_pb";
import { import {
RetrieveIdentityProviderIntentRequest, RetrieveIdentityProviderIntentRequest,
SetPasswordRequestSchema,
VerifyPasskeyRegistrationRequest, VerifyPasskeyRegistrationRequest,
VerifyU2FRegistrationRequest, VerifyU2FRegistrationRequest,
} from "@zitadel/proto/zitadel/user/v2/user_service_pb"; } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
@@ -315,7 +316,6 @@ export async function verifyInviteCode(
} }
export async function resendInviteCode(userId: string) { export async function resendInviteCode(userId: string) {
console.log("resetInit");
return userService.resendInviteCode({ userId }, {}); return userService.resendInviteCode({ userId }, {});
} }
@@ -580,24 +580,50 @@ export async function passwordReset(userId: string, host: string | null) {
); );
} }
/**
*
* @param userId userId of the user to set the password for
* @param password the new password
* @param code optional if the password should be set with a code (reset), no code for initial setup of password
* @returns
*/
export async function setPassword( export async function setPassword(
userId: string, userId: string,
password: string, password: string,
code: string, code?: string,
) { ) {
return userService.setPassword( let payload = create(SetPasswordRequestSchema, {
{ userId,
userId, newPassword: {
newPassword: { password,
password, },
}, });
// check if the user has no password set in order to set a password
if (!code) {
const authmethods = await listAuthenticationMethodTypes(userId);
// if the user has no authmethods set, we can set a password otherwise we need a code
if (
!authmethods ||
!authmethods.authMethodTypes ||
authmethods.authMethodTypes.length === 0
) {
return { error: "Provide a code to set a password" };
}
}
if (code) {
payload = {
...payload,
verification: { verification: {
case: "verificationCode", case: "verificationCode",
value: code, value: code,
}, },
}, };
{}, }
);
return userService.setPassword(payload, {});
} }
/** /**