tests, session using timestamp
@@ -1,10 +1,11 @@
|
|||||||
import { stub } from "../support/mock";
|
import { stub } from "../support/mock";
|
||||||
|
|
||||||
describe("/verify", () => {
|
describe("/verify", () => {
|
||||||
it("redirects after successful email verification", () => {
|
it("shows password and passkey method after successful invite verification", () => {
|
||||||
stub("zitadel.user.v2.UserService", "VerifyEmail");
|
stub("zitadel.user.v2.UserService", "VerifyEmail");
|
||||||
cy.visit("/verify?userId=123&code=abc&submit=true");
|
cy.visit("/verify?userId=123&code=abc&submit=true&invite=true");
|
||||||
cy.location("pathname", { timeout: 10_000 }).should("eq", "/loginname");
|
cy.contains("Password");
|
||||||
|
cy.contains("Passkey");
|
||||||
});
|
});
|
||||||
it("shows an error if validation failed", () => {
|
it("shows an error if validation failed", () => {
|
||||||
stub("zitadel.user.v2.UserService", "VerifyEmail", {
|
stub("zitadel.user.v2.UserService", "VerifyEmail", {
|
||||||
@@ -14,6 +15,6 @@ describe("/verify", () => {
|
|||||||
// TODO: Avoid uncaught exception in application
|
// TODO: Avoid uncaught exception in application
|
||||||
cy.once("uncaught:exception", () => false);
|
cy.once("uncaught:exception", () => false);
|
||||||
cy.visit("/verify?userId=123&code=abc&submit=true");
|
cy.visit("/verify?userId=123&code=abc&submit=true");
|
||||||
cy.contains("Could not verify email");
|
cy.contains("Could not verify user");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -139,6 +139,7 @@
|
|||||||
"title": "Benutzer einladen",
|
"title": "Benutzer einladen",
|
||||||
"description": "Geben Sie die E-Mail-Adresse des Benutzers ein, den Sie einladen möchten.",
|
"description": "Geben Sie die E-Mail-Adresse des Benutzers ein, den Sie einladen möchten.",
|
||||||
"info": "Der Benutzer erhält eine E-Mail mit einem Link, um sich zu registrieren.",
|
"info": "Der Benutzer erhält eine E-Mail mit einem Link, um sich zu registrieren.",
|
||||||
|
"notAllowed": "Sie haben keine Berechtigung, Benutzer einzuladen.",
|
||||||
"submit": "Einladen",
|
"submit": "Einladen",
|
||||||
"success": {
|
"success": {
|
||||||
"title": "Einladung erfolgreich",
|
"title": "Einladung erfolgreich",
|
||||||
@@ -153,11 +154,17 @@
|
|||||||
"description": "Sie sind angemeldet."
|
"description": "Sie sind angemeldet."
|
||||||
},
|
},
|
||||||
"verify": {
|
"verify": {
|
||||||
"title": "Benutzer verifizieren",
|
|
||||||
"description": "Geben Sie den Code ein, der in der Bestätigungs-E-Mail angegeben ist.",
|
|
||||||
"userIdMissing": "Keine Benutzer-ID angegeben!",
|
"userIdMissing": "Keine Benutzer-ID angegeben!",
|
||||||
"resendCode": "Code erneut senden",
|
"verify": {
|
||||||
"submit": "Weiter"
|
"title": "Benutzer verifizieren",
|
||||||
|
"description": "Geben Sie den Code ein, der in der Bestätigungs-E-Mail angegeben ist.",
|
||||||
|
"resendCode": "Code erneut senden",
|
||||||
|
"submit": "Weiter"
|
||||||
|
},
|
||||||
|
"setup": {
|
||||||
|
"title": "Authentifizierungsmethode auswählen",
|
||||||
|
"description": "Wählen Sie die Methode, mit der Sie sich authentifizieren möchten."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"unknownContext": "Der Kontext des Benutzers konnte nicht ermittelt werden. Stellen Sie sicher, dass Sie zuerst den Benutzernamen eingeben oder einen loginName als Suchparameter angeben.",
|
"unknownContext": "Der Kontext des Benutzers konnte nicht ermittelt werden. Stellen Sie sicher, dass Sie zuerst den Benutzernamen eingeben oder einen loginName als Suchparameter angeben.",
|
||||||
|
|||||||
@@ -139,6 +139,7 @@
|
|||||||
"title": "Invite User",
|
"title": "Invite User",
|
||||||
"description": "Provide the email address and the name of the user you want to invite.",
|
"description": "Provide the email address and the name of the user you want to invite.",
|
||||||
"info": "The user will receive an email with further instructions.",
|
"info": "The user will receive an email with further instructions.",
|
||||||
|
"notAllowed": "Your settings do not allow you to invite users.",
|
||||||
"submit": "Continue",
|
"submit": "Continue",
|
||||||
"success": {
|
"success": {
|
||||||
"title": "User invited",
|
"title": "User invited",
|
||||||
@@ -153,11 +154,17 @@
|
|||||||
"description": "You are signed in."
|
"description": "You are signed in."
|
||||||
},
|
},
|
||||||
"verify": {
|
"verify": {
|
||||||
"title": "Verify user",
|
|
||||||
"description": "Enter the Code provided in the verification email.",
|
|
||||||
"userIdMissing": "No userId provided!",
|
"userIdMissing": "No userId provided!",
|
||||||
"resendCode": "Resend code",
|
"verify": {
|
||||||
"submit": "Continue"
|
"title": "Verify user",
|
||||||
|
"description": "Enter the Code provided in the verification email.",
|
||||||
|
"resendCode": "Resend code",
|
||||||
|
"submit": "Continue"
|
||||||
|
},
|
||||||
|
"setup": {
|
||||||
|
"title": "Choose authentication method",
|
||||||
|
"description": "Select the method you would like to authenticate"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"unknownContext": "Could not get the context of the user. Make sure to enter the username first or provide a loginName as searchParam.",
|
"unknownContext": "Could not get the context of the user. Make sure to enter the username first or provide a loginName as searchParam.",
|
||||||
|
|||||||
@@ -139,6 +139,7 @@
|
|||||||
"title": "Invitar usuario",
|
"title": "Invitar usuario",
|
||||||
"description": "Introduce el correo electrónico del usuario que deseas invitar.",
|
"description": "Introduce el correo electrónico del usuario que deseas invitar.",
|
||||||
"info": "El usuario recibirá un correo electrónico con un enlace para completar el registro.",
|
"info": "El usuario recibirá un correo electrónico con un enlace para completar el registro.",
|
||||||
|
"notAllowed": "No tienes permiso para invitar usuarios.",
|
||||||
"submit": "Invitar usuario",
|
"submit": "Invitar usuario",
|
||||||
"success": {
|
"success": {
|
||||||
"title": "¡Usuario invitado!",
|
"title": "¡Usuario invitado!",
|
||||||
@@ -153,11 +154,17 @@
|
|||||||
"description": "Has iniciado sesión."
|
"description": "Has iniciado sesión."
|
||||||
},
|
},
|
||||||
"verify": {
|
"verify": {
|
||||||
"title": "Verificar usuario",
|
|
||||||
"description": "Introduce el código proporcionado en el correo electrónico de verificación.",
|
|
||||||
"userIdMissing": "¡No se proporcionó userId!",
|
"userIdMissing": "¡No se proporcionó userId!",
|
||||||
"resendCode": "Reenviar código",
|
"verify": {
|
||||||
"submit": "Continuar"
|
"title": "Verificar usuario",
|
||||||
|
"description": "Introduce el código proporcionado en el correo electrónico de verificación.",
|
||||||
|
"resendCode": "Reenviar código",
|
||||||
|
"submit": "Continuar"
|
||||||
|
},
|
||||||
|
"setup": {
|
||||||
|
"title": "Seleccionar método de autenticación",
|
||||||
|
"description": "Selecciona el método con el que deseas autenticarte"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"unknownContext": "No se pudo obtener el contexto del usuario. Asegúrate de ingresar primero el nombre de usuario o proporcionar un loginName como parámetro de búsqueda.",
|
"unknownContext": "No se pudo obtener el contexto del usuario. Asegúrate de ingresar primero el nombre de usuario o proporcionar un loginName como parámetro de búsqueda.",
|
||||||
|
|||||||
@@ -139,6 +139,7 @@
|
|||||||
"title": "Invita Utente",
|
"title": "Invita Utente",
|
||||||
"description": "Inserisci l'indirizzo email dell'utente che desideri invitare.",
|
"description": "Inserisci l'indirizzo email dell'utente che desideri invitare.",
|
||||||
"info": "L'utente riceverà un'email con ulteriori istruzioni.",
|
"info": "L'utente riceverà un'email con ulteriori istruzioni.",
|
||||||
|
"notAllowed": "Non hai i permessi per invitare un utente.",
|
||||||
"submit": "Invita Utente",
|
"submit": "Invita Utente",
|
||||||
"success": {
|
"success": {
|
||||||
"title": "Invito inviato",
|
"title": "Invito inviato",
|
||||||
@@ -153,11 +154,17 @@
|
|||||||
"description": "Sei connesso."
|
"description": "Sei connesso."
|
||||||
},
|
},
|
||||||
"verify": {
|
"verify": {
|
||||||
"title": "Verifica utente",
|
|
||||||
"description": "Inserisci il codice fornito nell'email di verifica.",
|
|
||||||
"userIdMissing": "Nessun userId fornito!",
|
"userIdMissing": "Nessun userId fornito!",
|
||||||
"resendCode": "Invia di nuovo il codice",
|
"verify": {
|
||||||
"submit": "Continua"
|
"title": "Verifica utente",
|
||||||
|
"description": "Inserisci il codice fornito nell'email di verifica.",
|
||||||
|
"resendCode": "Invia di nuovo il codice",
|
||||||
|
"submit": "Continua"
|
||||||
|
},
|
||||||
|
"setup": {
|
||||||
|
"title": "Seleziona metodo di autenticazione",
|
||||||
|
"description": "Seleziona il metodo con cui desideri autenticarti"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"unknownContext": "Impossibile ottenere il contesto dell'utente. Assicurati di inserire prima il nome utente o di fornire un loginName come parametro di ricerca.",
|
"unknownContext": "Impossibile ottenere il contesto dell'utente. Assicurati di inserire prima il nome utente o di fornire un loginName come parametro di ricerca.",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
<svg width="100%" height="100%" viewBox="0 0 467 467" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
<svg width="100%" height="100%" viewBox="0 0 467 467" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;strokeLinejoin:round;stroke-miterlimit:2;">
|
||||||
<g id="zitadel-logo-solo-darkdesign" transform="matrix(0.564847,0,0,0.659318,-1282.85,0)">
|
<g id="zitadel-logo-solo-darkdesign" transform="matrix(0.564847,0,0,0.659318,-1282.85,0)">
|
||||||
<rect x="2271.15" y="0" width="826.773" height="708.241" style="fill:none;"/>
|
<rect x="2271.15" y="0" width="826.773" height="708.241" style="fill:none;"/>
|
||||||
<g transform="matrix(4.96737,-1.14029,1.331,4.25561,-5923.46,-2258.26)">
|
<g transform="matrix(4.96737,-1.14029,1.331,4.25561,-5923.46,-2258.26)">
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 8.1 KiB |
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
<svg width="100%" height="100%" viewBox="0 0 467 468" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
<svg width="100%" height="100%" viewBox="0 0 467 468" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;strokeLinejoin:round;stroke-miterlimit:2;">
|
||||||
<g transform="matrix(1,0,0,1,0,-492)">
|
<g transform="matrix(1,0,0,1,0,-492)">
|
||||||
<g id="zitadel-logo-solo-lightdesign" transform="matrix(0.564847,0,0,0.659318,-1282.85,492.925)">
|
<g id="zitadel-logo-solo-lightdesign" transform="matrix(0.564847,0,0,0.659318,-1282.85,492.925)">
|
||||||
<rect x="2271.15" y="0" width="826.773" height="708.241" style="fill:none;"/>
|
<rect x="2271.15" y="0" width="826.773" height="708.241" style="fill:none;"/>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.5 KiB |
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
<svg width="100%" height="100%" viewBox="0 0 295 81" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
<svg width="100%" height="100%" viewBox="0 0 295 81" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;strokeLinejoin:round;stroke-miterlimit:2;">
|
||||||
<g transform="matrix(1,0,0,1,0,-107)">
|
<g transform="matrix(1,0,0,1,0,-107)">
|
||||||
<g id="zitadel-logo-dark" transform="matrix(1,0,0,1,-20.9181,18.2562)">
|
<g id="zitadel-logo-dark" transform="matrix(1,0,0,1,-20.9181,18.2562)">
|
||||||
<rect x="20.918" y="89.57" width="294.943" height="79.632" style="fill:none;"/>
|
<rect x="20.918" y="89.57" width="294.943" height="79.632" style="fill:none;"/>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
<svg width="100%" height="100%" viewBox="0 0 295 80" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
<svg width="100%" height="100%" viewBox="0 0 295 80" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;strokeLinejoin:round;stroke-miterlimit:2;">
|
||||||
<g id="zitadel-logo-light" transform="matrix(1,0,0,1,-20.9181,-89.5699)">
|
<g id="zitadel-logo-light" transform="matrix(1,0,0,1,-20.9181,-89.5699)">
|
||||||
<rect x="20.918" y="89.57" width="294.943" height="79.632" style="fill:none;"/>
|
<rect x="20.918" y="89.57" width="294.943" height="79.632" style="fill:none;"/>
|
||||||
<g transform="matrix(2.73883,0,0,1.55076,-35267,23.6366)">
|
<g transform="matrix(2.73883,0,0,1.55076,-35267,23.6366)">
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
@@ -4,6 +4,7 @@ import { InviteForm } from "@/components/invite-form";
|
|||||||
import {
|
import {
|
||||||
getBrandingSettings,
|
getBrandingSettings,
|
||||||
getDefaultOrg,
|
getDefaultOrg,
|
||||||
|
getLoginSettings,
|
||||||
getPasswordComplexitySettings,
|
getPasswordComplexitySettings,
|
||||||
} from "@/lib/zitadel";
|
} from "@/lib/zitadel";
|
||||||
import { getLocale, getTranslations } from "next-intl/server";
|
import { getLocale, getTranslations } from "next-intl/server";
|
||||||
@@ -27,6 +28,8 @@ export default async function Page({
|
|||||||
organization = org.id;
|
organization = org.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loginSettings = await getLoginSettings(organization);
|
||||||
|
|
||||||
const passwordComplexitySettings =
|
const passwordComplexitySettings =
|
||||||
await getPasswordComplexitySettings(organization);
|
await getPasswordComplexitySettings(organization);
|
||||||
|
|
||||||
@@ -38,9 +41,13 @@ export default async function Page({
|
|||||||
<h1>{t("title")}</h1>
|
<h1>{t("title")}</h1>
|
||||||
<p className="ztdl-p">{t("description")}</p>
|
<p className="ztdl-p">{t("description")}</p>
|
||||||
|
|
||||||
<Alert type={AlertType.INFO}>{t("info")}</Alert>
|
{!loginSettings?.allowRegister ? (
|
||||||
|
<Alert type={AlertType.ALERT}>{t("notAllowed")}</Alert>
|
||||||
|
) : (
|
||||||
|
<Alert type={AlertType.INFO}>{t("info")}</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
{passwordComplexitySettings && (
|
{passwordComplexitySettings && loginSettings?.allowRegister && (
|
||||||
<InviteForm
|
<InviteForm
|
||||||
organization={organization}
|
organization={organization}
|
||||||
firstname={firstname}
|
firstname={firstname}
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
|
import { Alert } from "@/components/alert";
|
||||||
|
import { AuthenticatorMethods } from "@/components/authenticator-methods";
|
||||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||||
import { VerifyEmailForm } from "@/components/verify-email-form";
|
import { UserAvatar } from "@/components/user-avatar";
|
||||||
import { getBrandingSettings, getLoginSettings } from "@/lib/zitadel";
|
import { VerifyForm } from "@/components/verify-form";
|
||||||
|
import { verifyUser } from "@/lib/server/email";
|
||||||
|
import { getBrandingSettings, getUserByID } from "@/lib/zitadel";
|
||||||
|
import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb";
|
||||||
import { getLocale, getTranslations } from "next-intl/server";
|
import { getLocale, getTranslations } from "next-intl/server";
|
||||||
|
|
||||||
export default async function Page({ searchParams }: { searchParams: any }) {
|
export default async function Page({ searchParams }: { searchParams: any }) {
|
||||||
@@ -20,21 +25,86 @@ export default async function Page({ searchParams }: { searchParams: any }) {
|
|||||||
|
|
||||||
const branding = await getBrandingSettings(organization);
|
const branding = await getBrandingSettings(organization);
|
||||||
|
|
||||||
const loginSettings = await getLoginSettings(organization);
|
let verifyResponse, error;
|
||||||
|
if (code && userId) {
|
||||||
|
verifyResponse = await verifyUser({
|
||||||
|
code,
|
||||||
|
userId,
|
||||||
|
isInvite: invite === "true",
|
||||||
|
}).catch(() => {
|
||||||
|
error = "Could not verify user";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let user: User | undefined;
|
||||||
|
let human: HumanUser | undefined;
|
||||||
|
if (userId) {
|
||||||
|
const userResponse = await getUserByID(userId);
|
||||||
|
if (userResponse) {
|
||||||
|
user = userResponse.user;
|
||||||
|
if (user?.type.case === "human") {
|
||||||
|
human = user.type.value as HumanUser;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
params.set("organization", organization);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authRequestId) {
|
||||||
|
params.set("authRequest", authRequestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sessionId) {
|
||||||
|
params.set("sessionId", sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DynamicTheme branding={branding}>
|
<DynamicTheme branding={branding}>
|
||||||
<div className="flex flex-col items-center space-y-4">
|
<div className="flex flex-col items-center space-y-4">
|
||||||
<VerifyEmailForm
|
{!userId && (
|
||||||
userId={userId}
|
<>
|
||||||
loginName={loginName}
|
<h1>{t("verify.title")}</h1>
|
||||||
code={code}
|
<p className="ztdl-p mb-6 block">{t("verify.description")}</p>
|
||||||
organization={organization}
|
|
||||||
authRequestId={authRequestId}
|
<div className="py-4">
|
||||||
sessionId={sessionId}
|
<Alert>{tError("unknownContext")}</Alert>
|
||||||
loginSettings={loginSettings}
|
</div>
|
||||||
isInvite={invite === "true"}
|
</>
|
||||||
/>
|
)}
|
||||||
|
{!verifyResponse || !verifyResponse.authMethodTypes ? (
|
||||||
|
<VerifyForm
|
||||||
|
userId={userId}
|
||||||
|
loginName={loginName}
|
||||||
|
code={!error ? code : ""}
|
||||||
|
organization={organization}
|
||||||
|
authRequestId={authRequestId}
|
||||||
|
sessionId={sessionId}
|
||||||
|
isInvite={invite === "true"}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<h1>{t("setup.title")}</h1>
|
||||||
|
<p className="ztdl-p mb-6 block">{t("setup.description")}</p>
|
||||||
|
{user && (
|
||||||
|
<UserAvatar
|
||||||
|
loginName={user.preferredLoginName}
|
||||||
|
displayName={human?.profile?.displayName}
|
||||||
|
showDropdown={false}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<AuthenticatorMethods
|
||||||
|
authMethods={verifyResponse.authMethodTypes}
|
||||||
|
params={params}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</DynamicTheme>
|
</DynamicTheme>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ export const EMAIL = (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"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
@@ -174,12 +174,12 @@ export const SMS = (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"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
stroke-linecap="round"
|
strokeLinecap="round"
|
||||||
stroke-linejoin="round"
|
strokeLinejoin="round"
|
||||||
d="M10.5 1.5H8.25A2.25 2.25 0 006 3.75v16.5a2.25 2.25 0 002.25 2.25h7.5A2.25 2.25 0 0018 20.25V3.75a2.25 2.25 0 00-2.25-2.25H13.5m-3 0V3h3V1.5m-3 0h3m-3 18.75h3"
|
d="M10.5 1.5H8.25A2.25 2.25 0 006 3.75v16.5a2.25 2.25 0 002.25 2.25h7.5A2.25 2.25 0 0018 20.25V3.75a2.25 2.25 0 00-2.25-2.25H13.5m-3 0V3h3V1.5m-3 0h3m-3 18.75h3"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -207,13 +207,13 @@ export const PASSKEYS = (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>
|
||||||
|
|||||||
18
apps/login/src/components/authenticator-methods.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||||
|
import { PASSKEYS, PASSWORD } from "./auth-methods";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
authMethods: AuthenticationMethodType[];
|
||||||
|
params: URLSearchParams;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function AuthenticatorMethods({ authMethods, params }: Props) {
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-1 gap-5 w-full pt-4">
|
||||||
|
{!authMethods.includes(AuthenticationMethodType.PASSWORD) &&
|
||||||
|
PASSWORD(false, "/password/set?" + params)}
|
||||||
|
{!authMethods.includes(AuthenticationMethodType.PASSKEY) &&
|
||||||
|
PASSKEYS(false, "/passkeys/set?" + params)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -77,6 +77,8 @@ export function ChangePasswordForm({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000)); // wait for a second, to prevent eventual consistency issues
|
||||||
|
|
||||||
const passwordResponse = await sendPassword({
|
const passwordResponse = await sendPassword({
|
||||||
loginName,
|
loginName,
|
||||||
organization,
|
organization,
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export function SetPasswordForm({
|
|||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
const [error, setError] = useState<string>("");
|
const [error, setError] = useState<string>("");
|
||||||
|
|
||||||
async function submitRegister(values: Inputs) {
|
async function submitPassword(values: Inputs) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
let payload: { userId: string; password: string; code?: string } = {
|
let payload: { userId: string; password: string; code?: string } = {
|
||||||
userId: userId,
|
userId: userId,
|
||||||
@@ -73,16 +73,19 @@ export function SetPasswordForm({
|
|||||||
|
|
||||||
const changeResponse = await changePassword(payload).catch(() => {
|
const changeResponse = await changePassword(payload).catch(() => {
|
||||||
setError("Could not set password");
|
setError("Could not set password");
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (changeResponse && "error" in changeResponse) {
|
|
||||||
setError(changeResponse.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|
||||||
|
if (changeResponse && "error" in changeResponse) {
|
||||||
|
setError(changeResponse.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!changeResponse) {
|
if (!changeResponse) {
|
||||||
setError("Could not register user");
|
setError("Could not set password");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,6 +98,8 @@ export function SetPasswordForm({
|
|||||||
params.append("organization", organization);
|
params.append("organization", organization);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 2000)); // Wait for a second to avoid eventual consistency issues with an initial password being set
|
||||||
|
|
||||||
const passwordResponse = await sendPassword({
|
const passwordResponse = await sendPassword({
|
||||||
loginName,
|
loginName,
|
||||||
organization,
|
organization,
|
||||||
@@ -213,7 +218,7 @@ export function SetPasswordForm({
|
|||||||
!formState.isValid ||
|
!formState.isValid ||
|
||||||
watchPassword !== watchConfirmPassword
|
watchPassword !== watchConfirmPassword
|
||||||
}
|
}
|
||||||
onClick={handleSubmit(submitRegister)}
|
onClick={handleSubmit(submitPassword)}
|
||||||
>
|
>
|
||||||
{loading && <Spinner className="h-5 w-5 mr-2" />}
|
{loading && <Spinner className="h-5 w-5 mr-2" />}
|
||||||
{t("set.submit")}
|
{t("set.submit")}
|
||||||
|
|||||||
@@ -2,13 +2,12 @@
|
|||||||
|
|
||||||
import { Alert } from "@/components/alert";
|
import { Alert } from "@/components/alert";
|
||||||
import { resendVerification, verifyUser } from "@/lib/server/email";
|
import { resendVerification, verifyUser } from "@/lib/server/email";
|
||||||
import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
|
||||||
import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useEffect, useState } from "react";
|
import { useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { PASSKEYS, PASSWORD } from "./auth-methods";
|
import { AuthenticatorMethods } from "./authenticator-methods";
|
||||||
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";
|
||||||
@@ -18,25 +17,25 @@ type Inputs = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
userId?: string;
|
userId: string;
|
||||||
loginName: string;
|
loginName: string;
|
||||||
code: string;
|
code: string;
|
||||||
organization?: string;
|
organization?: string;
|
||||||
authRequestId?: string;
|
authRequestId?: string;
|
||||||
sessionId?: string;
|
sessionId?: string;
|
||||||
loginSettings?: LoginSettings;
|
|
||||||
isInvite: boolean;
|
isInvite: boolean;
|
||||||
|
verifyError?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function VerifyEmailForm({
|
export function VerifyForm({
|
||||||
userId,
|
userId,
|
||||||
loginName,
|
loginName,
|
||||||
code,
|
code,
|
||||||
organization,
|
organization,
|
||||||
authRequestId,
|
authRequestId,
|
||||||
sessionId,
|
sessionId,
|
||||||
loginSettings,
|
|
||||||
isInvite,
|
isInvite,
|
||||||
|
verifyError,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const t = useTranslations("verify");
|
const t = useTranslations("verify");
|
||||||
const tError = useTranslations("error");
|
const tError = useTranslations("error");
|
||||||
@@ -52,30 +51,19 @@ export function VerifyEmailForm({
|
|||||||
AuthenticationMethodType[] | null
|
AuthenticationMethodType[] | null
|
||||||
>(null);
|
>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
const [error, setError] = useState<string>(verifyError || "");
|
||||||
if (code && userId) {
|
|
||||||
// When we navigate to this page, we always want to be redirected if submit is true and the parameters are valid.
|
|
||||||
// For programmatic verification, the /verifyemail API should be used.
|
|
||||||
submitCodeAndContinue({ code });
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const [error, setError] = useState<string>("");
|
|
||||||
|
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const params = new URLSearchParams({});
|
const params = new URLSearchParams({
|
||||||
|
userId: userId,
|
||||||
if (userId) {
|
});
|
||||||
params.append("userId", userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isInvite) {
|
if (isInvite) {
|
||||||
params.append("initial", "true");
|
params.append("initial", "true");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loginName) {
|
if (loginName) {
|
||||||
params.append("loginName", loginName);
|
params.append("loginName", loginName);
|
||||||
}
|
}
|
||||||
@@ -131,13 +119,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
|
initial: "true", // defines that a code is not required and is therefore not shown in the UI
|
||||||
});
|
});
|
||||||
|
|
||||||
if (userId) {
|
|
||||||
params.set("userId", userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (organization) {
|
if (organization) {
|
||||||
params.set("organization", organization);
|
params.set("organization", organization);
|
||||||
}
|
}
|
||||||
@@ -153,14 +138,8 @@ export function VerifyEmailForm({
|
|||||||
|
|
||||||
return !authMethods ? (
|
return !authMethods ? (
|
||||||
<>
|
<>
|
||||||
<h1>{t("title")}</h1>
|
<h1>{t("verify.title")}</h1>
|
||||||
<p className="ztdl-p mb-6 block">{t("description")}</p>
|
<p className="ztdl-p mb-6 block">{t("verify.description")}</p>
|
||||||
|
|
||||||
{!userId && (
|
|
||||||
<div className="py-4">
|
|
||||||
<Alert>{tError("unknownContext")}</Alert>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<form className="w-full">
|
<form className="w-full">
|
||||||
<div className="">
|
<div className="">
|
||||||
@@ -169,7 +148,6 @@ export function VerifyEmailForm({
|
|||||||
autoComplete="one-time-code"
|
autoComplete="one-time-code"
|
||||||
{...register("code", { required: "This field is required" })}
|
{...register("code", { required: "This field is required" })}
|
||||||
label="Code"
|
label="Code"
|
||||||
// error={errors.username?.message as string}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -185,7 +163,7 @@ export function VerifyEmailForm({
|
|||||||
onClick={() => resendCode()}
|
onClick={() => resendCode()}
|
||||||
variant={ButtonVariants.Secondary}
|
variant={ButtonVariants.Secondary}
|
||||||
>
|
>
|
||||||
{t("resendCode")}
|
{t("verify.resendCode")}
|
||||||
</Button>
|
</Button>
|
||||||
<span className="flex-grow"></span>
|
<span className="flex-grow"></span>
|
||||||
<Button
|
<Button
|
||||||
@@ -196,22 +174,17 @@ export function VerifyEmailForm({
|
|||||||
onClick={handleSubmit(submitCodeAndContinue)}
|
onClick={handleSubmit(submitCodeAndContinue)}
|
||||||
>
|
>
|
||||||
{loading && <Spinner className="h-5 w-5 mr-2" />}
|
{loading && <Spinner className="h-5 w-5 mr-2" />}
|
||||||
{t("submit")}
|
{t("verify.submit")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<h1>{t("title")}</h1>
|
<h1>{t("setup.title")}</h1>
|
||||||
<p className="ztdl-p mb-6 block">{t("description")}</p>
|
<p className="ztdl-p mb-6 block">{t("setup.description")}</p>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 gap-5 w-full pt-4">
|
<AuthenticatorMethods authMethods={authMethods} params={params} />
|
||||||
{!authMethods.includes(AuthenticationMethodType.PASSWORD) &&
|
|
||||||
PASSWORD(false, "/password/set?" + params)}
|
|
||||||
{!authMethods.includes(AuthenticationMethodType.PASSKEY) &&
|
|
||||||
PASSKEYS(false, "/passkeys/set?" + params)}
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
getSession,
|
getSession,
|
||||||
setSession,
|
setSession,
|
||||||
} from "@/lib/zitadel";
|
} from "@/lib/zitadel";
|
||||||
import { timestampDate } from "@zitadel/client";
|
import { timestampMs } from "@zitadel/client";
|
||||||
import {
|
import {
|
||||||
Challenges,
|
Challenges,
|
||||||
RequestChallenges,
|
RequestChallenges,
|
||||||
@@ -43,13 +43,13 @@ export async function createSessionAndUpdateCookie(
|
|||||||
id: createdSession.sessionId,
|
id: createdSession.sessionId,
|
||||||
token: createdSession.sessionToken,
|
token: createdSession.sessionToken,
|
||||||
creationDate: response.session.creationDate
|
creationDate: response.session.creationDate
|
||||||
? `${timestampDate(response.session.creationDate).toDateString()}`
|
? `${timestampMs(response.session.creationDate)}`
|
||||||
: "",
|
: "",
|
||||||
expirationDate: response.session.expirationDate
|
expirationDate: response.session.expirationDate
|
||||||
? `${timestampDate(response.session.expirationDate).toDateString()}`
|
? `${timestampMs(response.session.expirationDate)}`
|
||||||
: "",
|
: "",
|
||||||
changeDate: response.session.changeDate
|
changeDate: response.session.changeDate
|
||||||
? `${timestampDate(response.session.changeDate).toDateString()}`
|
? `${timestampMs(response.session.changeDate)}`
|
||||||
: "",
|
: "",
|
||||||
loginName: response.session.factors.user.loginName ?? "",
|
loginName: response.session.factors.user.loginName ?? "",
|
||||||
};
|
};
|
||||||
@@ -98,13 +98,13 @@ export async function createSessionForIdpAndUpdateCookie(
|
|||||||
id: createdSession.sessionId,
|
id: createdSession.sessionId,
|
||||||
token: createdSession.sessionToken,
|
token: createdSession.sessionToken,
|
||||||
creationDate: response.session.creationDate
|
creationDate: response.session.creationDate
|
||||||
? `${timestampDate(response.session.creationDate).toDateString()}`
|
? `${timestampMs(response.session.creationDate)}`
|
||||||
: "",
|
: "",
|
||||||
expirationDate: response.session.expirationDate
|
expirationDate: response.session.expirationDate
|
||||||
? `${timestampDate(response.session.expirationDate).toDateString()}`
|
? `${timestampMs(response.session.expirationDate)}`
|
||||||
: "",
|
: "",
|
||||||
changeDate: response.session.changeDate
|
changeDate: response.session.changeDate
|
||||||
? `${timestampDate(response.session.changeDate).toDateString()}`
|
? `${timestampMs(response.session.changeDate)}`
|
||||||
: "",
|
: "",
|
||||||
loginName: response.session.factors.user.loginName ?? "",
|
loginName: response.session.factors.user.loginName ?? "",
|
||||||
organization: response.session.factors.user.organizationId ?? "",
|
organization: response.session.factors.user.organizationId ?? "",
|
||||||
@@ -155,7 +155,7 @@ export async function setSessionAndUpdateCookie(
|
|||||||
expirationDate: recentCookie.expirationDate,
|
expirationDate: recentCookie.expirationDate,
|
||||||
// just overwrite the changeDate with the new one
|
// just overwrite the changeDate with the new one
|
||||||
changeDate: updatedSession.details?.changeDate
|
changeDate: updatedSession.details?.changeDate
|
||||||
? `${timestampDate(updatedSession.details.changeDate).toDateString()}`
|
? `${timestampMs(updatedSession.details.changeDate)}`
|
||||||
: "",
|
: "",
|
||||||
loginName: recentCookie.loginName,
|
loginName: recentCookie.loginName,
|
||||||
organization: recentCookie.organization,
|
organization: recentCookie.organization,
|
||||||
@@ -178,7 +178,7 @@ export async function setSessionAndUpdateCookie(
|
|||||||
expirationDate: sessionCookie.expirationDate,
|
expirationDate: sessionCookie.expirationDate,
|
||||||
// just overwrite the changeDate with the new one
|
// just overwrite the changeDate with the new one
|
||||||
changeDate: updatedSession.details?.changeDate
|
changeDate: updatedSession.details?.changeDate
|
||||||
? `${timestampDate(updatedSession.details.changeDate).toDateString()}`
|
? `${timestampMs(updatedSession.details.changeDate)}`
|
||||||
: "",
|
: "",
|
||||||
loginName: session.factors?.user?.loginName ?? "",
|
loginName: session.factors?.user?.loginName ?? "",
|
||||||
organization: session.factors?.user?.organizationId ?? "",
|
organization: session.factors?.user?.organizationId ?? "",
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
|||||||
if (users.result[0].state === UserState.INITIAL) {
|
if (users.result[0].state === UserState.INITIAL) {
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
loginName: session.factors?.user?.loginName,
|
loginName: session.factors?.user?.loginName,
|
||||||
|
initial: "true", // this does not require a code to be set
|
||||||
});
|
});
|
||||||
|
|
||||||
if (command.organization || session.factors?.user?.organizationId) {
|
if (command.organization || session.factors?.user?.organizationId) {
|
||||||
|
|||||||
@@ -284,5 +284,5 @@ export async function changePassword(command: {
|
|||||||
}
|
}
|
||||||
const userId = user.userId;
|
const userId = user.userId;
|
||||||
|
|
||||||
return setPassword(userId, command.password, command.code);
|
return setPassword(userId, command.password, user, command.code);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,11 @@ import {
|
|||||||
SearchQuery,
|
SearchQuery,
|
||||||
SearchQuerySchema,
|
SearchQuerySchema,
|
||||||
} from "@zitadel/proto/zitadel/user/v2/query_pb";
|
} from "@zitadel/proto/zitadel/user/v2/query_pb";
|
||||||
import { SendInviteCodeSchema } from "@zitadel/proto/zitadel/user/v2/user_pb";
|
import {
|
||||||
|
SendInviteCodeSchema,
|
||||||
|
User,
|
||||||
|
UserState,
|
||||||
|
} from "@zitadel/proto/zitadel/user/v2/user_pb";
|
||||||
import { unstable_cache } from "next/cache";
|
import { unstable_cache } from "next/cache";
|
||||||
import { PROVIDER_MAPPING } from "./idp";
|
import { PROVIDER_MAPPING } from "./idp";
|
||||||
|
|
||||||
@@ -327,7 +331,7 @@ export async function createInviteCode(userId: string, host: string | null) {
|
|||||||
if (host) {
|
if (host) {
|
||||||
medium = {
|
medium = {
|
||||||
...medium,
|
...medium,
|
||||||
urlTemplate: `https://${host}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}&invite=true`,
|
urlTemplate: `${host.includes("localhost") ? "http://" : "https://"}${host}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}&invite=true`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -564,7 +568,7 @@ export async function passwordReset(userId: string, host: string | null) {
|
|||||||
if (host) {
|
if (host) {
|
||||||
medium = {
|
medium = {
|
||||||
...medium,
|
...medium,
|
||||||
urlTemplate: `https://${host}/password/set?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}`,
|
urlTemplate: `${host.includes("localhost") ? "http://" : "https://"}${host}/password/set?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -590,6 +594,7 @@ export async function passwordReset(userId: string, host: string | null) {
|
|||||||
export async function setPassword(
|
export async function setPassword(
|
||||||
userId: string,
|
userId: string,
|
||||||
password: string,
|
password: string,
|
||||||
|
user: User,
|
||||||
code?: string,
|
code?: string,
|
||||||
) {
|
) {
|
||||||
let payload = create(SetPasswordRequestSchema, {
|
let payload = create(SetPasswordRequestSchema, {
|
||||||
@@ -605,9 +610,8 @@ export async function setPassword(
|
|||||||
|
|
||||||
// if the user has no authmethods set, we can set a password otherwise we need a code
|
// if the user has no authmethods set, we can set a password otherwise we need a code
|
||||||
if (
|
if (
|
||||||
!authmethods ||
|
!(authmethods.authMethodTypes.length === 0) &&
|
||||||
!authmethods.authMethodTypes ||
|
user.state !== UserState.INITIAL
|
||||||
authmethods.authMethodTypes.length === 0
|
|
||||||
) {
|
) {
|
||||||
return { error: "Provide a code to set a password" };
|
return { error: "Provide a code to set a password" };
|
||||||
}
|
}
|
||||||
@@ -623,7 +627,14 @@ export async function setPassword(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return userService.setPassword(payload, {});
|
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: error.message };
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ export { NewAuthorizationBearerInterceptor } from "./interceptors";
|
|||||||
|
|
||||||
// TODO: Move this to `./protobuf.ts` and export it from there
|
// TODO: Move this to `./protobuf.ts` and export it from there
|
||||||
export { create, fromJson, toJson } from "@bufbuild/protobuf";
|
export { create, fromJson, toJson } from "@bufbuild/protobuf";
|
||||||
export { TimestampSchema, timestampDate, timestampFromDate } from "@bufbuild/protobuf/wkt";
|
export { TimestampSchema, timestampDate, timestampFromDate, timestampMs } from "@bufbuild/protobuf/wkt";
|
||||||
export type { Timestamp } from "@bufbuild/protobuf/wkt";
|
export type { Timestamp } from "@bufbuild/protobuf/wkt";
|
||||||
|
|||||||