Merge pull request #316 from zitadel/qa

Promote qa to prod: smaller fixes
This commit is contained in:
Max Peintner
2024-12-13 16:29:14 +01:00
committed by GitHub
18 changed files with 277 additions and 166 deletions

View File

@@ -24,6 +24,7 @@
"title": "Passwort festlegen",
"description": "Legen Sie das Passwort für Ihr Konto fest",
"codeSent": "Ein Code wurde an Ihre E-Mail-Adresse gesendet.",
"noCodeReceived": "Keinen Code erhalten?",
"resend": "Erneut senden",
"submit": "Weiter"
},
@@ -173,6 +174,7 @@
"verify": {
"title": "Benutzer verifizieren",
"description": "Geben Sie den Code ein, der in der Bestätigungs-E-Mail angegeben ist.",
"noCodeReceived": "Keinen Code erhalten?",
"resendCode": "Code erneut senden",
"submit": "Weiter"
}

View File

@@ -24,6 +24,7 @@
"title": "Set Password",
"description": "Set the password for your account",
"codeSent": "A code has been sent to your email address.",
"noCodeReceived": "Didn't receive a code?",
"resend": "Resend code",
"submit": "Continue"
},
@@ -173,6 +174,7 @@
"verify": {
"title": "Verify user",
"description": "Enter the Code provided in the verification email.",
"noCodeReceived": "Didn't receive a code?",
"resendCode": "Resend code",
"submit": "Continue"
}

View File

@@ -24,6 +24,7 @@
"title": "Establecer Contraseña",
"description": "Establece la contraseña para tu cuenta",
"codeSent": "Se ha enviado un código a su correo electrónico.",
"noCodeReceived": "¿No recibiste un código?",
"resend": "Reenviar código",
"submit": "Continuar"
},
@@ -173,6 +174,7 @@
"verify": {
"title": "Verificar usuario",
"description": "Introduce el código proporcionado en el correo electrónico de verificación.",
"noCodeReceived": "¿No recibiste un código?",
"resendCode": "Reenviar código",
"submit": "Continuar"
}

View File

@@ -24,6 +24,7 @@
"title": "Imposta Password",
"description": "Imposta la password per il tuo account",
"codeSent": "Un codice è stato inviato al tuo indirizzo email.",
"noCodeReceived": "Non hai ricevuto un codice?",
"resend": "Invia di nuovo",
"submit": "Continua"
},
@@ -173,6 +174,7 @@
"verify": {
"title": "Verifica utente",
"description": "Inserisci il codice fornito nell'email di verifica.",
"noCodeReceived": "Non hai ricevuto un codice?",
"resendCode": "Invia di nuovo il codice",
"submit": "Continua"
}

View File

@@ -24,6 +24,7 @@
"title": "设置密码",
"description": "为您的账户设置密码",
"codeSent": "验证码已发送到您的邮箱。",
"noCodeReceived": "没有收到验证码?",
"resend": "重发验证码",
"submit": "继续"
},
@@ -122,6 +123,18 @@
}
},
"register": {
"methods": {
"passkey": "密钥",
"password": "密码"
},
"disabled": {
"title": "注册已禁用",
"description": "您的设置不允许注册新用户。"
},
"missingdata": {
"title": "缺少数据",
"description": "请提供所有必需的数据。"
},
"title": "注册",
"description": "创建您的 ZITADEL 账户。",
"selectMethod": "选择您想使用的认证方法",
@@ -151,7 +164,8 @@
},
"signedin": {
"title": "欢迎 {user}",
"description": "您已登录。"
"description": "您已登录。",
"continue": "继续"
},
"verify": {
"userIdMissing": "未提供用户 ID",
@@ -160,6 +174,7 @@
"verify": {
"title": "验证用户",
"description": "输入验证邮件中的验证码。",
"noCodeReceived": "没有收到验证码?",
"resendCode": "重发验证码",
"submit": "继续"
}

View File

@@ -1,8 +1,13 @@
import { DynamicTheme } from "@/components/dynamic-theme";
import { SessionsList } from "@/components/sessions-list";
import { getAllSessionCookieIds } from "@/lib/cookies";
import { getBrandingSettings, listSessions } from "@/lib/zitadel";
import {
getBrandingSettings,
getDefaultOrg,
listSessions,
} from "@/lib/zitadel";
import { UserPlusIcon } from "@heroicons/react/24/outline";
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
import { getLocale, getTranslations } from "next-intl/server";
import Link from "next/link";
@@ -30,9 +35,19 @@ export default async function Page(props: {
const authRequestId = searchParams?.authRequestId;
const organization = searchParams?.organization;
let defaultOrganization;
if (!organization) {
const org: Organization | null = await getDefaultOrg();
if (org) {
defaultOrganization = org.id;
}
}
let sessions = await loadSessions();
const branding = await getBrandingSettings(organization);
const branding = await getBrandingSettings(
organization ?? defaultOrganization,
);
return (
<DynamicTheme branding={branding}>

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

View File

@@ -1,30 +0,0 @@
import { getAllSessions } from "@/lib/cookies";
import { listSessions } from "@/lib/zitadel";
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
import { NextRequest, NextResponse } from "next/server";
async function loadSessions(ids: string[]): Promise<Session[]> {
const response = await listSessions(
ids.filter((id: string | undefined) => !!id),
);
return response?.sessions ?? [];
}
export async function GET(request: NextRequest) {
const sessionCookies = await getAllSessions();
const ids = sessionCookies.map((s) => s.id);
let sessions: Session[] = [];
if (ids && ids.length) {
sessions = await loadSessions(ids);
}
const responseHeaders = new Headers();
responseHeaders.set("Access-Control-Allow-Origin", "*");
responseHeaders.set("Access-Control-Allow-Headers", "*");
return NextResponse.json(
{ sessions },
{ status: 200, headers: responseHeaders },
);
}

View File

@@ -32,7 +32,7 @@ const LinkWrapper = ({
export const TOTP = (alreadyAdded: boolean, link: string) => {
return (
<LinkWrapper alreadyAdded={alreadyAdded} link={link} key={link}>
<LinkWrapper key={link} alreadyAdded={alreadyAdded} link={link}>
<div
className={clsx(
"font-medium flex items-center",
@@ -40,45 +40,12 @@ export const TOTP = (alreadyAdded: boolean, link: string) => {
)}
>
<svg
className="h-9 w-9 transform -translate-x-[2px] mr-4"
version="1.1"
baseProfile="basic"
id="Layer_1"
className="h-8 w-8 transform -translate-x-[2px] mr-4 fill-current text-black dark:text-white"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 512 512"
xmlSpace="preserve"
viewBox="0 0 24 24"
>
<path
fill="#1A73E8"
d="M440,255.99997v0.00006C440,273.12085,426.12085,287,409.00003,287H302l-46-93.01001l49.6507-85.9951
c8.56021-14.82629,27.51834-19.9065,42.34518-11.34724l0.00586,0.0034c14.82776,8.55979,19.90875,27.51928,11.34857,42.34682
L309.70001,225h99.30002C426.12085,225,440,238.87917,440,255.99997z"
/>
<path
fill="#EA4335"
d="M348.00174,415.34897l-0.00586,0.00339c-14.82684,8.55927-33.78497,3.47903-42.34518-11.34723L256,318.01001
l-49.65065,85.99509c-8.5602,14.82629-27.51834,19.90652-42.34517,11.34729l-0.00591-0.00342
c-14.82777-8.55978-19.90875-27.51929-11.34859-42.34683L202.29999,287L256,285l53.70001,2l49.6503,86.00214
C367.91049,387.82968,362.8295,406.78918,348.00174,415.34897z"
/>
<path
fill="#FBBC04"
d="M256,193.98999L242,232l-39.70001-7l-49.6503-86.00212
c-8.56017-14.82755-3.47919-33.78705,11.34859-42.34684l0.00591-0.00341c14.82683-8.55925,33.78497-3.47903,42.34517,11.34726
L256,193.98999z"
/>
<path
fill="#34A853"
d="M248,225l-36,62H102.99997C85.87916,287,72,273.12085,72,256.00003v-0.00006
C72,238.87917,85.87916,225,102.99997,225H248z"
/>
<polygon
fill="#185DB7"
points="309.70001,287 202.29999,287 256,193.98999 "
/>
<title>timer-lock-outline</title>
<path d="M11 8H13V14H11V8M13 19.92C12.67 19.97 12.34 20 12 20C8.13 20 5 16.87 5 13S8.13 6 12 6C14.82 6 17.24 7.67 18.35 10.06C18.56 10.04 18.78 10 19 10C19.55 10 20.07 10.11 20.57 10.28C20.23 9.22 19.71 8.24 19.03 7.39L20.45 5.97C20 5.46 19.55 5 19.04 4.56L17.62 6C16.07 4.74 14.12 4 12 4C7.03 4 3 8.03 3 13S7.03 22 12 22C12.42 22 12.83 21.96 13.24 21.91C13.09 21.53 13 21.12 13 20.7V19.92M15 1H9V3H15V1M23 17.3V20.8C23 21.4 22.4 22 21.7 22H16.2C15.6 22 15 21.4 15 20.7V17.2C15 16.6 15.6 16 16.2 16V14.5C16.2 13.1 17.6 12 19 12S21.8 13.1 21.8 14.5V16C22.4 16 23 16.6 23 17.3M20.5 14.5C20.5 13.7 19.8 13.2 19 13.2S17.5 13.7 17.5 14.5V16H20.5V14.5Z" />
</svg>{" "}
<span>Authenticator App</span>
</div>
@@ -93,7 +60,7 @@ C72,238.87917,85.87916,225,102.99997,225H248z"
export const U2F = (alreadyAdded: boolean, link: string) => {
return (
<LinkWrapper alreadyAdded={alreadyAdded} link={link}>
<LinkWrapper key={link} alreadyAdded={alreadyAdded} link={link}>
<div
className={clsx(
"font-medium flex items-center",
@@ -127,7 +94,7 @@ export const U2F = (alreadyAdded: boolean, link: string) => {
export const EMAIL = (alreadyAdded: boolean, link: string) => {
return (
<LinkWrapper alreadyAdded={alreadyAdded} link={link}>
<LinkWrapper key={link} alreadyAdded={alreadyAdded} link={link}>
<div
className={clsx(
"font-medium flex items-center",
@@ -162,7 +129,7 @@ export const EMAIL = (alreadyAdded: boolean, link: string) => {
export const SMS = (alreadyAdded: boolean, link: string) => {
return (
<LinkWrapper alreadyAdded={alreadyAdded} link={link}>
<LinkWrapper key={link} alreadyAdded={alreadyAdded} link={link}>
<div
className={clsx(
"font-medium flex items-center",
@@ -196,7 +163,7 @@ export const SMS = (alreadyAdded: boolean, link: string) => {
export const PASSKEYS = (alreadyAdded: boolean, link: string) => {
return (
<LinkWrapper alreadyAdded={alreadyAdded} link={link}>
<LinkWrapper key={link} alreadyAdded={alreadyAdded} link={link}>
<div
className={clsx(
"font-medium flex items-center",
@@ -230,7 +197,7 @@ export const PASSKEYS = (alreadyAdded: boolean, link: string) => {
export const PASSWORD = (alreadyAdded: boolean, link: string) => {
return (
<LinkWrapper alreadyAdded={alreadyAdded} link={link}>
<LinkWrapper key={link} alreadyAdded={alreadyAdded} link={link}>
<div
className={clsx(
"font-medium flex items-center",

View File

@@ -6,8 +6,10 @@ import {
symbolValidator,
upperCaseValidator,
} from "@/helpers/validators";
import { setMyPassword } from "@/lib/self";
import { sendPassword } from "@/lib/server/password";
import {
checkSessionAndSetPassword,
sendPassword,
} from "@/lib/server/password";
import { create } from "@zitadel/client";
import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb";
@@ -60,8 +62,9 @@ export function ChangePasswordForm({
async function submitChange(values: Inputs) {
setLoading(true);
const changeResponse = await setMyPassword({
sessionId: sessionId,
const changeResponse = checkSessionAndSetPassword({
sessionId,
password: values.password,
})
.catch(() => {
@@ -72,8 +75,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

@@ -18,7 +18,7 @@ import { useTranslations } from "next-intl";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { FieldValues, useForm } from "react-hook-form";
import { Alert } from "./alert";
import { Alert, AlertType } from "./alert";
import { BackButton } from "./back-button";
import { Button, ButtonVariants } from "./button";
import { TextInput } from "./input";
@@ -192,34 +192,42 @@ export function SetPasswordForm({
<form className="w-full">
<div className="pt-4 grid grid-cols-1 gap-4 mb-4">
{codeRequired && (
<div className="flex flex-row items-end">
<div className="flex-1">
<TextInput
type="text"
required
{...register("code", {
required: "This field is required",
})}
label="Code"
autoComplete="one-time-code"
error={errors.code?.message as string}
data-testid="code-text-input"
/>
</div>
<div className="ml-4 mb-1">
<Button
variant={ButtonVariants.Secondary}
data-testid="resend-button"
onClick={() => resendCode()}
<Alert type={AlertType.INFO}>
<div className="flex flex-row">
<span className="flex-1 mr-auto text-left">
{t("set.noCodeReceived")}
</span>
<button
aria-label="Resend OTP Code"
disabled={loading}
type="button"
className="ml-4 text-primary-light-500 dark:text-primary-dark-500 hover:dark:text-primary-dark-400 hover:text-primary-light-400 cursor-pointer disabled:cursor-default disabled:text-gray-400 dark:disabled:text-gray-700"
onClick={() => {
resendCode();
}}
data-testid="resend-button"
>
{t("set.resend")}
</Button>
</button>
</div>
</Alert>
)}
{codeRequired && (
<div>
<TextInput
type="text"
required
{...register("code", {
required: "This field is required",
})}
label="Code"
autoComplete="one-time-code"
error={errors.code?.message as string}
data-testid="code-text-input"
/>
</div>
)}
<div className="">
<div>
<TextInput
type="password"
autoComplete="new-password"
@@ -232,7 +240,7 @@ export function SetPasswordForm({
data-testid="password-text-input"
/>
</div>
<div className="">
<div>
<TextInput
type="password"
required

View File

@@ -1,11 +1,12 @@
"use client";
import { Alert } from "@/components/alert";
import { Alert, AlertType } from "@/components/alert";
import { resendVerification, sendVerification } from "@/lib/server/email";
import { useTranslations } from "next-intl";
import { useRouter } from "next/navigation";
import { useCallback, useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { BackButton } from "./back-button";
import { Button, ButtonVariants } from "./button";
import { TextInput } from "./input";
import { Spinner } from "./spinner";
@@ -96,7 +97,26 @@ export function VerifyForm({ userId, code, isInvite, params }: Props) {
return (
<>
<form className="w-full">
<div className="">
<Alert type={AlertType.INFO}>
<div className="flex flex-row">
<span className="flex-1 mr-auto text-left">
{t("verify.noCodeReceived")}
</span>
<button
aria-label="Resend OTP Code"
disabled={loading}
type="button"
className="ml-4 text-primary-light-500 dark:text-primary-dark-500 hover:dark:text-primary-dark-400 hover:text-primary-light-400 cursor-pointer disabled:cursor-default disabled:text-gray-400 dark:disabled:text-gray-700"
onClick={() => {
resendCode();
}}
data-testid="resend-button"
>
{t("verify.resendCode")}
</button>
</div>
</Alert>
<div className="mt-4">
<TextInput
type="text"
autoComplete="one-time-code"
@@ -112,13 +132,7 @@ export function VerifyForm({ userId, code, isInvite, params }: Props) {
)}
<div className="mt-8 flex w-full flex-row items-center">
<Button
type="button"
onClick={() => resendCode()}
variant={ButtonVariants.Secondary}
>
{t("verify.resendCode")}
</Button>
<BackButton />
<span className="flex-grow"></span>
<Button
type="submit"

View File

@@ -1,12 +1,28 @@
import { LANGUAGE_COOKIE_NAME } from "@/lib/i18n";
import { LANGS, LANGUAGE_COOKIE_NAME, LANGUAGE_HEADER_NAME } from "@/lib/i18n";
import deepmerge from "deepmerge";
import { getRequestConfig } from "next-intl/server";
import { cookies } from "next/headers";
import { cookies, headers } from "next/headers";
export default getRequestConfig(async () => {
const fallback = "en";
const cookiesList = await cookies();
const locale: string = cookiesList.get(LANGUAGE_COOKIE_NAME)?.value ?? "en";
let locale: string = fallback;
const languageHeader = await (await headers()).get(LANGUAGE_HEADER_NAME);
if (languageHeader) {
const headerLocale = languageHeader.split(",")[0].split("-")[0]; // Extract the language code
if (LANGS.map((l) => l.code).includes(headerLocale)) {
locale = headerLocale;
}
}
const languageCookie = cookiesList?.get(LANGUAGE_COOKIE_NAME);
if (languageCookie && languageCookie.value) {
if (LANGS.map((l) => l.code).includes(languageCookie.value)) {
locale = languageCookie.value;
}
}
const userMessages = (await import(`../../locales/${locale}.json`)).default;
const fallbackMessages = (await import(`../../locales/${fallback}.json`))

View File

@@ -27,3 +27,4 @@ export const LANGS: Lang[] = [
];
export const LANGUAGE_COOKIE_NAME = "NEXT_LOCALE";
export const LANGUAGE_HEADER_NAME = "accept-language";

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

@@ -6,23 +6,30 @@ import {
} from "@/lib/server/cookie";
import {
getLoginSettings,
getSession,
getUserByID,
listAuthenticationMethodTypes,
listUsers,
passwordReset,
setPassword,
setUserPassword,
} from "@/lib/zitadel";
import { create } from "@zitadel/client";
import { createUserServiceClient } from "@zitadel/client/v2";
import { createServerTransport } from "@zitadel/node";
import {
Checks,
ChecksSchema,
} from "@zitadel/proto/zitadel/session/v2/session_service_pb";
import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
import { User, UserState } from "@zitadel/proto/zitadel/user/v2/user_pb";
import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
import {
AuthenticationMethodType,
SetPasswordRequestSchema,
} from "@zitadel/proto/zitadel/user/v2/user_service_pb";
import { headers } from "next/headers";
import { getNextUrl } from "../client";
import { getSessionCookieByLoginName } from "../cookies";
import { getSessionCookieById, getSessionCookieByLoginName } from "../cookies";
type ResetPasswordCommand = {
loginName: string;
@@ -142,18 +149,10 @@ export async function sendPassword(command: UpdateSessionCommand) {
return { error: "Could not verify password!" };
}
const availableSecondFactors = authMethods?.filter(
(m: AuthenticationMethodType) =>
m !== AuthenticationMethodType.PASSWORD &&
m !== AuthenticationMethodType.PASSKEY,
);
const humanUser = user.type.case === "human" ? user.type.value : undefined;
if (
availableSecondFactors?.length == 0 &&
humanUser?.passwordChangeRequired
) {
// check if the user has to change password first
if (humanUser?.passwordChangeRequired) {
const params = new URLSearchParams({
loginName: session.factors?.user?.loginName,
});
@@ -169,7 +168,13 @@ export async function sendPassword(command: UpdateSessionCommand) {
return { redirect: "/password/change?" + params };
}
if (availableSecondFactors?.length == 1) {
const availableMultiFactors = authMethods?.filter(
(m: AuthenticationMethodType) =>
m !== AuthenticationMethodType.PASSWORD &&
m !== AuthenticationMethodType.PASSKEY,
);
if (availableMultiFactors?.length == 1) {
const params = new URLSearchParams({
loginName: session.factors?.user.loginName,
});
@@ -185,7 +190,7 @@ export async function sendPassword(command: UpdateSessionCommand) {
);
}
const factor = availableSecondFactors[0];
const factor = availableMultiFactors[0];
// if passwordless is other method, but user selected password as alternative, perform a login
if (factor === AuthenticationMethodType.TOTP) {
return { redirect: `/otp/time-based?` + params };
@@ -196,7 +201,7 @@ export async function sendPassword(command: UpdateSessionCommand) {
} else if (factor === AuthenticationMethodType.U2F) {
return { redirect: `/u2f?` + params };
}
} else if (availableSecondFactors?.length >= 1) {
} else if (availableMultiFactors?.length >= 1) {
const params = new URLSearchParams({
loginName: session.factors.user.loginName,
});
@@ -219,7 +224,7 @@ export async function sendPassword(command: UpdateSessionCommand) {
return { error: "Initial User not supported" };
} else if (
(loginSettings?.forceMfa || loginSettings?.forceMfaLocalOnly) &&
!availableSecondFactors.length
!availableMultiFactors.length
) {
const params = new URLSearchParams({
loginName: session.factors.user.loginName,
@@ -302,5 +307,99 @@ export async function changePassword(command: {
}
const userId = user.userId;
return setPassword(userId, command.password, user, command.code);
return setUserPassword(userId, command.password, user, command.code);
}
type CheckSessionAndSetPasswordCommand = {
sessionId: string;
password: string;
};
export async function checkSessionAndSetPassword({
sessionId,
password,
}: 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),
);
const loginSettings = await getLoginSettings(
session.factors.user.organizationId,
);
const forceMfa = !!(
loginSettings?.forceMfa || loginSettings?.forceMfaLocalOnly
);
// 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 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;
});
}
}

View File

@@ -32,23 +32,25 @@ export async function addU2F(command: RegisterU2FCommand) {
sessionToken: sessionCookie.token,
});
const domain = (await headers()).get("host");
const host = (await headers()).get("host");
if (!domain) {
if (!host) {
return { error: "Could not get domain" };
}
const [hostname, port] = host.split(":");
if (!hostname) {
throw new Error("Could not get hostname");
}
const userId = session?.session?.factors?.user?.id;
if (!session || !userId) {
return { error: "Could not get session" };
}
return registerU2F(
userId,
domain,
// sessionCookie.token
);
return registerU2F(userId, hostname);
}
export async function verifyU2F(command: VerifyU2FCommand) {

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 {
RetrieveIdentityProviderIntentRequest,
SetPasswordRequest,
SetPasswordRequestSchema,
VerifyPasskeyRegistrationRequest,
VerifyU2FRegistrationRequest,
@@ -536,7 +537,7 @@ export async function passwordReset(
* @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 setUserPassword(
userId: string,
password: string,
user: User,
@@ -582,6 +583,10 @@ export async function setPassword(
});
}
export async function setPassword(payload: SetPasswordRequest) {
return userService.setPassword(payload, {});
}
/**
*
* @param server
@@ -615,17 +620,7 @@ export async function createPasskeyRegistrationLink(
* @returns the newly set email
*/
// TODO check for token requirements!
export async function registerU2F(
userId: string,
domain: string,
// token: string,
) {
// const transport = createServerTransport(token, {
// baseUrl: process.env.ZITADEL_API_URL!,
// });
// const service = createUserServiceClient(transport);
export async function registerU2F(userId: string, domain: string) {
return userService.registerU2F({
userId,
domain,