mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 20:27:32 +00:00
fix(login): default lifetime, show expiration on accounts page (#10297)
This PR fixes an issue where the password lifetime was not applied correctly in certain scenarios. It also improves the sessions page by providing more information about expiration and verification timestamps and a mobile layout for clearing sessions. <img width="506" height="760" alt="Screenshot 2025-07-22 at 08 56 14" src="https://github.com/user-attachments/assets/1e621ca2-206c-4931-b27d-9592eebc646e" /> Closes https://github.com/zitadel/typescript/issues/481
This commit is contained in:
@@ -6,7 +6,9 @@
|
|||||||
"title": "Konten",
|
"title": "Konten",
|
||||||
"description": "Wählen Sie das Konto aus, das Sie verwenden möchten.",
|
"description": "Wählen Sie das Konto aus, das Sie verwenden möchten.",
|
||||||
"addAnother": "Ein weiteres Konto hinzufügen",
|
"addAnother": "Ein weiteres Konto hinzufügen",
|
||||||
"noResults": "Keine Konten gefunden"
|
"noResults": "Keine Konten gefunden",
|
||||||
|
"verified": "verifiziert",
|
||||||
|
"expired": "abgelaufen"
|
||||||
},
|
},
|
||||||
"logout": {
|
"logout": {
|
||||||
"title": "Logout",
|
"title": "Logout",
|
||||||
|
@@ -6,7 +6,9 @@
|
|||||||
"title": "Accounts",
|
"title": "Accounts",
|
||||||
"description": "Select the account you want to use.",
|
"description": "Select the account you want to use.",
|
||||||
"addAnother": "Add another account",
|
"addAnother": "Add another account",
|
||||||
"noResults": "No accounts found"
|
"noResults": "No accounts found",
|
||||||
|
"verified": "verified",
|
||||||
|
"expired": "expired"
|
||||||
},
|
},
|
||||||
"logout": {
|
"logout": {
|
||||||
"title": "Logout",
|
"title": "Logout",
|
||||||
|
@@ -4,9 +4,11 @@
|
|||||||
},
|
},
|
||||||
"accounts": {
|
"accounts": {
|
||||||
"title": "Cuentas",
|
"title": "Cuentas",
|
||||||
"description": "Selecciona la cuenta que deseas usar.",
|
"description": "Seleccione la cuenta que desea utilizar.",
|
||||||
"addAnother": "Agregar otra cuenta",
|
"addAnother": "Agregar otra cuenta",
|
||||||
"noResults": "No se encontraron cuentas"
|
"noResults": "No se encontraron cuentas",
|
||||||
|
"verified": "verificado",
|
||||||
|
"expired": "expirado"
|
||||||
},
|
},
|
||||||
"logout": {
|
"logout": {
|
||||||
"title": "Cerrar sesión",
|
"title": "Cerrar sesión",
|
||||||
|
@@ -4,9 +4,11 @@
|
|||||||
},
|
},
|
||||||
"accounts": {
|
"accounts": {
|
||||||
"title": "Account",
|
"title": "Account",
|
||||||
"description": "Seleziona l'account che desideri utilizzare.",
|
"description": "Seleziona l'account che vuoi utilizzare.",
|
||||||
"addAnother": "Aggiungi un altro account",
|
"addAnother": "Aggiungi un altro account",
|
||||||
"noResults": "Nessun account trovato"
|
"noResults": "Nessun account trovato",
|
||||||
|
"verified": "verificato",
|
||||||
|
"expired": "scaduto"
|
||||||
},
|
},
|
||||||
"logout": {
|
"logout": {
|
||||||
"title": "Esci",
|
"title": "Esci",
|
||||||
|
@@ -6,7 +6,9 @@
|
|||||||
"title": "Konta",
|
"title": "Konta",
|
||||||
"description": "Wybierz konto, którego chcesz użyć.",
|
"description": "Wybierz konto, którego chcesz użyć.",
|
||||||
"addAnother": "Dodaj kolejne konto",
|
"addAnother": "Dodaj kolejne konto",
|
||||||
"noResults": "Nie znaleziono kont"
|
"noResults": "Nie znaleziono kont",
|
||||||
|
"verified": "zweryfikowany",
|
||||||
|
"expired": "wygasł"
|
||||||
},
|
},
|
||||||
"logout": {
|
"logout": {
|
||||||
"title": "Wyloguj się",
|
"title": "Wyloguj się",
|
||||||
|
@@ -6,7 +6,9 @@
|
|||||||
"title": "Аккаунты",
|
"title": "Аккаунты",
|
||||||
"description": "Выберите аккаунт, который хотите использовать.",
|
"description": "Выберите аккаунт, который хотите использовать.",
|
||||||
"addAnother": "Добавить другой аккаунт",
|
"addAnother": "Добавить другой аккаунт",
|
||||||
"noResults": "Аккаунты не найдены"
|
"noResults": "Аккаунты не найдены",
|
||||||
|
"verified": "проверенный",
|
||||||
|
"expired": "истёк"
|
||||||
},
|
},
|
||||||
"logout": {
|
"logout": {
|
||||||
"title": "Выход",
|
"title": "Выход",
|
||||||
|
@@ -4,9 +4,11 @@
|
|||||||
},
|
},
|
||||||
"accounts": {
|
"accounts": {
|
||||||
"title": "账户",
|
"title": "账户",
|
||||||
"description": "选择您想使用的账户。",
|
"description": "选择您要使用的账户。",
|
||||||
"addAnother": "添加另一个账户",
|
"addAnother": "添加另一个账户",
|
||||||
"noResults": "未找到账户"
|
"noResults": "未找到账户",
|
||||||
|
"verified": "已验证",
|
||||||
|
"expired": "已过期"
|
||||||
},
|
},
|
||||||
"logout": {
|
"logout": {
|
||||||
"title": "注销",
|
"title": "注销",
|
||||||
|
@@ -25,6 +25,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/react": "^2.1.9",
|
"@headlessui/react": "^2.1.9",
|
||||||
"@heroicons/react": "2.1.3",
|
"@heroicons/react": "2.1.3",
|
||||||
|
"@radix-ui/react-tooltip": "^1.2.7",
|
||||||
"@tailwindcss/forms": "0.5.7",
|
"@tailwindcss/forms": "0.5.7",
|
||||||
"@vercel/analytics": "^1.2.2",
|
"@vercel/analytics": "^1.2.2",
|
||||||
"@zitadel/client": "latest",
|
"@zitadel/client": "latest",
|
||||||
|
@@ -15,12 +15,12 @@ import { headers } from "next/headers";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
async function loadSessions({ serviceUrl }: { serviceUrl: string }) {
|
async function loadSessions({ serviceUrl }: { serviceUrl: string }) {
|
||||||
const ids: (string | undefined)[] = await getAllSessionCookieIds();
|
const cookieIds = await getAllSessionCookieIds();
|
||||||
|
|
||||||
if (ids && ids.length) {
|
if (cookieIds && cookieIds.length) {
|
||||||
const response = await listSessions({
|
const response = await listSessions({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
ids: ids.filter((id) => !!id) as string[],
|
ids: cookieIds.filter((id) => !!id) as string[],
|
||||||
});
|
});
|
||||||
return response?.sessions ?? [];
|
return response?.sessions ?? [];
|
||||||
} else {
|
} else {
|
||||||
|
@@ -5,6 +5,7 @@ import { LanguageSwitcher } from "@/components/language-switcher";
|
|||||||
import { Skeleton } from "@/components/skeleton";
|
import { Skeleton } from "@/components/skeleton";
|
||||||
import { Theme } from "@/components/theme";
|
import { Theme } from "@/components/theme";
|
||||||
import { ThemeProvider } from "@/components/theme-provider";
|
import { ThemeProvider } from "@/components/theme-provider";
|
||||||
|
import * as Tooltip from "@radix-ui/react-tooltip";
|
||||||
import { Analytics } from "@vercel/analytics/react";
|
import { Analytics } from "@vercel/analytics/react";
|
||||||
import { Lato } from "next/font/google";
|
import { Lato } from "next/font/google";
|
||||||
import { ReactNode, Suspense } from "react";
|
import { ReactNode, Suspense } from "react";
|
||||||
@@ -24,6 +25,7 @@ export default async function RootLayout({
|
|||||||
<head />
|
<head />
|
||||||
<body>
|
<body>
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
|
<Tooltip.Provider>
|
||||||
<Suspense
|
<Suspense
|
||||||
fallback={
|
fallback={
|
||||||
<div
|
<div
|
||||||
@@ -54,6 +56,7 @@ export default async function RootLayout({
|
|||||||
</div>
|
</div>
|
||||||
</LanguageProvider>
|
</LanguageProvider>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
</Tooltip.Provider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
<Analytics />
|
<Analytics />
|
||||||
</body>
|
</body>
|
||||||
|
@@ -12,12 +12,12 @@ import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
|
|||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
|
|
||||||
async function loadSessions({ serviceUrl }: { serviceUrl: string }) {
|
async function loadSessions({ serviceUrl }: { serviceUrl: string }) {
|
||||||
const ids: (string | undefined)[] = await getAllSessionCookieIds();
|
const cookieIds = await getAllSessionCookieIds();
|
||||||
|
|
||||||
if (ids && ids.length) {
|
if (cookieIds && cookieIds.length) {
|
||||||
const response = await listSessions({
|
const response = await listSessions({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
ids: ids.filter((id) => !!id) as string[],
|
ids: cookieIds.filter((id) => !!id) as string[],
|
||||||
});
|
});
|
||||||
return response?.sessions ?? [];
|
return response?.sessions ?? [];
|
||||||
} else {
|
} else {
|
||||||
|
@@ -41,17 +41,13 @@ export default async function Page(props: {
|
|||||||
const { method } = params;
|
const { method } = params;
|
||||||
|
|
||||||
const session = sessionId
|
const session = sessionId
|
||||||
? await loadSessionById(serviceUrl, sessionId, organization)
|
? await loadSessionById(sessionId, organization)
|
||||||
: await loadMostRecentSession({
|
: await loadMostRecentSession({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
sessionParams: { loginName, organization },
|
sessionParams: { loginName, organization },
|
||||||
});
|
});
|
||||||
|
|
||||||
async function loadSessionById(
|
async function loadSessionById(sessionId: string, organization?: string) {
|
||||||
host: string,
|
|
||||||
sessionId: string,
|
|
||||||
organization?: string,
|
|
||||||
) {
|
|
||||||
const recent = await getSessionCookieById({ sessionId, organization });
|
const recent = await getSessionCookieById({ sessionId, organization });
|
||||||
return getSession({
|
return getSession({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
@@ -66,7 +66,6 @@ export default async function Page(props: {
|
|||||||
error = err;
|
error = err;
|
||||||
});
|
});
|
||||||
} else if (method === "sms") {
|
} else if (method === "sms") {
|
||||||
// does not work
|
|
||||||
await addOTPSMS({
|
await addOTPSMS({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
userId: session.factors.user.id,
|
userId: session.factors.user.id,
|
||||||
@@ -74,7 +73,6 @@ export default async function Page(props: {
|
|||||||
error = new Error("Could not add OTP via SMS");
|
error = new Error("Could not add OTP via SMS");
|
||||||
});
|
});
|
||||||
} else if (method === "email") {
|
} else if (method === "email") {
|
||||||
// works
|
|
||||||
await addOTPEmail({
|
await addOTPEmail({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
userId: session.factors.user.id,
|
userId: session.factors.user.id,
|
||||||
@@ -106,6 +104,7 @@ export default async function Page(props: {
|
|||||||
paramsToContinue.append("requestId", requestId);
|
paramsToContinue.append("requestId", requestId);
|
||||||
}
|
}
|
||||||
urlToContinue = `/otp/${method}?` + paramsToContinue;
|
urlToContinue = `/otp/${method}?` + paramsToContinue;
|
||||||
|
|
||||||
// immediately check the OTP on the next page if sms or email was set up
|
// immediately check the OTP on the next page if sms or email was set up
|
||||||
if (["email", "sms"].includes(method)) {
|
if (["email", "sms"].includes(method)) {
|
||||||
return redirect(urlToContinue);
|
return redirect(urlToContinue);
|
||||||
|
@@ -3,6 +3,7 @@
|
|||||||
import { sendLoginname } from "@/lib/server/loginname";
|
import { sendLoginname } from "@/lib/server/loginname";
|
||||||
import { clearSession, continueWithSession } from "@/lib/server/session";
|
import { clearSession, continueWithSession } from "@/lib/server/session";
|
||||||
import { XCircleIcon } from "@heroicons/react/24/outline";
|
import { XCircleIcon } from "@heroicons/react/24/outline";
|
||||||
|
import * as Tooltip from "@radix-ui/react-tooltip";
|
||||||
import { Timestamp, timestampDate } from "@zitadel/client";
|
import { Timestamp, timestampDate } from "@zitadel/client";
|
||||||
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
@@ -10,6 +11,7 @@ import { useLocale } from "next-intl";
|
|||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Avatar } from "./avatar";
|
import { Avatar } from "./avatar";
|
||||||
|
import { Translated } from "./translated";
|
||||||
|
|
||||||
export function isSessionValid(session: Partial<Session>): {
|
export function isSessionValid(session: Partial<Session>): {
|
||||||
valid: boolean;
|
valid: boolean;
|
||||||
@@ -66,6 +68,8 @@ export function SessionItem({
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<Tooltip.Root delayDuration={300}>
|
||||||
|
<Tooltip.Trigger asChild>
|
||||||
<button
|
<button
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
if (valid && session?.factors?.user) {
|
if (valid && session?.factors?.user) {
|
||||||
@@ -119,12 +123,13 @@ export function SessionItem({
|
|||||||
</span>
|
</span>
|
||||||
{valid ? (
|
{valid ? (
|
||||||
<span className="text-ellipsis text-xs opacity-80">
|
<span className="text-ellipsis text-xs opacity-80">
|
||||||
|
<Translated i18nKey="verified" namespace="accounts" />{" "}
|
||||||
{verifiedAt && moment(timestampDate(verifiedAt)).fromNow()}
|
{verifiedAt && moment(timestampDate(verifiedAt)).fromNow()}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
verifiedAt && (
|
verifiedAt && (
|
||||||
<span className="text-ellipsis text-xs opacity-80">
|
<span className="text-ellipsis text-xs opacity-80">
|
||||||
expired{" "}
|
<Translated i18nKey="expired" namespace="accounts" />{" "}
|
||||||
{session.expirationDate &&
|
{session.expirationDate &&
|
||||||
moment(timestampDate(session.expirationDate)).fromNow()}
|
moment(timestampDate(session.expirationDate)).fromNow()}
|
||||||
</span>
|
</span>
|
||||||
@@ -135,13 +140,13 @@ export function SessionItem({
|
|||||||
<span className="flex-grow"></span>
|
<span className="flex-grow"></span>
|
||||||
<div className="relative flex flex-row items-center">
|
<div className="relative flex flex-row items-center">
|
||||||
{valid ? (
|
{valid ? (
|
||||||
<div className="absolute right-0 mx-2 h-2 w-2 transform rounded-full bg-green-500 transition-all group-hover:right-6"></div>
|
<div className="absolute right-6 mx-2 h-2 w-2 transform rounded-full bg-green-500 transition-all group-hover:right-6 sm:right-0"></div>
|
||||||
) : (
|
) : (
|
||||||
<div className="absolute right-0 mx-2 h-2 w-2 transform rounded-full bg-red-500 transition-all group-hover:right-6"></div>
|
<div className="absolute right-6 mx-2 h-2 w-2 transform rounded-full bg-red-500 transition-all group-hover:right-6 sm:right-0"></div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<XCircleIcon
|
<XCircleIcon
|
||||||
className="hidden h-5 w-5 opacity-50 transition-all hover:opacity-100 group-hover:block"
|
className="h-5 w-5 opacity-50 transition-all hover:opacity-100 group-hover:block sm:hidden"
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
@@ -152,5 +157,18 @@ export function SessionItem({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
</Tooltip.Trigger>
|
||||||
|
{valid && session.expirationDate && (
|
||||||
|
<Tooltip.Portal>
|
||||||
|
<Tooltip.Content
|
||||||
|
className="z-50 select-none rounded-md border bg-background-light-500 px-3 py-2 text-xs text-black shadow-xl dark:border-white/20 dark:bg-background-dark-500 dark:text-white"
|
||||||
|
sideOffset={5}
|
||||||
|
>
|
||||||
|
Expires {moment(timestampDate(session.expirationDate)).fromNow()}
|
||||||
|
<Tooltip.Arrow className="fill-white dark:fill-white/20" />
|
||||||
|
</Tooltip.Content>
|
||||||
|
</Tooltip.Portal>
|
||||||
|
)}
|
||||||
|
</Tooltip.Root>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -242,7 +242,7 @@ export async function getSessionCookieByLoginName<T>({
|
|||||||
*/
|
*/
|
||||||
export async function getAllSessionCookieIds<T>(
|
export async function getAllSessionCookieIds<T>(
|
||||||
cleanup: boolean = false,
|
cleanup: boolean = false,
|
||||||
): Promise<any> {
|
): Promise<string[]> {
|
||||||
const cookiesList = await cookies();
|
const cookiesList = await cookies();
|
||||||
const stringifiedCookie = cookiesList.get("sessions");
|
const stringifiedCookie = cookiesList.get("sessions");
|
||||||
|
|
||||||
|
@@ -55,10 +55,21 @@ export async function createSessionAndUpdateCookie(command: {
|
|||||||
const _headers = await headers();
|
const _headers = await headers();
|
||||||
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
||||||
|
|
||||||
|
let sessionLifetime = command.lifetime;
|
||||||
|
|
||||||
|
if (!sessionLifetime) {
|
||||||
|
console.warn("No session lifetime provided, using default of 24 hours.");
|
||||||
|
|
||||||
|
sessionLifetime = {
|
||||||
|
seconds: BigInt(24 * 60 * 60), // 24 hours
|
||||||
|
nanos: 0,
|
||||||
|
} as Duration; // for usecases where the lifetime is not specified (user discovery)
|
||||||
|
}
|
||||||
|
|
||||||
const createdSession = await createSessionFromChecks({
|
const createdSession = await createSessionFromChecks({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
checks: command.checks,
|
checks: command.checks,
|
||||||
lifetime: command.lifetime,
|
lifetime: sessionLifetime,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (createdSession) {
|
if (createdSession) {
|
||||||
@@ -126,11 +137,24 @@ export async function createSessionForIdpAndUpdateCookie({
|
|||||||
const _headers = await headers();
|
const _headers = await headers();
|
||||||
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
||||||
|
|
||||||
|
let sessionLifetime = lifetime;
|
||||||
|
|
||||||
|
if (!sessionLifetime) {
|
||||||
|
console.warn(
|
||||||
|
"No IDP session lifetime provided, using default of 24 hours.",
|
||||||
|
);
|
||||||
|
|
||||||
|
sessionLifetime = {
|
||||||
|
seconds: BigInt(24 * 60 * 60), // 24 hours
|
||||||
|
nanos: 0,
|
||||||
|
} as Duration;
|
||||||
|
}
|
||||||
|
|
||||||
const createdSession = await createSessionForUserIdAndIdpIntent({
|
const createdSession = await createSessionForUserIdAndIdpIntent({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
userId,
|
userId,
|
||||||
idpIntent,
|
idpIntent,
|
||||||
lifetime,
|
lifetime: sessionLifetime,
|
||||||
}).catch((error: ErrorDetail | CredentialsCheckError) => {
|
}).catch((error: ErrorDetail | CredentialsCheckError) => {
|
||||||
console.error("Could not set session", error);
|
console.error("Could not set session", error);
|
||||||
if ("failedAttempts" in error && error.failedAttempts) {
|
if ("failedAttempts" in error && error.failedAttempts) {
|
||||||
@@ -190,41 +214,41 @@ export type SessionWithChallenges = Session & {
|
|||||||
challenges: Challenges | undefined;
|
challenges: Challenges | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function setSessionAndUpdateCookie(
|
export async function setSessionAndUpdateCookie(command: {
|
||||||
recentCookie: CustomCookieData,
|
recentCookie: CustomCookieData;
|
||||||
checks?: Checks,
|
checks?: Checks;
|
||||||
challenges?: RequestChallenges,
|
challenges?: RequestChallenges;
|
||||||
requestId?: string,
|
requestId?: string;
|
||||||
lifetime?: Duration,
|
lifetime: Duration;
|
||||||
) {
|
}) {
|
||||||
const _headers = await headers();
|
const _headers = await headers();
|
||||||
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
||||||
|
|
||||||
return setSession({
|
return setSession({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
sessionId: recentCookie.id,
|
sessionId: command.recentCookie.id,
|
||||||
sessionToken: recentCookie.token,
|
sessionToken: command.recentCookie.token,
|
||||||
challenges,
|
challenges: command.challenges,
|
||||||
checks,
|
checks: command.checks,
|
||||||
lifetime,
|
lifetime: command.lifetime,
|
||||||
})
|
})
|
||||||
.then((updatedSession) => {
|
.then((updatedSession) => {
|
||||||
if (updatedSession) {
|
if (updatedSession) {
|
||||||
const sessionCookie: CustomCookieData = {
|
const sessionCookie: CustomCookieData = {
|
||||||
id: recentCookie.id,
|
id: command.recentCookie.id,
|
||||||
token: updatedSession.sessionToken,
|
token: updatedSession.sessionToken,
|
||||||
creationTs: recentCookie.creationTs,
|
creationTs: command.recentCookie.creationTs,
|
||||||
expirationTs: recentCookie.expirationTs,
|
expirationTs: command.recentCookie.expirationTs,
|
||||||
// just overwrite the changeDate with the new one
|
// just overwrite the changeDate with the new one
|
||||||
changeTs: updatedSession.details?.changeDate
|
changeTs: updatedSession.details?.changeDate
|
||||||
? `${timestampMs(updatedSession.details.changeDate)}`
|
? `${timestampMs(updatedSession.details.changeDate)}`
|
||||||
: "",
|
: "",
|
||||||
loginName: recentCookie.loginName,
|
loginName: command.recentCookie.loginName,
|
||||||
organization: recentCookie.organization,
|
organization: command.recentCookie.organization,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (requestId) {
|
if (command.requestId) {
|
||||||
sessionCookie.requestId = requestId;
|
sessionCookie.requestId = command.requestId;
|
||||||
}
|
}
|
||||||
|
|
||||||
return getSession({
|
return getSession({
|
||||||
|
@@ -1,83 +0,0 @@
|
|||||||
"use server";
|
|
||||||
|
|
||||||
import { setSessionAndUpdateCookie } from "@/lib/server/cookie";
|
|
||||||
import { create } from "@zitadel/client";
|
|
||||||
import {
|
|
||||||
CheckOTPSchema,
|
|
||||||
ChecksSchema,
|
|
||||||
CheckTOTPSchema,
|
|
||||||
} from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
|
||||||
import { headers } from "next/headers";
|
|
||||||
import {
|
|
||||||
getMostRecentSessionCookie,
|
|
||||||
getSessionCookieById,
|
|
||||||
getSessionCookieByLoginName,
|
|
||||||
} from "../cookies";
|
|
||||||
import { getServiceUrlFromHeaders } from "../service-url";
|
|
||||||
import { getLoginSettings } from "../zitadel";
|
|
||||||
|
|
||||||
export type SetOTPCommand = {
|
|
||||||
loginName?: string;
|
|
||||||
sessionId?: string;
|
|
||||||
organization?: string;
|
|
||||||
requestId?: string;
|
|
||||||
code: string;
|
|
||||||
method: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function setOTP(command: SetOTPCommand) {
|
|
||||||
const _headers = await headers();
|
|
||||||
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
|
||||||
|
|
||||||
const recentSession = command.sessionId
|
|
||||||
? await getSessionCookieById({ sessionId: command.sessionId }).catch(
|
|
||||||
(error) => {
|
|
||||||
return Promise.reject(error);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
: command.loginName
|
|
||||||
? await getSessionCookieByLoginName({
|
|
||||||
loginName: command.loginName,
|
|
||||||
organization: command.organization,
|
|
||||||
}).catch((error) => {
|
|
||||||
return Promise.reject(error);
|
|
||||||
})
|
|
||||||
: await getMostRecentSessionCookie().catch((error) => {
|
|
||||||
return Promise.reject(error);
|
|
||||||
});
|
|
||||||
|
|
||||||
const checks = create(ChecksSchema, {});
|
|
||||||
|
|
||||||
if (command.method === "time-based") {
|
|
||||||
checks.totp = create(CheckTOTPSchema, {
|
|
||||||
code: command.code,
|
|
||||||
});
|
|
||||||
} else if (command.method === "sms") {
|
|
||||||
checks.otpSms = create(CheckOTPSchema, {
|
|
||||||
code: command.code,
|
|
||||||
});
|
|
||||||
} else if (command.method === "email") {
|
|
||||||
checks.otpEmail = create(CheckOTPSchema, {
|
|
||||||
code: command.code,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const loginSettings = await getLoginSettings({
|
|
||||||
serviceUrl,
|
|
||||||
organization: command.organization,
|
|
||||||
});
|
|
||||||
|
|
||||||
return setSessionAndUpdateCookie(
|
|
||||||
recentSession,
|
|
||||||
checks,
|
|
||||||
undefined,
|
|
||||||
command.requestId,
|
|
||||||
loginSettings?.secondFactorCheckLifetime,
|
|
||||||
).then((session) => {
|
|
||||||
return {
|
|
||||||
sessionId: session.id,
|
|
||||||
factors: session.factors,
|
|
||||||
challenges: session.challenges,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
@@ -211,19 +211,27 @@ export async function sendPasskey(command: SendPasskeyCommand) {
|
|||||||
organization,
|
organization,
|
||||||
});
|
});
|
||||||
|
|
||||||
const lifetime = checks?.webAuthN
|
let lifetime = checks?.webAuthN
|
||||||
? loginSettings?.multiFactorCheckLifetime // TODO different lifetime for webauthn u2f/passkey
|
? loginSettings?.multiFactorCheckLifetime // TODO different lifetime for webauthn u2f/passkey
|
||||||
: checks?.otpEmail || checks?.otpSms
|
: checks?.otpEmail || checks?.otpSms
|
||||||
? loginSettings?.secondFactorCheckLifetime
|
? loginSettings?.secondFactorCheckLifetime
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const session = await setSessionAndUpdateCookie(
|
if (!lifetime) {
|
||||||
recentSession,
|
console.warn("No passkey lifetime provided, defaulting to 24 hours");
|
||||||
|
|
||||||
|
lifetime = {
|
||||||
|
seconds: BigInt(60 * 60 * 24), // default to 24 hours
|
||||||
|
nanos: 0,
|
||||||
|
} as Duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
const session = await setSessionAndUpdateCookie({
|
||||||
|
recentCookie: recentSession,
|
||||||
checks,
|
checks,
|
||||||
undefined,
|
|
||||||
requestId,
|
requestId,
|
||||||
lifetime,
|
lifetime,
|
||||||
);
|
});
|
||||||
|
|
||||||
if (!session || !session?.factors?.user?.id) {
|
if (!session || !session?.factors?.user?.id) {
|
||||||
return { error: "Could not update session" };
|
return { error: "Could not update session" };
|
||||||
|
@@ -16,7 +16,7 @@ import {
|
|||||||
setPassword,
|
setPassword,
|
||||||
setUserPassword,
|
setUserPassword,
|
||||||
} from "@/lib/zitadel";
|
} from "@/lib/zitadel";
|
||||||
import { ConnectError, create } from "@zitadel/client";
|
import { ConnectError, create, Duration } from "@zitadel/client";
|
||||||
import { createUserServiceClient } from "@zitadel/client/v2";
|
import { createUserServiceClient } from "@zitadel/client/v2";
|
||||||
import {
|
import {
|
||||||
Checks,
|
Checks,
|
||||||
@@ -152,14 +152,32 @@ export async function sendPassword(command: UpdateSessionCommand) {
|
|||||||
// this is a fake error message to hide that the user does not even exist
|
// this is a fake error message to hide that the user does not even exist
|
||||||
return { error: "Could not verify password" };
|
return { error: "Could not verify password" };
|
||||||
} else {
|
} else {
|
||||||
|
loginSettings = await getLoginSettings({
|
||||||
|
serviceUrl,
|
||||||
|
organization: sessionCookie.organization,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!loginSettings) {
|
||||||
|
return { error: "Could not load login settings" };
|
||||||
|
}
|
||||||
|
|
||||||
|
let lifetime = loginSettings.passwordCheckLifetime;
|
||||||
|
|
||||||
|
if (!lifetime) {
|
||||||
|
console.warn("No password lifetime provided, defaulting to 24 hours");
|
||||||
|
lifetime = {
|
||||||
|
seconds: BigInt(60 * 60 * 24), // default to 24 hours
|
||||||
|
nanos: 0,
|
||||||
|
} as Duration;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
session = await setSessionAndUpdateCookie(
|
session = await setSessionAndUpdateCookie({
|
||||||
sessionCookie,
|
recentCookie: sessionCookie,
|
||||||
command.checks,
|
checks: command.checks,
|
||||||
undefined,
|
requestId: command.requestId,
|
||||||
command.requestId,
|
lifetime,
|
||||||
loginSettings?.passwordCheckLifetime,
|
});
|
||||||
);
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if ("failedAttempts" in error && error.failedAttempts) {
|
if ("failedAttempts" in error && error.failedAttempts) {
|
||||||
const lockoutSettings = await getLockoutSettings({
|
const lockoutSettings = await getLockoutSettings({
|
||||||
|
@@ -154,19 +154,27 @@ export async function updateSession(options: UpdateSessionCommand) {
|
|||||||
organization,
|
organization,
|
||||||
});
|
});
|
||||||
|
|
||||||
const lifetime = checks?.webAuthN
|
let lifetime = checks?.webAuthN
|
||||||
? loginSettings?.multiFactorCheckLifetime // TODO different lifetime for webauthn u2f/passkey
|
? loginSettings?.multiFactorCheckLifetime // TODO different lifetime for webauthn u2f/passkey
|
||||||
: checks?.otpEmail || checks?.otpSms
|
: checks?.otpEmail || checks?.otpSms
|
||||||
? loginSettings?.secondFactorCheckLifetime
|
? loginSettings?.secondFactorCheckLifetime
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const session = await setSessionAndUpdateCookie(
|
if (!lifetime) {
|
||||||
recentSession,
|
console.warn("No lifetime provided for session, defaulting to 24 hours");
|
||||||
|
lifetime = {
|
||||||
|
seconds: BigInt(60 * 60 * 24), // default to 24 hours
|
||||||
|
nanos: 0,
|
||||||
|
} as Duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
const session = await setSessionAndUpdateCookie({
|
||||||
|
recentCookie: recentSession,
|
||||||
checks,
|
checks,
|
||||||
challenges,
|
challenges,
|
||||||
requestId,
|
requestId,
|
||||||
lifetime,
|
lifetime,
|
||||||
);
|
});
|
||||||
|
|
||||||
if (!session) {
|
if (!session) {
|
||||||
return { error: "Could not update session" };
|
return { error: "Could not update session" };
|
||||||
|
@@ -298,7 +298,7 @@ export async function createSessionFromChecks({
|
|||||||
}: {
|
}: {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
checks: Checks;
|
checks: Checks;
|
||||||
lifetime?: Duration;
|
lifetime: Duration;
|
||||||
}) {
|
}) {
|
||||||
const sessionService: Client<typeof SessionService> =
|
const sessionService: Client<typeof SessionService> =
|
||||||
await createServiceForHost(SessionService, serviceUrl);
|
await createServiceForHost(SessionService, serviceUrl);
|
||||||
@@ -320,7 +320,7 @@ export async function createSessionForUserIdAndIdpIntent({
|
|||||||
idpIntentId?: string | undefined;
|
idpIntentId?: string | undefined;
|
||||||
idpIntentToken?: string | undefined;
|
idpIntentToken?: string | undefined;
|
||||||
};
|
};
|
||||||
lifetime?: Duration;
|
lifetime: Duration;
|
||||||
}) {
|
}) {
|
||||||
const sessionService: Client<typeof SessionService> =
|
const sessionService: Client<typeof SessionService> =
|
||||||
await createServiceForHost(SessionService, serviceUrl);
|
await createServiceForHost(SessionService, serviceUrl);
|
||||||
@@ -355,7 +355,7 @@ export async function setSession({
|
|||||||
sessionToken: string;
|
sessionToken: string;
|
||||||
challenges: RequestChallenges | undefined;
|
challenges: RequestChallenges | undefined;
|
||||||
checks?: Checks;
|
checks?: Checks;
|
||||||
lifetime?: Duration;
|
lifetime: Duration;
|
||||||
}) {
|
}) {
|
||||||
const sessionService: Client<typeof SessionService> =
|
const sessionService: Client<typeof SessionService> =
|
||||||
await createServiceForHost(SessionService, serviceUrl);
|
await createServiceForHost(SessionService, serviceUrl);
|
||||||
|
@@ -8,6 +8,9 @@
|
|||||||
"build:login:standalone": {
|
"build:login:standalone": {
|
||||||
"outputs": ["dist/**", ".next/**", "!.next/cache/**"]
|
"outputs": ["dist/**", ".next/**", "!.next/cache/**"]
|
||||||
},
|
},
|
||||||
|
"dev": {
|
||||||
|
"dependsOn": ["@zitadel/client#build", "@zitadel/proto#generate"]
|
||||||
|
},
|
||||||
"test": {
|
"test": {
|
||||||
"dependsOn": ["@zitadel/client#build"]
|
"dependsOn": ["@zitadel/client#build"]
|
||||||
},
|
},
|
||||||
|
201
pnpm-lock.yaml
generated
201
pnpm-lock.yaml
generated
@@ -421,6 +421,9 @@ importers:
|
|||||||
'@heroicons/react':
|
'@heroicons/react':
|
||||||
specifier: 2.1.3
|
specifier: 2.1.3
|
||||||
version: 2.1.3(react@19.1.0)
|
version: 2.1.3(react@19.1.0)
|
||||||
|
'@radix-ui/react-tooltip':
|
||||||
|
specifier: ^1.2.7
|
||||||
|
version: 1.2.7(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
'@tailwindcss/forms':
|
'@tailwindcss/forms':
|
||||||
specifier: 0.5.7
|
specifier: 0.5.7
|
||||||
version: 0.5.7(tailwindcss@3.4.14)
|
version: 0.5.7(tailwindcss@3.4.14)
|
||||||
@@ -4259,6 +4262,19 @@ packages:
|
|||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-tooltip@1.2.7':
|
||||||
|
resolution: {integrity: sha512-Ap+fNYwKTYJ9pzqW+Xe2HtMRbQ/EeWkj2qykZ6SuEV4iS/o1bZI5ssJbk4D2r8XuDuOBVz/tIx2JObtuqU+5Zw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-use-callback-ref@1.1.0':
|
'@radix-ui/react-use-callback-ref@1.1.0':
|
||||||
resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==}
|
resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -4398,6 +4414,19 @@ packages:
|
|||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-visually-hidden@1.2.3':
|
||||||
|
resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/rect@1.1.0':
|
'@radix-ui/rect@1.1.0':
|
||||||
resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==}
|
resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==}
|
||||||
|
|
||||||
@@ -19496,6 +19525,15 @@ snapshots:
|
|||||||
'@types/react': 19.1.2
|
'@types/react': 19.1.2
|
||||||
'@types/react-dom': 19.1.2(@types/react@19.1.2)
|
'@types/react-dom': 19.1.2(@types/react@19.1.2)
|
||||||
|
|
||||||
|
'@radix-ui/react-arrow@1.1.7(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
react: 19.1.0
|
||||||
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.1.2
|
||||||
|
'@types/react-dom': 19.1.2(@types/react@19.1.2)
|
||||||
|
|
||||||
'@radix-ui/react-avatar@1.1.2(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-avatar@1.1.2(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/react-context': 1.1.1(@types/react@19.1.2)(react@18.3.1)
|
'@radix-ui/react-context': 1.1.1(@types/react@19.1.2)(react@18.3.1)
|
||||||
@@ -19548,6 +19586,12 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.1.2
|
'@types/react': 19.1.2
|
||||||
|
|
||||||
|
'@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.2)(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
react: 19.1.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.1.2
|
||||||
|
|
||||||
'@radix-ui/react-context@1.1.1(@types/react@19.1.2)(react@18.3.1)':
|
'@radix-ui/react-context@1.1.1(@types/react@19.1.2)(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
@@ -19560,6 +19604,12 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.1.2
|
'@types/react': 19.1.2
|
||||||
|
|
||||||
|
'@radix-ui/react-context@1.1.2(@types/react@19.1.2)(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
react: 19.1.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.1.2
|
||||||
|
|
||||||
'@radix-ui/react-direction@1.1.0(@types/react@19.1.2)(react@18.3.1)':
|
'@radix-ui/react-direction@1.1.0(@types/react@19.1.2)(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
@@ -19585,6 +19635,19 @@ snapshots:
|
|||||||
'@types/react': 19.1.2
|
'@types/react': 19.1.2
|
||||||
'@types/react-dom': 19.1.2(@types/react@19.1.2)
|
'@types/react-dom': 19.1.2(@types/react@19.1.2)
|
||||||
|
|
||||||
|
'@radix-ui/react-dismissable-layer@1.1.10(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/primitive': 1.1.2
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
'@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
react: 19.1.0
|
||||||
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.1.2
|
||||||
|
'@types/react-dom': 19.1.2(@types/react@19.1.2)
|
||||||
|
|
||||||
'@radix-ui/react-dismissable-layer@1.1.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-dismissable-layer@1.1.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/primitive': 1.1.1
|
'@radix-ui/primitive': 1.1.1
|
||||||
@@ -19676,6 +19739,13 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.1.2
|
'@types/react': 19.1.2
|
||||||
|
|
||||||
|
'@radix-ui/react-id@1.1.1(@types/react@19.1.2)(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
react: 19.1.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.1.2
|
||||||
|
|
||||||
'@radix-ui/react-popover@1.1.6(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-popover@1.1.6(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/primitive': 1.1.1
|
'@radix-ui/primitive': 1.1.1
|
||||||
@@ -19753,6 +19823,24 @@ snapshots:
|
|||||||
'@types/react': 19.1.2
|
'@types/react': 19.1.2
|
||||||
'@types/react-dom': 19.1.2(@types/react@19.1.2)
|
'@types/react-dom': 19.1.2(@types/react@19.1.2)
|
||||||
|
|
||||||
|
'@radix-ui/react-popper@1.2.7(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@floating-ui/react-dom': 2.1.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
'@radix-ui/react-context': 1.1.2(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
'@radix-ui/react-use-rect': 1.1.1(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
'@radix-ui/react-use-size': 1.1.1(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
'@radix-ui/rect': 1.1.1
|
||||||
|
react: 19.1.0
|
||||||
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.1.2
|
||||||
|
'@types/react-dom': 19.1.2(@types/react@19.1.2)
|
||||||
|
|
||||||
'@radix-ui/react-portal@1.1.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-portal@1.1.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
'@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
@@ -19783,6 +19871,16 @@ snapshots:
|
|||||||
'@types/react': 19.1.2
|
'@types/react': 19.1.2
|
||||||
'@types/react-dom': 19.1.2(@types/react@19.1.2)
|
'@types/react-dom': 19.1.2(@types/react@19.1.2)
|
||||||
|
|
||||||
|
'@radix-ui/react-portal@1.1.9(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
react: 19.1.0
|
||||||
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.1.2
|
||||||
|
'@types/react-dom': 19.1.2(@types/react@19.1.2)
|
||||||
|
|
||||||
'@radix-ui/react-presence@1.1.2(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-presence@1.1.2(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/react-compose-refs': 1.1.1(@types/react@19.1.2)(react@18.3.1)
|
'@radix-ui/react-compose-refs': 1.1.1(@types/react@19.1.2)(react@18.3.1)
|
||||||
@@ -19803,6 +19901,16 @@ snapshots:
|
|||||||
'@types/react': 19.1.2
|
'@types/react': 19.1.2
|
||||||
'@types/react-dom': 19.1.2(@types/react@19.1.2)
|
'@types/react-dom': 19.1.2(@types/react@19.1.2)
|
||||||
|
|
||||||
|
'@radix-ui/react-presence@1.1.4(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
react: 19.1.0
|
||||||
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.1.2
|
||||||
|
'@types/react-dom': 19.1.2(@types/react@19.1.2)
|
||||||
|
|
||||||
'@radix-ui/react-primitive@2.0.1(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-primitive@2.0.1(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/react-slot': 1.1.1(@types/react@19.1.2)(react@18.3.1)
|
'@radix-ui/react-slot': 1.1.1(@types/react@19.1.2)(react@18.3.1)
|
||||||
@@ -19830,6 +19938,15 @@ snapshots:
|
|||||||
'@types/react': 19.1.2
|
'@types/react': 19.1.2
|
||||||
'@types/react-dom': 19.1.2(@types/react@19.1.2)
|
'@types/react-dom': 19.1.2(@types/react@19.1.2)
|
||||||
|
|
||||||
|
'@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-slot': 1.2.3(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
react: 19.1.0
|
||||||
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.1.2
|
||||||
|
'@types/react-dom': 19.1.2(@types/react@19.1.2)
|
||||||
|
|
||||||
'@radix-ui/react-roving-focus@1.1.10(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-roving-focus@1.1.10(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/primitive': 1.1.2
|
'@radix-ui/primitive': 1.1.2
|
||||||
@@ -19885,6 +20002,13 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.1.2
|
'@types/react': 19.1.2
|
||||||
|
|
||||||
|
'@radix-ui/react-slot@1.2.3(@types/react@19.1.2)(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
react: 19.1.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.1.2
|
||||||
|
|
||||||
'@radix-ui/react-tabs@1.1.12(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-tabs@1.1.12(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/primitive': 1.1.2
|
'@radix-ui/primitive': 1.1.2
|
||||||
@@ -19921,6 +20045,26 @@ snapshots:
|
|||||||
'@types/react': 19.1.2
|
'@types/react': 19.1.2
|
||||||
'@types/react-dom': 19.1.2(@types/react@19.1.2)
|
'@types/react-dom': 19.1.2(@types/react@19.1.2)
|
||||||
|
|
||||||
|
'@radix-ui/react-tooltip@1.2.7(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/primitive': 1.1.2
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
'@radix-ui/react-context': 1.1.2(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
'@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@radix-ui/react-id': 1.1.1(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
'@radix-ui/react-popper': 1.2.7(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@radix-ui/react-slot': 1.2.3(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
'@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
react: 19.1.0
|
||||||
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.1.2
|
||||||
|
'@types/react-dom': 19.1.2(@types/react@19.1.2)
|
||||||
|
|
||||||
'@radix-ui/react-use-callback-ref@1.1.0(@types/react@19.1.2)(react@18.3.1)':
|
'@radix-ui/react-use-callback-ref@1.1.0(@types/react@19.1.2)(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
@@ -19933,6 +20077,12 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.1.2
|
'@types/react': 19.1.2
|
||||||
|
|
||||||
|
'@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.2)(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
react: 19.1.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.1.2
|
||||||
|
|
||||||
'@radix-ui/react-use-controllable-state@1.1.0(@types/react@19.1.2)(react@18.3.1)':
|
'@radix-ui/react-use-controllable-state@1.1.0(@types/react@19.1.2)(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.1.2)(react@18.3.1)
|
'@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.1.2)(react@18.3.1)
|
||||||
@@ -19948,6 +20098,14 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.1.2
|
'@types/react': 19.1.2
|
||||||
|
|
||||||
|
'@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.1.2)(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
react: 19.1.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.1.2
|
||||||
|
|
||||||
'@radix-ui/react-use-effect-event@0.0.2(@types/react@19.1.2)(react@18.3.1)':
|
'@radix-ui/react-use-effect-event@0.0.2(@types/react@19.1.2)(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@18.3.1)
|
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@18.3.1)
|
||||||
@@ -19955,6 +20113,13 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.1.2
|
'@types/react': 19.1.2
|
||||||
|
|
||||||
|
'@radix-ui/react-use-effect-event@0.0.2(@types/react@19.1.2)(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
react: 19.1.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.1.2
|
||||||
|
|
||||||
'@radix-ui/react-use-escape-keydown@1.1.0(@types/react@19.1.2)(react@18.3.1)':
|
'@radix-ui/react-use-escape-keydown@1.1.0(@types/react@19.1.2)(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.1.2)(react@18.3.1)
|
'@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.1.2)(react@18.3.1)
|
||||||
@@ -19969,6 +20134,13 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.1.2
|
'@types/react': 19.1.2
|
||||||
|
|
||||||
|
'@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.1.2)(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
react: 19.1.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.1.2
|
||||||
|
|
||||||
'@radix-ui/react-use-layout-effect@1.1.0(@types/react@19.1.2)(react@18.3.1)':
|
'@radix-ui/react-use-layout-effect@1.1.0(@types/react@19.1.2)(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
@@ -19981,6 +20153,12 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.1.2
|
'@types/react': 19.1.2
|
||||||
|
|
||||||
|
'@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.2)(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
react: 19.1.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.1.2
|
||||||
|
|
||||||
'@radix-ui/react-use-previous@1.1.0(@types/react@19.1.2)(react@18.3.1)':
|
'@radix-ui/react-use-previous@1.1.0(@types/react@19.1.2)(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
@@ -20001,6 +20179,13 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.1.2
|
'@types/react': 19.1.2
|
||||||
|
|
||||||
|
'@radix-ui/react-use-rect@1.1.1(@types/react@19.1.2)(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/rect': 1.1.1
|
||||||
|
react: 19.1.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.1.2
|
||||||
|
|
||||||
'@radix-ui/react-use-size@1.1.0(@types/react@19.1.2)(react@18.3.1)':
|
'@radix-ui/react-use-size@1.1.0(@types/react@19.1.2)(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.1.2)(react@18.3.1)
|
'@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.1.2)(react@18.3.1)
|
||||||
@@ -20015,6 +20200,13 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.1.2
|
'@types/react': 19.1.2
|
||||||
|
|
||||||
|
'@radix-ui/react-use-size@1.1.1(@types/react@19.1.2)(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
react: 19.1.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.1.2
|
||||||
|
|
||||||
'@radix-ui/react-visually-hidden@1.1.1(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-visually-hidden@1.1.1(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
'@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
@@ -20024,6 +20216,15 @@ snapshots:
|
|||||||
'@types/react': 19.1.2
|
'@types/react': 19.1.2
|
||||||
'@types/react-dom': 19.1.2(@types/react@19.1.2)
|
'@types/react-dom': 19.1.2(@types/react@19.1.2)
|
||||||
|
|
||||||
|
'@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
react: 19.1.0
|
||||||
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.1.2
|
||||||
|
'@types/react-dom': 19.1.2(@types/react@19.1.2)
|
||||||
|
|
||||||
'@radix-ui/rect@1.1.0': {}
|
'@radix-ui/rect@1.1.0': {}
|
||||||
|
|
||||||
'@radix-ui/rect@1.1.1': {}
|
'@radix-ui/rect@1.1.1': {}
|
||||||
|
Reference in New Issue
Block a user