mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-24 19:28:17 +00:00
Merge branch 'main' into refactor/login
This commit is contained in:
@@ -24,6 +24,7 @@
|
|||||||
"title": "Passwort festlegen",
|
"title": "Passwort festlegen",
|
||||||
"description": "Legen Sie das Passwort für Ihr Konto fest",
|
"description": "Legen Sie das Passwort für Ihr Konto fest",
|
||||||
"codeSent": "Ein Code wurde an Ihre E-Mail-Adresse gesendet.",
|
"codeSent": "Ein Code wurde an Ihre E-Mail-Adresse gesendet.",
|
||||||
|
"noCodeReceived": "Keinen Code erhalten?",
|
||||||
"resend": "Erneut senden",
|
"resend": "Erneut senden",
|
||||||
"submit": "Weiter"
|
"submit": "Weiter"
|
||||||
},
|
},
|
||||||
@@ -173,6 +174,7 @@
|
|||||||
"verify": {
|
"verify": {
|
||||||
"title": "Benutzer verifizieren",
|
"title": "Benutzer verifizieren",
|
||||||
"description": "Geben Sie den Code ein, der in der Bestätigungs-E-Mail angegeben ist.",
|
"description": "Geben Sie den Code ein, der in der Bestätigungs-E-Mail angegeben ist.",
|
||||||
|
"noCodeReceived": "Keinen Code erhalten?",
|
||||||
"resendCode": "Code erneut senden",
|
"resendCode": "Code erneut senden",
|
||||||
"submit": "Weiter"
|
"submit": "Weiter"
|
||||||
}
|
}
|
||||||
|
@@ -24,6 +24,7 @@
|
|||||||
"title": "Set Password",
|
"title": "Set Password",
|
||||||
"description": "Set the password for your account",
|
"description": "Set the password for your account",
|
||||||
"codeSent": "A code has been sent to your email address.",
|
"codeSent": "A code has been sent to your email address.",
|
||||||
|
"noCodeReceived": "Didn't receive a code?",
|
||||||
"resend": "Resend code",
|
"resend": "Resend code",
|
||||||
"submit": "Continue"
|
"submit": "Continue"
|
||||||
},
|
},
|
||||||
@@ -173,6 +174,7 @@
|
|||||||
"verify": {
|
"verify": {
|
||||||
"title": "Verify user",
|
"title": "Verify user",
|
||||||
"description": "Enter the Code provided in the verification email.",
|
"description": "Enter the Code provided in the verification email.",
|
||||||
|
"noCodeReceived": "Didn't receive a code?",
|
||||||
"resendCode": "Resend code",
|
"resendCode": "Resend code",
|
||||||
"submit": "Continue"
|
"submit": "Continue"
|
||||||
}
|
}
|
||||||
|
@@ -24,6 +24,7 @@
|
|||||||
"title": "Establecer Contraseña",
|
"title": "Establecer Contraseña",
|
||||||
"description": "Establece la contraseña para tu cuenta",
|
"description": "Establece la contraseña para tu cuenta",
|
||||||
"codeSent": "Se ha enviado un código a su correo electrónico.",
|
"codeSent": "Se ha enviado un código a su correo electrónico.",
|
||||||
|
"noCodeReceived": "¿No recibiste un código?",
|
||||||
"resend": "Reenviar código",
|
"resend": "Reenviar código",
|
||||||
"submit": "Continuar"
|
"submit": "Continuar"
|
||||||
},
|
},
|
||||||
@@ -173,6 +174,7 @@
|
|||||||
"verify": {
|
"verify": {
|
||||||
"title": "Verificar usuario",
|
"title": "Verificar usuario",
|
||||||
"description": "Introduce el código proporcionado en el correo electrónico de verificación.",
|
"description": "Introduce el código proporcionado en el correo electrónico de verificación.",
|
||||||
|
"noCodeReceived": "¿No recibiste un código?",
|
||||||
"resendCode": "Reenviar código",
|
"resendCode": "Reenviar código",
|
||||||
"submit": "Continuar"
|
"submit": "Continuar"
|
||||||
}
|
}
|
||||||
|
@@ -24,6 +24,7 @@
|
|||||||
"title": "Imposta Password",
|
"title": "Imposta Password",
|
||||||
"description": "Imposta la password per il tuo account",
|
"description": "Imposta la password per il tuo account",
|
||||||
"codeSent": "Un codice è stato inviato al tuo indirizzo email.",
|
"codeSent": "Un codice è stato inviato al tuo indirizzo email.",
|
||||||
|
"noCodeReceived": "Non hai ricevuto un codice?",
|
||||||
"resend": "Invia di nuovo",
|
"resend": "Invia di nuovo",
|
||||||
"submit": "Continua"
|
"submit": "Continua"
|
||||||
},
|
},
|
||||||
@@ -173,6 +174,7 @@
|
|||||||
"verify": {
|
"verify": {
|
||||||
"title": "Verifica utente",
|
"title": "Verifica utente",
|
||||||
"description": "Inserisci il codice fornito nell'email di verifica.",
|
"description": "Inserisci il codice fornito nell'email di verifica.",
|
||||||
|
"noCodeReceived": "Non hai ricevuto un codice?",
|
||||||
"resendCode": "Invia di nuovo il codice",
|
"resendCode": "Invia di nuovo il codice",
|
||||||
"submit": "Continua"
|
"submit": "Continua"
|
||||||
}
|
}
|
||||||
|
@@ -24,6 +24,7 @@
|
|||||||
"title": "设置密码",
|
"title": "设置密码",
|
||||||
"description": "为您的账户设置密码",
|
"description": "为您的账户设置密码",
|
||||||
"codeSent": "验证码已发送到您的邮箱。",
|
"codeSent": "验证码已发送到您的邮箱。",
|
||||||
|
"noCodeReceived": "没有收到验证码?",
|
||||||
"resend": "重发验证码",
|
"resend": "重发验证码",
|
||||||
"submit": "继续"
|
"submit": "继续"
|
||||||
},
|
},
|
||||||
@@ -122,6 +123,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"register": {
|
"register": {
|
||||||
|
"methods": {
|
||||||
|
"passkey": "密钥",
|
||||||
|
"password": "密码"
|
||||||
|
},
|
||||||
|
"disabled": {
|
||||||
|
"title": "注册已禁用",
|
||||||
|
"description": "您的设置不允许注册新用户。"
|
||||||
|
},
|
||||||
|
"missingdata": {
|
||||||
|
"title": "缺少数据",
|
||||||
|
"description": "请提供所有必需的数据。"
|
||||||
|
},
|
||||||
"title": "注册",
|
"title": "注册",
|
||||||
"description": "创建您的 ZITADEL 账户。",
|
"description": "创建您的 ZITADEL 账户。",
|
||||||
"selectMethod": "选择您想使用的认证方法",
|
"selectMethod": "选择您想使用的认证方法",
|
||||||
@@ -151,7 +164,8 @@
|
|||||||
},
|
},
|
||||||
"signedin": {
|
"signedin": {
|
||||||
"title": "欢迎 {user}!",
|
"title": "欢迎 {user}!",
|
||||||
"description": "您已登录。"
|
"description": "您已登录。",
|
||||||
|
"continue": "继续"
|
||||||
},
|
},
|
||||||
"verify": {
|
"verify": {
|
||||||
"userIdMissing": "未提供用户 ID!",
|
"userIdMissing": "未提供用户 ID!",
|
||||||
@@ -160,6 +174,7 @@
|
|||||||
"verify": {
|
"verify": {
|
||||||
"title": "验证用户",
|
"title": "验证用户",
|
||||||
"description": "输入验证邮件中的验证码。",
|
"description": "输入验证邮件中的验证码。",
|
||||||
|
"noCodeReceived": "没有收到验证码?",
|
||||||
"resendCode": "重发验证码",
|
"resendCode": "重发验证码",
|
||||||
"submit": "继续"
|
"submit": "继续"
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,13 @@
|
|||||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||||
import { SessionsList } from "@/components/sessions-list";
|
import { SessionsList } from "@/components/sessions-list";
|
||||||
import { getAllSessionCookieIds } from "@/lib/cookies";
|
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 { UserPlusIcon } from "@heroicons/react/24/outline";
|
||||||
|
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
|
||||||
import { getLocale, getTranslations } from "next-intl/server";
|
import { getLocale, getTranslations } from "next-intl/server";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
@@ -30,9 +35,19 @@ export default async function Page(props: {
|
|||||||
const authRequestId = searchParams?.authRequestId;
|
const authRequestId = searchParams?.authRequestId;
|
||||||
const organization = searchParams?.organization;
|
const organization = searchParams?.organization;
|
||||||
|
|
||||||
|
let defaultOrganization;
|
||||||
|
if (!organization) {
|
||||||
|
const org: Organization | null = await getDefaultOrg();
|
||||||
|
if (org) {
|
||||||
|
defaultOrganization = org.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let sessions = await loadSessions();
|
let sessions = await loadSessions();
|
||||||
|
|
||||||
const branding = await getBrandingSettings(organization);
|
const branding = await getBrandingSettings(
|
||||||
|
organization ?? defaultOrganization,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DynamicTheme branding={branding}>
|
<DynamicTheme branding={branding}>
|
||||||
|
@@ -32,7 +32,9 @@ export default async function Page(props: {
|
|||||||
sessionFactors?.factors?.user?.organizationId,
|
sessionFactors?.factors?.user?.organizationId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const loginSettings = await getLoginSettings(organization);
|
const loginSettings = await getLoginSettings(
|
||||||
|
sessionFactors?.factors?.user?.organizationId,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DynamicTheme branding={branding}>
|
<DynamicTheme branding={branding}>
|
||||||
|
@@ -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 },
|
|
||||||
);
|
|
||||||
}
|
|
@@ -32,7 +32,7 @@ const LinkWrapper = ({
|
|||||||
|
|
||||||
export const TOTP = (alreadyAdded: boolean, link: string) => {
|
export const TOTP = (alreadyAdded: boolean, link: string) => {
|
||||||
return (
|
return (
|
||||||
<LinkWrapper alreadyAdded={alreadyAdded} link={link} key={link}>
|
<LinkWrapper key={link} alreadyAdded={alreadyAdded} link={link}>
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"font-medium flex items-center",
|
"font-medium flex items-center",
|
||||||
@@ -40,45 +40,12 @@ export const TOTP = (alreadyAdded: boolean, link: string) => {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
className="h-9 w-9 transform -translate-x-[2px] mr-4"
|
className="h-8 w-8 transform -translate-x-[2px] mr-4 fill-current text-black dark:text-white"
|
||||||
version="1.1"
|
|
||||||
baseProfile="basic"
|
|
||||||
id="Layer_1"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
viewBox="0 0 24 24"
|
||||||
x="0px"
|
|
||||||
y="0px"
|
|
||||||
viewBox="0 0 512 512"
|
|
||||||
xmlSpace="preserve"
|
|
||||||
>
|
>
|
||||||
<path
|
<title>timer-lock-outline</title>
|
||||||
fill="#1A73E8"
|
<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" />
|
||||||
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 "
|
|
||||||
/>
|
|
||||||
</svg>{" "}
|
</svg>{" "}
|
||||||
<span>Authenticator App</span>
|
<span>Authenticator App</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -93,7 +60,7 @@ C72,238.87917,85.87916,225,102.99997,225H248z"
|
|||||||
|
|
||||||
export const U2F = (alreadyAdded: boolean, link: string) => {
|
export const U2F = (alreadyAdded: boolean, link: string) => {
|
||||||
return (
|
return (
|
||||||
<LinkWrapper alreadyAdded={alreadyAdded} link={link}>
|
<LinkWrapper key={link} alreadyAdded={alreadyAdded} link={link}>
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"font-medium flex items-center",
|
"font-medium flex items-center",
|
||||||
@@ -127,7 +94,7 @@ export const U2F = (alreadyAdded: boolean, link: string) => {
|
|||||||
|
|
||||||
export const EMAIL = (alreadyAdded: boolean, link: string) => {
|
export const EMAIL = (alreadyAdded: boolean, link: string) => {
|
||||||
return (
|
return (
|
||||||
<LinkWrapper alreadyAdded={alreadyAdded} link={link}>
|
<LinkWrapper key={link} alreadyAdded={alreadyAdded} link={link}>
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"font-medium flex items-center",
|
"font-medium flex items-center",
|
||||||
@@ -162,7 +129,7 @@ export const EMAIL = (alreadyAdded: boolean, link: string) => {
|
|||||||
|
|
||||||
export const SMS = (alreadyAdded: boolean, link: string) => {
|
export const SMS = (alreadyAdded: boolean, link: string) => {
|
||||||
return (
|
return (
|
||||||
<LinkWrapper alreadyAdded={alreadyAdded} link={link}>
|
<LinkWrapper key={link} alreadyAdded={alreadyAdded} link={link}>
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"font-medium flex items-center",
|
"font-medium flex items-center",
|
||||||
@@ -196,7 +163,7 @@ export const SMS = (alreadyAdded: boolean, link: string) => {
|
|||||||
|
|
||||||
export const PASSKEYS = (alreadyAdded: boolean, link: string) => {
|
export const PASSKEYS = (alreadyAdded: boolean, link: string) => {
|
||||||
return (
|
return (
|
||||||
<LinkWrapper alreadyAdded={alreadyAdded} link={link}>
|
<LinkWrapper key={link} alreadyAdded={alreadyAdded} link={link}>
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"font-medium flex items-center",
|
"font-medium flex items-center",
|
||||||
@@ -230,7 +197,7 @@ export const PASSKEYS = (alreadyAdded: boolean, link: string) => {
|
|||||||
|
|
||||||
export const PASSWORD = (alreadyAdded: boolean, link: string) => {
|
export const PASSWORD = (alreadyAdded: boolean, link: string) => {
|
||||||
return (
|
return (
|
||||||
<LinkWrapper alreadyAdded={alreadyAdded} link={link}>
|
<LinkWrapper key={link} alreadyAdded={alreadyAdded} link={link}>
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"font-medium flex items-center",
|
"font-medium flex items-center",
|
||||||
|
@@ -6,8 +6,10 @@ import {
|
|||||||
symbolValidator,
|
symbolValidator,
|
||||||
upperCaseValidator,
|
upperCaseValidator,
|
||||||
} from "@/helpers/validators";
|
} from "@/helpers/validators";
|
||||||
import { setMyPassword } from "@/lib/self";
|
import {
|
||||||
import { sendPassword } from "@/lib/server/password";
|
checkSessionAndSetPassword,
|
||||||
|
sendPassword,
|
||||||
|
} from "@/lib/server/password";
|
||||||
import { create } from "@zitadel/client";
|
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";
|
||||||
@@ -60,8 +62,9 @@ export function ChangePasswordForm({
|
|||||||
|
|
||||||
async function submitChange(values: Inputs) {
|
async function submitChange(values: Inputs) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const changeResponse = await setMyPassword({
|
|
||||||
sessionId: sessionId,
|
const changeResponse = checkSessionAndSetPassword({
|
||||||
|
sessionId,
|
||||||
password: values.password,
|
password: values.password,
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@@ -72,8 +75,12 @@ export function ChangePasswordForm({
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (changeResponse && "error" in changeResponse) {
|
if (changeResponse && "error" in changeResponse && changeResponse.error) {
|
||||||
setError(changeResponse.error);
|
setError(
|
||||||
|
typeof changeResponse.error === "string"
|
||||||
|
? changeResponse.error
|
||||||
|
: "Unknown error",
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -18,7 +18,7 @@ import { useTranslations } from "next-intl";
|
|||||||
import { useRouter } from "next/navigation";
|
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, AlertType } from "./alert";
|
||||||
import { BackButton } from "./back-button";
|
import { BackButton } from "./back-button";
|
||||||
import { Button, ButtonVariants } from "./button";
|
import { Button, ButtonVariants } from "./button";
|
||||||
import { TextInput } from "./input";
|
import { TextInput } from "./input";
|
||||||
@@ -192,8 +192,28 @@ export function SetPasswordForm({
|
|||||||
<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">
|
||||||
{codeRequired && (
|
{codeRequired && (
|
||||||
<div className="flex flex-row items-end">
|
<Alert type={AlertType.INFO}>
|
||||||
<div className="flex-1">
|
<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>
|
||||||
|
</div>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
{codeRequired && (
|
||||||
|
<div>
|
||||||
<TextInput
|
<TextInput
|
||||||
type="text"
|
type="text"
|
||||||
required
|
required
|
||||||
@@ -206,20 +226,8 @@ export function SetPasswordForm({
|
|||||||
data-testid="code-text-input"
|
data-testid="code-text-input"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="ml-4 mb-1">
|
|
||||||
<Button
|
|
||||||
variant={ButtonVariants.Secondary}
|
|
||||||
data-testid="resend-button"
|
|
||||||
onClick={() => resendCode()}
|
|
||||||
disabled={loading}
|
|
||||||
>
|
|
||||||
{t("set.resend")}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
<div className="">
|
<div>
|
||||||
<TextInput
|
<TextInput
|
||||||
type="password"
|
type="password"
|
||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
@@ -232,7 +240,7 @@ export function SetPasswordForm({
|
|||||||
data-testid="password-text-input"
|
data-testid="password-text-input"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="">
|
<div>
|
||||||
<TextInput
|
<TextInput
|
||||||
type="password"
|
type="password"
|
||||||
required
|
required
|
||||||
|
@@ -1,11 +1,12 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Alert } from "@/components/alert";
|
import { Alert, AlertType } from "@/components/alert";
|
||||||
import { resendVerification, sendVerification } from "@/lib/server/email";
|
import { resendVerification, sendVerification } from "@/lib/server/email";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
|
import { BackButton } from "./back-button";
|
||||||
import { Button, ButtonVariants } from "./button";
|
import { Button, ButtonVariants } from "./button";
|
||||||
import { TextInput } from "./input";
|
import { TextInput } from "./input";
|
||||||
import { Spinner } from "./spinner";
|
import { Spinner } from "./spinner";
|
||||||
@@ -96,7 +97,26 @@ export function VerifyForm({ userId, code, isInvite, params }: Props) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<form className="w-full">
|
<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
|
<TextInput
|
||||||
type="text"
|
type="text"
|
||||||
autoComplete="one-time-code"
|
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">
|
<div className="mt-8 flex w-full flex-row items-center">
|
||||||
<Button
|
<BackButton />
|
||||||
type="button"
|
|
||||||
onClick={() => resendCode()}
|
|
||||||
variant={ButtonVariants.Secondary}
|
|
||||||
>
|
|
||||||
{t("verify.resendCode")}
|
|
||||||
</Button>
|
|
||||||
<span className="flex-grow"></span>
|
<span className="flex-grow"></span>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
|
@@ -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 deepmerge from "deepmerge";
|
||||||
import { getRequestConfig } from "next-intl/server";
|
import { getRequestConfig } from "next-intl/server";
|
||||||
import { cookies } from "next/headers";
|
import { cookies, headers } from "next/headers";
|
||||||
|
|
||||||
export default getRequestConfig(async () => {
|
export default getRequestConfig(async () => {
|
||||||
const fallback = "en";
|
const fallback = "en";
|
||||||
const cookiesList = await cookies();
|
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 userMessages = (await import(`../../locales/${locale}.json`)).default;
|
||||||
const fallbackMessages = (await import(`../../locales/${fallback}.json`))
|
const fallbackMessages = (await import(`../../locales/${fallback}.json`))
|
||||||
|
@@ -27,3 +27,4 @@ export const LANGS: Lang[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const LANGUAGE_COOKIE_NAME = "NEXT_LOCALE";
|
export const LANGUAGE_COOKIE_NAME = "NEXT_LOCALE";
|
||||||
|
export const LANGUAGE_HEADER_NAME = "accept-language";
|
||||||
|
@@ -1,9 +1,6 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import {
|
import { createUserServiceClient } from "@zitadel/client/v2";
|
||||||
createSessionServiceClient,
|
|
||||||
createUserServiceClient,
|
|
||||||
} from "@zitadel/client/v2";
|
|
||||||
import { createServerTransport } from "@zitadel/node";
|
import { createServerTransport } from "@zitadel/node";
|
||||||
import { getSessionCookieById } from "./cookies";
|
import { getSessionCookieById } from "./cookies";
|
||||||
import { getSession } from "./zitadel";
|
import { getSession } from "./zitadel";
|
||||||
@@ -13,12 +10,6 @@ const transport = (token: string) =>
|
|||||||
baseUrl: process.env.ZITADEL_API_URL!,
|
baseUrl: process.env.ZITADEL_API_URL!,
|
||||||
});
|
});
|
||||||
|
|
||||||
const sessionService = (sessionId: string) => {
|
|
||||||
return getSessionCookieById({ sessionId }).then((session) => {
|
|
||||||
return createSessionServiceClient(transport(session.token));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const myUserService = (sessionToken: string) => {
|
const myUserService = (sessionToken: string) => {
|
||||||
return createUserServiceClient(transport(sessionToken));
|
return createUserServiceClient(transport(sessionToken));
|
||||||
};
|
};
|
||||||
@@ -41,7 +32,7 @@ export async function setMyPassword({
|
|||||||
return { error: "Could not load session" };
|
return { error: "Could not load session" };
|
||||||
}
|
}
|
||||||
|
|
||||||
const service = await myUserService(sessionCookie.token);
|
const service = await myUserService(`${sessionCookie.token}`);
|
||||||
|
|
||||||
if (!session?.factors?.user?.id) {
|
if (!session?.factors?.user?.id) {
|
||||||
return { error: "No user id found in session" };
|
return { error: "No user id found in session" };
|
||||||
@@ -56,6 +47,7 @@ export async function setMyPassword({
|
|||||||
{},
|
{},
|
||||||
)
|
)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
console.log(error);
|
||||||
if (error.code === 7) {
|
if (error.code === 7) {
|
||||||
return { error: "Session is not valid." };
|
return { error: "Session is not valid." };
|
||||||
}
|
}
|
||||||
|
@@ -6,23 +6,30 @@ import {
|
|||||||
} from "@/lib/server/cookie";
|
} from "@/lib/server/cookie";
|
||||||
import {
|
import {
|
||||||
getLoginSettings,
|
getLoginSettings,
|
||||||
|
getSession,
|
||||||
getUserByID,
|
getUserByID,
|
||||||
listAuthenticationMethodTypes,
|
listAuthenticationMethodTypes,
|
||||||
listUsers,
|
listUsers,
|
||||||
passwordReset,
|
passwordReset,
|
||||||
setPassword,
|
setPassword,
|
||||||
|
setUserPassword,
|
||||||
} from "@/lib/zitadel";
|
} from "@/lib/zitadel";
|
||||||
import { create } from "@zitadel/client";
|
import { create } from "@zitadel/client";
|
||||||
|
import { createUserServiceClient } from "@zitadel/client/v2";
|
||||||
|
import { createServerTransport } from "@zitadel/node";
|
||||||
import {
|
import {
|
||||||
Checks,
|
Checks,
|
||||||
ChecksSchema,
|
ChecksSchema,
|
||||||
} from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
} from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
||||||
import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||||
import { User, UserState } from "@zitadel/proto/zitadel/user/v2/user_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 { headers } from "next/headers";
|
||||||
import { getNextUrl } from "../client";
|
import { getNextUrl } from "../client";
|
||||||
import { getSessionCookieByLoginName } from "../cookies";
|
import { getSessionCookieById, getSessionCookieByLoginName } from "../cookies";
|
||||||
|
|
||||||
type ResetPasswordCommand = {
|
type ResetPasswordCommand = {
|
||||||
loginName: string;
|
loginName: string;
|
||||||
@@ -142,18 +149,10 @@ export async function sendPassword(command: UpdateSessionCommand) {
|
|||||||
return { error: "Could not verify password!" };
|
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;
|
const humanUser = user.type.case === "human" ? user.type.value : undefined;
|
||||||
|
|
||||||
if (
|
// check if the user has to change password first
|
||||||
availableSecondFactors?.length == 0 &&
|
if (humanUser?.passwordChangeRequired) {
|
||||||
humanUser?.passwordChangeRequired
|
|
||||||
) {
|
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
loginName: session.factors?.user?.loginName,
|
loginName: session.factors?.user?.loginName,
|
||||||
});
|
});
|
||||||
@@ -169,7 +168,13 @@ export async function sendPassword(command: UpdateSessionCommand) {
|
|||||||
return { redirect: "/password/change?" + params };
|
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({
|
const params = new URLSearchParams({
|
||||||
loginName: session.factors?.user.loginName,
|
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 passwordless is other method, but user selected password as alternative, perform a login
|
||||||
if (factor === AuthenticationMethodType.TOTP) {
|
if (factor === AuthenticationMethodType.TOTP) {
|
||||||
return { redirect: `/otp/time-based?` + params };
|
return { redirect: `/otp/time-based?` + params };
|
||||||
@@ -196,7 +201,7 @@ export async function sendPassword(command: UpdateSessionCommand) {
|
|||||||
} else if (factor === AuthenticationMethodType.U2F) {
|
} else if (factor === AuthenticationMethodType.U2F) {
|
||||||
return { redirect: `/u2f?` + params };
|
return { redirect: `/u2f?` + params };
|
||||||
}
|
}
|
||||||
} else if (availableSecondFactors?.length >= 1) {
|
} else if (availableMultiFactors?.length >= 1) {
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
loginName: session.factors.user.loginName,
|
loginName: session.factors.user.loginName,
|
||||||
});
|
});
|
||||||
@@ -219,7 +224,7 @@ export async function sendPassword(command: UpdateSessionCommand) {
|
|||||||
return { error: "Initial User not supported" };
|
return { error: "Initial User not supported" };
|
||||||
} else if (
|
} else if (
|
||||||
(loginSettings?.forceMfa || loginSettings?.forceMfaLocalOnly) &&
|
(loginSettings?.forceMfa || loginSettings?.forceMfaLocalOnly) &&
|
||||||
!availableSecondFactors.length
|
!availableMultiFactors.length
|
||||||
) {
|
) {
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
loginName: session.factors.user.loginName,
|
loginName: session.factors.user.loginName,
|
||||||
@@ -302,5 +307,99 @@ export async function changePassword(command: {
|
|||||||
}
|
}
|
||||||
const userId = user.userId;
|
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;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -32,23 +32,25 @@ export async function addU2F(command: RegisterU2FCommand) {
|
|||||||
sessionToken: sessionCookie.token,
|
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" };
|
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;
|
const userId = session?.session?.factors?.user?.id;
|
||||||
|
|
||||||
if (!session || !userId) {
|
if (!session || !userId) {
|
||||||
return { error: "Could not get session" };
|
return { error: "Could not get session" };
|
||||||
}
|
}
|
||||||
|
|
||||||
return registerU2F(
|
return registerU2F(userId, hostname);
|
||||||
userId,
|
|
||||||
domain,
|
|
||||||
// sessionCookie.token
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function verifyU2F(command: VerifyU2FCommand) {
|
export async function verifyU2F(command: VerifyU2FCommand) {
|
||||||
|
@@ -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,
|
||||||
|
SetPasswordRequest,
|
||||||
SetPasswordRequestSchema,
|
SetPasswordRequestSchema,
|
||||||
VerifyPasskeyRegistrationRequest,
|
VerifyPasskeyRegistrationRequest,
|
||||||
VerifyU2FRegistrationRequest,
|
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
|
* @param code optional if the password should be set with a code (reset), no code for initial setup of password
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export async function setPassword(
|
export async function setUserPassword(
|
||||||
userId: string,
|
userId: string,
|
||||||
password: string,
|
password: string,
|
||||||
user: User,
|
user: User,
|
||||||
@@ -582,6 +583,10 @@ export async function setPassword(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function setPassword(payload: SetPasswordRequest) {
|
||||||
|
return userService.setPassword(payload, {});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param server
|
* @param server
|
||||||
@@ -615,17 +620,7 @@ export async function createPasskeyRegistrationLink(
|
|||||||
* @returns the newly set email
|
* @returns the newly set email
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// TODO check for token requirements!
|
export async function registerU2F(userId: string, domain: string) {
|
||||||
export async function registerU2F(
|
|
||||||
userId: string,
|
|
||||||
domain: string,
|
|
||||||
// token: string,
|
|
||||||
) {
|
|
||||||
// const transport = createServerTransport(token, {
|
|
||||||
// baseUrl: process.env.ZITADEL_API_URL!,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const service = createUserServiceClient(transport);
|
|
||||||
return userService.registerU2F({
|
return userService.registerU2F({
|
||||||
userId,
|
userId,
|
||||||
domain,
|
domain,
|
||||||
|
Reference in New Issue
Block a user